wayland: Rearrange the surface code
authorMatthias Clasen <mclasen@redhat.com>
Fri, 6 Jan 2023 18:44:18 +0000 (13:44 -0500)
committerMatthias Clasen <mclasen@redhat.com>
Fri, 6 Jan 2023 21:50:19 +0000 (16:50 -0500)
gdk/wayland/gdksurface-wayland.c

index 081087752a6af552507ae84207ebd5a090e9e77b..725404c327eb094c329468a95378e46905d18a85 100644 (file)
  * [method@GdkWayland.WaylandSurface.get_wl_surface].
  */
 
-/**
- * GdkWaylandToplevel:
- *
- * The Wayland implementation of `GdkToplevel`.
- *
- * Beyond the [iface@Gdk.Toplevel] API, the Wayland implementation
- * has API to set up cross-process parent-child relationships between
- * surfaces with [method@GdkWayland.WaylandToplevel.export_handle] and
- * [method@GdkWayland.WaylandToplevel.set_transient_for_exported].
- */
-
-/**
- * GdkWaylandPopup:
- *
- * The Wayland implementation of `GdkPopup`.
- */
-
 #define MAX_WL_BUFFER_SIZE (4083) /* 4096 minus header, string argument length and NUL byte */
 
 G_DEFINE_TYPE (GdkWaylandSurface, gdk_wayland_surface, GDK_TYPE_SURFACE)
 
-struct _GdkWaylandToplevel
-{
-  GdkWaylandSurface parent_instance;
-
-  struct {
-    struct gtk_surface1 *gtk_surface;
-    struct xdg_toplevel *xdg_toplevel;
-    struct zxdg_toplevel_v6 *zxdg_toplevel_v6;
-  } display_server;
-
-  GdkWaylandToplevel *transient_for;
-
-  struct org_kde_kwin_server_decoration *server_decoration;
-  struct zxdg_exported_v1 *xdg_exported;
-  struct zxdg_exported_v2 *xdg_exported_v2;
-
-  struct {
-    int width;
-    int height;
-    GdkToplevelState state;
-    gboolean is_resizing;
-
-    int bounds_width;
-    int bounds_height;
-    gboolean has_bounds;
-  } pending;
-
-  struct {
-      gboolean should_constrain;
-      gboolean size_is_fixed;
-  } next_layout;
-
-  struct {
-    GdkWaylandToplevelExported callback;
-    gpointer user_data;
-    GDestroyNotify destroy_func;
-  } exported;
-
-  struct {
-    gboolean was_set;
-
-    char *application_id;
-    char *app_menu_path;
-    char *menubar_path;
-    char *window_object_path;
-    char *application_object_path;
-    char *unique_bus_name;
-  } application;
-
-  struct zwp_idle_inhibitor_v1 *idle_inhibitor;
-  size_t idle_inhibitor_refcount;
-
-  struct wl_output *initial_fullscreen_output;
-
-  struct {
-    GdkToplevelState unset_flags;
-    GdkToplevelState set_flags;
-  } initial_state;
-
-  GdkToplevelLayout *layout;
-  int bounds_width;
-  int bounds_height;
-  gboolean has_bounds;
-
-  char *title;
-
-  GdkGeometry geometry_hints;
-  GdkSurfaceHints geometry_mask;
-  GdkGeometry last_sent_geometry_hints;
-
-  struct zxdg_imported_v1 *imported_transient_for;
-  struct zxdg_imported_v2 *imported_transient_for_v2;
-
-  GHashTable *shortcuts_inhibitors;
-};
-
-typedef struct
-{
-  GdkWaylandSurfaceClass parent_class;
-} GdkWaylandToplevelClass;
-
-static void gdk_wayland_toplevel_iface_init (GdkToplevelInterface *iface);
-
-G_DEFINE_TYPE_WITH_CODE (GdkWaylandToplevel, gdk_wayland_toplevel, GDK_TYPE_WAYLAND_SURFACE,
-                         G_IMPLEMENT_INTERFACE (GDK_TYPE_TOPLEVEL,
-                                                gdk_wayland_toplevel_iface_init))
-
-struct _GdkWaylandPopup
-{
-  GdkWaylandSurface parent_instance;
-
-  struct {
-    struct xdg_popup *xdg_popup;
-    struct zxdg_popup_v6 *zxdg_popup_v6;
-  } display_server;
-
-  PopupState state;
-  unsigned int thaw_upon_show : 1;
-  GdkPopupLayout *layout;
-  int unconstrained_width;
-  int unconstrained_height;
-
-  struct {
-    int x;
-    int y;
-    int width;
-    int height;
-    uint32_t repositioned_token;
-    gboolean has_repositioned_token;
-  } pending;
-
-  struct {
-    int x;
-    int y;
-  } next_layout;
-
-  uint32_t reposition_token;
-  uint32_t received_reposition_token;
-
-  GdkSeat *grab_input_seat;
-};
-
-typedef struct
-{
-  GdkWaylandSurfaceClass parent_class;
-} GdkWaylandPopupClass;
-
-static void gdk_wayland_popup_iface_init (GdkPopupInterface *iface);
-
-G_DEFINE_TYPE_WITH_CODE (GdkWaylandPopup, gdk_wayland_popup, GDK_TYPE_WAYLAND_SURFACE,
-                         G_IMPLEMENT_INTERFACE (GDK_TYPE_POPUP,
-                                                gdk_wayland_popup_iface_init))
-
 static void gdk_wayland_surface_maybe_resize (GdkSurface *surface,
                                               int         width,
                                               int         height,
@@ -218,9 +68,6 @@ static void gdk_wayland_surface_maybe_resize (GdkSurface *surface,
 
 static void gdk_wayland_surface_configure (GdkSurface *surface);
 
-static void maybe_set_gtk_surface_dbus_properties (GdkWaylandToplevel *wayland_toplevel);
-static void maybe_set_gtk_surface_modal (GdkWaylandToplevel *wayland_toplevel);
-
 static void gdk_wayland_surface_show (GdkSurface *surface);
 static void gdk_wayland_surface_hide (GdkSurface *surface);
 
@@ -245,59 +92,313 @@ static void update_popup_layout_state (GdkWaylandPopup *wayland_popup,
 
 static gboolean gdk_wayland_toplevel_is_exported (GdkWaylandToplevel *wayland_toplevel);
 
-static void configure_toplevel_geometry (GdkWaylandToplevel *wayland_toplevel);
+static void configure_toplevel_geometry                  (GdkWaylandToplevel *toplevel);
+static void gdk_wayland_surface_create_xdg_toplevel      (GdkWaylandToplevel *toplevel);
+static void gdk_wayland_surface_configure_toplevel       (GdkWaylandToplevel *toplevel);
+static void gdk_wayland_toplevel_sync_parent             (GdkWaylandToplevel *toplevel);
+static void gdk_wayland_toplevel_sync_parent_of_imported (GdkWaylandToplevel *toplevel);
+static void gdk_wayland_toplevel_sync_title              (GdkWaylandToplevel *toplevel);
+static void gdk_wayland_toplevel_set_geometry_hints      (GdkWaylandToplevel *toplevel,
+                                                          const GdkGeometry  *geometry,
+                                                          GdkSurfaceHints     geom_mask);
+static void gdk_wayland_toplevel_handle_configure        (GdkWaylandToplevel *toplevel,
+                                                          int32_t             width,
+                                                          int32_t             height,
+                                                          GdkToplevelState    state);
+static void gdk_wayland_toplevel_hide_surface            (GdkWaylandToplevel *toplevel);
+
+static void configure_popup_geometry                     (GdkWaylandPopup *popup);
+static void gdk_wayland_surface_configure_popup          (GdkWaylandPopup *popup);
+static void frame_callback_popup                         (GdkWaylandPopup *popup);
+static void gdk_wayland_popup_hide_surface               (GdkWaylandPopup *popup);
+
+/* {{{ Utilities */
 
 static void
-gdk_wayland_surface_init (GdkWaylandSurface *impl)
+fill_presentation_time_from_frame_time (GdkFrameTimings *timings,
+                                        guint32          frame_time)
 {
-  impl->scale = 1;
-  impl->saved_width = -1;
-  impl->saved_height = -1;
+  /* The timestamp in a wayland frame is a msec time value that in some
+   * way reflects the time at which the server started drawing the frame.
+   * This is not useful from our perspective.
+   *
+   * However, for the DRM backend of Weston, on reasonably recent
+   * Linux, we know that the time is the
+   * clock_gettime (CLOCK_MONOTONIC) value at the vblank, and that
+   * backend starts drawing immediately after receiving the vblank
+   * notification. If we detect this, and make the assumption that the
+   * compositor will finish drawing before the next vblank, we can
+   * then determine the presentation time as the frame time we
+   * received plus one refresh interval.
+   *
+   * If a backend is using clock_gettime(CLOCK_MONOTONIC), but not
+   * picking values right at the vblank, then the presentation times
+   * we compute won't be accurate, but not really worse than then
+   * the alternative of not providing presentation times at all.
+   *
+   * The complexity here is dealing with the fact that we receive
+   * only the low 32 bits of the CLOCK_MONOTONIC value in milliseconds.
+   */
+  gint64 now_monotonic = g_get_monotonic_time ();
+  gint64 now_monotonic_msec = now_monotonic / 1000;
+  uint32_t now_monotonic_low = (uint32_t)now_monotonic_msec;
+
+  if (frame_time - now_monotonic_low < 1000 ||
+      frame_time - now_monotonic_low > (uint32_t)-1000)
+    {
+      /* Timestamp we received is within one second of the current time.
+       */
+      gint64 last_frame_time = now_monotonic + (gint64)1000 * (gint32)(frame_time - now_monotonic_low);
+      if ((gint32)now_monotonic_low < 0 && (gint32)frame_time > 0)
+        last_frame_time += (gint64)1000 * G_GINT64_CONSTANT(0x100000000);
+      else if ((gint32)now_monotonic_low > 0 && (gint32)frame_time < 0)
+        last_frame_time -= (gint64)1000 * G_GINT64_CONSTANT(0x100000000);
+
+      timings->presentation_time = last_frame_time + timings->refresh_interval;
+    }
 }
 
 static void
-gdk_wayland_surface_freeze_state (GdkSurface *surface)
+gdk_wayland_surface_get_window_geometry (GdkSurface   *surface,
+                                         GdkRectangle *geometry)
 {
   GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
 
-  impl->state_freeze_count++;
+  *geometry = (GdkRectangle) {
+    .x = impl->shadow_left,
+    .y = impl->shadow_top,
+    .width = surface->width - (impl->shadow_left + impl->shadow_right),
+    .height = surface->height - (impl->shadow_top + impl->shadow_bottom)
+  };
 }
 
-static void
-gdk_wayland_surface_thaw_state (GdkSurface *surface)
+static struct wl_region *
+wl_region_from_cairo_region (GdkWaylandDisplay *display,
+                             cairo_region_t    *region)
 {
-  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
-
-  g_assert (impl->state_freeze_count > 0);
+  struct wl_region *wl_region;
+  int i, n_rects;
 
-  impl->state_freeze_count--;
+  wl_region = wl_compositor_create_region (display->compositor);
+  if (wl_region == NULL)
+    return NULL;
 
-  if (impl->state_freeze_count > 0)
-    return;
+  n_rects = cairo_region_num_rectangles (region);
+  for (i = 0; i < n_rects; i++)
+    {
+      cairo_rectangle_int_t rect;
+      cairo_region_get_rectangle (region, i, &rect);
+      wl_region_add (wl_region, rect.x, rect.y, rect.width, rect.height);
+    }
 
-  if (impl->pending.is_dirty)
-    gdk_wayland_surface_configure (surface);
+  return wl_region;
 }
 
-static void
-_gdk_wayland_surface_save_size (GdkSurface *surface)
+static const char *
+get_default_title (void)
 {
-  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
-
-  if (surface->state & (GDK_TOPLEVEL_STATE_FULLSCREEN |
-                        GDK_TOPLEVEL_STATE_MAXIMIZED |
-                        GDK_TOPLEVEL_STATE_TILED))
-    return;
+  const char *title;
 
-  if (surface->width <= 1 || surface->height <= 1)
-    return;
+  title = g_get_application_name ();
+  if (!title)
+    title = g_get_prgname ();
+  if (!title)
+    title = "";
 
-  impl->saved_width = surface->width - impl->shadow_left - impl->shadow_right;
-  impl->saved_height = surface->height - impl->shadow_top - impl->shadow_bottom;
+  return title;
 }
 
-static void
-_gdk_wayland_surface_clear_saved_size (GdkSurface *surface)
+static gboolean
+is_realized_shell_surface (GdkWaylandSurface *impl)
+{
+  return (impl->display_server.xdg_surface ||
+          impl->display_server.zxdg_surface_v6);
+}
+
+static enum xdg_positioner_anchor
+rect_anchor_to_anchor (GdkGravity rect_anchor)
+{
+  switch (rect_anchor)
+    {
+    case GDK_GRAVITY_NORTH_WEST:
+    case GDK_GRAVITY_STATIC:
+      return XDG_POSITIONER_ANCHOR_TOP_LEFT;
+    case GDK_GRAVITY_NORTH:
+      return XDG_POSITIONER_ANCHOR_TOP;
+    case GDK_GRAVITY_NORTH_EAST:
+      return XDG_POSITIONER_ANCHOR_TOP_RIGHT;
+    case GDK_GRAVITY_WEST:
+      return XDG_POSITIONER_ANCHOR_LEFT;
+    case GDK_GRAVITY_CENTER:
+      return XDG_POSITIONER_ANCHOR_NONE;
+    case GDK_GRAVITY_EAST:
+      return XDG_POSITIONER_ANCHOR_RIGHT;
+    case GDK_GRAVITY_SOUTH_WEST:
+      return XDG_POSITIONER_ANCHOR_BOTTOM_LEFT;
+    case GDK_GRAVITY_SOUTH:
+      return XDG_POSITIONER_ANCHOR_BOTTOM;
+    case GDK_GRAVITY_SOUTH_EAST:
+      return XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT;
+    default:
+      g_assert_not_reached ();
+    }
+}
+
+static enum xdg_positioner_gravity
+surface_anchor_to_gravity (GdkGravity rect_anchor)
+{
+  switch (rect_anchor)
+    {
+    case GDK_GRAVITY_NORTH_WEST:
+    case GDK_GRAVITY_STATIC:
+      return XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT;
+    case GDK_GRAVITY_NORTH:
+      return XDG_POSITIONER_GRAVITY_BOTTOM;
+    case GDK_GRAVITY_NORTH_EAST:
+      return XDG_POSITIONER_GRAVITY_BOTTOM_LEFT;
+    case GDK_GRAVITY_WEST:
+      return XDG_POSITIONER_GRAVITY_RIGHT;
+    case GDK_GRAVITY_CENTER:
+      return XDG_POSITIONER_GRAVITY_NONE;
+    case GDK_GRAVITY_EAST:
+      return XDG_POSITIONER_GRAVITY_LEFT;
+    case GDK_GRAVITY_SOUTH_WEST:
+      return XDG_POSITIONER_GRAVITY_TOP_RIGHT;
+    case GDK_GRAVITY_SOUTH:
+      return XDG_POSITIONER_GRAVITY_TOP;
+    case GDK_GRAVITY_SOUTH_EAST:
+      return XDG_POSITIONER_GRAVITY_TOP_LEFT;
+    default:
+      g_assert_not_reached ();
+    }
+}
+
+static enum zxdg_positioner_v6_anchor
+rect_anchor_to_anchor_legacy (GdkGravity rect_anchor)
+{
+  switch (rect_anchor)
+    {
+    case GDK_GRAVITY_NORTH_WEST:
+    case GDK_GRAVITY_STATIC:
+      return (ZXDG_POSITIONER_V6_ANCHOR_TOP |
+              ZXDG_POSITIONER_V6_ANCHOR_LEFT);
+    case GDK_GRAVITY_NORTH:
+      return ZXDG_POSITIONER_V6_ANCHOR_TOP;
+    case GDK_GRAVITY_NORTH_EAST:
+      return (ZXDG_POSITIONER_V6_ANCHOR_TOP |
+              ZXDG_POSITIONER_V6_ANCHOR_RIGHT);
+    case GDK_GRAVITY_WEST:
+      return ZXDG_POSITIONER_V6_ANCHOR_LEFT;
+    case GDK_GRAVITY_CENTER:
+      return ZXDG_POSITIONER_V6_ANCHOR_NONE;
+    case GDK_GRAVITY_EAST:
+      return ZXDG_POSITIONER_V6_ANCHOR_RIGHT;
+    case GDK_GRAVITY_SOUTH_WEST:
+      return (ZXDG_POSITIONER_V6_ANCHOR_BOTTOM |
+              ZXDG_POSITIONER_V6_ANCHOR_LEFT);
+    case GDK_GRAVITY_SOUTH:
+      return ZXDG_POSITIONER_V6_ANCHOR_BOTTOM;
+    case GDK_GRAVITY_SOUTH_EAST:
+      return (ZXDG_POSITIONER_V6_ANCHOR_BOTTOM |
+              ZXDG_POSITIONER_V6_ANCHOR_RIGHT);
+    default:
+      g_assert_not_reached ();
+    }
+
+  return (ZXDG_POSITIONER_V6_ANCHOR_TOP |
+          ZXDG_POSITIONER_V6_ANCHOR_LEFT);
+}
+
+static enum zxdg_positioner_v6_gravity
+surface_anchor_to_gravity_legacy (GdkGravity rect_anchor)
+{
+  switch (rect_anchor)
+    {
+    case GDK_GRAVITY_NORTH_WEST:
+    case GDK_GRAVITY_STATIC:
+      return (ZXDG_POSITIONER_V6_GRAVITY_BOTTOM |
+              ZXDG_POSITIONER_V6_GRAVITY_RIGHT);
+    case GDK_GRAVITY_NORTH:
+      return ZXDG_POSITIONER_V6_GRAVITY_BOTTOM;
+    case GDK_GRAVITY_NORTH_EAST:
+      return (ZXDG_POSITIONER_V6_GRAVITY_BOTTOM |
+              ZXDG_POSITIONER_V6_GRAVITY_LEFT);
+    case GDK_GRAVITY_WEST:
+      return ZXDG_POSITIONER_V6_GRAVITY_RIGHT;
+    case GDK_GRAVITY_CENTER:
+      return ZXDG_POSITIONER_V6_GRAVITY_NONE;
+    case GDK_GRAVITY_EAST:
+      return ZXDG_POSITIONER_V6_GRAVITY_LEFT;
+    case GDK_GRAVITY_SOUTH_WEST:
+      return (ZXDG_POSITIONER_V6_GRAVITY_TOP |
+              ZXDG_POSITIONER_V6_GRAVITY_RIGHT);
+    case GDK_GRAVITY_SOUTH:
+      return ZXDG_POSITIONER_V6_GRAVITY_TOP;
+    case GDK_GRAVITY_SOUTH_EAST:
+      return (ZXDG_POSITIONER_V6_GRAVITY_TOP |
+              ZXDG_POSITIONER_V6_GRAVITY_LEFT);
+    default:
+      g_assert_not_reached ();
+    }
+
+  return (ZXDG_POSITIONER_V6_GRAVITY_BOTTOM |
+          ZXDG_POSITIONER_V6_GRAVITY_RIGHT);
+}
+
+/* }}} */
+/* {{{ Surface implementation */
+
+static void
+gdk_wayland_surface_init (GdkWaylandSurface *impl)
+{
+  impl->scale = 1;
+  impl->saved_width = -1;
+  impl->saved_height = -1;
+}
+
+static void
+gdk_wayland_surface_freeze_state (GdkSurface *surface)
+{
+  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
+
+  impl->state_freeze_count++;
+}
+
+static void
+gdk_wayland_surface_thaw_state (GdkSurface *surface)
+{
+  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
+
+  g_assert (impl->state_freeze_count > 0);
+
+  impl->state_freeze_count--;
+
+  if (impl->state_freeze_count > 0)
+    return;
+
+  if (impl->pending.is_dirty)
+    gdk_wayland_surface_configure (surface);
+}
+
+static void
+_gdk_wayland_surface_save_size (GdkSurface *surface)
+{
+  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
+
+  if (surface->state & (GDK_TOPLEVEL_STATE_FULLSCREEN |
+                        GDK_TOPLEVEL_STATE_MAXIMIZED |
+                        GDK_TOPLEVEL_STATE_TILED))
+    return;
+
+  if (surface->width <= 1 || surface->height <= 1)
+    return;
+
+  impl->saved_width = surface->width - impl->shadow_left - impl->shadow_right;
+  impl->saved_height = surface->height - impl->shadow_top - impl->shadow_bottom;
+}
+
+static void
+_gdk_wayland_surface_clear_saved_size (GdkSurface *surface)
 {
   GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
 
@@ -345,64 +446,6 @@ gdk_wayland_surface_update_size (GdkSurface *surface,
   _gdk_surface_update_size (surface);
 }
 
-static const char *
-get_default_title (void)
-{
-  const char *title;
-
-  title = g_get_application_name ();
-  if (!title)
-    title = g_get_prgname ();
-  if (!title)
-    title = "";
-
-  return title;
-}
-
-static void
-fill_presentation_time_from_frame_time (GdkFrameTimings *timings,
-                                        guint32          frame_time)
-{
-  /* The timestamp in a wayland frame is a msec time value that in some
-   * way reflects the time at which the server started drawing the frame.
-   * This is not useful from our perspective.
-   *
-   * However, for the DRM backend of Weston, on reasonably recent
-   * Linux, we know that the time is the
-   * clock_gettime (CLOCK_MONOTONIC) value at the vblank, and that
-   * backend starts drawing immediately after receiving the vblank
-   * notification. If we detect this, and make the assumption that the
-   * compositor will finish drawing before the next vblank, we can
-   * then determine the presentation time as the frame time we
-   * received plus one refresh interval.
-   *
-   * If a backend is using clock_gettime(CLOCK_MONOTONIC), but not
-   * picking values right at the vblank, then the presentation times
-   * we compute won't be accurate, but not really worse than then
-   * the alternative of not providing presentation times at all.
-   *
-   * The complexity here is dealing with the fact that we receive
-   * only the low 32 bits of the CLOCK_MONOTONIC value in milliseconds.
-   */
-  gint64 now_monotonic = g_get_monotonic_time ();
-  gint64 now_monotonic_msec = now_monotonic / 1000;
-  uint32_t now_monotonic_low = (uint32_t)now_monotonic_msec;
-
-  if (frame_time - now_monotonic_low < 1000 ||
-      frame_time - now_monotonic_low > (uint32_t)-1000)
-    {
-      /* Timestamp we received is within one second of the current time.
-       */
-      gint64 last_frame_time = now_monotonic + (gint64)1000 * (gint32)(frame_time - now_monotonic_low);
-      if ((gint32)now_monotonic_low < 0 && (gint32)frame_time > 0)
-        last_frame_time += (gint64)1000 * G_GINT64_CONSTANT(0x100000000);
-      else if ((gint32)now_monotonic_low > 0 && (gint32)frame_time < 0)
-        last_frame_time -= (gint64)1000 * G_GINT64_CONSTANT(0x100000000);
-
-      timings->presentation_time = last_frame_time + timings->refresh_interval;
-    }
-}
-
 static GdkSurface *
 get_popup_toplevel (GdkSurface *surface)
 {
@@ -430,15 +473,6 @@ thaw_popup_toplevel_state (GdkWaylandPopup *wayland_popup)
   gdk_wayland_surface_thaw_state (toplevel);
 }
 
-static void
-finish_pending_relayout (GdkWaylandPopup *wayland_popup)
-{
-  g_assert (wayland_popup->state == POPUP_STATE_WAITING_FOR_FRAME);
-  wayland_popup->state = POPUP_STATE_IDLE;
-
-  thaw_popup_toplevel_state (wayland_popup);
-}
-
 static void
 frame_callback (void               *data,
                 struct wl_callback *callback,
@@ -463,22 +497,7 @@ frame_callback (void               *data,
     return;
 
   if (GDK_IS_WAYLAND_POPUP (surface))
-    {
-      GdkWaylandPopup *wayland_popup = GDK_WAYLAND_POPUP (surface);
-
-      switch (wayland_popup->state)
-        {
-        case POPUP_STATE_IDLE:
-        case POPUP_STATE_WAITING_FOR_REPOSITIONED:
-        case POPUP_STATE_WAITING_FOR_CONFIGURE:
-          break;
-        case POPUP_STATE_WAITING_FOR_FRAME:
-          finish_pending_relayout (wayland_popup);
-          break;
-        default:
-          g_assert_not_reached ();
-        }
-    }
+    frame_callback_popup (GDK_WAYLAND_POPUP (surface));
 
   impl->awaiting_frame = FALSE;
   if (impl->awaiting_frame_frozen)
@@ -557,25 +576,6 @@ on_frame_clock_before_paint (GdkFrameClock *clock,
   gdk_surface_apply_state_change (surface);
 }
 
-static void
-configure_popup_geometry (GdkWaylandPopup *wayland_popup)
-{
-  GdkWaylandSurface *wayland_surface = GDK_WAYLAND_SURFACE (wayland_popup);
-  int x, y;
-  int width, height;
-
-  x = wayland_popup->next_layout.x - wayland_surface->shadow_left;
-  y = wayland_popup->next_layout.y - wayland_surface->shadow_top;
-  width =
-    wayland_surface->next_layout.configured_width +
-    (wayland_surface->shadow_left + wayland_surface->shadow_right);
-  height =
-    wayland_surface->next_layout.configured_height +
-    (wayland_surface->shadow_top + wayland_surface->shadow_bottom);
-
-  gdk_wayland_surface_move_resize (GDK_SURFACE (wayland_popup), x, y, width, height);
-}
-
 static void
 configure_drag_surface_geometry (GdkSurface *surface)
 {
@@ -727,7 +727,6 @@ _gdk_wayland_display_create_surface (GdkDisplay     *display,
 {
   GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display);
   GdkSurface *surface;
-  GdkWaylandSurface *impl;
   GdkFrameClock *frame_clock;
 
   if (parent)
@@ -738,16 +737,16 @@ _gdk_wayland_display_create_surface (GdkDisplay     *display,
   switch (surface_type)
     {
     case GDK_SURFACE_TOPLEVEL:
+      g_warn_if_fail (parent == NULL);
       surface = g_object_new (GDK_TYPE_WAYLAND_TOPLEVEL,
                               "display", display,
                               "frame-clock", frame_clock,
                               "title", get_default_title (),
                               NULL);
-      display_wayland->toplevels = g_list_prepend (display_wayland->toplevels,
-                                                   surface);
-      g_warn_if_fail (!parent);
+      display_wayland->toplevels = g_list_prepend (display_wayland->toplevels, surface);
       break;
     case GDK_SURFACE_POPUP:
+      g_warn_if_fail (parent != NULL);
       surface = g_object_new (GDK_TYPE_WAYLAND_POPUP,
                               "parent", parent,
                               "display", display,
@@ -755,6 +754,7 @@ _gdk_wayland_display_create_surface (GdkDisplay     *display,
                               NULL);
       break;
     case GDK_SURFACE_DRAG:
+      g_warn_if_fail (parent == NULL);
       surface = g_object_new (GDK_TYPE_WAYLAND_DRAG_SURFACE,
                               "display", display,
                               "frame-clock", frame_clock,
@@ -765,8 +765,6 @@ _gdk_wayland_display_create_surface (GdkDisplay     *display,
       break;
     }
 
-  impl = GDK_WAYLAND_SURFACE (surface);
-
   if (width > 65535)
     {
       g_warning ("Native Surfaces wider than 65535 pixels are not supported");
@@ -791,7 +789,7 @@ _gdk_wayland_display_create_surface (GdkDisplay     *display,
       GdkMonitor *monitor = g_list_model_get_item (gdk_display_get_monitors (display), 0);
       if (monitor)
         {
-          impl->scale = gdk_monitor_get_scale_factor (monitor);
+          GDK_WAYLAND_SURFACE (surface)->scale = gdk_monitor_get_scale_factor (monitor);
           g_object_unref (monitor);
         }
     }
@@ -874,8 +872,7 @@ gdk_wayland_surface_sync (GdkSurface *surface)
 static gboolean
 gdk_wayland_surface_beep (GdkSurface *surface)
 {
-  gdk_wayland_display_system_bell (gdk_surface_get_display (surface),
-                                   surface);
+  gdk_wayland_display_system_bell (gdk_surface_get_display (surface), surface);
 
   return TRUE;
 }
@@ -933,41 +930,6 @@ gdk_wayland_surface_finalize (GObject *object)
   G_OBJECT_CLASS (gdk_wayland_surface_parent_class)->finalize (object);
 }
 
-static gboolean
-is_realized_shell_surface (GdkWaylandSurface *impl)
-{
-  return (impl->display_server.xdg_surface ||
-          impl->display_server.zxdg_surface_v6);
-}
-
-static gboolean
-is_realized_toplevel (GdkWaylandSurface *impl)
-{
-  GdkWaylandToplevel *toplevel;
-
-  if (!GDK_IS_WAYLAND_TOPLEVEL (impl))
-    return FALSE;
-
-  toplevel = GDK_WAYLAND_TOPLEVEL (impl);
-
-  return (toplevel->display_server.xdg_toplevel ||
-          toplevel->display_server.zxdg_toplevel_v6);
-}
-
-static gboolean
-is_realized_popup (GdkWaylandSurface *impl)
-{
-  GdkWaylandPopup *popup;
-
-  if (!GDK_IS_WAYLAND_POPUP (impl))
-    return FALSE;
-
-  popup = GDK_WAYLAND_POPUP (impl);
-
-  return (popup->display_server.xdg_popup ||
-          popup->display_server.zxdg_popup_v6);
-}
-
 static void
 gdk_wayland_surface_maybe_resize (GdkSurface *surface,
                                   int         width,
@@ -989,7 +951,7 @@ gdk_wayland_surface_maybe_resize (GdkSurface *surface,
    * force the new size onto the compositor. See bug #772505.
    */
 
-  is_xdg_popup = is_realized_popup (impl);
+  is_xdg_popup = GDK_IS_WAYLAND_POPUP (surface);
   is_visible = gdk_surface_get_mapped (surface);
 
   if (is_xdg_popup && is_visible && !impl->initial_configure_received)
@@ -1001,132 +963,11 @@ gdk_wayland_surface_maybe_resize (GdkSurface *surface,
     gdk_wayland_surface_show (surface);
 }
 
-static void
-gdk_wayland_surface_sync_parent (GdkSurface *surface,
-                                 GdkSurface *parent)
-{
-  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
-  GdkWaylandToplevel *toplevel = GDK_WAYLAND_TOPLEVEL (impl);
-  GdkWaylandDisplay *display_wayland =
-    GDK_WAYLAND_DISPLAY (gdk_surface_get_display (surface));
-  GdkWaylandSurface *impl_parent = NULL;
-
-  g_assert (parent == NULL ||
-            gdk_surface_get_display (surface) == gdk_surface_get_display (parent));
-
-  if (!is_realized_toplevel (impl))
-    return;
-
-  if (toplevel->transient_for)
-    impl_parent = GDK_WAYLAND_SURFACE (toplevel->transient_for);
-  else if (parent)
-    impl_parent = GDK_WAYLAND_SURFACE (parent);
-
-  /* XXX: Is this correct? */
-  if (impl_parent && !impl_parent->display_server.wl_surface)
-    return;
-
-  switch (display_wayland->shell_variant)
-    {
-    case GDK_WAYLAND_SHELL_VARIANT_XDG_SHELL:
-      {
-        struct xdg_toplevel *parent_toplevel;
-
-        if (impl_parent)
-          parent_toplevel = GDK_WAYLAND_TOPLEVEL (impl_parent)->display_server.xdg_toplevel;
-        else
-          parent_toplevel = NULL;
-
-        xdg_toplevel_set_parent (GDK_WAYLAND_TOPLEVEL (impl)->display_server.xdg_toplevel, parent_toplevel);
-        break;
-      }
-      break;
-    case GDK_WAYLAND_SHELL_VARIANT_ZXDG_SHELL_V6:
-      {
-        struct zxdg_toplevel_v6 *parent_toplevel;
-
-        if (impl_parent)
-          parent_toplevel = GDK_WAYLAND_TOPLEVEL (impl_parent)->display_server.zxdg_toplevel_v6;
-        else
-          parent_toplevel = NULL;
-
-        zxdg_toplevel_v6_set_parent (GDK_WAYLAND_TOPLEVEL (impl)->display_server.zxdg_toplevel_v6, parent_toplevel);
-        break;
-      }
-    default:
-      g_assert_not_reached ();
-    }
-}
-
-static void
-gdk_wayland_toplevel_sync_parent_of_imported (GdkWaylandToplevel *toplevel)
-{
-  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (toplevel);
-
-  if (!toplevel->imported_transient_for && !toplevel->imported_transient_for_v2)
-    return;
-
-  if (!impl->display_server.wl_surface)
-    return;
-
-  if (toplevel->imported_transient_for)
-    zxdg_imported_v1_set_parent_of (toplevel->imported_transient_for,
-                                    impl->display_server.wl_surface);
-  else
-    zxdg_imported_v2_set_parent_of (toplevel->imported_transient_for_v2,
-                                    impl->display_server.wl_surface);
-}
-
-static void
-gdk_wayland_toplevel_sync_title (GdkWaylandToplevel *toplevel)
-{
-  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (toplevel);
-  GdkWaylandDisplay *display_wayland =
-    GDK_WAYLAND_DISPLAY (gdk_surface_get_display (GDK_SURFACE (toplevel)));
-
-  if (!is_realized_toplevel (impl))
-    return;
-
-  if (!toplevel->title)
-    return;
-
-  switch (display_wayland->shell_variant)
-    {
-    case GDK_WAYLAND_SHELL_VARIANT_XDG_SHELL:
-      xdg_toplevel_set_title (toplevel->display_server.xdg_toplevel, toplevel->title);
-      break;
-    case GDK_WAYLAND_SHELL_VARIANT_ZXDG_SHELL_V6:
-      zxdg_toplevel_v6_set_title (toplevel->display_server.zxdg_toplevel_v6, toplevel->title);
-      break;
-    default:
-      g_assert_not_reached ();
-    }
-}
-
-static void
-gdk_wayland_surface_get_window_geometry (GdkSurface   *surface,
-                                         GdkRectangle *geometry)
-{
-  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
-
-  *geometry = (GdkRectangle) {
-    .x = impl->shadow_left,
-    .y = impl->shadow_top,
-    .width = surface->width - (impl->shadow_left + impl->shadow_right),
-    .height = surface->height - (impl->shadow_top + impl->shadow_bottom)
-  };
-}
-
-static void gdk_wayland_toplevel_set_geometry_hints (GdkWaylandToplevel *toplevel,
-                                                     const GdkGeometry  *geometry,
-                                                     GdkSurfaceHints     geom_mask);
-
 static void
 gdk_wayland_surface_sync_shadow (GdkSurface *surface)
 {
   GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
-  GdkWaylandDisplay *display_wayland =
-    GDK_WAYLAND_DISPLAY (gdk_surface_get_display (surface));
+  GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (gdk_surface_get_display (surface));
   GdkRectangle geometry;
 
   if (!is_realized_shell_surface (impl))
@@ -1136,9 +977,7 @@ gdk_wayland_surface_sync_shadow (GdkSurface *surface)
   if (GDK_IS_WAYLAND_TOPLEVEL (impl))
     {
       GdkWaylandToplevel *toplevel = GDK_WAYLAND_TOPLEVEL (impl);
-      gdk_wayland_toplevel_set_geometry_hints (toplevel,
-                                               &toplevel->geometry_hints,
-                                               toplevel->geometry_mask);
+      gdk_wayland_toplevel_set_geometry_hints (toplevel, NULL, 0);
     }
 
   if (gdk_rectangle_equal (&geometry, &impl->last_sent_window_geometry))
@@ -1167,28 +1006,6 @@ gdk_wayland_surface_sync_shadow (GdkSurface *surface)
   impl->last_sent_window_geometry = geometry;
 }
 
-static struct wl_region *
-wl_region_from_cairo_region (GdkWaylandDisplay *display,
-                             cairo_region_t    *region)
-{
-  struct wl_region *wl_region;
-  int i, n_rects;
-
-  wl_region = wl_compositor_create_region (display->compositor);
-  if (wl_region == NULL)
-    return NULL;
-
-  n_rects = cairo_region_num_rectangles (region);
-  for (i = 0; i < n_rects; i++)
-    {
-      cairo_rectangle_int_t rect;
-      cairo_region_get_rectangle (region, i, &rect);
-      wl_region_add (wl_region, rect.x, rect.y, rect.width, rect.height);
-    }
-
-  return wl_region;
-}
-
 static void
 gdk_wayland_surface_sync_opaque_region (GdkSurface *surface)
 {
@@ -1300,722 +1117,730 @@ gdk_wayland_surface_create_wl_surface (GdkSurface *surface)
 }
 
 static void
-configure_toplevel_geometry (GdkWaylandToplevel *wayland_toplevel)
+maybe_notify_mapped (GdkSurface *surface)
 {
-  GdkSurface *surface = GDK_SURFACE (wayland_toplevel);
-  GdkWaylandSurface *wayland_surface = GDK_WAYLAND_SURFACE (wayland_toplevel);
-  GdkDisplay *display = gdk_surface_get_display (surface);
-  int bounds_width, bounds_height;
-  GdkToplevelSize size;
-  GdkToplevelLayout *layout;
-  GdkGeometry geometry;
-  GdkSurfaceHints mask;
+  if (surface->destroyed)
+    return;
 
-  if (wayland_toplevel->has_bounds)
+  if (!GDK_SURFACE_IS_MAPPED (surface))
+    gdk_surface_set_is_mapped (surface, TRUE);
+}
+
+static void
+gdk_wayland_surface_configure (GdkSurface *surface)
+{
+  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
+
+  if (!impl->initial_configure_received)
     {
-      bounds_width = wayland_toplevel->bounds_width;
-      bounds_height = wayland_toplevel->bounds_height;
+      gdk_surface_thaw_updates (surface);
+      impl->initial_configure_received = TRUE;
+      impl->pending.is_initial_configure = TRUE;
+      maybe_notify_mapped (surface);
     }
-  else
-    {
-      GdkMonitor *monitor;
-      GListModel *monitors;
-      GdkRectangle monitor_geometry, display_geometry = { 0 };
-      guint i;
 
-      monitors = gdk_display_get_monitors (display);
+  impl->has_uncommitted_ack_configure = TRUE;
 
-      for (i = 0; i < g_list_model_get_n_items (monitors); i++)
-        {
-          monitor = g_list_model_get_item (monitors, i);
-          gdk_monitor_get_geometry (monitor, &monitor_geometry);
-          gdk_rectangle_union (&display_geometry, &monitor_geometry, &display_geometry);
-          g_object_unref (monitor);
-        }
+  if (GDK_IS_WAYLAND_POPUP (surface))
+    gdk_wayland_surface_configure_popup (GDK_WAYLAND_POPUP (surface));
+  else if (GDK_IS_WAYLAND_TOPLEVEL (surface))
+    gdk_wayland_surface_configure_toplevel (GDK_WAYLAND_TOPLEVEL (surface));
+  else
+    g_warn_if_reached ();
 
-      bounds_width = display_geometry.width;
-      bounds_height = display_geometry.height;
-    }
+  impl->last_configure_serial = impl->pending.serial;
 
-  gdk_toplevel_size_init (&size, bounds_width, bounds_height);
-  gdk_toplevel_notify_compute_size (GDK_TOPLEVEL (surface), &size);
-  g_warn_if_fail (size.width > 0);
-  g_warn_if_fail (size.height > 0);
+  memset (&impl->pending, 0, sizeof (impl->pending));
+}
 
-  layout = wayland_toplevel->layout;
-  if (gdk_toplevel_layout_get_resizable (layout))
-    {
-      geometry.min_width = size.min_width;
-      geometry.min_height = size.min_height;
-      mask = GDK_HINT_MIN_SIZE;
-    }
-  else
-    {
-      geometry.max_width = geometry.min_width = size.width;
-      geometry.max_height = geometry.min_height = size.height;
-      mask = GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE;
-    }
+static void
+gdk_wayland_surface_handle_configure (GdkWaylandSurface *impl,
+                                      uint32_t           serial)
+{
+  impl->pending.is_dirty = TRUE;
+  impl->pending.serial = serial;
 
-  gdk_wayland_toplevel_set_geometry_hints (wayland_toplevel, &geometry, mask);
+  if (impl->state_freeze_count > 0)
+    return;
 
-  if (size.shadow.is_valid)
-    {
-      wayland_surface->shadow_left = size.shadow.left;
-      wayland_surface->shadow_right = size.shadow.right;
-      wayland_surface->shadow_top = size.shadow.top;
-      wayland_surface->shadow_bottom = size.shadow.bottom;
-    }
+  gdk_wayland_surface_configure (GDK_SURFACE (impl));
+}
 
-  if (wayland_surface->next_layout.configured_width > 0 &&
-      wayland_surface->next_layout.configured_height > 0)
-    {
-      int width, height;
+static void
+gdk_wayland_surface_handle_close (GdkSurface *surface)
+{
+  GdkDisplay *display;
+  GdkEvent *event;
 
-      width = wayland_surface->next_layout.configured_width +
-        wayland_surface->shadow_left + wayland_surface->shadow_right;
-      height = wayland_surface->next_layout.configured_height +
-        wayland_surface->shadow_top + wayland_surface->shadow_bottom;
+  display = gdk_surface_get_display (surface);
 
-      if (wayland_toplevel->next_layout.should_constrain)
-        {
-          gdk_surface_constrain_size (&wayland_toplevel->geometry_hints,
-                                      wayland_toplevel->geometry_mask,
-                                      width, height,
-                                      &width, &height);
-        }
-      gdk_wayland_surface_update_size (surface, width, height, wayland_surface->scale);
+  GDK_DISPLAY_DEBUG (display, EVENTS, "close %p", surface);
 
-      if (!wayland_toplevel->next_layout.size_is_fixed)
-        {
-          wayland_toplevel->next_layout.should_constrain = FALSE;
-          wayland_surface->next_layout.configured_width = 0;
-          wayland_surface->next_layout.configured_height = 0;
-        }
-    }
-  else
-    {
-      int width, height;
+  event = gdk_delete_event_new (surface);
 
-      width = size.width;
-      height = size.height;
-      gdk_surface_constrain_size (&geometry, mask,
-                                  width, height,
-                                  &width, &height);
-      gdk_wayland_surface_update_size (surface, width, height, wayland_surface->scale);
-    }
+  _gdk_wayland_display_deliver_event (display, event);
 }
 
 static void
-synthesize_initial_surface_state (GdkWaylandToplevel *wayland_toplevel,
-                                  GdkToplevelState    unset_flags,
-                                  GdkToplevelState    set_flags)
+xdg_surface_configure (void               *data,
+                       struct xdg_surface *xdg_surface,
+                       uint32_t            serial)
 {
-  wayland_toplevel->initial_state.unset_flags |= unset_flags;
-  wayland_toplevel->initial_state.set_flags &= ~unset_flags;
-
-  wayland_toplevel->initial_state.set_flags |= set_flags;
-  wayland_toplevel->initial_state.unset_flags &= ~set_flags;
+  gdk_wayland_surface_handle_configure (GDK_WAYLAND_SURFACE (data), serial);
 }
 
+static const struct xdg_surface_listener xdg_surface_listener = {
+  xdg_surface_configure,
+};
+
 static void
-gdk_wayland_surface_configure_toplevel (GdkWaylandToplevel *wayland_toplevel)
+zxdg_surface_v6_configure (void                   *data,
+                           struct zxdg_surface_v6 *xdg_surface,
+                           uint32_t                serial)
 {
-  GdkSurface *surface = GDK_SURFACE (wayland_toplevel);
-  GdkWaylandSurface *wayland_surface = GDK_WAYLAND_SURFACE (wayland_toplevel);
-  GdkWaylandDisplay *display_wayland =
-    GDK_WAYLAND_DISPLAY (gdk_surface_get_display (surface));
-  GdkToplevelState new_state;
-  int width, height;
-  gboolean is_resizing;
-  gboolean fixed_size;
-  gboolean was_fixed_size;
-  gboolean saved_size;
+  gdk_wayland_surface_handle_configure (GDK_WAYLAND_SURFACE (data), serial);
+}
 
-  new_state = wayland_toplevel->pending.state;
-  wayland_toplevel->pending.state = 0;
+static const struct zxdg_surface_v6_listener zxdg_surface_v6_listener = {
+  zxdg_surface_v6_configure,
+};
 
-  is_resizing = wayland_toplevel->pending.is_resizing;
-  wayland_toplevel->pending.is_resizing = FALSE;
+static void
+gdk_wayland_surface_create_xdg_surface_resources (GdkSurface *surface)
+{
+  GdkWaylandDisplay *display =
+    GDK_WAYLAND_DISPLAY (gdk_surface_get_display (surface));
+  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
 
-  if (wayland_toplevel->pending.has_bounds)
+  switch (display->shell_variant)
     {
-      wayland_toplevel->bounds_width = wayland_toplevel->pending.bounds_width;
-      wayland_toplevel->bounds_height = wayland_toplevel->pending.bounds_height;
-      wayland_toplevel->has_bounds = TRUE;
+    case GDK_WAYLAND_SHELL_VARIANT_XDG_SHELL:
+      impl->display_server.xdg_surface =
+        xdg_wm_base_get_xdg_surface (display->xdg_wm_base,
+                                     impl->display_server.wl_surface);
+      wl_proxy_set_queue ((struct wl_proxy *) impl->display_server.xdg_surface,
+                          impl->event_queue);
+      xdg_surface_add_listener (impl->display_server.xdg_surface,
+                                &xdg_surface_listener,
+                                surface);
+      break;
+    case GDK_WAYLAND_SHELL_VARIANT_ZXDG_SHELL_V6:
+      impl->display_server.zxdg_surface_v6 =
+        zxdg_shell_v6_get_xdg_surface (display->zxdg_shell_v6,
+                                       impl->display_server.wl_surface);
+      zxdg_surface_v6_add_listener (impl->display_server.zxdg_surface_v6,
+                                    &zxdg_surface_v6_listener,
+                                    surface);
+      break;
+    default:
+      g_assert_not_reached ();
     }
+}
 
-  fixed_size =
-    new_state & (GDK_TOPLEVEL_STATE_MAXIMIZED |
-                 GDK_TOPLEVEL_STATE_FULLSCREEN |
-                 GDK_TOPLEVEL_STATE_TILED) ||
-    is_resizing;
-
-  was_fixed_size =
-    surface->state & (GDK_TOPLEVEL_STATE_MAXIMIZED |
-                      GDK_TOPLEVEL_STATE_FULLSCREEN |
-                      GDK_TOPLEVEL_STATE_TILED);
+static void
+gdk_wayland_surface_map_toplevel (GdkSurface *surface)
+{
+  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
 
-  width = wayland_toplevel->pending.width;
-  height = wayland_toplevel->pending.height;
+  if (!GDK_IS_WAYLAND_TOPLEVEL (surface))
+    return;
 
-  saved_size = (width == 0 && height == 0);
-  /* According to xdg_shell, an xdg_surface.configure with size 0x0
-   * should be interpreted as that it is up to the client to set a
-   * size.
-   *
-   * When transitioning from maximize or fullscreen state, this means
-   * the client should configure its size back to what it was before
-   * being maximize or fullscreen.
-   */
-  if (saved_size && !fixed_size && was_fixed_size)
-    {
-      width = wayland_surface->saved_width;
-      height = wayland_surface->saved_height;
-    }
+  if (impl->mapped)
+    return;
 
-  if (width > 0 && height > 0)
-    {
-      if (!saved_size)
-        {
-          wayland_toplevel->next_layout.should_constrain = TRUE;
+  gdk_wayland_surface_create_xdg_toplevel (GDK_WAYLAND_TOPLEVEL (surface));
 
-          /* Save size for next time we get 0x0 */
-          _gdk_wayland_surface_save_size (surface);
-        }
-      else if (is_resizing)
-        {
-          wayland_toplevel->next_layout.should_constrain = TRUE;
-        }
-      else
-        {
-          wayland_toplevel->next_layout.should_constrain = FALSE;
-        }
+  impl->mapped = TRUE;
+}
 
-      wayland_toplevel->next_layout.size_is_fixed = fixed_size;
-      wayland_surface->next_layout.configured_width = width;
-      wayland_surface->next_layout.configured_height = height;
-    }
-  else
-    {
-      wayland_toplevel->next_layout.should_constrain = FALSE;
-      wayland_toplevel->next_layout.size_is_fixed = FALSE;
-      wayland_surface->next_layout.configured_width = 0;
-      wayland_surface->next_layout.configured_height = 0;
-    }
+static void
+gdk_wayland_surface_show (GdkSurface *surface)
+{
+  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
 
-  wayland_surface->next_layout.surface_geometry_dirty = TRUE;
-  gdk_surface_request_layout (surface);
+  if (!impl->display_server.wl_surface)
+    gdk_wayland_surface_create_wl_surface (surface);
 
-  GDK_DISPLAY_DEBUG (gdk_surface_get_display (surface), EVENTS,
-                     "configure, surface %p %dx%d,%s%s%s%s",
-                     surface, width, height,
-                     (new_state & GDK_TOPLEVEL_STATE_FULLSCREEN) ? " fullscreen" : "",
-                     (new_state & GDK_TOPLEVEL_STATE_MAXIMIZED) ? " maximized" : "",
-                     (new_state & GDK_TOPLEVEL_STATE_FOCUSED) ? " focused" : "",
-                     (new_state & GDK_TOPLEVEL_STATE_TILED) ? " tiled" : "");
+  gdk_wayland_surface_map_toplevel (surface);
+}
 
-  gdk_surface_queue_state_change (surface, ~0 & ~new_state, new_state);
+static void
+unmap_popups_for_surface (GdkSurface *surface)
+{
+  GdkWaylandDisplay *display_wayland;
+  GList *l;
 
-  switch (display_wayland->shell_variant)
+  display_wayland = GDK_WAYLAND_DISPLAY (gdk_surface_get_display (surface));
+  for (l = display_wayland->current_popups; l; l = l->next)
     {
-    case GDK_WAYLAND_SHELL_VARIANT_XDG_SHELL:
-      xdg_surface_ack_configure (wayland_surface->display_server.xdg_surface,
-                                 wayland_surface->pending.serial);
-      break;
-    case GDK_WAYLAND_SHELL_VARIANT_ZXDG_SHELL_V6:
-      zxdg_surface_v6_ack_configure (wayland_surface->display_server.zxdg_surface_v6,
-                                     wayland_surface->pending.serial);
-      break;
-    default:
-      g_assert_not_reached ();
+       GdkSurface *popup = l->data;
+
+       if (popup->parent == surface)
+         {
+           g_warning ("Tried to unmap the parent of a popup");
+           gdk_surface_hide (popup);
+
+           return;
+         }
     }
 }
 
 static void
-gdk_wayland_surface_configure_popup (GdkWaylandPopup *wayland_popup)
+gdk_wayland_surface_hide_surface (GdkSurface *surface)
 {
-  GdkSurface *surface = GDK_SURFACE (wayland_popup);
-  GdkWaylandSurface *wayland_surface = GDK_WAYLAND_SURFACE (wayland_popup);
-  GdkRectangle parent_geometry;
-  int x, y, width, height;
+  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
 
-  if (wayland_popup->display_server.xdg_popup)
-    {
-      xdg_surface_ack_configure (wayland_surface->display_server.xdg_surface,
-                                 wayland_surface->pending.serial);
-    }
-  else if (wayland_popup->display_server.zxdg_popup_v6)
+  unmap_popups_for_surface (surface);
+
+  if (impl->display_server.wl_surface)
     {
-      zxdg_surface_v6_ack_configure (wayland_surface->display_server.zxdg_surface_v6,
-                                     wayland_surface->pending.serial);
-    }
+      if (impl->display_server.egl_window)
+        {
+          gdk_surface_set_egl_native_window (surface, NULL);
+          wl_egl_window_destroy (impl->display_server.egl_window);
+          impl->display_server.egl_window = NULL;
+        }
 
-  if (wayland_popup->pending.has_repositioned_token)
-    wayland_popup->received_reposition_token = wayland_popup->pending.repositioned_token;
+      if (impl->display_server.xdg_surface)
+        {
+          xdg_surface_destroy (impl->display_server.xdg_surface);
+          impl->display_server.xdg_surface = NULL;
+          if (!impl->initial_configure_received)
+            gdk_surface_thaw_updates (surface);
+          else
+            impl->initial_configure_received = FALSE;
+        }
+      if (impl->display_server.zxdg_surface_v6)
+        {
+          g_clear_pointer (&impl->display_server.zxdg_surface_v6, zxdg_surface_v6_destroy);
+          if (!impl->initial_configure_received)
+            gdk_surface_thaw_updates (surface);
+          else
+            impl->initial_configure_received = FALSE;
+        }
 
-  switch (wayland_popup->state)
-    {
-    case POPUP_STATE_WAITING_FOR_REPOSITIONED:
-      if (wayland_popup->received_reposition_token != wayland_popup->reposition_token)
-        return;
-      else
-        gdk_surface_thaw_updates (surface);
-      G_GNUC_FALLTHROUGH;
-    case POPUP_STATE_WAITING_FOR_CONFIGURE:
-      wayland_popup->state = POPUP_STATE_WAITING_FOR_FRAME;
-      break;
-    case POPUP_STATE_IDLE:
-    case POPUP_STATE_WAITING_FOR_FRAME:
-      break;
-    default:
-      g_assert_not_reached ();
+      impl->awaiting_frame = FALSE;
+      if (impl->awaiting_frame_frozen)
+        {
+          impl->awaiting_frame_frozen = FALSE;
+          gdk_surface_thaw_updates (surface);
+        }
+
+      if (GDK_IS_WAYLAND_TOPLEVEL (surface))
+        gdk_wayland_toplevel_hide_surface (GDK_WAYLAND_TOPLEVEL (surface));
+
+      if (GDK_IS_WAYLAND_POPUP (surface))
+        gdk_wayland_popup_hide_surface (GDK_WAYLAND_POPUP (surface));
+
+      g_clear_pointer (&impl->display_server.wl_surface, wl_surface_destroy);
+
+      g_slist_free (impl->display_server.outputs);
+      impl->display_server.outputs = NULL;
     }
 
-  x = wayland_popup->pending.x;
-  y = wayland_popup->pending.y;
-  width = wayland_popup->pending.width;
-  height = wayland_popup->pending.height;
+  impl->has_uncommitted_ack_configure = FALSE;
+  impl->input_region_dirty = TRUE;
+  impl->opaque_region_dirty = TRUE;
 
-  gdk_wayland_surface_get_window_geometry (surface->parent, &parent_geometry);
-  x += parent_geometry.x;
-  y += parent_geometry.y;
+  unset_transient_for_exported (surface);
 
-  update_popup_layout_state (wayland_popup,
-                             x, y,
-                             width, height,
-                             wayland_popup->layout);
+  impl->last_sent_window_geometry = (GdkRectangle) { 0 };
 
-  wayland_popup->next_layout.x = x;
-  wayland_popup->next_layout.y = y;
-  wayland_surface->next_layout.configured_width = width;
-  wayland_surface->next_layout.configured_height = height;
-  wayland_surface->next_layout.surface_geometry_dirty = TRUE;
-  gdk_surface_request_layout (surface);
+  _gdk_wayland_surface_clear_saved_size (surface);
+  impl->mapped = FALSE;
 }
 
 static void
-maybe_notify_mapped (GdkSurface *surface)
+gdk_wayland_surface_hide (GdkSurface *surface)
 {
-  if (surface->destroyed)
-    return;
+  GdkSeat *seat;
 
-  if (!GDK_SURFACE_IS_MAPPED (surface))
-    gdk_surface_set_is_mapped (surface, TRUE);
+  seat = gdk_display_get_default_seat (surface->display);
+  if (seat)
+    {
+      if (surface->autohide)
+        gdk_seat_ungrab (seat);
+
+      gdk_wayland_seat_clear_touchpoints (GDK_WAYLAND_SEAT (seat), surface);
+    }
+  gdk_wayland_surface_hide_surface (surface);
+  _gdk_surface_clear_update_area (surface);
 }
 
 static void
-gdk_wayland_surface_configure (GdkSurface *surface)
+gdk_wayland_surface_move_resize (GdkSurface *surface,
+                                 int         x,
+                                 int         y,
+                                 int         width,
+                                 int         height)
 {
   GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
 
-  if (!impl->initial_configure_received)
-    {
-      gdk_surface_thaw_updates (surface);
-      impl->initial_configure_received = TRUE;
-      impl->pending.is_initial_configure = TRUE;
-      maybe_notify_mapped (surface);
-    }
-
-  impl->has_uncommitted_ack_configure = TRUE;
+  surface->x = x;
+  surface->y = y;
+  gdk_wayland_surface_maybe_resize (surface, width, height, impl->scale);
+}
 
-  if (is_realized_popup (impl))
-    {
-      g_assert (GDK_IS_WAYLAND_POPUP (surface));
-      GdkWaylandPopup *wayland_popup = GDK_WAYLAND_POPUP (surface);
-      gdk_wayland_surface_configure_popup (wayland_popup);
-    }
-  else if (is_realized_toplevel (impl))
+static void
+gdk_wayland_surface_get_geometry (GdkSurface *surface,
+                                  int        *x,
+                                  int        *y,
+                                  int        *width,
+                                  int        *height)
+{
+  if (!GDK_SURFACE_DESTROYED (surface))
     {
-      g_assert (GDK_IS_WAYLAND_TOPLEVEL (surface));
-      GdkWaylandToplevel *wayland_toplevel = GDK_WAYLAND_TOPLEVEL (surface);
-      gdk_wayland_surface_configure_toplevel (wayland_toplevel);
+      if (x)
+        *x = surface->x;
+      if (y)
+        *y = surface->y;
+      if (width)
+        *width = surface->width;
+      if (height)
+        *height = surface->height;
     }
-  else
-    g_warn_if_reached ();
-
-  impl->last_configure_serial = impl->pending.serial;
-
-  memset (&impl->pending, 0, sizeof (impl->pending));
 }
 
 static void
-gdk_wayland_surface_handle_configure (GdkSurface *surface,
-                                      uint32_t    serial)
+gdk_wayland_surface_get_root_coords (GdkSurface *surface,
+                                     int         x,
+                                     int         y,
+                                     int        *root_x,
+                                     int        *root_y)
 {
-  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
-
-  impl->pending.is_dirty = TRUE;
-  impl->pending.serial = serial;
+  /*
+   * Wayland does not have a global coordinate space shared between surfaces. In
+   * fact, for regular toplevels, we have no idea where our surfaces are
+   * positioned, relatively.
+   *
+   * However, there are some cases like popups and subsurfaces where we do have
+   * some amount of control over the placement of our surface, and we can
+   * semi-accurately control the x/y position of these surfaces, if they are
+   * relative to another surface.
+   *
+   * To pretend we have something called a root coordinate space, assume all
+   * parent-less surfaces are positioned in (0, 0), and all relative positioned
+   * popups and subsurfaces are placed within this fake root coordinate space.
+   *
+   * For example a 200x200 large toplevel surface will have the position (0, 0).
+   * If a popup positioned in the middle of the toplevel will have the fake
+   * position (100,100). Furthermore, if a positioned is placed in the middle
+   * that popup, will have the fake position (150,150), even though it has the
+   * relative position (50,50). These three surfaces would make up one single
+   * fake root coordinate space.
+   */
 
-  if (impl->state_freeze_count > 0)
-    return;
+  if (root_x)
+    *root_x = surface->x + x;
 
-  gdk_wayland_surface_configure (surface);
+  if (root_y)
+    *root_y = surface->y + y;
 }
 
-static void
-gdk_wayland_surface_handle_configure_toplevel (GdkSurface      *surface,
-                                               int32_t          width,
-                                               int32_t          height,
-                                               GdkToplevelState  state)
+static gboolean
+gdk_wayland_surface_get_device_state (GdkSurface       *surface,
+                                      GdkDevice        *device,
+                                      double           *x,
+                                      double           *y,
+                                      GdkModifierType  *mask)
 {
-  GdkWaylandToplevel *toplevel = GDK_WAYLAND_TOPLEVEL (surface);
+  if (GDK_SURFACE_DESTROYED (surface))
+    return FALSE;
 
-  toplevel->pending.state |= state;
-  toplevel->pending.width = width;
-  toplevel->pending.height = height;
+  gdk_wayland_device_query_state (device, surface, x, y, mask);
+
+  return *x >= 0 && *y >= 0 && *x < surface->width && *y < surface->height;
 }
 
 static void
-gdk_wayland_surface_handle_close (GdkSurface *surface)
+gdk_wayland_surface_set_input_region (GdkSurface     *surface,
+                                      cairo_region_t *input_region)
 {
-  GdkDisplay *display;
-  GdkEvent *event;
+  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
 
-  display = gdk_surface_get_display (surface);
+  if (GDK_SURFACE_DESTROYED (surface))
+    return;
 
-  GDK_DISPLAY_DEBUG (display, EVENTS, "close %p", surface);
+  g_clear_pointer (&impl->input_region, cairo_region_destroy);
 
-  event = gdk_delete_event_new (surface);
+  if (input_region)
+    impl->input_region = cairo_region_copy (input_region);
 
-  _gdk_wayland_display_deliver_event (display, event);
+  impl->input_region_dirty = TRUE;
 }
 
 static void
-xdg_surface_configure (void               *data,
-                       struct xdg_surface *xdg_surface,
-                       uint32_t            serial)
+gdk_wayland_surface_destroy (GdkSurface *surface,
+                             gboolean    foreign_destroy)
 {
-  GdkSurface *surface = GDK_SURFACE (data);
+  GdkWaylandDisplay *display;
+  GdkFrameClock *frame_clock;
 
-  gdk_wayland_surface_handle_configure (surface, serial);
-}
+  g_return_if_fail (GDK_IS_SURFACE (surface));
 
-static const struct xdg_surface_listener xdg_surface_listener = {
-  xdg_surface_configure,
-};
+  /* Wayland surfaces can't be externally destroyed; we may possibly
+   * eventually want to use this path at display close-down
+   */
+  g_return_if_fail (!foreign_destroy);
 
-static void
-zxdg_surface_v6_configure (void                   *data,
-                           struct zxdg_surface_v6 *xdg_surface,
-                           uint32_t                serial)
-{
-  GdkSurface *surface = GDK_SURFACE (data);
+  gdk_wayland_surface_hide_surface (surface);
 
-  gdk_wayland_surface_handle_configure (surface, serial);
-}
+  frame_clock = gdk_surface_get_frame_clock (surface);
+  g_signal_handlers_disconnect_by_func (frame_clock, on_frame_clock_before_paint, surface);
+  g_signal_handlers_disconnect_by_func (frame_clock, on_frame_clock_after_paint, surface);
 
-static const struct zxdg_surface_v6_listener zxdg_surface_v6_listener = {
-  zxdg_surface_v6_configure,
-};
+  display = GDK_WAYLAND_DISPLAY (gdk_surface_get_display (surface));
+  display->toplevels = g_list_remove (display->toplevels, surface);
+}
 
 static void
-gdk_wayland_surface_create_xdg_surface_resources (GdkSurface *surface)
+gdk_wayland_surface_destroy_notify (GdkSurface *surface)
 {
-  GdkWaylandDisplay *display =
-    GDK_WAYLAND_DISPLAY (gdk_surface_get_display (surface));
-  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
-
-  switch (display->shell_variant)
+  if (!GDK_SURFACE_DESTROYED (surface))
     {
-    case GDK_WAYLAND_SHELL_VARIANT_XDG_SHELL:
-      impl->display_server.xdg_surface =
-        xdg_wm_base_get_xdg_surface (display->xdg_wm_base,
-                                     impl->display_server.wl_surface);
-      wl_proxy_set_queue ((struct wl_proxy *) impl->display_server.xdg_surface,
-                          impl->event_queue);
-      xdg_surface_add_listener (impl->display_server.xdg_surface,
-                                &xdg_surface_listener,
-                                surface);
-      break;
-    case GDK_WAYLAND_SHELL_VARIANT_ZXDG_SHELL_V6:
-      impl->display_server.zxdg_surface_v6 =
-        zxdg_shell_v6_get_xdg_surface (display->zxdg_shell_v6,
-                                       impl->display_server.wl_surface);
-      zxdg_surface_v6_add_listener (impl->display_server.zxdg_surface_v6,
-                                    &zxdg_surface_v6_listener,
-                                    surface);
-      break;
-    default:
-      g_assert_not_reached ();
+      g_warning ("GdkSurface %p unexpectedly destroyed", surface);
+      _gdk_surface_destroy (surface, TRUE);
     }
+
+  g_object_unref (surface);
 }
 
-static void
-xdg_toplevel_configure (void                *data,
-                        struct xdg_toplevel *xdg_toplevel,
-                        int32_t              width,
-                        int32_t              height,
-                        struct wl_array     *states)
+static int
+gdk_wayland_surface_get_scale_factor (GdkSurface *surface)
 {
-  GdkSurface *surface = GDK_SURFACE (data);
-  GdkWaylandToplevel *toplevel = GDK_WAYLAND_TOPLEVEL (surface);
-  uint32_t *p;
-  GdkToplevelState pending_state = 0;
-
-  toplevel->pending.is_resizing = FALSE;
-
-  wl_array_for_each (p, states)
-    {
-      uint32_t state = *p;
+  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
 
-      switch (state)
-        {
-        case XDG_TOPLEVEL_STATE_FULLSCREEN:
-          pending_state |= GDK_TOPLEVEL_STATE_FULLSCREEN;
-          break;
-        case XDG_TOPLEVEL_STATE_MAXIMIZED:
-          pending_state |= GDK_TOPLEVEL_STATE_MAXIMIZED;
-          break;
-        case XDG_TOPLEVEL_STATE_ACTIVATED:
-          pending_state |= GDK_TOPLEVEL_STATE_FOCUSED;
-          break;
-        case XDG_TOPLEVEL_STATE_RESIZING:
-          toplevel->pending.is_resizing = TRUE;
-          break;
-        case XDG_TOPLEVEL_STATE_TILED_TOP:
-          pending_state |= (GDK_TOPLEVEL_STATE_TILED |
-                            GDK_TOPLEVEL_STATE_TOP_TILED);
-          break;
-        case XDG_TOPLEVEL_STATE_TILED_RIGHT:
-          pending_state |= (GDK_TOPLEVEL_STATE_TILED |
-                            GDK_TOPLEVEL_STATE_RIGHT_TILED);
-          break;
-        case XDG_TOPLEVEL_STATE_TILED_BOTTOM:
-          pending_state |= (GDK_TOPLEVEL_STATE_TILED |
-                            GDK_TOPLEVEL_STATE_BOTTOM_TILED);
-          break;
-        case XDG_TOPLEVEL_STATE_TILED_LEFT:
-          pending_state |= (GDK_TOPLEVEL_STATE_TILED |
-                            GDK_TOPLEVEL_STATE_LEFT_TILED);
-          break;
-        default:
-          /* Unknown state */
-          break;
-        }
-    }
+  if (GDK_SURFACE_DESTROYED (surface))
+    return 1;
 
-  gdk_wayland_surface_handle_configure_toplevel (surface, width, height, pending_state);
+  return impl->scale;
 }
 
 static void
-xdg_toplevel_close (void                *data,
-                    struct xdg_toplevel *xdg_toplevel)
+gdk_wayland_surface_set_opaque_region (GdkSurface     *surface,
+                                       cairo_region_t *region)
 {
-  GdkSurface *surface = GDK_SURFACE (data);
+  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
 
-  gdk_wayland_surface_handle_close (surface);
+  if (GDK_SURFACE_DESTROYED (surface))
+    return;
+
+  g_clear_pointer (&impl->opaque_region, cairo_region_destroy);
+  impl->opaque_region = cairo_region_reference (region);
+  impl->opaque_region_dirty = TRUE;
 }
 
 static void
-xdg_toplevel_configure_bounds (void                *data,
-                               struct xdg_toplevel *xdg_toplevel,
-                               int32_t              width,
-                               int32_t              height)
+gdk_wayland_surface_class_init (GdkWaylandSurfaceClass *klass)
 {
-  GdkSurface *surface = GDK_SURFACE (data);
-  GdkWaylandToplevel *toplevel = GDK_WAYLAND_TOPLEVEL (surface);
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GdkSurfaceClass *impl_class = GDK_SURFACE_CLASS (klass);
 
-  toplevel->pending.bounds_width = width;
-  toplevel->pending.bounds_height = height;
-  toplevel->pending.has_bounds = TRUE;
+  object_class->constructed = gdk_wayland_surface_constructed;
+  object_class->dispose = gdk_wayland_surface_dispose;
+  object_class->finalize = gdk_wayland_surface_finalize;
+
+  impl_class->hide = gdk_wayland_surface_hide;
+  impl_class->get_geometry = gdk_wayland_surface_get_geometry;
+  impl_class->get_root_coords = gdk_wayland_surface_get_root_coords;
+  impl_class->get_device_state = gdk_wayland_surface_get_device_state;
+  impl_class->set_input_region = gdk_wayland_surface_set_input_region;
+  impl_class->destroy = gdk_wayland_surface_destroy;
+  impl_class->beep = gdk_wayland_surface_beep;
+
+  impl_class->destroy_notify = gdk_wayland_surface_destroy_notify;
+  impl_class->drag_begin = _gdk_wayland_surface_drag_begin;
+  impl_class->get_scale_factor = gdk_wayland_surface_get_scale_factor;
+  impl_class->set_opaque_region = gdk_wayland_surface_set_opaque_region;
+  impl_class->request_layout = gdk_wayland_surface_request_layout;
+  impl_class->compute_size = gdk_wayland_surface_compute_size;
 }
 
-static const struct xdg_toplevel_listener xdg_toplevel_listener = {
-  xdg_toplevel_configure,
-  xdg_toplevel_close,
-  xdg_toplevel_configure_bounds,
-};
+/* }}} */
+/* {{{ Private Surface API */
 
-static void
-create_xdg_toplevel_resources (GdkWaylandToplevel *toplevel)
+struct wl_output *
+gdk_wayland_surface_get_wl_output (GdkSurface *surface)
 {
-  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (toplevel);
+  GdkWaylandSurface *impl;
 
-  toplevel->display_server.xdg_toplevel =
-    xdg_surface_get_toplevel (impl->display_server.xdg_surface);
-  xdg_toplevel_add_listener (toplevel->display_server.xdg_toplevel,
-                             &xdg_toplevel_listener,
-                             toplevel);
+  g_return_val_if_fail (GDK_IS_WAYLAND_SURFACE (surface), NULL);
+
+  impl = GDK_WAYLAND_SURFACE (surface);
+  /* We pick the head of the list as this is the last entered output */
+  if (impl->display_server.outputs)
+    return (struct wl_output *) impl->display_server.outputs->data;
+
+  return NULL;
 }
 
-static void
-zxdg_toplevel_v6_configure (void                    *data,
-                            struct zxdg_toplevel_v6 *xdg_toplevel,
-                            int32_t                  width,
-                            int32_t                  height,
-                            struct wl_array         *states)
+void
+_gdk_wayland_surface_offset_next_wl_buffer (GdkSurface *surface,
+                                            int         x,
+                                            int         y)
 {
-  GdkSurface *surface = GDK_SURFACE (data);
-  GdkWaylandToplevel *toplevel = GDK_WAYLAND_TOPLEVEL (surface);
-  uint32_t *p;
-  GdkToplevelState pending_state = 0;
-
-  toplevel->pending.is_resizing = FALSE;
+  GdkWaylandSurface *impl;
 
-  wl_array_for_each (p, states)
-    {
-      uint32_t state = *p;
+  g_return_if_fail (GDK_IS_WAYLAND_SURFACE (surface));
 
-      switch (state)
-        {
-        case ZXDG_TOPLEVEL_V6_STATE_FULLSCREEN:
-          pending_state |= GDK_TOPLEVEL_STATE_FULLSCREEN;
-          break;
-        case ZXDG_TOPLEVEL_V6_STATE_MAXIMIZED:
-          pending_state |= GDK_TOPLEVEL_STATE_MAXIMIZED;
-          break;
-        case ZXDG_TOPLEVEL_V6_STATE_ACTIVATED:
-          pending_state |= GDK_TOPLEVEL_STATE_FOCUSED;
-          break;
-        case ZXDG_TOPLEVEL_V6_STATE_RESIZING:
-          toplevel->pending.is_resizing = TRUE;
-          break;
-        default:
-          /* Unknown state */
-          break;
-        }
-    }
+  impl = GDK_WAYLAND_SURFACE (surface);
 
-  gdk_wayland_surface_handle_configure_toplevel (surface, width, height, pending_state);
+  impl->pending_buffer_offset_x = x;
+  impl->pending_buffer_offset_y = y;
 }
 
-static void
-zxdg_toplevel_v6_close (void                    *data,
-                        struct zxdg_toplevel_v6 *xdg_toplevel)
+void
+gdk_wayland_surface_ensure_wl_egl_window (GdkSurface *surface)
 {
-  GdkSurface *surface = GDK_SURFACE (data);
+  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
 
-  gdk_wayland_surface_handle_close (surface);
+  if (impl->display_server.egl_window == NULL)
+    {
+      impl->display_server.egl_window =
+        wl_egl_window_create (impl->display_server.wl_surface,
+                              surface->width * impl->scale,
+                              surface->height * impl->scale);
+      wl_surface_set_buffer_scale (impl->display_server.wl_surface, impl->scale);
+
+      gdk_surface_set_egl_native_window (surface, impl->display_server.egl_window);
+    }
 }
 
-static const struct zxdg_toplevel_v6_listener zxdg_toplevel_v6_listener = {
-  zxdg_toplevel_v6_configure,
-  zxdg_toplevel_v6_close,
-};
+/* }}} */
+/* {{{ Surface API */
 
-static void
-create_zxdg_toplevel_v6_resources (GdkWaylandToplevel *toplevel)
+/**
+ * gdk_wayland_surface_get_wl_surface: (skip)
+ * @surface: (type GdkWaylandSurface): a `GdkSurface`
+ *
+ * Returns the Wayland `wl_surface` of a `GdkSurface`.
+ *
+ * Returns: (transfer none): a Wayland `wl_surface`
+ */
+struct wl_surface *
+gdk_wayland_surface_get_wl_surface (GdkSurface *surface)
 {
-  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (toplevel);
+  g_return_val_if_fail (GDK_IS_WAYLAND_SURFACE (surface), NULL);
 
-  toplevel->display_server.zxdg_toplevel_v6 =
-    zxdg_surface_v6_get_toplevel (impl->display_server.zxdg_surface_v6);
-  zxdg_toplevel_v6_add_listener (toplevel->display_server.zxdg_toplevel_v6,
-                                 &zxdg_toplevel_v6_listener,
-                                 toplevel);
+  return GDK_WAYLAND_SURFACE (surface)->display_server.wl_surface;
 }
 
+/* }}}} */
+/* {{{ GdkWaylandPopup definition */
+
 /**
- * gdk_wayland_toplevel_set_application_id:
- * @toplevel: (type GdkWaylandToplevel): a `GdkToplevel`
- * @application_id: the application id for the @toplevel
+ * GdkWaylandPopup:
  *
- * Sets the application id on a `GdkToplevel`.
+ * The Wayland implementation of `GdkPopup`.
  */
-void
-gdk_wayland_toplevel_set_application_id (GdkToplevel *toplevel,
-                                         const char  *application_id)
+
+struct _GdkWaylandPopup
 {
-  GdkWaylandToplevel *wayland_toplevel = GDK_WAYLAND_TOPLEVEL (toplevel);
-  GdkWaylandSurface *impl;
-  GdkWaylandDisplay *display_wayland;
+  GdkWaylandSurface parent_instance;
 
-  g_return_if_fail (GDK_IS_WAYLAND_TOPLEVEL (toplevel));
+  struct {
+    struct xdg_popup *xdg_popup;
+    struct zxdg_popup_v6 *zxdg_popup_v6;
+  } display_server;
 
-  g_return_if_fail (application_id != NULL);
+  PopupState state;
+  unsigned int thaw_upon_show : 1;
+  GdkPopupLayout *layout;
+  int unconstrained_width;
+  int unconstrained_height;
 
-  if (GDK_SURFACE_DESTROYED (toplevel))
-    return;
+  struct {
+    int x;
+    int y;
+    int width;
+    int height;
+    uint32_t repositioned_token;
+    gboolean has_repositioned_token;
+  } pending;
 
-  impl = GDK_WAYLAND_SURFACE (toplevel);
+  struct {
+    int x;
+    int y;
+  } next_layout;
 
-  if (!is_realized_toplevel (impl))
-    return;
+  uint32_t reposition_token;
+  uint32_t received_reposition_token;
 
-  wayland_toplevel = GDK_WAYLAND_TOPLEVEL (toplevel);
-  display_wayland = GDK_WAYLAND_DISPLAY (gdk_surface_get_display (GDK_SURFACE (toplevel)));
+  GdkSeat *grab_input_seat;
+};
 
-  switch (display_wayland->shell_variant)
+typedef struct
+{
+  GdkWaylandSurfaceClass parent_class;
+} GdkWaylandPopupClass;
+
+static void gdk_wayland_popup_iface_init (GdkPopupInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GdkWaylandPopup, gdk_wayland_popup, GDK_TYPE_WAYLAND_SURFACE,
+                         G_IMPLEMENT_INTERFACE (GDK_TYPE_POPUP,
+                                                gdk_wayland_popup_iface_init))
+
+/* }}} */
+/* {{{ Popup implementation */
+
+static void
+gdk_wayland_popup_hide_surface (GdkWaylandPopup *popup)
+{
+  GdkSurface *surface = GDK_SURFACE (popup);
+  GdkDisplay *display = gdk_surface_get_display (surface);
+  GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display);
+
+  g_clear_pointer (&popup->display_server.xdg_popup, xdg_popup_destroy);
+  g_clear_pointer (&popup->display_server.zxdg_popup_v6, zxdg_popup_v6_destroy);
+  display_wayland->current_popups =
+      g_list_remove (display_wayland->current_popups, surface);
+  display_wayland->current_grabbing_popups =
+      g_list_remove (display_wayland->current_grabbing_popups, surface);
+
+  popup->thaw_upon_show = TRUE;
+  gdk_surface_freeze_updates (surface);
+
+  switch (popup->state)
     {
-    case GDK_WAYLAND_SHELL_VARIANT_XDG_SHELL:
-      xdg_toplevel_set_app_id (wayland_toplevel->display_server.xdg_toplevel, application_id);
+    case POPUP_STATE_WAITING_FOR_REPOSITIONED:
+      gdk_surface_thaw_updates (surface);
+      G_GNUC_FALLTHROUGH;
+    case POPUP_STATE_WAITING_FOR_CONFIGURE:
+    case POPUP_STATE_WAITING_FOR_FRAME:
+      thaw_popup_toplevel_state (popup);
       break;
-    case GDK_WAYLAND_SHELL_VARIANT_ZXDG_SHELL_V6:
-      zxdg_toplevel_v6_set_app_id (wayland_toplevel->display_server.zxdg_toplevel_v6, application_id);
+    case POPUP_STATE_IDLE:
       break;
     default:
       g_assert_not_reached ();
     }
+
+  popup->state = POPUP_STATE_IDLE;
+
+  g_clear_pointer (&popup->layout, gdk_popup_layout_unref);
+}
+
+static gboolean
+is_realized_popup (GdkWaylandSurface *impl)
+{
+  GdkWaylandPopup *popup;
+
+  if (!GDK_IS_WAYLAND_POPUP (impl))
+    return FALSE;
+
+  popup = GDK_WAYLAND_POPUP (impl);
+
+  return (popup->display_server.xdg_popup ||
+          popup->display_server.zxdg_popup_v6);
 }
 
 static void
-gdk_wayland_surface_create_xdg_toplevel (GdkWaylandToplevel *wayland_toplevel)
+finish_pending_relayout (GdkWaylandPopup *wayland_popup)
 {
-  GdkSurface *surface = GDK_SURFACE (wayland_toplevel);
-  GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (gdk_surface_get_display (surface));
-  GdkWaylandSurface *wayland_surface = GDK_WAYLAND_SURFACE (wayland_toplevel);
-  const char *app_id;
+  g_assert (wayland_popup->state == POPUP_STATE_WAITING_FOR_FRAME);
+  wayland_popup->state = POPUP_STATE_IDLE;
 
-  gdk_surface_freeze_updates (surface);
-  gdk_wayland_surface_create_xdg_surface_resources (surface);
+  thaw_popup_toplevel_state (wayland_popup);
+}
 
-  switch (display_wayland->shell_variant)
+static void
+frame_callback_popup (GdkWaylandPopup *wayland_popup)
+{
+      switch (wayland_popup->state)
+        {
+        case POPUP_STATE_IDLE:
+        case POPUP_STATE_WAITING_FOR_REPOSITIONED:
+        case POPUP_STATE_WAITING_FOR_CONFIGURE:
+          break;
+        case POPUP_STATE_WAITING_FOR_FRAME:
+          finish_pending_relayout (wayland_popup);
+          break;
+        default:
+          g_assert_not_reached ();
+        }
+}
+
+static void
+configure_popup_geometry (GdkWaylandPopup *wayland_popup)
+{
+  GdkWaylandSurface *wayland_surface = GDK_WAYLAND_SURFACE (wayland_popup);
+  int x, y;
+  int width, height;
+
+  x = wayland_popup->next_layout.x - wayland_surface->shadow_left;
+  y = wayland_popup->next_layout.y - wayland_surface->shadow_top;
+  width =
+    wayland_surface->next_layout.configured_width +
+    (wayland_surface->shadow_left + wayland_surface->shadow_right);
+  height =
+    wayland_surface->next_layout.configured_height +
+    (wayland_surface->shadow_top + wayland_surface->shadow_bottom);
+
+  gdk_wayland_surface_move_resize (GDK_SURFACE (wayland_popup), x, y, width, height);
+}
+
+static void
+gdk_wayland_surface_configure_popup (GdkWaylandPopup *wayland_popup)
+{
+  GdkSurface *surface = GDK_SURFACE (wayland_popup);
+  GdkWaylandSurface *wayland_surface = GDK_WAYLAND_SURFACE (wayland_popup);
+  GdkRectangle parent_geometry;
+  int x, y, width, height;
+
+  if (wayland_popup->display_server.xdg_popup)
     {
-    case GDK_WAYLAND_SHELL_VARIANT_XDG_SHELL:
-      create_xdg_toplevel_resources (wayland_toplevel);
-      break;
-    case GDK_WAYLAND_SHELL_VARIANT_ZXDG_SHELL_V6:
-      create_zxdg_toplevel_v6_resources (wayland_toplevel);
-      break;
-    default:
-      g_assert_not_reached ();
+      xdg_surface_ack_configure (wayland_surface->display_server.xdg_surface,
+                                 wayland_surface->pending.serial);
+    }
+  else if (wayland_popup->display_server.zxdg_popup_v6)
+    {
+      zxdg_surface_v6_ack_configure (wayland_surface->display_server.zxdg_surface_v6,
+                                     wayland_surface->pending.serial);
     }
+  else
+    g_warn_if_reached ();
 
-  gdk_wayland_surface_sync_parent (surface, NULL);
-  gdk_wayland_toplevel_sync_parent_of_imported (wayland_toplevel);
-  gdk_wayland_toplevel_sync_title (wayland_toplevel);
+  if (wayland_popup->pending.has_repositioned_token)
+    wayland_popup->received_reposition_token = wayland_popup->pending.repositioned_token;
 
-  switch (display_wayland->shell_variant)
+  switch (wayland_popup->state)
     {
-    case GDK_WAYLAND_SHELL_VARIANT_XDG_SHELL:
-      if (wayland_toplevel->initial_state.set_flags & GDK_TOPLEVEL_STATE_MAXIMIZED)
-        xdg_toplevel_set_maximized (wayland_toplevel->display_server.xdg_toplevel);
-      if (wayland_toplevel->initial_state.set_flags & GDK_TOPLEVEL_STATE_MINIMIZED)
-        xdg_toplevel_set_minimized (wayland_toplevel->display_server.xdg_toplevel);
-      if (wayland_toplevel->initial_state.set_flags & GDK_TOPLEVEL_STATE_FULLSCREEN)
-        xdg_toplevel_set_fullscreen (wayland_toplevel->display_server.xdg_toplevel,
-                                     wayland_toplevel->initial_fullscreen_output);
+    case POPUP_STATE_WAITING_FOR_REPOSITIONED:
+      if (wayland_popup->received_reposition_token != wayland_popup->reposition_token)
+        return;
+      else
+        gdk_surface_thaw_updates (surface);
+      G_GNUC_FALLTHROUGH;
+    case POPUP_STATE_WAITING_FOR_CONFIGURE:
+      wayland_popup->state = POPUP_STATE_WAITING_FOR_FRAME;
       break;
-    case GDK_WAYLAND_SHELL_VARIANT_ZXDG_SHELL_V6:
-      if (wayland_toplevel->initial_state.set_flags & GDK_TOPLEVEL_STATE_MAXIMIZED)
-        zxdg_toplevel_v6_set_maximized (wayland_toplevel->display_server.zxdg_toplevel_v6);
-      if (wayland_toplevel->initial_state.set_flags & GDK_TOPLEVEL_STATE_MINIMIZED)
-        zxdg_toplevel_v6_set_minimized (wayland_toplevel->display_server.zxdg_toplevel_v6);
-      if (wayland_toplevel->initial_state.set_flags & GDK_TOPLEVEL_STATE_FULLSCREEN)
-        zxdg_toplevel_v6_set_fullscreen (wayland_toplevel->display_server.zxdg_toplevel_v6,
-                                         wayland_toplevel->initial_fullscreen_output);
+    case POPUP_STATE_IDLE:
+    case POPUP_STATE_WAITING_FOR_FRAME:
       break;
     default:
       g_assert_not_reached ();
     }
 
-  wayland_toplevel->initial_fullscreen_output = NULL;
-
-  app_id = wayland_toplevel->application.application_id;
-  if (app_id == NULL)
-    app_id = g_get_prgname ();
-
-  if (app_id == NULL)
-    app_id = "GTK Application";
+  x = wayland_popup->pending.x;
+  y = wayland_popup->pending.y;
+  width = wayland_popup->pending.width;
+  height = wayland_popup->pending.height;
 
-  gdk_wayland_toplevel_set_application_id (GDK_TOPLEVEL (wayland_toplevel), app_id);
+  gdk_wayland_surface_get_window_geometry (surface->parent, &parent_geometry);
+  x += parent_geometry.x;
+  y += parent_geometry.y;
 
-  maybe_set_gtk_surface_dbus_properties (wayland_toplevel);
-  maybe_set_gtk_surface_modal (wayland_toplevel);
+  update_popup_layout_state (wayland_popup,
+                             x, y,
+                             width, height,
+                             wayland_popup->layout);
 
-  gdk_profiler_add_mark (GDK_PROFILER_CURRENT_TIME, 0, "wayland", "surface commit");
-  wl_surface_commit (wayland_surface->display_server.wl_surface);
+  wayland_popup->next_layout.x = x;
+  wayland_popup->next_layout.y = y;
+  wayland_surface->next_layout.configured_width = width;
+  wayland_surface->next_layout.configured_height = height;
+  wayland_surface->next_layout.surface_geometry_dirty = TRUE;
+  gdk_surface_request_layout (surface);
 }
 
 static void
@@ -2112,258 +1937,47 @@ static const struct zxdg_popup_v6_listener zxdg_popup_v6_listener = {
   zxdg_popup_v6_done,
 };
 
-static enum xdg_positioner_anchor
-rect_anchor_to_anchor (GdkGravity rect_anchor)
+static void
+calculate_popup_rect (GdkWaylandPopup *wayland_popup,
+                      GdkPopupLayout  *layout,
+                      GdkRectangle    *out_rect)
 {
-  switch (rect_anchor)
+  int width, height;
+  GdkRectangle anchor_rect;
+  int dx, dy;
+  int shadow_left, shadow_right, shadow_top, shadow_bottom;
+  int x = 0, y = 0;
+
+  gdk_popup_layout_get_shadow_width (layout,
+                                     &shadow_left,
+                                     &shadow_right,
+                                     &shadow_top,
+                                     &shadow_bottom);
+
+  width = (wayland_popup->unconstrained_width - (shadow_left + shadow_right));
+  height = (wayland_popup->unconstrained_height - (shadow_top + shadow_bottom));
+
+  anchor_rect = *gdk_popup_layout_get_anchor_rect (layout);
+  gdk_popup_layout_get_offset (layout, &dx, &dy);
+  anchor_rect.x += dx;
+  anchor_rect.y += dy;
+
+  switch (gdk_popup_layout_get_rect_anchor (layout))
     {
-    case GDK_GRAVITY_NORTH_WEST:
+    default:
     case GDK_GRAVITY_STATIC:
-      return XDG_POSITIONER_ANCHOR_TOP_LEFT;
+    case GDK_GRAVITY_NORTH_WEST:
+      x = anchor_rect.x;
+      y = anchor_rect.y;
+      break;
     case GDK_GRAVITY_NORTH:
-      return XDG_POSITIONER_ANCHOR_TOP;
+      x = anchor_rect.x + (anchor_rect.width / 2);
+      y = anchor_rect.y;
+      break;
     case GDK_GRAVITY_NORTH_EAST:
-      return XDG_POSITIONER_ANCHOR_TOP_RIGHT;
-    case GDK_GRAVITY_WEST:
-      return XDG_POSITIONER_ANCHOR_LEFT;
-    case GDK_GRAVITY_CENTER:
-      return XDG_POSITIONER_ANCHOR_NONE;
-    case GDK_GRAVITY_EAST:
-      return XDG_POSITIONER_ANCHOR_RIGHT;
-    case GDK_GRAVITY_SOUTH_WEST:
-      return XDG_POSITIONER_ANCHOR_BOTTOM_LEFT;
-    case GDK_GRAVITY_SOUTH:
-      return XDG_POSITIONER_ANCHOR_BOTTOM;
-    case GDK_GRAVITY_SOUTH_EAST:
-      return XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT;
-    default:
-      g_assert_not_reached ();
-    }
-}
-
-static enum xdg_positioner_gravity
-surface_anchor_to_gravity (GdkGravity rect_anchor)
-{
-  switch (rect_anchor)
-    {
-    case GDK_GRAVITY_NORTH_WEST:
-    case GDK_GRAVITY_STATIC:
-      return XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT;
-    case GDK_GRAVITY_NORTH:
-      return XDG_POSITIONER_GRAVITY_BOTTOM;
-    case GDK_GRAVITY_NORTH_EAST:
-      return XDG_POSITIONER_GRAVITY_BOTTOM_LEFT;
-    case GDK_GRAVITY_WEST:
-      return XDG_POSITIONER_GRAVITY_RIGHT;
-    case GDK_GRAVITY_CENTER:
-      return XDG_POSITIONER_GRAVITY_NONE;
-    case GDK_GRAVITY_EAST:
-      return XDG_POSITIONER_GRAVITY_LEFT;
-    case GDK_GRAVITY_SOUTH_WEST:
-      return XDG_POSITIONER_GRAVITY_TOP_RIGHT;
-    case GDK_GRAVITY_SOUTH:
-      return XDG_POSITIONER_GRAVITY_TOP;
-    case GDK_GRAVITY_SOUTH_EAST:
-      return XDG_POSITIONER_GRAVITY_TOP_LEFT;
-    default:
-      g_assert_not_reached ();
-    }
-}
-
-static enum zxdg_positioner_v6_anchor
-rect_anchor_to_anchor_legacy (GdkGravity rect_anchor)
-{
-  switch (rect_anchor)
-    {
-    case GDK_GRAVITY_NORTH_WEST:
-    case GDK_GRAVITY_STATIC:
-      return (ZXDG_POSITIONER_V6_ANCHOR_TOP |
-              ZXDG_POSITIONER_V6_ANCHOR_LEFT);
-    case GDK_GRAVITY_NORTH:
-      return ZXDG_POSITIONER_V6_ANCHOR_TOP;
-    case GDK_GRAVITY_NORTH_EAST:
-      return (ZXDG_POSITIONER_V6_ANCHOR_TOP |
-              ZXDG_POSITIONER_V6_ANCHOR_RIGHT);
-    case GDK_GRAVITY_WEST:
-      return ZXDG_POSITIONER_V6_ANCHOR_LEFT;
-    case GDK_GRAVITY_CENTER:
-      return ZXDG_POSITIONER_V6_ANCHOR_NONE;
-    case GDK_GRAVITY_EAST:
-      return ZXDG_POSITIONER_V6_ANCHOR_RIGHT;
-    case GDK_GRAVITY_SOUTH_WEST:
-      return (ZXDG_POSITIONER_V6_ANCHOR_BOTTOM |
-              ZXDG_POSITIONER_V6_ANCHOR_LEFT);
-    case GDK_GRAVITY_SOUTH:
-      return ZXDG_POSITIONER_V6_ANCHOR_BOTTOM;
-    case GDK_GRAVITY_SOUTH_EAST:
-      return (ZXDG_POSITIONER_V6_ANCHOR_BOTTOM |
-              ZXDG_POSITIONER_V6_ANCHOR_RIGHT);
-    default:
-      g_assert_not_reached ();
-    }
-
-  return (ZXDG_POSITIONER_V6_ANCHOR_TOP |
-          ZXDG_POSITIONER_V6_ANCHOR_LEFT);
-}
-
-static enum zxdg_positioner_v6_gravity
-surface_anchor_to_gravity_legacy (GdkGravity rect_anchor)
-{
-  switch (rect_anchor)
-    {
-    case GDK_GRAVITY_NORTH_WEST:
-    case GDK_GRAVITY_STATIC:
-      return (ZXDG_POSITIONER_V6_GRAVITY_BOTTOM |
-              ZXDG_POSITIONER_V6_GRAVITY_RIGHT);
-    case GDK_GRAVITY_NORTH:
-      return ZXDG_POSITIONER_V6_GRAVITY_BOTTOM;
-    case GDK_GRAVITY_NORTH_EAST:
-      return (ZXDG_POSITIONER_V6_GRAVITY_BOTTOM |
-              ZXDG_POSITIONER_V6_GRAVITY_LEFT);
-    case GDK_GRAVITY_WEST:
-      return ZXDG_POSITIONER_V6_GRAVITY_RIGHT;
-    case GDK_GRAVITY_CENTER:
-      return ZXDG_POSITIONER_V6_GRAVITY_NONE;
-    case GDK_GRAVITY_EAST:
-      return ZXDG_POSITIONER_V6_GRAVITY_LEFT;
-    case GDK_GRAVITY_SOUTH_WEST:
-      return (ZXDG_POSITIONER_V6_GRAVITY_TOP |
-              ZXDG_POSITIONER_V6_GRAVITY_RIGHT);
-    case GDK_GRAVITY_SOUTH:
-      return ZXDG_POSITIONER_V6_GRAVITY_TOP;
-    case GDK_GRAVITY_SOUTH_EAST:
-      return (ZXDG_POSITIONER_V6_GRAVITY_TOP |
-              ZXDG_POSITIONER_V6_GRAVITY_LEFT);
-    default:
-      g_assert_not_reached ();
-    }
-
-  return (ZXDG_POSITIONER_V6_GRAVITY_BOTTOM |
-          ZXDG_POSITIONER_V6_GRAVITY_RIGHT);
-}
-
-void
-gdk_wayland_toplevel_announce_csd (GdkToplevel *toplevel)
-{
-  GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (gdk_surface_get_display (GDK_SURFACE (toplevel)));
-  GdkWaylandToplevel *toplevel_wayland;
-
-  g_return_if_fail (GDK_IS_WAYLAND_TOPLEVEL (toplevel));
-  toplevel_wayland = GDK_WAYLAND_TOPLEVEL (toplevel);
-
-  if (!display_wayland->server_decoration_manager)
-    return;
-  toplevel_wayland->server_decoration =
-      org_kde_kwin_server_decoration_manager_create (display_wayland->server_decoration_manager,
-                                                     gdk_wayland_surface_get_wl_surface (GDK_SURFACE (toplevel_wayland)));
-  if (toplevel_wayland->server_decoration)
-    org_kde_kwin_server_decoration_request_mode (toplevel_wayland->server_decoration,
-                                                 ORG_KDE_KWIN_SERVER_DECORATION_MANAGER_MODE_CLIENT);
-}
-
-void
-gdk_wayland_toplevel_announce_ssd (GdkToplevel *toplevel)
-{
-  GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (gdk_surface_get_display (GDK_SURFACE (toplevel)));
-  GdkWaylandToplevel *toplevel_wayland;
-
-  g_return_if_fail (GDK_IS_WAYLAND_TOPLEVEL (toplevel));
-  toplevel_wayland = GDK_WAYLAND_TOPLEVEL (toplevel);
-
-  if (!display_wayland->server_decoration_manager)
-    return;
-  toplevel_wayland->server_decoration =
-      org_kde_kwin_server_decoration_manager_create (display_wayland->server_decoration_manager,
-                                                     gdk_wayland_surface_get_wl_surface (GDK_SURFACE (toplevel_wayland)));
-  if (toplevel_wayland->server_decoration)
-    org_kde_kwin_server_decoration_request_mode (toplevel_wayland->server_decoration,
-                                                 ORG_KDE_KWIN_SERVER_DECORATION_MANAGER_MODE_SERVER);
-}
-
-gboolean
-gdk_wayland_toplevel_inhibit_idle (GdkToplevel *toplevel)
-{
-  GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (gdk_surface_get_display (GDK_SURFACE (toplevel)));
-  GdkWaylandToplevel *wayland_toplevel;
-
-  g_return_val_if_fail (GDK_IS_WAYLAND_TOPLEVEL (toplevel), FALSE);
-  wayland_toplevel = GDK_WAYLAND_TOPLEVEL (toplevel);
-
-  if (!display_wayland->idle_inhibit_manager)
-    return FALSE;
-
-  if (!wayland_toplevel->idle_inhibitor)
-    {
-      g_assert (wayland_toplevel->idle_inhibitor_refcount == 0);
-
-      wayland_toplevel->idle_inhibitor =
-          zwp_idle_inhibit_manager_v1_create_inhibitor (display_wayland->idle_inhibit_manager,
-                                                        gdk_wayland_surface_get_wl_surface (GDK_SURFACE (wayland_toplevel)));
-    }
-  ++wayland_toplevel->idle_inhibitor_refcount;
-
-  return TRUE;
-}
-
-void
-gdk_wayland_toplevel_uninhibit_idle (GdkToplevel *toplevel)
-{
-  GdkWaylandToplevel *wayland_toplevel;
-
-  g_return_if_fail (GDK_IS_WAYLAND_TOPLEVEL (toplevel));
-  wayland_toplevel = GDK_WAYLAND_TOPLEVEL (toplevel);
-
-  g_assert (wayland_toplevel->idle_inhibitor &&
-            wayland_toplevel->idle_inhibitor_refcount > 0);
-
-  if (--wayland_toplevel->idle_inhibitor_refcount == 0)
-    {
-      g_clear_pointer (&wayland_toplevel->idle_inhibitor,
-                       zwp_idle_inhibitor_v1_destroy);
-    }
-}
-
-static void
-calculate_popup_rect (GdkWaylandPopup *wayland_popup,
-                      GdkPopupLayout  *layout,
-                      GdkRectangle    *out_rect)
-{
-  int width, height;
-  GdkRectangle anchor_rect;
-  int dx, dy;
-  int shadow_left, shadow_right, shadow_top, shadow_bottom;
-  int x = 0, y = 0;
-
-  gdk_popup_layout_get_shadow_width (layout,
-                                     &shadow_left,
-                                     &shadow_right,
-                                     &shadow_top,
-                                     &shadow_bottom);
-
-  width = (wayland_popup->unconstrained_width - (shadow_left + shadow_right));
-  height = (wayland_popup->unconstrained_height - (shadow_top + shadow_bottom));
-
-  anchor_rect = *gdk_popup_layout_get_anchor_rect (layout);
-  gdk_popup_layout_get_offset (layout, &dx, &dy);
-  anchor_rect.x += dx;
-  anchor_rect.y += dy;
-
-  switch (gdk_popup_layout_get_rect_anchor (layout))
-    {
-    default:
-    case GDK_GRAVITY_STATIC:
-    case GDK_GRAVITY_NORTH_WEST:
-      x = anchor_rect.x;
-      y = anchor_rect.y;
-      break;
-    case GDK_GRAVITY_NORTH:
-      x = anchor_rect.x + (anchor_rect.width / 2);
-      y = anchor_rect.y;
-      break;
-    case GDK_GRAVITY_NORTH_EAST:
-      x = anchor_rect.x + anchor_rect.width;
-      y = anchor_rect.y;
-      break;
+      x = anchor_rect.x + anchor_rect.width;
+      y = anchor_rect.y;
+      break;
     case GDK_GRAVITY_WEST:
       x = anchor_rect.x;
       y = anchor_rect.y + (anchor_rect.height / 2);
@@ -2713,11 +2327,6 @@ gdk_wayland_surface_create_xdg_popup (GdkWaylandPopup *wayland_popup,
   if (!is_realized_shell_surface (parent_impl))
     return FALSE;
 
-  if (is_realized_toplevel (impl))
-    {
-      g_warning ("Can't map popup, already mapped as toplevel");
-      return FALSE;
-    }
   if (is_realized_popup (impl))
     {
       g_warning ("Can't map popup, already mapped");
@@ -2809,274 +2418,73 @@ gdk_wayland_surface_create_xdg_popup (GdkWaylandPopup *wayland_popup,
   return TRUE;
 }
 
-static GdkWaylandSeat *
-find_grab_input_seat (GdkSurface *surface,
-                      GdkSurface *parent)
-{
-  GdkWaylandPopup *popup = GDK_WAYLAND_POPUP (surface);
-  GdkWaylandPopup *tmp_popup;
 
-  /* Use the device that was used for the grab as the device for
-   * the popup surface setup - so this relies on GTK taking the
-   * grab before showing the popup surface.
-   */
-  if (popup->grab_input_seat)
-    return GDK_WAYLAND_SEAT (popup->grab_input_seat);
-
-  while (parent)
-    {
-      if (!GDK_IS_WAYLAND_POPUP (parent))
-        break;
-
-      tmp_popup = GDK_WAYLAND_POPUP (parent);
-
-      if (tmp_popup->grab_input_seat)
-        return GDK_WAYLAND_SEAT (tmp_popup->grab_input_seat);
-
-      parent = parent->parent;
-    }
-
-  return NULL;
-}
-
-static void
-gdk_wayland_surface_map_toplevel (GdkSurface *surface)
-{
-  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
-
-  if (!GDK_IS_WAYLAND_TOPLEVEL (surface))
-    return;
-
-  if (impl->mapped)
-    return;
-
-  gdk_wayland_surface_create_xdg_toplevel (GDK_WAYLAND_TOPLEVEL (surface));
-
-  impl->mapped = TRUE;
-}
+#define LAST_PROP 1
 
 static void
-gdk_wayland_surface_show (GdkSurface *surface)
+gdk_wayland_popup_init (GdkWaylandPopup *popup)
 {
-  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
-
-  if (!impl->display_server.wl_surface)
-    gdk_wayland_surface_create_wl_surface (surface);
-
-  gdk_wayland_surface_map_toplevel (surface);
 }
 
 static void
-unmap_popups_for_surface (GdkSurface *surface)
+gdk_wayland_popup_get_property (GObject    *object,
+                                guint       prop_id,
+                                GValue     *value,
+                                GParamSpec *pspec)
 {
-  GdkWaylandDisplay *display_wayland;
-  GList *l;
+  GdkSurface *surface = GDK_SURFACE (object);
 
-  display_wayland = GDK_WAYLAND_DISPLAY (gdk_surface_get_display (surface));
-  for (l = display_wayland->current_popups; l; l = l->next)
+  switch (prop_id)
     {
-       GdkSurface *popup = l->data;
+    case LAST_PROP + GDK_POPUP_PROP_PARENT:
+      g_value_set_object (value, surface->parent);
+      break;
 
-       if (popup->parent == surface)
-         {
-           g_warning ("Tried to unmap the parent of a popup");
-           gdk_surface_hide (popup);
+    case LAST_PROP + GDK_POPUP_PROP_AUTOHIDE:
+      g_value_set_boolean (value, surface->autohide);
+      break;
 
-           return;
-         }
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
     }
 }
 
 static void
-gdk_wayland_surface_hide_surface (GdkSurface *surface)
+gdk_wayland_popup_set_property (GObject      *object,
+                                guint         prop_id,
+                                const GValue *value,
+                                GParamSpec   *pspec)
 {
-  GdkDisplay *display = gdk_surface_get_display (surface);
-  GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display);
-  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
-
-  unmap_popups_for_surface (surface);
+  GdkSurface *surface = GDK_SURFACE (object);
 
-  if (impl->display_server.wl_surface)
+  switch (prop_id)
     {
-      if (impl->display_server.egl_window)
-        {
-          gdk_surface_set_egl_native_window (surface, NULL);
-          wl_egl_window_destroy (impl->display_server.egl_window);
-          impl->display_server.egl_window = NULL;
-        }
-
-      if (GDK_IS_WAYLAND_TOPLEVEL (surface))
-        {
-          GdkWaylandToplevel *toplevel = GDK_WAYLAND_TOPLEVEL (surface);
-
-          if (toplevel->display_server.xdg_toplevel)
-            {
-              xdg_toplevel_destroy (toplevel->display_server.xdg_toplevel);
-              toplevel->display_server.xdg_toplevel = NULL;
-            }
-          if (toplevel->display_server.zxdg_toplevel_v6)
-            {
-              zxdg_toplevel_v6_destroy (toplevel->display_server.zxdg_toplevel_v6);
-              toplevel->display_server.zxdg_toplevel_v6 = NULL;
-            }
-         }
-
-      if (GDK_IS_WAYLAND_POPUP (surface))
-        {
-          GdkWaylandPopup *popup = GDK_WAYLAND_POPUP (surface);
-
-          if (popup->display_server.xdg_popup)
-            {
-              xdg_popup_destroy (popup->display_server.xdg_popup);
-              popup->display_server.xdg_popup = NULL;
-              display_wayland->current_popups =
-                g_list_remove (display_wayland->current_popups, surface);
-              display_wayland->current_grabbing_popups =
-                g_list_remove (display_wayland->current_grabbing_popups, surface);
-            }
-          if (popup->display_server.zxdg_popup_v6)
-            {
-              zxdg_popup_v6_destroy (popup->display_server.zxdg_popup_v6);
-              popup->display_server.zxdg_popup_v6 = NULL;
-              display_wayland->current_popups =
-                g_list_remove (display_wayland->current_popups, surface);
-              display_wayland->current_grabbing_popups =
-                g_list_remove (display_wayland->current_grabbing_popups, surface);
-            }
-        }
-
-      if (impl->display_server.xdg_surface)
-        {
-          xdg_surface_destroy (impl->display_server.xdg_surface);
-          impl->display_server.xdg_surface = NULL;
-          if (!impl->initial_configure_received)
-            gdk_surface_thaw_updates (surface);
-          else
-            impl->initial_configure_received = FALSE;
-        }
-      if (impl->display_server.zxdg_surface_v6)
-        {
-          zxdg_surface_v6_destroy (impl->display_server.zxdg_surface_v6);
-          impl->display_server.zxdg_surface_v6 = NULL;
-          if (!impl->initial_configure_received)
-            gdk_surface_thaw_updates (surface);
-          else
-            impl->initial_configure_received = FALSE;
-        }
-
-      impl->awaiting_frame = FALSE;
-      if (impl->awaiting_frame_frozen)
-        {
-          impl->awaiting_frame_frozen = FALSE;
-          gdk_surface_thaw_updates (surface);
-        }
-
-      if (GDK_IS_WAYLAND_POPUP (surface))
-        {
-          GdkWaylandPopup *wayland_popup = GDK_WAYLAND_POPUP (surface);
-
-          wayland_popup->thaw_upon_show = TRUE;
-          gdk_surface_freeze_updates (surface);
-
-          switch (wayland_popup->state)
-            {
-            case POPUP_STATE_WAITING_FOR_REPOSITIONED:
-              gdk_surface_thaw_updates (surface);
-              G_GNUC_FALLTHROUGH;
-            case POPUP_STATE_WAITING_FOR_CONFIGURE:
-            case POPUP_STATE_WAITING_FOR_FRAME:
-              thaw_popup_toplevel_state (wayland_popup);
-              break;
-            case POPUP_STATE_IDLE:
-              break;
-            default:
-              g_assert_not_reached ();
-            }
-
-          wayland_popup->state = POPUP_STATE_IDLE;
-        }
-
-      if (GDK_IS_WAYLAND_TOPLEVEL (surface))
-        {
-          GdkWaylandToplevel *wayland_toplevel = GDK_WAYLAND_TOPLEVEL (surface);
-
-          if (wayland_toplevel->display_server.gtk_surface)
-            {
-              if (display_wayland->gtk_shell_version >=
-                  GTK_SURFACE1_RELEASE_SINCE_VERSION)
-                gtk_surface1_release (wayland_toplevel->display_server.gtk_surface);
-              else
-                gtk_surface1_destroy (wayland_toplevel->display_server.gtk_surface);
-              wayland_toplevel->display_server.gtk_surface = NULL;
-              wayland_toplevel->application.was_set = FALSE;
-            }
-
-          g_clear_pointer (&wayland_toplevel->layout, gdk_toplevel_layout_unref);
-        }
-
-      wl_surface_destroy (impl->display_server.wl_surface);
-      impl->display_server.wl_surface = NULL;
-
-      g_slist_free (impl->display_server.outputs);
-      impl->display_server.outputs = NULL;
-
-      if (GDK_IS_WAYLAND_POPUP (surface))
-        {
-          GdkWaylandPopup *wayland_popup = GDK_WAYLAND_POPUP (surface);
-          g_clear_pointer (&wayland_popup->layout, gdk_popup_layout_unref);
-        }
-    }
-
-  impl->has_uncommitted_ack_configure = FALSE;
-  impl->input_region_dirty = TRUE;
-  impl->opaque_region_dirty = TRUE;
-
-  unset_transient_for_exported (surface);
+    case LAST_PROP + GDK_POPUP_PROP_PARENT:
+      surface->parent = g_value_dup_object (value);
+      if (surface->parent != NULL)
+        surface->parent->children = g_list_prepend (surface->parent->children, surface);
+      break;
 
-  impl->last_sent_window_geometry = (GdkRectangle) { 0 };
+    case LAST_PROP + GDK_POPUP_PROP_AUTOHIDE:
+      surface->autohide = g_value_get_boolean (value);
+      break;
 
-  if (GDK_IS_WAYLAND_TOPLEVEL (impl))
-    {
-      GdkWaylandToplevel *toplevel = GDK_WAYLAND_TOPLEVEL (impl);
-      toplevel->last_sent_geometry_hints.min_width = 0;
-      toplevel->last_sent_geometry_hints.min_height = 0;
-      toplevel->last_sent_geometry_hints.max_width = 0;
-      toplevel->last_sent_geometry_hints.max_height = 0;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
     }
-
-  _gdk_wayland_surface_clear_saved_size (surface);
-  impl->mapped = FALSE;
 }
 
 static void
-gdk_wayland_surface_hide (GdkSurface *surface)
+gdk_wayland_popup_class_init (GdkWaylandPopupClass *class)
 {
-  GdkSeat *seat;
-
-  seat = gdk_display_get_default_seat (surface->display);
-  if (seat)
-    {
-      if (surface->autohide)
-        gdk_seat_ungrab (seat);
-
-      gdk_wayland_seat_clear_touchpoints (GDK_WAYLAND_SEAT (seat), surface);
-    }
-  gdk_wayland_surface_hide_surface (surface);
-  _gdk_surface_clear_update_area (surface);
-}
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
 
-static void
-gdk_wayland_surface_move_resize (GdkSurface *surface,
-                                 int         x,
-                                 int         y,
-                                 int         width,
-                                 int         height)
-{
-  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
+  object_class->get_property = gdk_wayland_popup_get_property;
+  object_class->set_property = gdk_wayland_popup_set_property;
 
-  surface->x = x;
-  surface->y = y;
-  gdk_wayland_surface_maybe_resize (surface, width, height, impl->scale);
+  gdk_popup_install_properties (object_class, 1);
 }
 
 static gboolean
@@ -3187,6 +2595,36 @@ is_relayout_finished (GdkSurface *surface)
   return TRUE;
 }
 
+static GdkWaylandSeat *
+find_grab_input_seat (GdkSurface *surface,
+                      GdkSurface *parent)
+{
+  GdkWaylandPopup *popup = GDK_WAYLAND_POPUP (surface);
+  GdkWaylandPopup *tmp_popup;
+
+  /* Use the device that was used for the grab as the device for
+   * the popup surface setup - so this relies on GTK taking the
+   * grab before showing the popup surface.
+   */
+  if (popup->grab_input_seat)
+    return GDK_WAYLAND_SEAT (popup->grab_input_seat);
+
+  while (parent)
+    {
+      if (!GDK_IS_WAYLAND_POPUP (parent))
+        break;
+
+      tmp_popup = GDK_WAYLAND_POPUP (parent);
+
+      if (tmp_popup->grab_input_seat)
+        return GDK_WAYLAND_SEAT (tmp_popup->grab_input_seat);
+
+      parent = parent->parent;
+    }
+
+  return NULL;
+}
+
 static void
 gdk_wayland_surface_map_popup (GdkWaylandPopup *wayland_popup,
                                int              width,
@@ -3335,225 +2773,801 @@ gdk_wayland_surface_present_popup (GdkWaylandPopup *wayland_popup,
           show_popup (wayland_popup, width, height, layout);
         }
     }
-  else
-    {
-      if (wayland_popup->unconstrained_width == width &&
-          wayland_popup->unconstrained_height == height &&
-          gdk_popup_layout_equal (wayland_popup->layout, layout))
-        return TRUE;
+  else
+    {
+      if (wayland_popup->unconstrained_width == width &&
+          wayland_popup->unconstrained_height == height &&
+          gdk_popup_layout_equal (wayland_popup->layout, layout))
+        return TRUE;
+
+      reposition_popup (wayland_popup, width, height, layout);
+    }
+
+  while (wayland_popup->display_server.xdg_popup && !is_relayout_finished (surface))
+    wl_display_dispatch_queue (display_wayland->wl_display, wayland_surface->event_queue);
+
+  if (wayland_popup->display_server.xdg_popup)
+    {
+      gdk_surface_invalidate_rect (surface, NULL);
+      return TRUE;
+    }
+  else
+    {
+      return FALSE;
+    }
+}
+
+static gboolean
+gdk_wayland_popup_present (GdkPopup       *popup,
+                           int             width,
+                           int             height,
+                           GdkPopupLayout *layout)
+{
+  return gdk_wayland_surface_present_popup (GDK_WAYLAND_POPUP (popup), width, height, layout);
+}
+
+static GdkGravity
+gdk_wayland_popup_get_surface_anchor (GdkPopup *popup)
+{
+  return GDK_SURFACE (popup)->popup.surface_anchor;
+}
+
+static GdkGravity
+gdk_wayland_popup_get_rect_anchor (GdkPopup *popup)
+{
+  return GDK_SURFACE (popup)->popup.rect_anchor;
+}
+
+static int
+gdk_wayland_popup_get_position_x (GdkPopup *popup)
+{
+  return GDK_SURFACE (popup)->x;
+}
+
+static int
+gdk_wayland_popup_get_position_y (GdkPopup *popup)
+{
+  return GDK_SURFACE (popup)->y;
+}
+
+static void
+gdk_wayland_popup_iface_init (GdkPopupInterface *iface)
+{
+  iface->present = gdk_wayland_popup_present;
+  iface->get_surface_anchor = gdk_wayland_popup_get_surface_anchor;
+  iface->get_rect_anchor = gdk_wayland_popup_get_rect_anchor;
+  iface->get_position_x = gdk_wayland_popup_get_position_x;
+  iface->get_position_y = gdk_wayland_popup_get_position_y;
+}
+
+/* }}} */
+/* {{{ Private Popup API */
+
+void
+_gdk_wayland_surface_set_grab_seat (GdkSurface *surface,
+                                    GdkSeat    *seat)
+{
+  GdkWaylandPopup *popup;
+
+  g_return_if_fail (surface != NULL);
+
+  popup = GDK_WAYLAND_POPUP (surface);
+  popup->grab_input_seat = seat;
+}
+
+/* }}} */
+/* {{{ GdkWaylandToplevel definition */
+
+/**
+ * GdkWaylandToplevel:
+ *
+ * The Wayland implementation of `GdkToplevel`.
+ *
+ * Beyond the [iface@Gdk.Toplevel] API, the Wayland implementation
+ * has API to set up cross-process parent-child relationships between
+ * surfaces with [method@GdkWayland.WaylandToplevel.export_handle] and
+ * [method@GdkWayland.WaylandToplevel.set_transient_for_exported].
+ */
+
+struct _GdkWaylandToplevel
+{
+  GdkWaylandSurface parent_instance;
+
+  struct {
+    struct gtk_surface1 *gtk_surface;
+    struct xdg_toplevel *xdg_toplevel;
+    struct zxdg_toplevel_v6 *zxdg_toplevel_v6;
+  } display_server;
+
+  GdkWaylandToplevel *transient_for;
+
+  struct org_kde_kwin_server_decoration *server_decoration;
+  struct zxdg_exported_v1 *xdg_exported;
+  struct zxdg_exported_v2 *xdg_exported_v2;
+
+  struct {
+    int width;
+    int height;
+    GdkToplevelState state;
+    gboolean is_resizing;
+
+    int bounds_width;
+    int bounds_height;
+    gboolean has_bounds;
+  } pending;
+
+  struct {
+      gboolean should_constrain;
+      gboolean size_is_fixed;
+  } next_layout;
+
+  struct {
+    GdkWaylandToplevelExported callback;
+    gpointer user_data;
+    GDestroyNotify destroy_func;
+  } exported;
+
+  struct {
+    gboolean was_set;
+
+    char *application_id;
+    char *app_menu_path;
+    char *menubar_path;
+    char *window_object_path;
+    char *application_object_path;
+    char *unique_bus_name;
+  } application;
+
+  struct zwp_idle_inhibitor_v1 *idle_inhibitor;
+  size_t idle_inhibitor_refcount;
+
+  struct wl_output *initial_fullscreen_output;
+
+  struct {
+    GdkToplevelState unset_flags;
+    GdkToplevelState set_flags;
+  } initial_state;
+
+  GdkToplevelLayout *layout;
+  int bounds_width;
+  int bounds_height;
+  gboolean has_bounds;
+
+  char *title;
+
+  GdkGeometry geometry_hints;
+  GdkSurfaceHints geometry_mask;
+  GdkGeometry last_sent_geometry_hints;
+
+  struct zxdg_imported_v1 *imported_transient_for;
+  struct zxdg_imported_v2 *imported_transient_for_v2;
+  GHashTable *shortcuts_inhibitors;
+};
+
+typedef struct
+{
+  GdkWaylandSurfaceClass parent_class;
+} GdkWaylandToplevelClass;
+
+static void gdk_wayland_toplevel_iface_init (GdkToplevelInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GdkWaylandToplevel, gdk_wayland_toplevel, GDK_TYPE_WAYLAND_SURFACE,
+                         G_IMPLEMENT_INTERFACE (GDK_TYPE_TOPLEVEL,
+                                                gdk_wayland_toplevel_iface_init))
+
+/* }}} */
+/* {{{ Toplevel implementation */
+
+static void maybe_set_gtk_surface_dbus_properties (GdkWaylandToplevel *wayland_toplevel);
+static void maybe_set_gtk_surface_modal (GdkWaylandToplevel *wayland_toplevel);
+
+static void
+gdk_wayland_toplevel_hide_surface (GdkWaylandToplevel *toplevel)
+{
+  GdkDisplay *display = gdk_surface_get_display (GDK_SURFACE (toplevel));
+  GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display);
+
+  g_clear_pointer (&toplevel->display_server.xdg_toplevel, xdg_toplevel_destroy);
+  g_clear_pointer (&toplevel->display_server.zxdg_toplevel_v6, zxdg_toplevel_v6_destroy);
+
+  if (toplevel->display_server.gtk_surface)
+    {
+      if (display_wayland->gtk_shell_version >= GTK_SURFACE1_RELEASE_SINCE_VERSION)
+        gtk_surface1_release (toplevel->display_server.gtk_surface);
+      else
+        gtk_surface1_destroy (toplevel->display_server.gtk_surface);
+      toplevel->display_server.gtk_surface = NULL;
+      toplevel->application.was_set = FALSE;
+    }
+
+  g_clear_pointer (&toplevel->layout, gdk_toplevel_layout_unref);
+
+  toplevel->last_sent_geometry_hints.min_width = 0;
+  toplevel->last_sent_geometry_hints.min_height = 0;
+  toplevel->last_sent_geometry_hints.max_width = 0;
+  toplevel->last_sent_geometry_hints.max_height = 0;
+}
+
+static gboolean
+is_realized_toplevel (GdkWaylandSurface *impl)
+{
+  GdkWaylandToplevel *toplevel;
+
+  if (!GDK_IS_WAYLAND_TOPLEVEL (impl))
+    return FALSE;
+
+  toplevel = GDK_WAYLAND_TOPLEVEL (impl);
+
+  return (toplevel->display_server.xdg_toplevel ||
+          toplevel->display_server.zxdg_toplevel_v6);
+}
+
+static void
+gdk_wayland_toplevel_sync_parent (GdkWaylandToplevel *toplevel)
+{
+  GdkSurface *surface = GDK_SURFACE (toplevel);
+  GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (gdk_surface_get_display (surface));
+  GdkWaylandToplevel *parent;
+
+  if (!is_realized_toplevel (GDK_WAYLAND_SURFACE (toplevel)))
+    return;
+
+  if (toplevel->transient_for)
+    parent = toplevel->transient_for;
+  else
+    parent = NULL;
+
+  /* XXX: Is this correct? */
+  if (parent && !is_realized_shell_surface (GDK_WAYLAND_SURFACE (parent)))
+    return;
+
+  switch (display_wayland->shell_variant)
+    {
+    case GDK_WAYLAND_SHELL_VARIANT_XDG_SHELL:
+      {
+        struct xdg_toplevel *parent_toplevel;
+
+        if (parent)
+          parent_toplevel = parent->display_server.xdg_toplevel;
+        else
+          parent_toplevel = NULL;
+
+        xdg_toplevel_set_parent (toplevel->display_server.xdg_toplevel, parent_toplevel);
+        break;
+      }
+      break;
+    case GDK_WAYLAND_SHELL_VARIANT_ZXDG_SHELL_V6:
+      {
+        struct zxdg_toplevel_v6 *parent_toplevel;
+
+        if (parent)
+          parent_toplevel = parent->display_server.zxdg_toplevel_v6;
+        else
+          parent_toplevel = NULL;
+
+        zxdg_toplevel_v6_set_parent (toplevel->display_server.zxdg_toplevel_v6, parent_toplevel);
+        break;
+      }
+    default:
+      g_assert_not_reached ();
+    }
+}
+
+static void
+gdk_wayland_toplevel_sync_parent_of_imported (GdkWaylandToplevel *toplevel)
+{
+  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (toplevel);
+
+  if (!impl->display_server.wl_surface)
+    return;
+
+  if (!is_realized_toplevel (impl))
+    return;
+
+  if (toplevel->imported_transient_for)
+    zxdg_imported_v1_set_parent_of (toplevel->imported_transient_for,
+                                    impl->display_server.wl_surface);
+  else if (toplevel->imported_transient_for_v2)
+    zxdg_imported_v2_set_parent_of (toplevel->imported_transient_for_v2,
+                                    impl->display_server.wl_surface);
+}
+
+static void
+gdk_wayland_toplevel_sync_title (GdkWaylandToplevel *toplevel)
+{
+  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (toplevel);
+  GdkWaylandDisplay *display_wayland =
+    GDK_WAYLAND_DISPLAY (gdk_surface_get_display (GDK_SURFACE (toplevel)));
+
+  if (!is_realized_toplevel (impl))
+    return;
+
+  if (!toplevel->title)
+    return;
+
+  switch (display_wayland->shell_variant)
+    {
+    case GDK_WAYLAND_SHELL_VARIANT_XDG_SHELL:
+      xdg_toplevel_set_title (toplevel->display_server.xdg_toplevel, toplevel->title);
+      break;
+    case GDK_WAYLAND_SHELL_VARIANT_ZXDG_SHELL_V6:
+      zxdg_toplevel_v6_set_title (toplevel->display_server.zxdg_toplevel_v6, toplevel->title);
+      break;
+    default:
+      g_assert_not_reached ();
+    }
+}
+
+static void
+configure_toplevel_geometry (GdkWaylandToplevel *wayland_toplevel)
+{
+  GdkSurface *surface = GDK_SURFACE (wayland_toplevel);
+  GdkWaylandSurface *wayland_surface = GDK_WAYLAND_SURFACE (wayland_toplevel);
+  GdkDisplay *display = gdk_surface_get_display (surface);
+  int bounds_width, bounds_height;
+  GdkToplevelSize size;
+  GdkToplevelLayout *layout;
+  GdkGeometry geometry;
+  GdkSurfaceHints mask;
+
+  if (wayland_toplevel->has_bounds)
+    {
+      bounds_width = wayland_toplevel->bounds_width;
+      bounds_height = wayland_toplevel->bounds_height;
+    }
+  else
+    {
+      GdkMonitor *monitor;
+      GListModel *monitors;
+      GdkRectangle monitor_geometry, display_geometry = { 0 };
+      guint i;
+
+      monitors = gdk_display_get_monitors (display);
+
+      for (i = 0; i < g_list_model_get_n_items (monitors); i++)
+        {
+          monitor = g_list_model_get_item (monitors, i);
+          gdk_monitor_get_geometry (monitor, &monitor_geometry);
+          gdk_rectangle_union (&display_geometry, &monitor_geometry, &display_geometry);
+          g_object_unref (monitor);
+        }
+
+      bounds_width = display_geometry.width;
+      bounds_height = display_geometry.height;
+    }
+
+  gdk_toplevel_size_init (&size, bounds_width, bounds_height);
+  gdk_toplevel_notify_compute_size (GDK_TOPLEVEL (surface), &size);
+  g_warn_if_fail (size.width > 0);
+  g_warn_if_fail (size.height > 0);
+
+  layout = wayland_toplevel->layout;
+  if (gdk_toplevel_layout_get_resizable (layout))
+    {
+      geometry.min_width = size.min_width;
+      geometry.min_height = size.min_height;
+      mask = GDK_HINT_MIN_SIZE;
+    }
+  else
+    {
+      geometry.max_width = geometry.min_width = size.width;
+      geometry.max_height = geometry.min_height = size.height;
+      mask = GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE;
+    }
+
+  gdk_wayland_toplevel_set_geometry_hints (wayland_toplevel, &geometry, mask);
+
+  if (size.shadow.is_valid)
+    {
+      wayland_surface->shadow_left = size.shadow.left;
+      wayland_surface->shadow_right = size.shadow.right;
+      wayland_surface->shadow_top = size.shadow.top;
+      wayland_surface->shadow_bottom = size.shadow.bottom;
+    }
+
+  if (wayland_surface->next_layout.configured_width > 0 &&
+      wayland_surface->next_layout.configured_height > 0)
+    {
+      int width, height;
+
+      width = wayland_surface->next_layout.configured_width +
+        wayland_surface->shadow_left + wayland_surface->shadow_right;
+      height = wayland_surface->next_layout.configured_height +
+        wayland_surface->shadow_top + wayland_surface->shadow_bottom;
+
+      if (wayland_toplevel->next_layout.should_constrain)
+        {
+          gdk_surface_constrain_size (&wayland_toplevel->geometry_hints,
+                                      wayland_toplevel->geometry_mask,
+                                      width, height,
+                                      &width, &height);
+        }
+      gdk_wayland_surface_update_size (surface, width, height, wayland_surface->scale);
+
+      if (!wayland_toplevel->next_layout.size_is_fixed)
+        {
+          wayland_toplevel->next_layout.should_constrain = FALSE;
+          wayland_surface->next_layout.configured_width = 0;
+          wayland_surface->next_layout.configured_height = 0;
+        }
+    }
+  else
+    {
+      int width, height;
+
+      width = size.width;
+      height = size.height;
+      gdk_surface_constrain_size (&geometry, mask,
+                                  width, height,
+                                  &width, &height);
+      gdk_wayland_surface_update_size (surface, width, height, wayland_surface->scale);
+    }
+}
+
+static void
+gdk_wayland_surface_configure_toplevel (GdkWaylandToplevel *wayland_toplevel)
+{
+  GdkSurface *surface = GDK_SURFACE (wayland_toplevel);
+  GdkWaylandSurface *wayland_surface = GDK_WAYLAND_SURFACE (wayland_toplevel);
+  GdkWaylandDisplay *display_wayland =
+    GDK_WAYLAND_DISPLAY (gdk_surface_get_display (surface));
+  GdkToplevelState new_state;
+  int width, height;
+  gboolean is_resizing;
+  gboolean fixed_size;
+  gboolean was_fixed_size;
+  gboolean saved_size;
+
+  new_state = wayland_toplevel->pending.state;
+  wayland_toplevel->pending.state = 0;
+
+  is_resizing = wayland_toplevel->pending.is_resizing;
+  wayland_toplevel->pending.is_resizing = FALSE;
+
+  if (wayland_toplevel->pending.has_bounds)
+    {
+      wayland_toplevel->bounds_width = wayland_toplevel->pending.bounds_width;
+      wayland_toplevel->bounds_height = wayland_toplevel->pending.bounds_height;
+      wayland_toplevel->has_bounds = TRUE;
+    }
+
+  fixed_size =
+    new_state & (GDK_TOPLEVEL_STATE_MAXIMIZED |
+                 GDK_TOPLEVEL_STATE_FULLSCREEN |
+                 GDK_TOPLEVEL_STATE_TILED) ||
+    is_resizing;
+
+  was_fixed_size =
+    surface->state & (GDK_TOPLEVEL_STATE_MAXIMIZED |
+                      GDK_TOPLEVEL_STATE_FULLSCREEN |
+                      GDK_TOPLEVEL_STATE_TILED);
+
+  width = wayland_toplevel->pending.width;
+  height = wayland_toplevel->pending.height;
+
+  saved_size = (width == 0 && height == 0);
+  /* According to xdg_shell, an xdg_surface.configure with size 0x0
+   * should be interpreted as that it is up to the client to set a
+   * size.
+   *
+   * When transitioning from maximize or fullscreen state, this means
+   * the client should configure its size back to what it was before
+   * being maximize or fullscreen.
+   */
+  if (saved_size && !fixed_size && was_fixed_size)
+    {
+      width = wayland_surface->saved_width;
+      height = wayland_surface->saved_height;
+    }
+
+  if (width > 0 && height > 0)
+    {
+      if (!saved_size)
+        {
+          wayland_toplevel->next_layout.should_constrain = TRUE;
+
+          /* Save size for next time we get 0x0 */
+          _gdk_wayland_surface_save_size (surface);
+        }
+      else if (is_resizing)
+        {
+          wayland_toplevel->next_layout.should_constrain = TRUE;
+        }
+      else
+        {
+          wayland_toplevel->next_layout.should_constrain = FALSE;
+        }
+
+      wayland_toplevel->next_layout.size_is_fixed = fixed_size;
+      wayland_surface->next_layout.configured_width = width;
+      wayland_surface->next_layout.configured_height = height;
+    }
+  else
+    {
+      wayland_toplevel->next_layout.should_constrain = FALSE;
+      wayland_toplevel->next_layout.size_is_fixed = FALSE;
+      wayland_surface->next_layout.configured_width = 0;
+      wayland_surface->next_layout.configured_height = 0;
+    }
 
-      reposition_popup (wayland_popup, width, height, layout);
-    }
+  wayland_surface->next_layout.surface_geometry_dirty = TRUE;
+  gdk_surface_request_layout (surface);
 
-  while (wayland_popup->display_server.xdg_popup && !is_relayout_finished (surface))
-    wl_display_dispatch_queue (display_wayland->wl_display, wayland_surface->event_queue);
+  GDK_DISPLAY_DEBUG (gdk_surface_get_display (surface), EVENTS,
+                     "configure, surface %p %dx%d,%s%s%s%s",
+                     surface, width, height,
+                     (new_state & GDK_TOPLEVEL_STATE_FULLSCREEN) ? " fullscreen" : "",
+                     (new_state & GDK_TOPLEVEL_STATE_MAXIMIZED) ? " maximized" : "",
+                     (new_state & GDK_TOPLEVEL_STATE_FOCUSED) ? " focused" : "",
+                     (new_state & GDK_TOPLEVEL_STATE_TILED) ? " tiled" : "");
 
-  if (wayland_popup->display_server.xdg_popup)
-    {
-      gdk_surface_invalidate_rect (surface, NULL);
-      return TRUE;
-    }
-  else
+  gdk_surface_queue_state_change (surface, ~0 & ~new_state, new_state);
+
+  switch (display_wayland->shell_variant)
     {
-      return FALSE;
+    case GDK_WAYLAND_SHELL_VARIANT_XDG_SHELL:
+      xdg_surface_ack_configure (wayland_surface->display_server.xdg_surface,
+                                 wayland_surface->pending.serial);
+      break;
+    case GDK_WAYLAND_SHELL_VARIANT_ZXDG_SHELL_V6:
+      zxdg_surface_v6_ack_configure (wayland_surface->display_server.zxdg_surface_v6,
+                                     wayland_surface->pending.serial);
+      break;
+    default:
+      g_assert_not_reached ();
     }
 }
 
 static void
-gdk_wayland_surface_get_geometry (GdkSurface *surface,
-                                  int        *x,
-                                  int        *y,
-                                  int        *width,
-                                  int        *height)
+xdg_toplevel_configure (void                *data,
+                        struct xdg_toplevel *xdg_toplevel,
+                        int32_t              width,
+                        int32_t              height,
+                        struct wl_array     *states)
 {
-  if (!GDK_SURFACE_DESTROYED (surface))
+  GdkSurface *surface = GDK_SURFACE (data);
+  GdkWaylandToplevel *toplevel = GDK_WAYLAND_TOPLEVEL (surface);
+  uint32_t *p;
+  GdkToplevelState pending_state = 0;
+
+  toplevel->pending.is_resizing = FALSE;
+
+  wl_array_for_each (p, states)
     {
-      if (x)
-        *x = surface->x;
-      if (y)
-        *y = surface->y;
-      if (width)
-        *width = surface->width;
-      if (height)
-        *height = surface->height;
+      uint32_t state = *p;
+
+      switch (state)
+        {
+        case XDG_TOPLEVEL_STATE_FULLSCREEN:
+          pending_state |= GDK_TOPLEVEL_STATE_FULLSCREEN;
+          break;
+        case XDG_TOPLEVEL_STATE_MAXIMIZED:
+          pending_state |= GDK_TOPLEVEL_STATE_MAXIMIZED;
+          break;
+        case XDG_TOPLEVEL_STATE_ACTIVATED:
+          pending_state |= GDK_TOPLEVEL_STATE_FOCUSED;
+          break;
+        case XDG_TOPLEVEL_STATE_RESIZING:
+          toplevel->pending.is_resizing = TRUE;
+          break;
+        case XDG_TOPLEVEL_STATE_TILED_TOP:
+          pending_state |= (GDK_TOPLEVEL_STATE_TILED |
+                            GDK_TOPLEVEL_STATE_TOP_TILED);
+          break;
+        case XDG_TOPLEVEL_STATE_TILED_RIGHT:
+          pending_state |= (GDK_TOPLEVEL_STATE_TILED |
+                            GDK_TOPLEVEL_STATE_RIGHT_TILED);
+          break;
+        case XDG_TOPLEVEL_STATE_TILED_BOTTOM:
+          pending_state |= (GDK_TOPLEVEL_STATE_TILED |
+                            GDK_TOPLEVEL_STATE_BOTTOM_TILED);
+          break;
+        case XDG_TOPLEVEL_STATE_TILED_LEFT:
+          pending_state |= (GDK_TOPLEVEL_STATE_TILED |
+                            GDK_TOPLEVEL_STATE_LEFT_TILED);
+          break;
+        default:
+          /* Unknown state */
+          break;
+        }
     }
+
+  gdk_wayland_toplevel_handle_configure (toplevel, width, height, pending_state);
 }
 
 static void
-gdk_wayland_surface_get_root_coords (GdkSurface *surface,
-                                     int         x,
-                                     int         y,
-                                     int        *root_x,
-                                     int        *root_y)
+gdk_wayland_toplevel_handle_configure (GdkWaylandToplevel *toplevel,
+                                       int32_t             width,
+                                       int32_t             height,
+                                       GdkToplevelState    state)
 {
-  /*
-   * Wayland does not have a global coordinate space shared between surfaces. In
-   * fact, for regular toplevels, we have no idea where our surfaces are
-   * positioned, relatively.
-   *
-   * However, there are some cases like popups and subsurfaces where we do have
-   * some amount of control over the placement of our surface, and we can
-   * semi-accurately control the x/y position of these surfaces, if they are
-   * relative to another surface.
-   *
-   * To pretend we have something called a root coordinate space, assume all
-   * parent-less surfaces are positioned in (0, 0), and all relative positioned
-   * popups and subsurfaces are placed within this fake root coordinate space.
-   *
-   * For example a 200x200 large toplevel surface will have the position (0, 0).
-   * If a popup positioned in the middle of the toplevel will have the fake
-   * position (100,100). Furthermore, if a positioned is placed in the middle
-   * that popup, will have the fake position (150,150), even though it has the
-   * relative position (50,50). These three surfaces would make up one single
-   * fake root coordinate space.
-   */
-
-  if (root_x)
-    *root_x = surface->x + x;
-
-  if (root_y)
-    *root_y = surface->y + y;
+  toplevel->pending.state |= state;
+  toplevel->pending.width = width;
+  toplevel->pending.height = height;
 }
 
-static gboolean
-gdk_wayland_surface_get_device_state (GdkSurface       *surface,
-                                      GdkDevice        *device,
-                                      double           *x,
-                                      double           *y,
-                                      GdkModifierType  *mask)
+static void
+xdg_toplevel_close (void                *data,
+                    struct xdg_toplevel *xdg_toplevel)
 {
-  if (GDK_SURFACE_DESTROYED (surface))
-    return FALSE;
-
-  gdk_wayland_device_query_state (device, surface, x, y, mask);
+  GdkSurface *surface = GDK_SURFACE (data);
 
-  return *x >= 0 && *y >= 0 && *x < surface->width && *y < surface->height;
+  gdk_wayland_surface_handle_close (surface);
 }
 
 static void
-gdk_wayland_surface_set_input_region (GdkSurface     *surface,
-                                      cairo_region_t *input_region)
+xdg_toplevel_configure_bounds (void                *data,
+                               struct xdg_toplevel *xdg_toplevel,
+                               int32_t              width,
+                               int32_t              height)
 {
-  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
+  GdkSurface *surface = GDK_SURFACE (data);
+  GdkWaylandToplevel *toplevel = GDK_WAYLAND_TOPLEVEL (surface);
 
-  if (GDK_SURFACE_DESTROYED (surface))
-    return;
+  toplevel->pending.bounds_width = width;
+  toplevel->pending.bounds_height = height;
+  toplevel->pending.has_bounds = TRUE;
+}
 
-  g_clear_pointer (&impl->input_region, cairo_region_destroy);
+static const struct xdg_toplevel_listener xdg_toplevel_listener = {
+  xdg_toplevel_configure,
+  xdg_toplevel_close,
+  xdg_toplevel_configure_bounds,
+};
 
-  if (input_region)
-    impl->input_region = cairo_region_copy (input_region);
+static void
+create_xdg_toplevel_resources (GdkWaylandToplevel *toplevel)
+{
+  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (toplevel);
 
-  impl->input_region_dirty = TRUE;
+  toplevel->display_server.xdg_toplevel =
+    xdg_surface_get_toplevel (impl->display_server.xdg_surface);
+  xdg_toplevel_add_listener (toplevel->display_server.xdg_toplevel,
+                             &xdg_toplevel_listener,
+                             toplevel);
 }
 
 static void
-gdk_wayland_surface_destroy (GdkSurface *surface,
-                             gboolean    foreign_destroy)
+zxdg_toplevel_v6_configure (void                    *data,
+                            struct zxdg_toplevel_v6 *xdg_toplevel,
+                            int32_t                  width,
+                            int32_t                  height,
+                            struct wl_array         *states)
 {
-  GdkWaylandDisplay *display;
-  GdkFrameClock *frame_clock;
+  GdkSurface *surface = GDK_SURFACE (data);
+  GdkWaylandToplevel *toplevel = GDK_WAYLAND_TOPLEVEL (surface);
+  uint32_t *p;
+  GdkToplevelState pending_state = 0;
 
-  g_return_if_fail (GDK_IS_SURFACE (surface));
+  toplevel->pending.is_resizing = FALSE;
 
-  /* Wayland surfaces can't be externally destroyed; we may possibly
-   * eventually want to use this path at display close-down
-   */
-  g_return_if_fail (!foreign_destroy);
+  wl_array_for_each (p, states)
+    {
+      uint32_t state = *p;
 
-  gdk_wayland_surface_hide_surface (surface);
+      switch (state)
+        {
+        case ZXDG_TOPLEVEL_V6_STATE_FULLSCREEN:
+          pending_state |= GDK_TOPLEVEL_STATE_FULLSCREEN;
+          break;
+        case ZXDG_TOPLEVEL_V6_STATE_MAXIMIZED:
+          pending_state |= GDK_TOPLEVEL_STATE_MAXIMIZED;
+          break;
+        case ZXDG_TOPLEVEL_V6_STATE_ACTIVATED:
+          pending_state |= GDK_TOPLEVEL_STATE_FOCUSED;
+          break;
+        case ZXDG_TOPLEVEL_V6_STATE_RESIZING:
+          toplevel->pending.is_resizing = TRUE;
+          break;
+        default:
+          /* Unknown state */
+          break;
+        }
+    }
 
-  frame_clock = gdk_surface_get_frame_clock (surface);
-  g_signal_handlers_disconnect_by_func (frame_clock, on_frame_clock_before_paint, surface);
-  g_signal_handlers_disconnect_by_func (frame_clock, on_frame_clock_after_paint, surface);
+  gdk_wayland_toplevel_handle_configure (toplevel, width, height, pending_state);
+}
 
-  display = GDK_WAYLAND_DISPLAY (gdk_surface_get_display (surface));
-  display->toplevels = g_list_remove (display->toplevels, surface);
+static void
+zxdg_toplevel_v6_close (void                    *data,
+                        struct zxdg_toplevel_v6 *xdg_toplevel)
+{
+  GdkSurface *surface = GDK_SURFACE (data);
+
+  gdk_wayland_surface_handle_close (surface);
 }
 
+static const struct zxdg_toplevel_v6_listener zxdg_toplevel_v6_listener = {
+  zxdg_toplevel_v6_configure,
+  zxdg_toplevel_v6_close,
+};
+
 static void
-token_done (gpointer                        data,
-            struct xdg_activation_token_v1 *provider,
-            const char                     *token)
+create_zxdg_toplevel_v6_resources (GdkWaylandToplevel *toplevel)
 {
-  char **token_out = data;
+  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (toplevel);
 
-  *token_out = g_strdup (token);
+  toplevel->display_server.zxdg_toplevel_v6 =
+    zxdg_surface_v6_get_toplevel (impl->display_server.zxdg_surface_v6);
+  zxdg_toplevel_v6_add_listener (toplevel->display_server.zxdg_toplevel_v6,
+                                 &zxdg_toplevel_v6_listener,
+                                 toplevel);
 }
 
-static const struct xdg_activation_token_v1_listener token_listener = {
-  token_done,
-};
-
 static void
-gdk_wayland_toplevel_focus (GdkToplevel *toplevel,
-                            guint32      timestamp)
+gdk_wayland_surface_create_xdg_toplevel (GdkWaylandToplevel *wayland_toplevel)
 {
-  GdkSurface *surface = GDK_SURFACE (toplevel);
-  GdkWaylandToplevel *wayland_toplevel = GDK_WAYLAND_TOPLEVEL (toplevel);
-  GdkWaylandSurface *wayland_surface = GDK_WAYLAND_SURFACE (toplevel);
-  GdkDisplay *display = gdk_surface_get_display (surface);
-  GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display);
-  gchar *startup_id = NULL;
+  GdkSurface *surface = GDK_SURFACE (wayland_toplevel);
+  GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (gdk_surface_get_display (surface));
+  GdkWaylandSurface *wayland_surface = GDK_WAYLAND_SURFACE (wayland_toplevel);
+  const char *app_id;
 
-  startup_id = g_steal_pointer (&display_wayland->startup_notification_id);
+  gdk_surface_freeze_updates (surface);
+  gdk_wayland_surface_create_xdg_surface_resources (surface);
 
-  if (display_wayland->xdg_activation)
+  switch (display_wayland->shell_variant)
     {
-      GdkWaylandSeat *seat =
-        GDK_WAYLAND_SEAT (gdk_display_get_default_seat (display));
-
-      /* If the focus request does not have a startup ID associated, get a
-       * new token to activate the window.
-       */
-      if (!startup_id)
-        {
-          struct xdg_activation_token_v1 *token;
-          struct wl_event_queue *event_queue;
-          struct wl_surface *wl_surface = NULL;
-          GdkSurface *focus_surface;
+    case GDK_WAYLAND_SHELL_VARIANT_XDG_SHELL:
+      create_xdg_toplevel_resources (wayland_toplevel);
+      break;
+    case GDK_WAYLAND_SHELL_VARIANT_ZXDG_SHELL_V6:
+      create_zxdg_toplevel_v6_resources (wayland_toplevel);
+      break;
+    default:
+      g_assert_not_reached ();
+    }
 
-          event_queue = wl_display_create_queue (display_wayland->wl_display);
+  gdk_wayland_toplevel_sync_parent (wayland_toplevel);
+  gdk_wayland_toplevel_sync_parent_of_imported (wayland_toplevel);
+  gdk_wayland_toplevel_sync_title (wayland_toplevel);
 
-          token = xdg_activation_v1_get_activation_token (display_wayland->xdg_activation);
-          wl_proxy_set_queue ((struct wl_proxy *) token, event_queue);
+  switch (display_wayland->shell_variant)
+    {
+    case GDK_WAYLAND_SHELL_VARIANT_XDG_SHELL:
+      if (wayland_toplevel->initial_state.set_flags & GDK_TOPLEVEL_STATE_MAXIMIZED)
+        xdg_toplevel_set_maximized (wayland_toplevel->display_server.xdg_toplevel);
+      if (wayland_toplevel->initial_state.set_flags & GDK_TOPLEVEL_STATE_MINIMIZED)
+        xdg_toplevel_set_minimized (wayland_toplevel->display_server.xdg_toplevel);
+      if (wayland_toplevel->initial_state.set_flags & GDK_TOPLEVEL_STATE_FULLSCREEN)
+        xdg_toplevel_set_fullscreen (wayland_toplevel->display_server.xdg_toplevel,
+                                     wayland_toplevel->initial_fullscreen_output);
+      break;
+    case GDK_WAYLAND_SHELL_VARIANT_ZXDG_SHELL_V6:
+      if (wayland_toplevel->initial_state.set_flags & GDK_TOPLEVEL_STATE_MAXIMIZED)
+        zxdg_toplevel_v6_set_maximized (wayland_toplevel->display_server.zxdg_toplevel_v6);
+      if (wayland_toplevel->initial_state.set_flags & GDK_TOPLEVEL_STATE_MINIMIZED)
+        zxdg_toplevel_v6_set_minimized (wayland_toplevel->display_server.zxdg_toplevel_v6);
+      if (wayland_toplevel->initial_state.set_flags & GDK_TOPLEVEL_STATE_FULLSCREEN)
+        zxdg_toplevel_v6_set_fullscreen (wayland_toplevel->display_server.zxdg_toplevel_v6,
+                                         wayland_toplevel->initial_fullscreen_output);
+      break;
+    default:
+      g_assert_not_reached ();
+    }
 
-          xdg_activation_token_v1_add_listener (token,
-                                                &token_listener,
-                                                &startup_id);
-          xdg_activation_token_v1_set_serial (token,
-                                              _gdk_wayland_seat_get_last_implicit_grab_serial (seat, NULL),
-                                              gdk_wayland_seat_get_wl_seat (GDK_SEAT (seat)));
+  wayland_toplevel->initial_fullscreen_output = NULL;
 
-          focus_surface = gdk_wayland_device_get_focus (gdk_seat_get_keyboard (GDK_SEAT (seat)));
-          if (focus_surface)
-            wl_surface = gdk_wayland_surface_get_wl_surface (focus_surface);
-          if (wl_surface)
-            xdg_activation_token_v1_set_surface (token, wl_surface);
+  app_id = wayland_toplevel->application.application_id;
+  if (app_id == NULL)
+    app_id = g_get_prgname ();
 
-          xdg_activation_token_v1_commit (token);
+  if (app_id == NULL)
+    app_id = "GTK Application";
 
-          while (startup_id == NULL)
-            wl_display_dispatch_queue (display_wayland->wl_display, event_queue);
+  gdk_wayland_toplevel_set_application_id (GDK_TOPLEVEL (wayland_toplevel), app_id);
 
-          xdg_activation_token_v1_destroy (token);
-          wl_event_queue_destroy (event_queue);
-        }
+  maybe_set_gtk_surface_dbus_properties (wayland_toplevel);
+  maybe_set_gtk_surface_modal (wayland_toplevel);
 
-      xdg_activation_v1_activate (display_wayland->xdg_activation,
-                                  startup_id,
-                                  wayland_surface->display_server.wl_surface);
-    }
-  else if (wayland_toplevel->display_server.gtk_surface)
-    {
-      if (timestamp != GDK_CURRENT_TIME)
-        gtk_surface1_present (wayland_toplevel->display_server.gtk_surface, timestamp);
-      else if (startup_id && display_wayland->gtk_shell_version >= 3)
-        gtk_surface1_request_focus (wayland_toplevel->display_server.gtk_surface,
-                                    startup_id);
-    }
+  gdk_profiler_add_mark (GDK_PROFILER_CURRENT_TIME, 0, "wayland", "surface commit");
+  wl_surface_commit (wayland_surface->display_server.wl_surface);
+}
 
-  g_free (startup_id);
+static void
+gdk_wayland_toplevel_init (GdkWaylandToplevel *toplevel)
+{
+  toplevel->initial_fullscreen_output = NULL;
+  toplevel->shortcuts_inhibitors = g_hash_table_new (NULL, NULL);
 }
 
 static void
@@ -3667,6 +3681,63 @@ gdk_wayland_toplevel_init_gtk_surface (GdkWaylandToplevel *wayland_toplevel)
                              wayland_surface);
 }
 
+static void
+gdk_wayland_toplevel_set_title (GdkWaylandToplevel *toplevel,
+                                const char         *title)
+{
+  const char *end;
+  gsize title_length;
+
+  g_return_if_fail (title != NULL);
+
+  if (GDK_SURFACE_DESTROYED (GDK_SURFACE (toplevel)))
+    return;
+
+  if (g_strcmp0 (toplevel->title, title) == 0)
+    return;
+
+  g_free (toplevel->title);
+
+  title_length = MIN (strlen (title), MAX_WL_BUFFER_SIZE);
+  if (g_utf8_validate (title, title_length, &end))
+    {
+      toplevel->title = g_malloc (end - title + 1);
+      memcpy (toplevel->title, title, end - title);
+      toplevel->title[end - title] = '\0';
+    }
+  else
+    {
+      toplevel->title = g_utf8_make_valid (title, title_length);
+      g_warning ("Invalid utf8 passed to gdk_surface_set_title: '%s'", title);
+    }
+
+  gdk_wayland_toplevel_sync_title (toplevel);
+}
+
+static void
+gdk_wayland_toplevel_set_startup_id (GdkWaylandToplevel *toplevel,
+                                     const char         *startup_id)
+{
+  GdkWaylandDisplay *display =
+    GDK_WAYLAND_DISPLAY (gdk_surface_get_display (GDK_SURFACE (toplevel)));
+  char *free_me = NULL;
+
+  if (!startup_id)
+    {
+      free_me = g_steal_pointer (&display_wayland->startup_notification_id);
+      startup_id = free_me;
+    }
+
+  if (startup_id)
+    {
+      xdg_activation_v1_activate (display_wayland->xdg_activation,
+                                  startup_id,
+                                  wayland_toplevel->display_server.wl_surface);
+    }
+
+  g_free (free_me);
+}
+
 static void
 maybe_set_gtk_surface_modal (GdkWaylandToplevel *wayland_toplevel)
 {
@@ -3695,14 +3766,18 @@ gdk_wayland_toplevel_set_geometry_hints (GdkWaylandToplevel *toplevel,
                                          GdkSurfaceHints     geom_mask)
 {
   GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (toplevel);
-  GdkWaylandDisplay *display_wayland;
+  GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (gdk_surface_get_display (GDK_SURFACE (toplevel)));
   int min_width, min_height;
   int max_width, max_height;
 
   if (GDK_SURFACE_DESTROYED (toplevel))
     return;
 
-  display_wayland = GDK_WAYLAND_DISPLAY (gdk_surface_get_display (GDK_SURFACE (toplevel)));
+  if (!geometry)
+    {
+      geometry = &toplevel->geometry_hints;
+      geom_mask = toplevel->geometry_mask;
+    }
 
   toplevel->geometry_hints = *geometry;
   toplevel->geometry_mask = geom_mask;
@@ -3730,98 +3805,40 @@ gdk_wayland_toplevel_set_geometry_hints (GdkWaylandToplevel *toplevel,
       max_height = MAX (0, (geometry->max_height -
                             (impl->shadow_top + impl->shadow_bottom)));
     }
-  else
-    {
-      max_width = 0;
-      max_height = 0;
-    }
-
-  if (toplevel->last_sent_geometry_hints.min_width == min_width &&
-      toplevel->last_sent_geometry_hints.min_height == min_height &&
-      toplevel->last_sent_geometry_hints.max_width == max_width &&
-      toplevel->last_sent_geometry_hints.max_height == max_height)
-    return;
-
-  switch (display_wayland->shell_variant)
-    {
-    case GDK_WAYLAND_SHELL_VARIANT_XDG_SHELL:
-      xdg_toplevel_set_min_size (toplevel->display_server.xdg_toplevel,
-                                 min_width, min_height);
-      xdg_toplevel_set_max_size (toplevel->display_server.xdg_toplevel,
-                                 max_width, max_height);
-      break;
-    case GDK_WAYLAND_SHELL_VARIANT_ZXDG_SHELL_V6:
-      zxdg_toplevel_v6_set_min_size (toplevel->display_server.zxdg_toplevel_v6,
-                                     min_width, min_height);
-      zxdg_toplevel_v6_set_max_size (toplevel->display_server.zxdg_toplevel_v6,
-                                     max_width, max_height);
-      break;
-    default:
-      g_assert_not_reached ();
-    }
-
-  toplevel->last_sent_geometry_hints.min_width = min_width;
-  toplevel->last_sent_geometry_hints.min_height = min_height;
-  toplevel->last_sent_geometry_hints.max_width = max_width;
-  toplevel->last_sent_geometry_hints.max_height = max_height;
-}
-
-static void
-gdk_wayland_toplevel_set_title (GdkWaylandToplevel *toplevel,
-                                const char         *title)
-{
-  const char *end;
-  gsize title_length;
-
-  g_return_if_fail (title != NULL);
-
-  if (GDK_SURFACE_DESTROYED (GDK_SURFACE (toplevel)))
-    return;
-
-  if (g_strcmp0 (toplevel->title, title) == 0)
-    return;
-
-  g_free (toplevel->title);
-
-  title_length = MIN (strlen (title), MAX_WL_BUFFER_SIZE);
-  if (g_utf8_validate (title, title_length, &end))
-    {
-      toplevel->title = g_malloc (end - title + 1);
-      memcpy (toplevel->title, title, end - title);
-      toplevel->title[end - title] = '\0';
-    }
-  else
-    {
-      toplevel->title = g_utf8_make_valid (title, title_length);
-      g_warning ("Invalid utf8 passed to gdk_surface_set_title: '%s'", title);
-    }
-
-  gdk_wayland_toplevel_sync_title (toplevel);
-}
-
-static void
-gdk_wayland_surface_set_startup_id (GdkSurface  *surface,
-                                    const char *startup_id)
-{
-  GdkWaylandSurface *wayland_surface = GDK_WAYLAND_SURFACE (surface);
-  GdkDisplay *display = gdk_surface_get_display (surface);
-  GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display);
-  gchar *free_me = NULL;
-
-  if (!startup_id)
+  else
     {
-      free_me = g_steal_pointer (&display_wayland->startup_notification_id);
-      startup_id = free_me;
+      max_width = 0;
+      max_height = 0;
     }
 
-  if (startup_id)
+  if (toplevel->last_sent_geometry_hints.min_width == min_width &&
+      toplevel->last_sent_geometry_hints.min_height == min_height &&
+      toplevel->last_sent_geometry_hints.max_width == max_width &&
+      toplevel->last_sent_geometry_hints.max_height == max_height)
+    return;
+
+  switch (display_wayland->shell_variant)
     {
-      xdg_activation_v1_activate (display_wayland->xdg_activation,
-                                  startup_id,
-                                  wayland_surface->display_server.wl_surface);
+    case GDK_WAYLAND_SHELL_VARIANT_XDG_SHELL:
+      xdg_toplevel_set_min_size (toplevel->display_server.xdg_toplevel,
+                                 min_width, min_height);
+      xdg_toplevel_set_max_size (toplevel->display_server.xdg_toplevel,
+                                 max_width, max_height);
+      break;
+    case GDK_WAYLAND_SHELL_VARIANT_ZXDG_SHELL_V6:
+      zxdg_toplevel_v6_set_min_size (toplevel->display_server.zxdg_toplevel_v6,
+                                     min_width, min_height);
+      zxdg_toplevel_v6_set_max_size (toplevel->display_server.zxdg_toplevel_v6,
+                                     max_width, max_height);
+      break;
+    default:
+      g_assert_not_reached ();
     }
 
-  g_free (free_me);
+  toplevel->last_sent_geometry_hints.min_width = min_width;
+  toplevel->last_sent_geometry_hints.min_height = min_height;
+  toplevel->last_sent_geometry_hints.max_width = max_width;
+  toplevel->last_sent_geometry_hints.max_height = max_height;
 }
 
 static gboolean
@@ -3864,118 +3881,207 @@ gdk_wayland_toplevel_set_transient_for (GdkWaylandToplevel *toplevel,
   else
     toplevel->transient_for = NULL;
 
-  gdk_wayland_surface_sync_parent (GDK_SURFACE (toplevel), NULL);
+  gdk_wayland_toplevel_sync_parent (toplevel);
 }
 
-static gboolean
-gdk_wayland_toplevel_minimize (GdkToplevel *toplevel)
+static void
+gdk_wayland_toplevel_set_property (GObject      *object,
+                                   guint         prop_id,
+                                   const GValue *value,
+                                   GParamSpec   *pspec)
 {
-  GdkSurface *surface = GDK_SURFACE (toplevel);
-  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
-  GdkWaylandToplevel *wayland_toplevel = GDK_WAYLAND_TOPLEVEL (surface);
-  GdkWaylandDisplay *display_wayland;
+  GdkSurface *surface = GDK_SURFACE (object);
+  GdkWaylandToplevel *toplevel = GDK_WAYLAND_TOPLEVEL (surface);
 
-  if (GDK_SURFACE_DESTROYED (surface))
-    return TRUE;
+  switch (prop_id)
+    {
+    case LAST_PROP + GDK_TOPLEVEL_PROP_TITLE:
+      gdk_wayland_toplevel_set_title (toplevel, g_value_get_string (value));
+      g_object_notify_by_pspec (object, pspec);
+      break;
 
-  if (!is_realized_toplevel (impl))
-    return TRUE;
+    case LAST_PROP + GDK_TOPLEVEL_PROP_STARTUP_ID:
+      gdk_wayland_toplevel_set_startup_id (toplevel, g_value_get_string (value));
+      g_object_notify_by_pspec (object, pspec);
+      break;
 
-  /* FIXME: xdg_toplevel does not come with a minimized state that we can
-   * query or get notified of. This means we cannot implement the full
-   * GdkSurface API, and our state will not reflect minimization.
-   */
-  display_wayland = GDK_WAYLAND_DISPLAY (gdk_surface_get_display (surface));
-  switch (display_wayland->shell_variant)
-    {
-    case GDK_WAYLAND_SHELL_VARIANT_XDG_SHELL:
-      xdg_toplevel_set_minimized (wayland_toplevel->display_server.xdg_toplevel);
+    case LAST_PROP + GDK_TOPLEVEL_PROP_TRANSIENT_FOR:
+      gdk_wayland_toplevel_set_transient_for (toplevel, g_value_get_object (value));
+      g_object_notify_by_pspec (object, pspec);
       break;
-    case GDK_WAYLAND_SHELL_VARIANT_ZXDG_SHELL_V6:
-      zxdg_toplevel_v6_set_minimized (wayland_toplevel->display_server.zxdg_toplevel_v6);
+
+    case LAST_PROP + GDK_TOPLEVEL_PROP_MODAL:
+      gdk_wayland_toplevel_set_modal_hint (toplevel, g_value_get_boolean (value));
+      g_object_notify_by_pspec (object, pspec);
+      break;
+
+    case LAST_PROP + GDK_TOPLEVEL_PROP_ICON_LIST:
+      break;
+
+    case LAST_PROP + GDK_TOPLEVEL_PROP_DECORATED:
       break;
+
+    case LAST_PROP + GDK_TOPLEVEL_PROP_DELETABLE:
+      break;
+
+    case LAST_PROP + GDK_TOPLEVEL_PROP_FULLSCREEN_MODE:
+      surface->fullscreen_mode = g_value_get_enum (value);
+      g_object_notify_by_pspec (object, pspec);
+      break;
+
+    case LAST_PROP + GDK_TOPLEVEL_PROP_SHORTCUTS_INHIBITED:
+      break;
+
     default:
-      g_assert_not_reached ();
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
     }
-
-  return TRUE;
 }
 
 static void
-gdk_wayland_toplevel_maximize (GdkToplevel *toplevel)
+gdk_wayland_toplevel_get_property (GObject    *object,
+                                   guint       prop_id,
+                                   GValue     *value,
+                                   GParamSpec *pspec)
 {
-  GdkSurface *surface = GDK_SURFACE (toplevel);
-  GdkWaylandSurface *wayland_surface = GDK_WAYLAND_SURFACE (surface);
-  GdkWaylandToplevel *wayland_toplevel = GDK_WAYLAND_TOPLEVEL (toplevel);
+  GdkSurface *surface = GDK_SURFACE (object);
+  GdkWaylandToplevel *toplevel = GDK_WAYLAND_TOPLEVEL (surface);
 
-  if (GDK_SURFACE_DESTROYED (surface))
-    return;
+  switch (prop_id)
+    {
+    case LAST_PROP + GDK_TOPLEVEL_PROP_STATE:
+      g_value_set_flags (value, surface->state);
+      break;
 
-  _gdk_wayland_surface_save_size (surface);
+    case LAST_PROP + GDK_TOPLEVEL_PROP_TITLE:
+      g_value_set_string (value, toplevel->title);
+      break;
 
-  if (is_realized_toplevel (wayland_surface))
-    {
-      GdkWaylandDisplay *display_wayland =
-        GDK_WAYLAND_DISPLAY (gdk_surface_get_display (surface));
+    case LAST_PROP + GDK_TOPLEVEL_PROP_STARTUP_ID:
+      g_value_set_string (value, "");
+      break;
 
-      switch (display_wayland->shell_variant)
-        {
-        case GDK_WAYLAND_SHELL_VARIANT_XDG_SHELL:
-          xdg_toplevel_set_maximized (wayland_toplevel->display_server.xdg_toplevel);
-          break;
-        case GDK_WAYLAND_SHELL_VARIANT_ZXDG_SHELL_V6:
-          zxdg_toplevel_v6_set_maximized (wayland_toplevel->display_server.zxdg_toplevel_v6);
-          break;
-        default:
-          g_assert_not_reached ();
-        }
-    }
-  else
-    {
-      synthesize_initial_surface_state (wayland_toplevel, 0, GDK_TOPLEVEL_STATE_MAXIMIZED);
+    case LAST_PROP + GDK_TOPLEVEL_PROP_TRANSIENT_FOR:
+      g_value_set_object (value, toplevel->transient_for);
+      break;
+
+    case LAST_PROP + GDK_TOPLEVEL_PROP_MODAL:
+      g_value_set_boolean (value, surface->modal_hint);
+      break;
+
+    case LAST_PROP + GDK_TOPLEVEL_PROP_ICON_LIST:
+      g_value_set_pointer (value, NULL);
+      break;
+
+    case LAST_PROP + GDK_TOPLEVEL_PROP_DECORATED:
+      break;
+
+    case LAST_PROP + GDK_TOPLEVEL_PROP_DELETABLE:
+      break;
+
+    case LAST_PROP + GDK_TOPLEVEL_PROP_FULLSCREEN_MODE:
+      g_value_set_enum (value, surface->fullscreen_mode);
+      break;
+
+    case LAST_PROP + GDK_TOPLEVEL_PROP_SHORTCUTS_INHIBITED:
+      g_value_set_boolean (value, surface->shortcuts_inhibited);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
     }
 }
 
 static void
-gdk_wayland_toplevel_unmaximize (GdkToplevel *toplevel)
+gdk_wayland_toplevel_finalize (GObject *object)
+{
+  GdkWaylandToplevel *wayland_toplevel;
+
+  g_return_if_fail (GDK_IS_WAYLAND_TOPLEVEL (object));
+
+  wayland_toplevel = GDK_WAYLAND_TOPLEVEL (object);
+
+  if (gdk_wayland_toplevel_is_exported (wayland_toplevel))
+    gdk_wayland_toplevel_unexport_handle (GDK_TOPLEVEL (wayland_toplevel));
+
+  g_free (wayland_toplevel->application.application_id);
+  g_free (wayland_toplevel->application.app_menu_path);
+  g_free (wayland_toplevel->application.menubar_path);
+  g_free (wayland_toplevel->application.window_object_path);
+  g_free (wayland_toplevel->application.application_object_path);
+  g_free (wayland_toplevel->application.unique_bus_name);
+
+  g_free (wayland_toplevel->title);
+  g_clear_pointer (&wayland_toplevel->shortcuts_inhibitors, g_hash_table_unref);
+
+  G_OBJECT_CLASS (gdk_wayland_toplevel_parent_class)->finalize (object);
+}
+
+static void
+gdk_wayland_toplevel_class_init (GdkWaylandToplevelClass *class)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+  object_class->get_property = gdk_wayland_toplevel_get_property;
+  object_class->set_property = gdk_wayland_toplevel_set_property;
+  object_class->finalize = gdk_wayland_toplevel_finalize;
+
+  gdk_toplevel_install_properties (object_class, 1);
+}
+
+static void
+synthesize_initial_surface_state (GdkWaylandToplevel *wayland_toplevel,
+                                  GdkToplevelState    unset_flags,
+                                  GdkToplevelState    set_flags)
+{
+  wayland_toplevel->initial_state.unset_flags |= unset_flags;
+  wayland_toplevel->initial_state.set_flags &= ~unset_flags;
+
+  wayland_toplevel->initial_state.set_flags |= set_flags;
+  wayland_toplevel->initial_state.unset_flags &= ~set_flags;
+}
+
+static gboolean
+gdk_wayland_toplevel_minimize (GdkToplevel *toplevel)
 {
   GdkSurface *surface = GDK_SURFACE (toplevel);
-  GdkWaylandSurface *wayland_surface = GDK_WAYLAND_SURFACE (surface);
-  GdkWaylandToplevel *wayland_toplevel = GDK_WAYLAND_TOPLEVEL (toplevel);
+  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
+  GdkWaylandToplevel *wayland_toplevel = GDK_WAYLAND_TOPLEVEL (surface);
+  GdkWaylandDisplay *display_wayland;
 
   if (GDK_SURFACE_DESTROYED (surface))
-    return;
+    return TRUE;
 
-  if (is_realized_toplevel (wayland_surface))
-    {
-      GdkWaylandDisplay *display_wayland =
-        GDK_WAYLAND_DISPLAY (gdk_surface_get_display (surface));
+  if (!is_realized_toplevel (impl))
+    return TRUE;
 
-      switch (display_wayland->shell_variant)
-        {
-        case GDK_WAYLAND_SHELL_VARIANT_XDG_SHELL:
-          xdg_toplevel_unset_maximized (wayland_toplevel->display_server.xdg_toplevel);
-          break;
-        case GDK_WAYLAND_SHELL_VARIANT_ZXDG_SHELL_V6:
-          zxdg_toplevel_v6_unset_maximized (wayland_toplevel->display_server.zxdg_toplevel_v6);
-          break;
-        default:
-          g_assert_not_reached ();
-        }
-    }
-  else
+  /* FIXME: xdg_toplevel does not come with a minimized state that we can
+   * query or get notified of. This means we cannot implement the full
+   * GdkSurface API, and our state will not reflect minimization.
+   */
+  display_wayland = GDK_WAYLAND_DISPLAY (gdk_surface_get_display (surface));
+  switch (display_wayland->shell_variant)
     {
-      synthesize_initial_surface_state (wayland_toplevel, GDK_TOPLEVEL_STATE_MAXIMIZED, 0);
+    case GDK_WAYLAND_SHELL_VARIANT_XDG_SHELL:
+      xdg_toplevel_set_minimized (wayland_toplevel->display_server.xdg_toplevel);
+      break;
+    case GDK_WAYLAND_SHELL_VARIANT_ZXDG_SHELL_V6:
+      zxdg_toplevel_v6_set_minimized (wayland_toplevel->display_server.zxdg_toplevel_v6);
+      break;
+    default:
+      g_assert_not_reached ();
     }
+
+  return TRUE;
 }
 
 static void
-gdk_wayland_toplevel_fullscreen_on_monitor (GdkWaylandToplevel *wayland_toplevel,
-                                            GdkMonitor         *monitor)
+gdk_wayland_toplevel_maximize (GdkToplevel *toplevel)
 {
-  GdkSurface *surface = GDK_SURFACE (wayland_toplevel);
+  GdkSurface *surface = GDK_SURFACE (toplevel);
   GdkWaylandSurface *wayland_surface = GDK_WAYLAND_SURFACE (surface);
-  struct wl_output *output = ((GdkWaylandMonitor *)monitor)->output;
+  GdkWaylandToplevel *wayland_toplevel = GDK_WAYLAND_TOPLEVEL (toplevel);
 
   if (GDK_SURFACE_DESTROYED (surface))
     return;
@@ -3990,10 +4096,10 @@ gdk_wayland_toplevel_fullscreen_on_monitor (GdkWaylandToplevel *wayland_toplevel
       switch (display_wayland->shell_variant)
         {
         case GDK_WAYLAND_SHELL_VARIANT_XDG_SHELL:
-          xdg_toplevel_set_fullscreen (wayland_toplevel->display_server.xdg_toplevel, output);
+          xdg_toplevel_set_maximized (wayland_toplevel->display_server.xdg_toplevel);
           break;
         case GDK_WAYLAND_SHELL_VARIANT_ZXDG_SHELL_V6:
-          zxdg_toplevel_v6_set_fullscreen (wayland_toplevel->display_server.zxdg_toplevel_v6, output);
+          zxdg_toplevel_v6_set_maximized (wayland_toplevel->display_server.zxdg_toplevel_v6);
           break;
         default:
           g_assert_not_reached ();
@@ -4001,24 +4107,20 @@ gdk_wayland_toplevel_fullscreen_on_monitor (GdkWaylandToplevel *wayland_toplevel
     }
   else
     {
-      synthesize_initial_surface_state (wayland_toplevel, 0, GDK_TOPLEVEL_STATE_FULLSCREEN);
-      wayland_toplevel->initial_fullscreen_output = output;
+      synthesize_initial_surface_state (wayland_toplevel, 0, GDK_TOPLEVEL_STATE_MAXIMIZED);
     }
 }
 
 static void
-gdk_wayland_toplevel_fullscreen (GdkWaylandToplevel *wayland_toplevel)
+gdk_wayland_toplevel_unmaximize (GdkToplevel *toplevel)
 {
-  GdkSurface *surface = GDK_SURFACE (wayland_toplevel);
-  GdkWaylandSurface *wayland_surface = GDK_WAYLAND_SURFACE (wayland_toplevel);
+  GdkSurface *surface = GDK_SURFACE (toplevel);
+  GdkWaylandSurface *wayland_surface = GDK_WAYLAND_SURFACE (surface);
+  GdkWaylandToplevel *wayland_toplevel = GDK_WAYLAND_TOPLEVEL (toplevel);
 
   if (GDK_SURFACE_DESTROYED (surface))
     return;
 
-  wayland_toplevel->initial_fullscreen_output = NULL;
-
-  _gdk_wayland_surface_save_size (surface);
-
   if (is_realized_toplevel (wayland_surface))
     {
       GdkWaylandDisplay *display_wayland =
@@ -4027,10 +4129,10 @@ gdk_wayland_toplevel_fullscreen (GdkWaylandToplevel *wayland_toplevel)
       switch (display_wayland->shell_variant)
         {
         case GDK_WAYLAND_SHELL_VARIANT_XDG_SHELL:
-          xdg_toplevel_set_fullscreen (wayland_toplevel->display_server.xdg_toplevel, NULL);
+          xdg_toplevel_unset_maximized (wayland_toplevel->display_server.xdg_toplevel);
           break;
         case GDK_WAYLAND_SHELL_VARIANT_ZXDG_SHELL_V6:
-          zxdg_toplevel_v6_set_fullscreen (wayland_toplevel->display_server.zxdg_toplevel_v6, NULL);
+          zxdg_toplevel_v6_unset_maximized (wayland_toplevel->display_server.zxdg_toplevel_v6);
           break;
         default:
           g_assert_not_reached ();
@@ -4038,20 +4140,22 @@ gdk_wayland_toplevel_fullscreen (GdkWaylandToplevel *wayland_toplevel)
     }
   else
     {
-      synthesize_initial_surface_state (wayland_toplevel, 0, GDK_TOPLEVEL_STATE_FULLSCREEN);
+      synthesize_initial_surface_state (wayland_toplevel, GDK_TOPLEVEL_STATE_MAXIMIZED, 0);
     }
 }
 
 static void
-gdk_wayland_toplevel_unfullscreen (GdkWaylandToplevel *wayland_toplevel)
+gdk_wayland_toplevel_fullscreen_on_monitor (GdkWaylandToplevel *wayland_toplevel,
+                                            GdkMonitor         *monitor)
 {
   GdkSurface *surface = GDK_SURFACE (wayland_toplevel);
-  GdkWaylandSurface *wayland_surface = GDK_WAYLAND_SURFACE (wayland_toplevel);
+  GdkWaylandSurface *wayland_surface = GDK_WAYLAND_SURFACE (surface);
+  struct wl_output *output = ((GdkWaylandMonitor *)monitor)->output;
 
   if (GDK_SURFACE_DESTROYED (surface))
     return;
 
-  wayland_toplevel->initial_fullscreen_output = NULL;
+  _gdk_wayland_surface_save_size (surface);
 
   if (is_realized_toplevel (wayland_surface))
     {
@@ -4061,10 +4165,10 @@ gdk_wayland_toplevel_unfullscreen (GdkWaylandToplevel *wayland_toplevel)
       switch (display_wayland->shell_variant)
         {
         case GDK_WAYLAND_SHELL_VARIANT_XDG_SHELL:
-          xdg_toplevel_unset_fullscreen (wayland_toplevel->display_server.xdg_toplevel);
+          xdg_toplevel_set_fullscreen (wayland_toplevel->display_server.xdg_toplevel, output);
           break;
         case GDK_WAYLAND_SHELL_VARIANT_ZXDG_SHELL_V6:
-          zxdg_toplevel_v6_unset_fullscreen (wayland_toplevel->display_server.zxdg_toplevel_v6);
+          zxdg_toplevel_v6_set_fullscreen (wayland_toplevel->display_server.zxdg_toplevel_v6, output);
           break;
         default:
           g_assert_not_reached ();
@@ -4072,1218 +4176,1103 @@ gdk_wayland_toplevel_unfullscreen (GdkWaylandToplevel *wayland_toplevel)
     }
   else
     {
-      synthesize_initial_surface_state (wayland_toplevel, GDK_TOPLEVEL_STATE_FULLSCREEN, 0);
-    }
-}
-
-static void
-gdk_wayland_toplevel_begin_resize (GdkToplevel    *toplevel,
-                                   GdkSurfaceEdge  edge,
-                                   GdkDevice      *device,
-                                   int             button,
-                                   double          x,
-                                   double          y,
-                                   guint32         timestamp)
-{
-  GdkSurface *surface = GDK_SURFACE (toplevel);
-  GdkWaylandSurface *impl;
-  GdkWaylandToplevel *wayland_toplevel;
-  GdkWaylandDisplay *display_wayland;
-  GdkEventSequence *sequence;
-  uint32_t resize_edges, serial;
-
-  if (GDK_SURFACE_DESTROYED (surface))
-    return;
-
-  switch (edge)
-    {
-    case GDK_SURFACE_EDGE_NORTH_WEST:
-      resize_edges = ZXDG_TOPLEVEL_V6_RESIZE_EDGE_TOP_LEFT;
-      break;
-
-    case GDK_SURFACE_EDGE_NORTH:
-      resize_edges = ZXDG_TOPLEVEL_V6_RESIZE_EDGE_TOP;
-      break;
-
-    case GDK_SURFACE_EDGE_NORTH_EAST:
-      resize_edges = ZXDG_TOPLEVEL_V6_RESIZE_EDGE_TOP_RIGHT;
-      break;
-
-    case GDK_SURFACE_EDGE_WEST:
-      resize_edges = ZXDG_TOPLEVEL_V6_RESIZE_EDGE_LEFT;
-      break;
-
-    case GDK_SURFACE_EDGE_EAST:
-      resize_edges = ZXDG_TOPLEVEL_V6_RESIZE_EDGE_RIGHT;
-      break;
-
-    case GDK_SURFACE_EDGE_SOUTH_WEST:
-      resize_edges = ZXDG_TOPLEVEL_V6_RESIZE_EDGE_BOTTOM_LEFT;
-      break;
-
-    case GDK_SURFACE_EDGE_SOUTH:
-      resize_edges = ZXDG_TOPLEVEL_V6_RESIZE_EDGE_BOTTOM;
-      break;
-
-    case GDK_SURFACE_EDGE_SOUTH_EAST:
-      resize_edges = ZXDG_TOPLEVEL_V6_RESIZE_EDGE_BOTTOM_RIGHT;
-      break;
-
-    default:
-      g_warning ("gdk_toplevel_begin_resize: bad resize edge %d!", edge);
-      return;
-    }
-
-  impl = GDK_WAYLAND_SURFACE (surface);
-  wayland_toplevel = GDK_WAYLAND_TOPLEVEL (surface);
-  display_wayland = GDK_WAYLAND_DISPLAY (gdk_surface_get_display (surface));
-
-  if (!is_realized_toplevel (impl))
-    return;
-
-  serial = _gdk_wayland_seat_get_last_implicit_grab_serial (GDK_WAYLAND_SEAT (gdk_device_get_seat (device)),
-                                                            &sequence);
-
-  switch (display_wayland->shell_variant)
-    {
-    case GDK_WAYLAND_SHELL_VARIANT_XDG_SHELL:
-      xdg_toplevel_resize (wayland_toplevel->display_server.xdg_toplevel,
-                           gdk_wayland_device_get_wl_seat (device),
-                           serial, resize_edges);
-      break;
-    case GDK_WAYLAND_SHELL_VARIANT_ZXDG_SHELL_V6:
-      zxdg_toplevel_v6_resize (wayland_toplevel->display_server.zxdg_toplevel_v6,
-                               gdk_wayland_device_get_wl_seat (device),
-                               serial, resize_edges);
-      break;
-    default:
-      g_assert_not_reached ();
-    }
-
-  if (sequence)
-    gdk_wayland_device_unset_touch_grab (device, sequence);
-}
-
-static void
-gdk_wayland_toplevel_begin_move (GdkToplevel *toplevel,
-                                 GdkDevice   *device,
-                                 int          button,
-                                 double       x,
-                                 double       y,
-                                 guint32      timestamp)
-{
-  GdkSurface *surface = GDK_SURFACE (toplevel);
-  GdkWaylandSurface *impl;
-  GdkWaylandToplevel *wayland_toplevel;
-  GdkWaylandDisplay *display_wayland;
-  GdkEventSequence *sequence;
-  uint32_t serial;
-
-  if (GDK_SURFACE_DESTROYED (surface))
-    return;
-
-  impl = GDK_WAYLAND_SURFACE (surface);
-  wayland_toplevel = GDK_WAYLAND_TOPLEVEL (surface);
-  display_wayland = GDK_WAYLAND_DISPLAY (gdk_surface_get_display (surface));
-
-  if (!is_realized_toplevel (impl))
-    return;
-
-  serial = _gdk_wayland_seat_get_last_implicit_grab_serial (GDK_WAYLAND_SEAT (gdk_device_get_seat (device)),
-                                                            &sequence);
-  switch (display_wayland->shell_variant)
-    {
-    case GDK_WAYLAND_SHELL_VARIANT_XDG_SHELL:
-      xdg_toplevel_move (wayland_toplevel->display_server.xdg_toplevel,
-                         gdk_wayland_device_get_wl_seat (device),
-                         serial);
-      break;
-    case GDK_WAYLAND_SHELL_VARIANT_ZXDG_SHELL_V6:
-      zxdg_toplevel_v6_move (wayland_toplevel->display_server.zxdg_toplevel_v6,
-                             gdk_wayland_device_get_wl_seat (device),
-                             serial);
-      break;
-    default:
-      g_assert_not_reached ();
-    }
-
-  if (sequence)
-    gdk_wayland_device_unset_touch_grab (device, sequence);
-}
-
-static void
-gdk_wayland_surface_destroy_notify (GdkSurface *surface)
-{
-  if (!GDK_SURFACE_DESTROYED (surface))
-    {
-      g_warning ("GdkSurface %p unexpectedly destroyed", surface);
-      _gdk_surface_destroy (surface, TRUE);
+      synthesize_initial_surface_state (wayland_toplevel, 0, GDK_TOPLEVEL_STATE_FULLSCREEN);
+      wayland_toplevel->initial_fullscreen_output = output;
     }
-
-  g_object_unref (surface);
-}
-
-static int
-gdk_wayland_surface_get_scale_factor (GdkSurface *surface)
-{
-  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
-
-  if (GDK_SURFACE_DESTROYED (surface))
-    return 1;
-
-  return impl->scale;
 }
 
 static void
-gdk_wayland_surface_set_opaque_region (GdkSurface     *surface,
-                                       cairo_region_t *region)
+gdk_wayland_toplevel_fullscreen (GdkWaylandToplevel *wayland_toplevel)
 {
-  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
+  GdkSurface *surface = GDK_SURFACE (wayland_toplevel);
+  GdkWaylandSurface *wayland_surface = GDK_WAYLAND_SURFACE (wayland_toplevel);
 
   if (GDK_SURFACE_DESTROYED (surface))
     return;
 
-  g_clear_pointer (&impl->opaque_region, cairo_region_destroy);
-  impl->opaque_region = cairo_region_reference (region);
-  impl->opaque_region_dirty = TRUE;
-}
-
-static gboolean
-gdk_wayland_toplevel_show_window_menu (GdkToplevel *toplevel,
-                                       GdkEvent    *event)
-{
-  GdkSurface *surface = GDK_SURFACE (toplevel);
-  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
-  GdkWaylandToplevel *wayland_toplevel = GDK_WAYLAND_TOPLEVEL (surface);
-  GdkWaylandDisplay *display_wayland =
-    GDK_WAYLAND_DISPLAY (gdk_surface_get_display (surface));
-  GdkSeat *seat;
-  struct wl_seat *wl_seat;
-  double x, y;
-  uint32_t serial;
-
-  GdkEventType event_type = gdk_event_get_event_type (event);
-  switch ((guint) event_type)
-    {
-    case GDK_BUTTON_PRESS:
-    case GDK_BUTTON_RELEASE:
-    case GDK_TOUCH_BEGIN:
-    case GDK_TOUCH_END:
-      break;
-    default:
-      return FALSE;
-    }
-
-  if (!is_realized_toplevel (impl))
-    return FALSE;
+  wayland_toplevel->initial_fullscreen_output = NULL;
 
-  seat = gdk_event_get_seat (event);
-  wl_seat = gdk_wayland_seat_get_wl_seat (seat);
-  gdk_event_get_position (event, &x, &y);
+  _gdk_wayland_surface_save_size (surface);
 
-  serial = _gdk_wayland_seat_get_implicit_grab_serial (seat, event);
+  if (is_realized_toplevel (wayland_surface))
+    {
+      GdkWaylandDisplay *display_wayland =
+        GDK_WAYLAND_DISPLAY (gdk_surface_get_display (surface));
 
-  switch (display_wayland->shell_variant)
+      switch (display_wayland->shell_variant)
+        {
+        case GDK_WAYLAND_SHELL_VARIANT_XDG_SHELL:
+          xdg_toplevel_set_fullscreen (wayland_toplevel->display_server.xdg_toplevel, NULL);
+          break;
+        case GDK_WAYLAND_SHELL_VARIANT_ZXDG_SHELL_V6:
+          zxdg_toplevel_v6_set_fullscreen (wayland_toplevel->display_server.zxdg_toplevel_v6, NULL);
+          break;
+        default:
+          g_assert_not_reached ();
+        }
+    }
+  else
     {
-    case GDK_WAYLAND_SHELL_VARIANT_XDG_SHELL:
-      xdg_toplevel_show_window_menu (wayland_toplevel->display_server.xdg_toplevel,
-                                     wl_seat, serial, x, y);
-      break;
-    case GDK_WAYLAND_SHELL_VARIANT_ZXDG_SHELL_V6:
-      zxdg_toplevel_v6_show_window_menu (wayland_toplevel->display_server.zxdg_toplevel_v6,
-                                         wl_seat, serial, x, y);
-      break;
-    default:
-      g_assert_not_reached ();
+      synthesize_initial_surface_state (wayland_toplevel, 0, GDK_TOPLEVEL_STATE_FULLSCREEN);
     }
-
-  return TRUE;
 }
 
-static gboolean
-translate_gesture (GdkTitlebarGesture         gesture,
-                   enum gtk_surface1_gesture *out_gesture)
+static void
+gdk_wayland_toplevel_unfullscreen (GdkWaylandToplevel *wayland_toplevel)
 {
-  switch (gesture)
-    {
-    case GDK_TITLEBAR_GESTURE_DOUBLE_CLICK:
-      *out_gesture = GTK_SURFACE1_GESTURE_DOUBLE_CLICK;
-      break;
+  GdkSurface *surface = GDK_SURFACE (wayland_toplevel);
+  GdkWaylandSurface *wayland_surface = GDK_WAYLAND_SURFACE (wayland_toplevel);
 
-    case GDK_TITLEBAR_GESTURE_RIGHT_CLICK:
-      *out_gesture = GTK_SURFACE1_GESTURE_RIGHT_CLICK;
-      break;
+  if (GDK_SURFACE_DESTROYED (surface))
+    return;
 
-    case GDK_TITLEBAR_GESTURE_MIDDLE_CLICK:
-      *out_gesture = GTK_SURFACE1_GESTURE_MIDDLE_CLICK;
-      break;
+  wayland_toplevel->initial_fullscreen_output = NULL;
 
-    default:
-      g_warning ("Not handling unknown titlebar gesture %u", gesture);
-      return FALSE;
-    }
+  if (is_realized_toplevel (wayland_surface))
+    {
+      GdkWaylandDisplay *display_wayland =
+        GDK_WAYLAND_DISPLAY (gdk_surface_get_display (surface));
 
-  return TRUE;
+      switch (display_wayland->shell_variant)
+        {
+        case GDK_WAYLAND_SHELL_VARIANT_XDG_SHELL:
+          xdg_toplevel_unset_fullscreen (wayland_toplevel->display_server.xdg_toplevel);
+          break;
+        case GDK_WAYLAND_SHELL_VARIANT_ZXDG_SHELL_V6:
+          zxdg_toplevel_v6_unset_fullscreen (wayland_toplevel->display_server.zxdg_toplevel_v6);
+          break;
+        default:
+          g_assert_not_reached ();
+        }
+    }
+  else
+    {
+      synthesize_initial_surface_state (wayland_toplevel, GDK_TOPLEVEL_STATE_FULLSCREEN, 0);
+    }
 }
 
-static gboolean
-gdk_wayland_toplevel_titlebar_gesture (GdkToplevel        *toplevel,
-                                       GdkTitlebarGesture  gesture)
+static void
+gdk_wayland_toplevel_present (GdkToplevel       *toplevel,
+                              GdkToplevelLayout *layout)
 {
   GdkSurface *surface = GDK_SURFACE (toplevel);
+  GdkWaylandSurface *wayland_surface = GDK_WAYLAND_SURFACE (toplevel);
   GdkWaylandToplevel *wayland_toplevel = GDK_WAYLAND_TOPLEVEL (toplevel);
-  struct gtk_surface1 *gtk_surface = wayland_toplevel->display_server.gtk_surface;
-  enum gtk_surface1_gesture gtk_gesture;
-  GdkSeat *seat;
-  struct wl_seat *wl_seat;
-  uint32_t serial;
-
-  if (!gtk_surface)
-    return FALSE;
+  gboolean pending_configure = FALSE;
+  gboolean maximize;
+  gboolean fullscreen;
 
-  if (gtk_surface1_get_version (gtk_surface) < GTK_SURFACE1_TITLEBAR_GESTURE_SINCE_VERSION)
-    return FALSE;
+  if (gdk_toplevel_layout_get_maximized (layout, &maximize))
+    {
+      if (maximize)
+        gdk_wayland_toplevel_maximize (toplevel);
+      else
+        gdk_wayland_toplevel_unmaximize (toplevel);
+      pending_configure = TRUE;
+    }
 
-  if (!translate_gesture (gesture, &gtk_gesture))
-    return FALSE;
+  if (gdk_toplevel_layout_get_fullscreen (layout, &fullscreen))
+    {
+      if (fullscreen)
+        {
+          GdkMonitor *monitor;
 
-  seat = gdk_display_get_default_seat (surface->display);
-  wl_seat = gdk_wayland_seat_get_wl_seat (seat);
+          monitor = gdk_toplevel_layout_get_fullscreen_monitor (layout);
+          if (monitor)
+            gdk_wayland_toplevel_fullscreen_on_monitor (wayland_toplevel, monitor);
+          else
+            gdk_wayland_toplevel_fullscreen (wayland_toplevel);
+        }
+      else
+        {
+          gdk_wayland_toplevel_unfullscreen (wayland_toplevel);
+        }
+      pending_configure = TRUE;
+    }
 
-  serial = _gdk_wayland_seat_get_last_implicit_grab_serial (GDK_WAYLAND_SEAT (seat), NULL);
+  g_clear_pointer (&wayland_toplevel->layout, gdk_toplevel_layout_unref);
+  wayland_toplevel->layout = gdk_toplevel_layout_copy (layout);
 
-  gtk_surface1_titlebar_gesture (wayland_toplevel->display_server.gtk_surface,
-                                 serial,
-                                 wl_seat,
-                                 gtk_gesture);
+  gdk_wayland_surface_show (surface);
 
-  return TRUE;
+  if (!pending_configure)
+    {
+      wayland_surface->next_layout.surface_geometry_dirty = TRUE;
+      gdk_surface_request_layout (surface);
+    }
 }
 
 static gboolean
-gdk_wayland_toplevel_supports_edge_constraints (GdkToplevel *toplevel)
+gdk_wayland_toplevel_lower (GdkToplevel *toplevel)
 {
-  GdkWaylandToplevel *wayland_toplevel = GDK_WAYLAND_TOPLEVEL (toplevel);
-  struct gtk_surface1 *gtk_surface = wayland_toplevel->display_server.gtk_surface;
+  return FALSE;
+}
 
-  if (!gtk_surface)
-    return FALSE;
+static void
+inhibitor_active (void *data,
+                  struct zwp_keyboard_shortcuts_inhibitor_v1 *inhibitor)
+{
+  GdkToplevel *toplevel = GDK_TOPLEVEL (data);
+  GdkSurface *surface = GDK_SURFACE (toplevel);
 
-  return gtk_surface1_get_version (gtk_surface) >= GTK_SURFACE1_CONFIGURE_EDGES_SINCE_VERSION;
+  surface->shortcuts_inhibited = TRUE;
+  g_object_notify (G_OBJECT (toplevel), "shortcuts-inhibited");
 }
 
 static void
-gdk_wayland_surface_class_init (GdkWaylandSurfaceClass *klass)
+inhibitor_inactive (void *data,
+                    struct zwp_keyboard_shortcuts_inhibitor_v1 *inhibitor)
 {
-  GObjectClass *object_class = G_OBJECT_CLASS (klass);
-  GdkSurfaceClass *impl_class = GDK_SURFACE_CLASS (klass);
+  GdkToplevel *toplevel = GDK_TOPLEVEL (data);
+  GdkSurface *surface = GDK_SURFACE (toplevel);
 
-  object_class->constructed = gdk_wayland_surface_constructed;
-  object_class->dispose = gdk_wayland_surface_dispose;
-  object_class->finalize = gdk_wayland_surface_finalize;
+  surface->shortcuts_inhibited = FALSE;
+  g_object_notify (G_OBJECT (toplevel), "shortcuts-inhibited");
+}
 
-  impl_class->hide = gdk_wayland_surface_hide;
-  impl_class->get_geometry = gdk_wayland_surface_get_geometry;
-  impl_class->get_root_coords = gdk_wayland_surface_get_root_coords;
-  impl_class->get_device_state = gdk_wayland_surface_get_device_state;
-  impl_class->set_input_region = gdk_wayland_surface_set_input_region;
-  impl_class->destroy = gdk_wayland_surface_destroy;
-  impl_class->beep = gdk_wayland_surface_beep;
+static const struct zwp_keyboard_shortcuts_inhibitor_v1_listener
+zwp_keyboard_shortcuts_inhibitor_listener = {
+  inhibitor_active,
+  inhibitor_inactive,
+};
 
-  impl_class->destroy_notify = gdk_wayland_surface_destroy_notify;
-  impl_class->drag_begin = _gdk_wayland_surface_drag_begin;
-  impl_class->get_scale_factor = gdk_wayland_surface_get_scale_factor;
-  impl_class->set_opaque_region = gdk_wayland_surface_set_opaque_region;
-  impl_class->request_layout = gdk_wayland_surface_request_layout;
-  impl_class->compute_size = gdk_wayland_surface_compute_size;
+static struct zwp_keyboard_shortcuts_inhibitor_v1 *
+gdk_wayland_toplevel_get_inhibitor (GdkWaylandToplevel *toplevel,
+                                    GdkSeat            *gdk_seat)
+{
+  return g_hash_table_lookup (toplevel->shortcuts_inhibitors, gdk_seat);
 }
 
 void
-_gdk_wayland_surface_set_grab_seat (GdkSurface *surface,
-                                    GdkSeat    *seat)
+gdk_wayland_surface_inhibit_shortcuts (GdkSurface *surface,
+                                       GdkSeat    *gdk_seat)
 {
-  GdkWaylandPopup *popup;
-
-  g_return_if_fail (surface != NULL);
-
-  popup = GDK_WAYLAND_POPUP (surface);
-  popup->grab_input_seat = seat;
-}
+  GdkWaylandDisplay *display = GDK_WAYLAND_DISPLAY (gdk_surface_get_display (surface));
+  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
+  struct wl_surface *wl_surface = impl->display_server.wl_surface;
+  struct wl_seat *seat = gdk_wayland_seat_get_wl_seat (gdk_seat);
+  struct zwp_keyboard_shortcuts_inhibitor_v1 *inhibitor;
+  GdkWaylandToplevel *toplevel;
 
-/**
- * gdk_wayland_surface_get_wl_surface: (skip)
- * @surface: (type GdkWaylandSurface): a `GdkSurface`
- *
- * Returns the Wayland `wl_surface` of a `GdkSurface`.
- *
- * Returns: (transfer none): a Wayland `wl_surface`
- */
-struct wl_surface *
-gdk_wayland_surface_get_wl_surface (GdkSurface *surface)
-{
-  g_return_val_if_fail (GDK_IS_WAYLAND_SURFACE (surface), NULL);
+  if (display->keyboard_shortcuts_inhibit == NULL)
+    return;
 
-  return GDK_WAYLAND_SURFACE (surface)->display_server.wl_surface;
-}
+  if (!is_realized_toplevel (GDK_WAYLAND_SURFACE (surface)))
+    return;
 
-struct wl_output *
-gdk_wayland_surface_get_wl_output (GdkSurface *surface)
-{
-  GdkWaylandSurface *impl;
+  toplevel = GDK_WAYLAND_TOPLEVEL (surface);
 
-  g_return_val_if_fail (GDK_IS_WAYLAND_SURFACE (surface), NULL);
+  if (gdk_wayland_toplevel_get_inhibitor (toplevel, gdk_seat))
+    return; /* Already inhibited */
 
-  impl = GDK_WAYLAND_SURFACE (surface);
-  /* We pick the head of the list as this is the last entered output */
-  if (impl->display_server.outputs)
-    return (struct wl_output *) impl->display_server.outputs->data;
+  inhibitor =
+      zwp_keyboard_shortcuts_inhibit_manager_v1_inhibit_shortcuts (
+          display->keyboard_shortcuts_inhibit, wl_surface, seat);
 
-  return NULL;
+  g_hash_table_insert (toplevel->shortcuts_inhibitors, gdk_seat, inhibitor);
 }
 
 void
-gdk_wayland_surface_ensure_wl_egl_window (GdkSurface *surface)
+gdk_wayland_surface_restore_shortcuts (GdkSurface *surface,
+                                       GdkSeat    *gdk_seat)
 {
+  GdkWaylandToplevel *toplevel;
   GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
+  struct zwp_keyboard_shortcuts_inhibitor_v1 *inhibitor;
 
-  if (impl->display_server.egl_window == NULL)
-    {
-      impl->display_server.egl_window =
-        wl_egl_window_create (impl->display_server.wl_surface,
-                              surface->width * impl->scale,
-                              surface->height * impl->scale);
-      wl_surface_set_buffer_scale (impl->display_server.wl_surface, impl->scale);
-
-      gdk_surface_set_egl_native_window (surface, impl->display_server.egl_window);
-    }
-}
+  if (!is_realized_toplevel (impl))
+    return;
 
-struct gtk_surface1 *
-gdk_wayland_toplevel_get_gtk_surface (GdkWaylandToplevel *wayland_toplevel)
-{
-  return wayland_toplevel->display_server.gtk_surface;
+  toplevel = GDK_WAYLAND_TOPLEVEL (impl);
+
+  inhibitor = gdk_wayland_toplevel_get_inhibitor (toplevel, gdk_seat);
+  if (inhibitor == NULL)
+    return; /* Not inhibitted */
+
+  zwp_keyboard_shortcuts_inhibitor_v1_destroy (inhibitor);
+  g_hash_table_remove (toplevel->shortcuts_inhibitors, gdk_seat);
 }
 
 static void
-maybe_set_gtk_surface_dbus_properties (GdkWaylandToplevel *wayland_toplevel)
+gdk_wayland_toplevel_inhibit_system_shortcuts (GdkToplevel *toplevel,
+                                               GdkEvent    *event)
 {
-  if (wayland_toplevel->application.was_set)
-    return;
+  struct zwp_keyboard_shortcuts_inhibitor_v1 *inhibitor;
+  GdkSurface *surface = GDK_SURFACE (toplevel);
+  GdkWaylandToplevel *wayland_toplevel = GDK_WAYLAND_TOPLEVEL (toplevel);
+  GdkSeat *gdk_seat;
 
-  if (wayland_toplevel->application.application_id == NULL &&
-      wayland_toplevel->application.app_menu_path == NULL &&
-      wayland_toplevel->application.menubar_path == NULL &&
-      wayland_toplevel->application.window_object_path == NULL &&
-      wayland_toplevel->application.application_object_path == NULL &&
-      wayland_toplevel->application.unique_bus_name == NULL)
+  if (surface->shortcuts_inhibited)
     return;
 
-  gdk_wayland_toplevel_init_gtk_surface (wayland_toplevel);
-  if (wayland_toplevel->display_server.gtk_surface == NULL)
+  gdk_seat = gdk_surface_get_seat_from_event (surface, event);
+  gdk_wayland_surface_inhibit_shortcuts (surface, gdk_seat);
+  inhibitor = gdk_wayland_toplevel_get_inhibitor (wayland_toplevel, gdk_seat);
+  if (!inhibitor)
     return;
 
-  gtk_surface1_set_dbus_properties (wayland_toplevel->display_server.gtk_surface,
-                                    wayland_toplevel->application.application_id,
-                                    wayland_toplevel->application.app_menu_path,
-                                    wayland_toplevel->application.menubar_path,
-                                    wayland_toplevel->application.window_object_path,
-                                    wayland_toplevel->application.application_object_path,
-                                    wayland_toplevel->application.unique_bus_name);
-  wayland_toplevel->application.was_set = TRUE;
+  surface->current_shortcuts_inhibited_seat = gdk_seat;
+  zwp_keyboard_shortcuts_inhibitor_v1_add_listener
+    (inhibitor, &zwp_keyboard_shortcuts_inhibitor_listener, toplevel);
 }
 
-void
-gdk_wayland_toplevel_set_dbus_properties (GdkToplevel *toplevel,
-                                          const char  *application_id,
-                                          const char  *app_menu_path,
-                                          const char  *menubar_path,
-                                          const char  *window_object_path,
-                                          const char  *application_object_path,
-                                          const char *unique_bus_name)
+static void
+gdk_wayland_toplevel_restore_system_shortcuts (GdkToplevel *toplevel)
 {
-  GdkWaylandToplevel *wayland_toplevel;
+  GdkSurface *surface = GDK_SURFACE (toplevel);
 
-  g_return_if_fail (GDK_IS_WAYLAND_TOPLEVEL (toplevel));
+  gdk_wayland_surface_restore_shortcuts (surface, surface->current_shortcuts_inhibited_seat);
+  surface->current_shortcuts_inhibited_seat = NULL;
+  surface->shortcuts_inhibited = FALSE;
+  g_object_notify (G_OBJECT (toplevel), "shortcuts-inhibited");
+}
 
-  wayland_toplevel = GDK_WAYLAND_TOPLEVEL (toplevel);
+static void
+xdg_exported_handle_v1 (void                    *data,
+                        struct zxdg_exported_v1 *zxdg_exported_v1,
+                        const char              *handle)
+{
+  g_task_return_pointer (G_TASK (data), g_strdup (handle), g_free);
+  g_object_unref (data);
+}
 
-  wayland_toplevel->application.application_id = g_strdup (application_id);
-  wayland_toplevel->application.app_menu_path = g_strdup (app_menu_path);
-  wayland_toplevel->application.menubar_path = g_strdup (menubar_path);
-  wayland_toplevel->application.window_object_path = g_strdup (window_object_path);
-  wayland_toplevel->application.application_object_path =
-    g_strdup (application_object_path);
-  wayland_toplevel->application.unique_bus_name = g_strdup (unique_bus_name);
+static const struct zxdg_exported_v1_listener xdg_exported_listener_v1 = {
+  xdg_exported_handle_v1
+};
 
-  maybe_set_gtk_surface_dbus_properties (wayland_toplevel);
+static void
+xdg_exported_handle_v2 (void                    *data,
+                        struct zxdg_exported_v2 *zxdg_exported_v2,
+                        const char              *handle)
+{
+  g_task_return_pointer (G_TASK (data), g_strdup (handle), g_free);
+  g_object_unref (data);
 }
 
-void
-_gdk_wayland_surface_offset_next_wl_buffer (GdkSurface *surface,
-                                            int         x,
-                                            int         y)
-{
-  GdkWaylandSurface *impl;
+static const struct zxdg_exported_v2_listener xdg_exported_listener_v2 = {
+  xdg_exported_handle_v2
+};
 
-  g_return_if_fail (GDK_IS_WAYLAND_SURFACE (surface));
+static void
+gdk_wayland_toplevel_real_export_handle (GdkToplevel          *toplevel,
+                                         GCancellable         *cancellable,
+                                         GAsyncReadyCallback   callback,
+                                         gpointer              user_data)
+{
+  GdkWaylandToplevel *wayland_toplevel = GDK_WAYLAND_TOPLEVEL (toplevel);
+  GdkSurface *surface = GDK_SURFACE (toplevel);
+  GdkDisplay *display = gdk_surface_get_display (GDK_SURFACE (toplevel));
+  GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display);
+  GTask *task;
 
-  impl = GDK_WAYLAND_SURFACE (surface);
+  task = g_task_new (toplevel, cancellable, callback, user_data);
 
-  impl->pending_buffer_offset_x = x;
-  impl->pending_buffer_offset_y = y;
+  if (display_wayland->xdg_exporter_v2)
+    {
+      wayland_toplevel->xdg_exported_v2 =
+        zxdg_exporter_v2_export_toplevel (display_wayland->xdg_exporter_v2,
+                                          gdk_wayland_surface_get_wl_surface (surface));
+      zxdg_exported_v2_add_listener (wayland_toplevel->xdg_exported_v2,
+                                     &xdg_exported_listener_v2, task);
+    }
+  else if (display_wayland->xdg_exporter)
+    {
+      wayland_toplevel->xdg_exported =
+        zxdg_exporter_v1_export (display_wayland->xdg_exporter,
+                                 gdk_wayland_surface_get_wl_surface (surface));
+      zxdg_exported_v1_add_listener (wayland_toplevel->xdg_exported,
+                                     &xdg_exported_listener_v1, task);
+    }
+  else
+    {
+      g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED, "Exporting surface handles not supported");
+      g_object_unref (task);
+      return;
+    }
 }
 
-/**
- * GdkWaylandToplevelExported:
- * @toplevel: (type GdkWaylandToplevel): the `GdkToplevel` that is exported
- * @handle: the handle
- * @user_data: user data that was passed to [method@GdkWayland.WaylandToplevel.export_handle]
- *
- * Callback that gets called when the handle for a surface has been
- * obtained from the Wayland compositor.
- *
- * This callback is used in [method@GdkWayland.WaylandToplevel.export_handle].
- *
- * The @handle can be passed to other processes, for the purpose of
- * marking surfaces as transient for out-of-process surfaces.
- */
-
-static gboolean
-gdk_wayland_toplevel_is_exported (GdkWaylandToplevel *wayland_toplevel)
+static char *
+gdk_wayland_toplevel_real_export_handle_finish (GdkToplevel   *toplevel,
+                                                GAsyncResult  *result,
+                                                GError       **error)
 {
-  return wayland_toplevel->xdg_exported != NULL || wayland_toplevel->xdg_exported_v2 != NULL;
+  return g_task_propagate_pointer (G_TASK (result), error);
 }
 
-typedef struct {
-  GdkWaylandToplevelExported callback;
-  gpointer user_data;
-  GDestroyNotify destroy;
-} ExportHandleData;
-
 static void
-export_handle_done (GObject      *source,
-                    GAsyncResult *result,
-                    void         *user_data)
+gdk_wayland_toplevel_real_unexport_handle (GdkToplevel *toplevel)
 {
-  GdkToplevel *toplevel = GDK_TOPLEVEL (source);
-  ExportHandleData *data = (ExportHandleData *)user_data;
-  char *handle;
-
-  handle = gdk_toplevel_export_handle_finish (toplevel, result, NULL);
-  data->callback (toplevel, handle, data->user_data);
-  g_free (handle);
+  GdkWaylandToplevel *wayland_toplevel = GDK_WAYLAND_TOPLEVEL (toplevel);
 
-  if (data->destroy)
-    data->destroy (data->user_data);
+  g_return_if_fail (GDK_IS_WAYLAND_TOPLEVEL (toplevel));
+  g_return_if_fail (wayland_toplevel->xdg_exported_v2 || wayland_toplevel->xdg_exported);
 
-  g_free (data);
+  g_clear_pointer (&wayland_toplevel->xdg_exported_v2, zxdg_exported_v2_destroy);
+  g_clear_pointer (&wayland_toplevel->xdg_exported, zxdg_exported_v1_destroy);
 }
 
-/**
- * gdk_wayland_toplevel_export_handle:
- * @toplevel: (type GdkWaylandToplevel): the `GdkToplevel` to obtain a handle for
- * @callback: callback to call with the handle
- * @user_data: (closure): user data for @callback
- * @destroy_func: destroy notify for @user_data
- *
- * Asynchronously obtains a handle for a surface that can be passed
- * to other processes.
- *
- * When the handle has been obtained, @callback will be called.
- *
- * It is an error to call this function on a surface that is already
- * exported.
- *
- * When the handle is no longer needed, [method@GdkWayland.WaylandToplevel.unexport_handle]
- * should be called to clean up resources.
- *
- * The main purpose for obtaining a handle is to mark a surface
- * from another surface as transient for this one, see
- * [method@GdkWayland.WaylandToplevel.set_transient_for_exported].
- *
- * Note that this API depends on an unstable Wayland protocol,
- * and thus may require changes in the future.
- *
- * Return value: %TRUE if the handle has been requested, %FALSE if
- *   an error occurred.
- */
-gboolean
-gdk_wayland_toplevel_export_handle (GdkToplevel                *toplevel,
-                                    GdkWaylandToplevelExported  callback,
-                                    gpointer                    user_data,
-                                    GDestroyNotify              destroy_func)
+static gboolean
+gdk_wayland_toplevel_show_window_menu (GdkToplevel *toplevel,
+                                       GdkEvent    *event)
 {
-  ExportHandleData *data;
+  GdkSurface *surface = GDK_SURFACE (toplevel);
+  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
+  GdkWaylandToplevel *wayland_toplevel = GDK_WAYLAND_TOPLEVEL (surface);
+  GdkWaylandDisplay *display_wayland =
+    GDK_WAYLAND_DISPLAY (gdk_surface_get_display (surface));
+  GdkSeat *seat;
+  struct wl_seat *wl_seat;
+  double x, y;
+  uint32_t serial;
 
-  g_return_val_if_fail (GDK_IS_WAYLAND_TOPLEVEL (toplevel), FALSE);
+  GdkEventType event_type = gdk_event_get_event_type (event);
+  switch ((guint) event_type)
+    {
+    case GDK_BUTTON_PRESS:
+    case GDK_BUTTON_RELEASE:
+    case GDK_TOUCH_BEGIN:
+    case GDK_TOUCH_END:
+      break;
+    default:
+      return FALSE;
+    }
 
-  data = g_new (ExportHandleData, 1);
-  data->callback = callback;
-  data->user_data = user_data;
-  data->destroy = destroy_func;
+  if (!is_realized_toplevel (impl))
+    return FALSE;
 
-  gdk_toplevel_export_handle (toplevel, NULL, export_handle_done, data);
+  seat = gdk_event_get_seat (event);
+  wl_seat = gdk_wayland_seat_get_wl_seat (seat);
+  gdk_event_get_position (event, &x, &y);
 
-  return TRUE;
-}
+  serial = _gdk_wayland_seat_get_implicit_grab_serial (seat, event);
 
-/**
- * gdk_wayland_toplevel_unexport_handle:
- * @toplevel: (type GdkWaylandToplevel): the `GdkToplevel` to unexport
- *
- * Destroys the handle that was obtained with
- * gdk_wayland_toplevel_export_handle().
- *
- * It is an error to call this function on a surface that
- * does not have a handle.
- *
- * Note that this API depends on an unstable Wayland protocol,
- * and thus may require changes in the future.
- */
-void
-gdk_wayland_toplevel_unexport_handle (GdkToplevel *toplevel)
-{
-  g_return_if_fail (GDK_IS_WAYLAND_TOPLEVEL (toplevel));
+  switch (display_wayland->shell_variant)
+    {
+    case GDK_WAYLAND_SHELL_VARIANT_XDG_SHELL:
+      xdg_toplevel_show_window_menu (wayland_toplevel->display_server.xdg_toplevel,
+                                     wl_seat, serial, x, y);
+      break;
+    case GDK_WAYLAND_SHELL_VARIANT_ZXDG_SHELL_V6:
+      zxdg_toplevel_v6_show_window_menu (wayland_toplevel->display_server.zxdg_toplevel_v6,
+                                         wl_seat, serial, x, y);
+      break;
+    default:
+      g_assert_not_reached ();
+    }
 
-  gdk_toplevel_unexport_handle (toplevel);
+  return TRUE;
 }
 
-static void
-unset_transient_for_exported (GdkSurface *surface)
+static gboolean
+translate_gesture (GdkTitlebarGesture         gesture,
+                   enum gtk_surface1_gesture *out_gesture)
 {
-  if (GDK_IS_WAYLAND_TOPLEVEL (surface))
+  switch (gesture)
     {
-      GdkWaylandToplevel *toplevel = GDK_WAYLAND_TOPLEVEL (surface);
+    case GDK_TITLEBAR_GESTURE_DOUBLE_CLICK:
+      *out_gesture = GTK_SURFACE1_GESTURE_DOUBLE_CLICK;
+      break;
 
-      g_clear_pointer (&toplevel->imported_transient_for, zxdg_imported_v1_destroy);
-      g_clear_pointer (&toplevel->imported_transient_for_v2, zxdg_imported_v2_destroy);
-    }
-}
+    case GDK_TITLEBAR_GESTURE_RIGHT_CLICK:
+      *out_gesture = GTK_SURFACE1_GESTURE_RIGHT_CLICK;
+      break;
 
-static void
-xdg_imported_destroyed (void                    *data,
-                        struct zxdg_imported_v1 *imported)
-{
-  unset_transient_for_exported (GDK_SURFACE (data));
-}
+    case GDK_TITLEBAR_GESTURE_MIDDLE_CLICK:
+      *out_gesture = GTK_SURFACE1_GESTURE_MIDDLE_CLICK;
+      break;
 
-static const struct zxdg_imported_v1_listener xdg_imported_listener = {
-  xdg_imported_destroyed,
-};
+    default:
+      g_warning ("Not handling unknown titlebar gesture %u", gesture);
+      return FALSE;
+    }
 
-static void
-xdg_imported_v2_destroyed (void                    *data,
-                           struct zxdg_imported_v2 *imported)
-{
-  unset_transient_for_exported (GDK_SURFACE (data));
+  return TRUE;
 }
 
-static const struct zxdg_imported_v2_listener xdg_imported_listener_v2 = {
-  xdg_imported_v2_destroyed,
-};
-
-/**
- * gdk_wayland_toplevel_set_transient_for_exported:
- * @toplevel: (type GdkWaylandToplevel): the `GdkToplevel` to make as transient
- * @parent_handle_str: an exported handle for a surface
- *
- * Marks @toplevel as transient for the surface to which the given
- * @parent_handle_str refers.
- *
- * Typically, the handle will originate from a
- * [method@GdkWayland.WaylandToplevel.export_handle] call in another process.
- *
- * Note that this API depends on an unstable Wayland protocol,
- * and thus may require changes in the future.
- *
- * Return value: %TRUE if the surface has been marked as transient,
- *   %FALSE if an error occurred.
- */
-gboolean
-gdk_wayland_toplevel_set_transient_for_exported (GdkToplevel *toplevel,
-                                                 const char  *parent_handle_str)
+static gboolean
+gdk_wayland_toplevel_titlebar_gesture (GdkToplevel        *toplevel,
+                                       GdkTitlebarGesture  gesture)
 {
+  GdkSurface *surface = GDK_SURFACE (toplevel);
   GdkWaylandToplevel *wayland_toplevel = GDK_WAYLAND_TOPLEVEL (toplevel);
-  GdkDisplay *display = gdk_surface_get_display (GDK_SURFACE (toplevel));
-  GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display);
+  struct gtk_surface1 *gtk_surface = wayland_toplevel->display_server.gtk_surface;
+  enum gtk_surface1_gesture gtk_gesture;
+  GdkSeat *seat;
+  struct wl_seat *wl_seat;
+  uint32_t serial;
 
-  g_return_val_if_fail (GDK_IS_WAYLAND_TOPLEVEL (toplevel), FALSE);
-  g_return_val_if_fail (GDK_IS_WAYLAND_DISPLAY (display), FALSE);
+  if (!gtk_surface)
+    return FALSE;
 
-  display_wayland = GDK_WAYLAND_DISPLAY (display);
+  if (gtk_surface1_get_version (gtk_surface) < GTK_SURFACE1_TITLEBAR_GESTURE_SINCE_VERSION)
+    return FALSE;
 
-  if (!display_wayland->xdg_importer && !display_wayland->xdg_importer_v2)
-    {
-      g_warning ("Server is missing xdg_foreign support");
-      return FALSE;
-    }
+  if (!translate_gesture (gesture, &gtk_gesture))
+    return FALSE;
 
-  gdk_wayland_toplevel_set_transient_for (wayland_toplevel, NULL);
+  seat = gdk_display_get_default_seat (surface->display);
+  wl_seat = gdk_wayland_seat_get_wl_seat (seat);
 
-  if (display_wayland->xdg_importer)
-    {
-      wayland_toplevel->imported_transient_for =
-        zxdg_importer_v1_import (display_wayland->xdg_importer, parent_handle_str);
-      zxdg_imported_v1_add_listener (wayland_toplevel->imported_transient_for,
-                                     &xdg_imported_listener,
-                                     toplevel);
-    }
-  else
-    {
-      wayland_toplevel->imported_transient_for_v2 =
-        zxdg_importer_v2_import_toplevel (display_wayland->xdg_importer_v2, parent_handle_str);
-      zxdg_imported_v2_add_listener (wayland_toplevel->imported_transient_for_v2,
-                                     &xdg_imported_listener_v2,
-                                     toplevel);
-    }
+  serial = _gdk_wayland_seat_get_last_implicit_grab_serial (GDK_WAYLAND_SEAT (seat), NULL);
 
-  gdk_wayland_toplevel_sync_parent_of_imported (wayland_toplevel);
+  gtk_surface1_titlebar_gesture (wayland_toplevel->display_server.gtk_surface,
+                                 serial,
+                                 wl_seat,
+                                 gtk_gesture);
 
   return TRUE;
 }
 
-static struct zwp_keyboard_shortcuts_inhibitor_v1 *
-gdk_wayland_toplevel_get_inhibitor (GdkWaylandToplevel *toplevel,
-                                    GdkSeat            *gdk_seat)
+static gboolean
+gdk_wayland_toplevel_supports_edge_constraints (GdkToplevel *toplevel)
 {
-  return g_hash_table_lookup (toplevel->shortcuts_inhibitors, gdk_seat);
+  GdkWaylandToplevel *wayland_toplevel = GDK_WAYLAND_TOPLEVEL (toplevel);
+  struct gtk_surface1 *gtk_surface = wayland_toplevel->display_server.gtk_surface;
+
+  if (!gtk_surface)
+    return FALSE;
+
+  return gtk_surface1_get_version (gtk_surface) >= GTK_SURFACE1_CONFIGURE_EDGES_SINCE_VERSION;
 }
 
-void
-gdk_wayland_surface_inhibit_shortcuts (GdkSurface *surface,
-                                       GdkSeat    *gdk_seat)
+static void
+gdk_wayland_toplevel_begin_resize (GdkToplevel    *toplevel,
+                                   GdkSurfaceEdge  edge,
+                                   GdkDevice      *device,
+                                   int             button,
+                                   double          x,
+                                   double          y,
+                                   guint32         timestamp)
 {
-  GdkWaylandDisplay *display = GDK_WAYLAND_DISPLAY (gdk_surface_get_display (surface));
-  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
-  struct wl_surface *wl_surface = impl->display_server.wl_surface;
-  struct wl_seat *seat = gdk_wayland_seat_get_wl_seat (gdk_seat);
-  struct zwp_keyboard_shortcuts_inhibitor_v1 *inhibitor;
-  GdkWaylandToplevel *toplevel;
-
-  if (display->keyboard_shortcuts_inhibit == NULL)
-    return;
+  GdkSurface *surface = GDK_SURFACE (toplevel);
+  GdkWaylandSurface *impl;
+  GdkWaylandToplevel *wayland_toplevel;
+  GdkWaylandDisplay *display_wayland;
+  GdkEventSequence *sequence;
+  uint32_t resize_edges, serial;
 
-  if (!is_realized_toplevel (GDK_WAYLAND_SURFACE (surface)))
+  if (GDK_SURFACE_DESTROYED (surface))
     return;
 
-  toplevel = GDK_WAYLAND_TOPLEVEL (surface);
+  switch (edge)
+    {
+    case GDK_SURFACE_EDGE_NORTH_WEST:
+      resize_edges = ZXDG_TOPLEVEL_V6_RESIZE_EDGE_TOP_LEFT;
+      break;
 
-  if (gdk_wayland_toplevel_get_inhibitor (toplevel, gdk_seat))
-    return; /* Already inhibited */
+    case GDK_SURFACE_EDGE_NORTH:
+      resize_edges = ZXDG_TOPLEVEL_V6_RESIZE_EDGE_TOP;
+      break;
 
-  inhibitor =
-      zwp_keyboard_shortcuts_inhibit_manager_v1_inhibit_shortcuts (
-          display->keyboard_shortcuts_inhibit, wl_surface, seat);
+    case GDK_SURFACE_EDGE_NORTH_EAST:
+      resize_edges = ZXDG_TOPLEVEL_V6_RESIZE_EDGE_TOP_RIGHT;
+      break;
 
-  g_hash_table_insert (toplevel->shortcuts_inhibitors, gdk_seat, inhibitor);
-}
+    case GDK_SURFACE_EDGE_WEST:
+      resize_edges = ZXDG_TOPLEVEL_V6_RESIZE_EDGE_LEFT;
+      break;
 
-void
-gdk_wayland_surface_restore_shortcuts (GdkSurface *surface,
-                                       GdkSeat    *gdk_seat)
-{
-  GdkWaylandToplevel *toplevel;
-  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
-  struct zwp_keyboard_shortcuts_inhibitor_v1 *inhibitor;
+    case GDK_SURFACE_EDGE_EAST:
+      resize_edges = ZXDG_TOPLEVEL_V6_RESIZE_EDGE_RIGHT;
+      break;
 
-  if (!is_realized_toplevel (impl))
-    return;
+    case GDK_SURFACE_EDGE_SOUTH_WEST:
+      resize_edges = ZXDG_TOPLEVEL_V6_RESIZE_EDGE_BOTTOM_LEFT;
+      break;
 
-  toplevel = GDK_WAYLAND_TOPLEVEL (impl);
+    case GDK_SURFACE_EDGE_SOUTH:
+      resize_edges = ZXDG_TOPLEVEL_V6_RESIZE_EDGE_BOTTOM;
+      break;
 
-  inhibitor = gdk_wayland_toplevel_get_inhibitor (toplevel, gdk_seat);
-  if (inhibitor == NULL)
-    return; /* Not inhibitted */
+    case GDK_SURFACE_EDGE_SOUTH_EAST:
+      resize_edges = ZXDG_TOPLEVEL_V6_RESIZE_EDGE_BOTTOM_RIGHT;
+      break;
 
-  zwp_keyboard_shortcuts_inhibitor_v1_destroy (inhibitor);
-  g_hash_table_remove (toplevel->shortcuts_inhibitors, gdk_seat);
-}
+    default:
+      g_warning ("gdk_toplevel_begin_resize: bad resize edge %d!", edge);
+      return;
+    }
 
-#define LAST_PROP 1
+  impl = GDK_WAYLAND_SURFACE (surface);
+  wayland_toplevel = GDK_WAYLAND_TOPLEVEL (surface);
+  display_wayland = GDK_WAYLAND_DISPLAY (gdk_surface_get_display (surface));
 
-static void
-gdk_wayland_popup_init (GdkWaylandPopup *popup)
-{
-}
+  if (!is_realized_toplevel (impl))
+    return;
 
-static void
-gdk_wayland_popup_get_property (GObject    *object,
-                                guint       prop_id,
-                                GValue     *value,
-                                GParamSpec *pspec)
-{
-  GdkSurface *surface = GDK_SURFACE (object);
+  serial = _gdk_wayland_seat_get_last_implicit_grab_serial (GDK_WAYLAND_SEAT (gdk_device_get_seat (device)),
+                                                            &sequence);
 
-  switch (prop_id)
+  switch (display_wayland->shell_variant)
     {
-    case LAST_PROP + GDK_POPUP_PROP_PARENT:
-      g_value_set_object (value, surface->parent);
+    case GDK_WAYLAND_SHELL_VARIANT_XDG_SHELL:
+      xdg_toplevel_resize (wayland_toplevel->display_server.xdg_toplevel,
+                           gdk_wayland_device_get_wl_seat (device),
+                           serial, resize_edges);
       break;
-
-    case LAST_PROP + GDK_POPUP_PROP_AUTOHIDE:
-      g_value_set_boolean (value, surface->autohide);
+    case GDK_WAYLAND_SHELL_VARIANT_ZXDG_SHELL_V6:
+      zxdg_toplevel_v6_resize (wayland_toplevel->display_server.zxdg_toplevel_v6,
+                               gdk_wayland_device_get_wl_seat (device),
+                               serial, resize_edges);
       break;
-
     default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-      break;
+      g_assert_not_reached ();
     }
+
+  if (sequence)
+    gdk_wayland_device_unset_touch_grab (device, sequence);
 }
 
 static void
-gdk_wayland_popup_set_property (GObject      *object,
-                                guint         prop_id,
-                                const GValue *value,
-                                GParamSpec   *pspec)
+gdk_wayland_toplevel_begin_move (GdkToplevel *toplevel,
+                                 GdkDevice   *device,
+                                 int          button,
+                                 double       x,
+                                 double       y,
+                                 guint32      timestamp)
 {
-  GdkSurface *surface = GDK_SURFACE (object);
+  GdkSurface *surface = GDK_SURFACE (toplevel);
+  GdkWaylandSurface *impl;
+  GdkWaylandToplevel *wayland_toplevel;
+  GdkWaylandDisplay *display_wayland;
+  GdkEventSequence *sequence;
+  uint32_t serial;
 
-  switch (prop_id)
+  if (GDK_SURFACE_DESTROYED (surface))
+    return;
+
+  impl = GDK_WAYLAND_SURFACE (surface);
+  wayland_toplevel = GDK_WAYLAND_TOPLEVEL (surface);
+  display_wayland = GDK_WAYLAND_DISPLAY (gdk_surface_get_display (surface));
+
+  if (!is_realized_toplevel (impl))
+    return;
+
+  serial = _gdk_wayland_seat_get_last_implicit_grab_serial (GDK_WAYLAND_SEAT (gdk_device_get_seat (device)),
+                                                            &sequence);
+  switch (display_wayland->shell_variant)
     {
-    case LAST_PROP + GDK_POPUP_PROP_PARENT:
-      surface->parent = g_value_dup_object (value);
-      if (surface->parent != NULL)
-        surface->parent->children = g_list_prepend (surface->parent->children, surface);
+    case GDK_WAYLAND_SHELL_VARIANT_XDG_SHELL:
+      xdg_toplevel_move (wayland_toplevel->display_server.xdg_toplevel,
+                         gdk_wayland_device_get_wl_seat (device),
+                         serial);
       break;
-
-    case LAST_PROP + GDK_POPUP_PROP_AUTOHIDE:
-      surface->autohide = g_value_get_boolean (value);
+    case GDK_WAYLAND_SHELL_VARIANT_ZXDG_SHELL_V6:
+      zxdg_toplevel_v6_move (wayland_toplevel->display_server.zxdg_toplevel_v6,
+                             gdk_wayland_device_get_wl_seat (device),
+                             serial);
       break;
-
     default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-      break;
+      g_assert_not_reached ();
     }
+
+  if (sequence)
+    gdk_wayland_device_unset_touch_grab (device, sequence);
 }
 
 static void
-gdk_wayland_popup_class_init (GdkWaylandPopupClass *class)
+token_done (gpointer                        data,
+            struct xdg_activation_token_v1 *provider,
+            const char                     *token)
 {
-  GObjectClass *object_class = G_OBJECT_CLASS (class);
-
-  object_class->get_property = gdk_wayland_popup_get_property;
-  object_class->set_property = gdk_wayland_popup_set_property;
+  char **token_out = data;
 
-  gdk_popup_install_properties (object_class, 1);
+  *token_out = g_strdup (token);
 }
 
-static gboolean
-gdk_wayland_popup_present (GdkPopup       *popup,
-                           int             width,
-                           int             height,
-                           GdkPopupLayout *layout)
-{
-  return gdk_wayland_surface_present_popup (GDK_WAYLAND_POPUP (popup), width, height, layout);
-}
+static const struct xdg_activation_token_v1_listener token_listener = {
+  token_done,
+};
 
-static GdkGravity
-gdk_wayland_popup_get_surface_anchor (GdkPopup *popup)
+static void
+gdk_wayland_toplevel_focus (GdkToplevel *toplevel,
+                            guint32      timestamp)
 {
-  return GDK_SURFACE (popup)->popup.surface_anchor;
-}
+  GdkSurface *surface = GDK_SURFACE (toplevel);
+  GdkWaylandToplevel *wayland_toplevel = GDK_WAYLAND_TOPLEVEL (toplevel);
+  GdkWaylandSurface *wayland_surface = GDK_WAYLAND_SURFACE (toplevel);
+  GdkDisplay *display = gdk_surface_get_display (surface);
+  GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display);
+  gchar *startup_id = NULL;
 
-static GdkGravity
-gdk_wayland_popup_get_rect_anchor (GdkPopup *popup)
-{
-  return GDK_SURFACE (popup)->popup.rect_anchor;
-}
+  startup_id = g_steal_pointer (&display_wayland->startup_notification_id);
 
-static int
-gdk_wayland_popup_get_position_x (GdkPopup *popup)
-{
-  return GDK_SURFACE (popup)->x;
-}
+  if (display_wayland->xdg_activation)
+    {
+      GdkWaylandSeat *seat =
+        GDK_WAYLAND_SEAT (gdk_display_get_default_seat (display));
 
-static int
-gdk_wayland_popup_get_position_y (GdkPopup *popup)
-{
-  return GDK_SURFACE (popup)->y;
+      /* If the focus request does not have a startup ID associated, get a
+       * new token to activate the window.
+       */
+      if (!startup_id)
+        {
+          struct xdg_activation_token_v1 *token;
+          struct wl_event_queue *event_queue;
+          struct wl_surface *wl_surface = NULL;
+          GdkSurface *focus_surface;
+
+          event_queue = wl_display_create_queue (display_wayland->wl_display);
+
+          token = xdg_activation_v1_get_activation_token (display_wayland->xdg_activation);
+          wl_proxy_set_queue ((struct wl_proxy *) token, event_queue);
+
+          xdg_activation_token_v1_add_listener (token,
+                                                &token_listener,
+                                                &startup_id);
+          xdg_activation_token_v1_set_serial (token,
+                                              _gdk_wayland_seat_get_last_implicit_grab_serial (seat, NULL),
+                                              gdk_wayland_seat_get_wl_seat (GDK_SEAT (seat)));
+
+
+          focus_surface = gdk_wayland_device_get_focus (gdk_seat_get_keyboard (GDK_SEAT (seat)));
+          if (focus_surface)
+            wl_surface = gdk_wayland_surface_get_wl_surface (focus_surface);
+          if (wl_surface)
+            xdg_activation_token_v1_set_surface (token, wl_surface);
+
+          xdg_activation_token_v1_commit (token);
+
+          while (startup_id == NULL)
+            wl_display_dispatch_queue (display_wayland->wl_display, event_queue);
+
+          xdg_activation_token_v1_destroy (token);
+          wl_event_queue_destroy (event_queue);
+        }
+
+      xdg_activation_v1_activate (display_wayland->xdg_activation,
+                                  startup_id,
+                                  wayland_surface->display_server.wl_surface);
+    }
+  else if (wayland_toplevel->display_server.gtk_surface)
+    {
+      if (timestamp != GDK_CURRENT_TIME)
+        gtk_surface1_present (wayland_toplevel->display_server.gtk_surface, timestamp);
+      else if (startup_id && display_wayland->gtk_shell_version >= 3)
+        gtk_surface1_request_focus (wayland_toplevel->display_server.gtk_surface,
+                                    startup_id);
+    }
+
+  g_free (startup_id);
 }
 
 static void
-gdk_wayland_popup_iface_init (GdkPopupInterface *iface)
+gdk_wayland_toplevel_iface_init (GdkToplevelInterface *iface)
 {
-  iface->present = gdk_wayland_popup_present;
-  iface->get_surface_anchor = gdk_wayland_popup_get_surface_anchor;
-  iface->get_rect_anchor = gdk_wayland_popup_get_rect_anchor;
-  iface->get_position_x = gdk_wayland_popup_get_position_x;
-  iface->get_position_y = gdk_wayland_popup_get_position_y;
+  iface->present = gdk_wayland_toplevel_present;
+  iface->minimize = gdk_wayland_toplevel_minimize;
+  iface->lower = gdk_wayland_toplevel_lower;
+  iface->focus = gdk_wayland_toplevel_focus;
+  iface->show_window_menu = gdk_wayland_toplevel_show_window_menu;
+  iface->titlebar_gesture = gdk_wayland_toplevel_titlebar_gesture;
+  iface->supports_edge_constraints = gdk_wayland_toplevel_supports_edge_constraints;
+  iface->inhibit_system_shortcuts = gdk_wayland_toplevel_inhibit_system_shortcuts;
+  iface->restore_system_shortcuts = gdk_wayland_toplevel_restore_system_shortcuts;
+  iface->begin_resize = gdk_wayland_toplevel_begin_resize;
+  iface->begin_move = gdk_wayland_toplevel_begin_move;
+  iface->export_handle = gdk_wayland_toplevel_real_export_handle;
+  iface->export_handle_finish = gdk_wayland_toplevel_real_export_handle_finish;
+  iface->unexport_handle = gdk_wayland_toplevel_real_unexport_handle;
 }
 
-static void
-gdk_wayland_toplevel_init (GdkWaylandToplevel *toplevel)
+/* }}} */
+/* {{{ Private Toplevel API */
+
+struct gtk_surface1 *
+gdk_wayland_toplevel_get_gtk_surface (GdkWaylandToplevel *wayland_toplevel)
 {
-  toplevel->initial_fullscreen_output = NULL;
-  toplevel->shortcuts_inhibitors = g_hash_table_new (NULL, NULL);
+  return wayland_toplevel->display_server.gtk_surface;
 }
 
 static void
-gdk_wayland_toplevel_set_property (GObject      *object,
-                                   guint         prop_id,
-                                   const GValue *value,
-                                   GParamSpec   *pspec)
+maybe_set_gtk_surface_dbus_properties (GdkWaylandToplevel *wayland_toplevel)
 {
-  GdkSurface *surface = GDK_SURFACE (object);
-  GdkWaylandToplevel *toplevel = GDK_WAYLAND_TOPLEVEL (surface);
-
-  switch (prop_id)
-    {
-    case LAST_PROP + GDK_TOPLEVEL_PROP_TITLE:
-      gdk_wayland_toplevel_set_title (toplevel, g_value_get_string (value));
-      g_object_notify_by_pspec (G_OBJECT (surface), pspec);
-      break;
-
-    case LAST_PROP + GDK_TOPLEVEL_PROP_STARTUP_ID:
-      gdk_wayland_surface_set_startup_id (surface, g_value_get_string (value));
-      g_object_notify_by_pspec (G_OBJECT (surface), pspec);
-      break;
+  if (wayland_toplevel->application.was_set)
+    return;
 
-    case LAST_PROP + GDK_TOPLEVEL_PROP_TRANSIENT_FOR:
-      gdk_wayland_toplevel_set_transient_for (toplevel,
-                                              g_value_get_object (value));
-      g_object_notify_by_pspec (G_OBJECT (surface), pspec);
-      break;
+  if (wayland_toplevel->application.application_id == NULL &&
+      wayland_toplevel->application.app_menu_path == NULL &&
+      wayland_toplevel->application.menubar_path == NULL &&
+      wayland_toplevel->application.window_object_path == NULL &&
+      wayland_toplevel->application.application_object_path == NULL &&
+      wayland_toplevel->application.unique_bus_name == NULL)
+    return;
 
-    case LAST_PROP + GDK_TOPLEVEL_PROP_MODAL:
-      gdk_wayland_toplevel_set_modal_hint (toplevel, g_value_get_boolean (value));
-      g_object_notify_by_pspec (G_OBJECT (surface), pspec);
-      break;
+  gdk_wayland_toplevel_init_gtk_surface (wayland_toplevel);
+  if (wayland_toplevel->display_server.gtk_surface == NULL)
+    return;
 
-    case LAST_PROP + GDK_TOPLEVEL_PROP_ICON_LIST:
-      break;
+  gtk_surface1_set_dbus_properties (wayland_toplevel->display_server.gtk_surface,
+                                    wayland_toplevel->application.application_id,
+                                    wayland_toplevel->application.app_menu_path,
+                                    wayland_toplevel->application.menubar_path,
+                                    wayland_toplevel->application.window_object_path,
+                                    wayland_toplevel->application.application_object_path,
+                                    wayland_toplevel->application.unique_bus_name);
+  wayland_toplevel->application.was_set = TRUE;
+}
 
-    case LAST_PROP + GDK_TOPLEVEL_PROP_DECORATED:
-      break;
+void
+gdk_wayland_toplevel_set_dbus_properties (GdkToplevel *toplevel,
+                                          const char  *application_id,
+                                          const char  *app_menu_path,
+                                          const char  *menubar_path,
+                                          const char  *window_object_path,
+                                          const char  *application_object_path,
+                                          const char *unique_bus_name)
+{
+  GdkWaylandToplevel *wayland_toplevel;
 
-    case LAST_PROP + GDK_TOPLEVEL_PROP_DELETABLE:
-      break;
+  g_return_if_fail (GDK_IS_WAYLAND_TOPLEVEL (toplevel));
 
-    case LAST_PROP + GDK_TOPLEVEL_PROP_FULLSCREEN_MODE:
-      surface->fullscreen_mode = g_value_get_enum (value);
-      g_object_notify_by_pspec (G_OBJECT (surface), pspec);
-      break;
+  wayland_toplevel = GDK_WAYLAND_TOPLEVEL (toplevel);
 
-    case LAST_PROP + GDK_TOPLEVEL_PROP_SHORTCUTS_INHIBITED:
-      break;
+  wayland_toplevel->application.application_id = g_strdup (application_id);
+  wayland_toplevel->application.app_menu_path = g_strdup (app_menu_path);
+  wayland_toplevel->application.menubar_path = g_strdup (menubar_path);
+  wayland_toplevel->application.window_object_path = g_strdup (window_object_path);
+  wayland_toplevel->application.application_object_path =
+    g_strdup (application_object_path);
+  wayland_toplevel->application.unique_bus_name = g_strdup (unique_bus_name);
 
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-      break;
-    }
+  maybe_set_gtk_surface_dbus_properties (wayland_toplevel);
 }
 
-static void
-gdk_wayland_toplevel_get_property (GObject    *object,
-                                   guint       prop_id,
-                                   GValue     *value,
-                                   GParamSpec *pspec)
-{
-  GdkSurface *surface = GDK_SURFACE (object);
-  GdkWaylandToplevel *toplevel = GDK_WAYLAND_TOPLEVEL (surface);
-
-  switch (prop_id)
-    {
-    case LAST_PROP + GDK_TOPLEVEL_PROP_STATE:
-      g_value_set_flags (value, surface->state);
-      break;
+/* }}} */
+/* {{{ Toplevel API */
 
-    case LAST_PROP + GDK_TOPLEVEL_PROP_TITLE:
-      g_value_set_string (value, toplevel->title);
-      break;
+/**
+ * gdk_wayland_toplevel_set_application_id:
+ * @toplevel: (type GdkWaylandToplevel): a `GdkToplevel`
+ * @application_id: the application id for the @toplevel
+ *
+ * Sets the application id on a `GdkToplevel`.
+ */
+void
+gdk_wayland_toplevel_set_application_id (GdkToplevel *toplevel,
+                                         const char  *application_id)
+{
+  GdkWaylandToplevel *wayland_toplevel = GDK_WAYLAND_TOPLEVEL (toplevel);
+  GdkWaylandSurface *impl;
+  GdkWaylandDisplay *display_wayland;
 
-    case LAST_PROP + GDK_TOPLEVEL_PROP_STARTUP_ID:
-      g_value_set_string (value, "");
-      break;
+  g_return_if_fail (GDK_IS_WAYLAND_TOPLEVEL (toplevel));
 
-    case LAST_PROP + GDK_TOPLEVEL_PROP_TRANSIENT_FOR:
-      g_value_set_object (value, toplevel->transient_for);
-      break;
+  g_return_if_fail (application_id != NULL);
 
-    case LAST_PROP + GDK_TOPLEVEL_PROP_MODAL:
-      g_value_set_boolean (value, surface->modal_hint);
-      break;
+  if (GDK_SURFACE_DESTROYED (toplevel))
+    return;
 
-    case LAST_PROP + GDK_TOPLEVEL_PROP_ICON_LIST:
-      g_value_set_pointer (value, NULL);
-      break;
+  impl = GDK_WAYLAND_SURFACE (toplevel);
 
-    case LAST_PROP + GDK_TOPLEVEL_PROP_DECORATED:
-      break;
+  if (!is_realized_toplevel (impl))
+    return;
 
-    case LAST_PROP + GDK_TOPLEVEL_PROP_DELETABLE:
-      break;
+  wayland_toplevel = GDK_WAYLAND_TOPLEVEL (toplevel);
+  display_wayland = GDK_WAYLAND_DISPLAY (gdk_surface_get_display (GDK_SURFACE (toplevel)));
 
-    case LAST_PROP + GDK_TOPLEVEL_PROP_FULLSCREEN_MODE:
-      g_value_set_enum (value, surface->fullscreen_mode);
+  switch (display_wayland->shell_variant)
+    {
+    case GDK_WAYLAND_SHELL_VARIANT_XDG_SHELL:
+      xdg_toplevel_set_app_id (wayland_toplevel->display_server.xdg_toplevel, application_id);
       break;
-
-    case LAST_PROP + GDK_TOPLEVEL_PROP_SHORTCUTS_INHIBITED:
-      g_value_set_boolean (value, surface->shortcuts_inhibited);
+    case GDK_WAYLAND_SHELL_VARIANT_ZXDG_SHELL_V6:
+      zxdg_toplevel_v6_set_app_id (wayland_toplevel->display_server.zxdg_toplevel_v6, application_id);
       break;
-
     default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-      break;
+      g_assert_not_reached ();
     }
 }
 
-static void
-gdk_wayland_toplevel_finalize (GObject *object)
+void
+gdk_wayland_toplevel_announce_csd (GdkToplevel *toplevel)
 {
-  GdkWaylandToplevel *wayland_toplevel;
-
-  g_return_if_fail (GDK_IS_WAYLAND_TOPLEVEL (object));
-
-  wayland_toplevel = GDK_WAYLAND_TOPLEVEL (object);
-
-  if (gdk_wayland_toplevel_is_exported (wayland_toplevel))
-    gdk_wayland_toplevel_unexport_handle (GDK_TOPLEVEL (wayland_toplevel));
-
-  g_free (wayland_toplevel->application.application_id);
-  g_free (wayland_toplevel->application.app_menu_path);
-  g_free (wayland_toplevel->application.menubar_path);
-  g_free (wayland_toplevel->application.window_object_path);
-  g_free (wayland_toplevel->application.application_object_path);
-  g_free (wayland_toplevel->application.unique_bus_name);
+  GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (gdk_surface_get_display (GDK_SURFACE (toplevel)));
+  GdkWaylandToplevel *toplevel_wayland;
 
-  g_free (wayland_toplevel->title);
-  g_clear_pointer (&wayland_toplevel->shortcuts_inhibitors, g_hash_table_unref);
+  g_return_if_fail (GDK_IS_WAYLAND_TOPLEVEL (toplevel));
+  toplevel_wayland = GDK_WAYLAND_TOPLEVEL (toplevel);
 
-  G_OBJECT_CLASS (gdk_wayland_toplevel_parent_class)->finalize (object);
+  if (!display_wayland->server_decoration_manager)
+    return;
+  toplevel_wayland->server_decoration =
+      org_kde_kwin_server_decoration_manager_create (display_wayland->server_decoration_manager,
+                                                     gdk_wayland_surface_get_wl_surface (GDK_SURFACE (toplevel_wayland)));
+  if (toplevel_wayland->server_decoration)
+    org_kde_kwin_server_decoration_request_mode (toplevel_wayland->server_decoration,
+                                                 ORG_KDE_KWIN_SERVER_DECORATION_MANAGER_MODE_CLIENT);
 }
 
-static void
-gdk_wayland_toplevel_class_init (GdkWaylandToplevelClass *class)
+void
+gdk_wayland_toplevel_announce_ssd (GdkToplevel *toplevel)
 {
-  GObjectClass *object_class = G_OBJECT_CLASS (class);
+  GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (gdk_surface_get_display (GDK_SURFACE (toplevel)));
+  GdkWaylandToplevel *toplevel_wayland;
 
-  object_class->get_property = gdk_wayland_toplevel_get_property;
-  object_class->set_property = gdk_wayland_toplevel_set_property;
-  object_class->finalize = gdk_wayland_toplevel_finalize;
+  g_return_if_fail (GDK_IS_WAYLAND_TOPLEVEL (toplevel));
+  toplevel_wayland = GDK_WAYLAND_TOPLEVEL (toplevel);
 
-  gdk_toplevel_install_properties (object_class, 1);
+  if (!display_wayland->server_decoration_manager)
+    return;
+  toplevel_wayland->server_decoration =
+      org_kde_kwin_server_decoration_manager_create (display_wayland->server_decoration_manager,
+                                                     gdk_wayland_surface_get_wl_surface (GDK_SURFACE (toplevel_wayland)));
+  if (toplevel_wayland->server_decoration)
+    org_kde_kwin_server_decoration_request_mode (toplevel_wayland->server_decoration,
+                                                 ORG_KDE_KWIN_SERVER_DECORATION_MANAGER_MODE_SERVER);
 }
 
-static void
-gdk_wayland_toplevel_present (GdkToplevel       *toplevel,
-                              GdkToplevelLayout *layout)
+gboolean
+gdk_wayland_toplevel_inhibit_idle (GdkToplevel *toplevel)
 {
-  GdkSurface *surface = GDK_SURFACE (toplevel);
-  GdkWaylandSurface *wayland_surface = GDK_WAYLAND_SURFACE (toplevel);
-  GdkWaylandToplevel *wayland_toplevel = GDK_WAYLAND_TOPLEVEL (toplevel);
-  gboolean pending_configure = FALSE;
-  gboolean maximize;
-  gboolean fullscreen;
+  GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (gdk_surface_get_display (GDK_SURFACE (toplevel)));
+  GdkWaylandToplevel *wayland_toplevel;
 
-  if (gdk_toplevel_layout_get_maximized (layout, &maximize))
-    {
-      if (maximize)
-        gdk_wayland_toplevel_maximize (toplevel);
-      else
-        gdk_wayland_toplevel_unmaximize (toplevel);
-      pending_configure = TRUE;
-    }
+  g_return_val_if_fail (GDK_IS_WAYLAND_TOPLEVEL (toplevel), FALSE);
+  wayland_toplevel = GDK_WAYLAND_TOPLEVEL (toplevel);
 
-  if (gdk_toplevel_layout_get_fullscreen (layout, &fullscreen))
+  if (!display_wayland->idle_inhibit_manager)
+    return FALSE;
+
+  if (!wayland_toplevel->idle_inhibitor)
     {
-      if (fullscreen)
-        {
-          GdkMonitor *monitor;
+      g_assert (wayland_toplevel->idle_inhibitor_refcount == 0);
 
-          monitor = gdk_toplevel_layout_get_fullscreen_monitor (layout);
-          if (monitor)
-            gdk_wayland_toplevel_fullscreen_on_monitor (wayland_toplevel, monitor);
-          else
-            gdk_wayland_toplevel_fullscreen (wayland_toplevel);
-        }
-      else
-        {
-          gdk_wayland_toplevel_unfullscreen (wayland_toplevel);
-        }
-      pending_configure = TRUE;
+      wayland_toplevel->idle_inhibitor =
+          zwp_idle_inhibit_manager_v1_create_inhibitor (display_wayland->idle_inhibit_manager,
+                                                        gdk_wayland_surface_get_wl_surface (GDK_SURFACE (wayland_toplevel)));
     }
+  ++wayland_toplevel->idle_inhibitor_refcount;
 
-  g_clear_pointer (&wayland_toplevel->layout, gdk_toplevel_layout_unref);
-  wayland_toplevel->layout = gdk_toplevel_layout_copy (layout);
+  return TRUE;
+}
 
-  gdk_wayland_surface_show (surface);
+void
+gdk_wayland_toplevel_uninhibit_idle (GdkToplevel *toplevel)
+{
+  GdkWaylandToplevel *wayland_toplevel;
 
-  if (!pending_configure)
+  g_return_if_fail (GDK_IS_WAYLAND_TOPLEVEL (toplevel));
+  wayland_toplevel = GDK_WAYLAND_TOPLEVEL (toplevel);
+
+  g_assert (wayland_toplevel->idle_inhibitor &&
+            wayland_toplevel->idle_inhibitor_refcount > 0);
+
+  if (--wayland_toplevel->idle_inhibitor_refcount == 0)
     {
-      wayland_surface->next_layout.surface_geometry_dirty = TRUE;
-      gdk_surface_request_layout (surface);
+      g_clear_pointer (&wayland_toplevel->idle_inhibitor,
+                       zwp_idle_inhibitor_v1_destroy);
     }
 }
 
+/**
+ * GdkWaylandToplevelExported:
+ * @toplevel: (type GdkWaylandToplevel): the `GdkToplevel` that is exported
+ * @handle: the handle
+ * @user_data: user data that was passed to [method@GdkWayland.WaylandToplevel.export_handle]
+ *
+ * Callback that gets called when the handle for a surface has been
+ * obtained from the Wayland compositor.
+ *
+ * This callback is used in [method@GdkWayland.WaylandToplevel.export_handle].
+ *
+ * The @handle can be passed to other processes, for the purpose of
+ * marking surfaces as transient for out-of-process surfaces.
+ */
+
 static gboolean
-gdk_wayland_toplevel_lower (GdkToplevel *toplevel)
+gdk_wayland_toplevel_is_exported (GdkWaylandToplevel *wayland_toplevel)
 {
-  return FALSE;
+  return wayland_toplevel->xdg_exported != NULL || wayland_toplevel->xdg_exported_v2 != NULL;
 }
 
+typedef struct {
+  GdkWaylandToplevelExported callback;
+  gpointer user_data;
+  GDestroyNotify destroy;
+} ExportHandleData;
+
 static void
-inhibitor_active (void *data,
-                  struct zwp_keyboard_shortcuts_inhibitor_v1 *inhibitor)
+export_handle_done (GObject      *source,
+                    GAsyncResult *result,
+                    void         *user_data)
 {
-  GdkToplevel *toplevel = GDK_TOPLEVEL (data);
-  GdkSurface *surface = GDK_SURFACE (toplevel);
+  GdkToplevel *toplevel = GDK_TOPLEVEL (source);
+  ExportHandleData *data = (ExportHandleData *)user_data;
+  char *handle;
 
-  surface->shortcuts_inhibited = TRUE;
-  g_object_notify (G_OBJECT (toplevel), "shortcuts-inhibited");
+  handle = gdk_toplevel_export_handle_finish (toplevel, result, NULL);
+  data->callback (toplevel, handle, data->user_data);
+  g_free (handle);
+
+  if (data->destroy)
+    data->destroy (data->user_data);
+
+  g_free (data);
 }
 
-static void
-inhibitor_inactive (void *data,
-                    struct zwp_keyboard_shortcuts_inhibitor_v1 *inhibitor)
+/**
+ * gdk_wayland_toplevel_export_handle:
+ * @toplevel: (type GdkWaylandToplevel): the `GdkToplevel` to obtain a handle for
+ * @callback: callback to call with the handle
+ * @user_data: (closure): user data for @callback
+ * @destroy_func: destroy notify for @user_data
+ *
+ * Asynchronously obtains a handle for a surface that can be passed
+ * to other processes.
+ *
+ * When the handle has been obtained, @callback will be called.
+ *
+ * It is an error to call this function on a surface that is already
+ * exported.
+ *
+ * When the handle is no longer needed, [method@GdkWayland.WaylandToplevel.unexport_handle]
+ * should be called to clean up resources.
+ *
+ * The main purpose for obtaining a handle is to mark a surface
+ * from another surface as transient for this one, see
+ * [method@GdkWayland.WaylandToplevel.set_transient_for_exported].
+ *
+ * Note that this API depends on an unstable Wayland protocol,
+ * and thus may require changes in the future.
+ *
+ * Return value: %TRUE if the handle has been requested, %FALSE if
+ *   an error occurred.
+ */
+gboolean
+gdk_wayland_toplevel_export_handle (GdkToplevel                *toplevel,
+                                    GdkWaylandToplevelExported  callback,
+                                    gpointer                    user_data,
+                                    GDestroyNotify              destroy_func)
 {
-  GdkToplevel *toplevel = GDK_TOPLEVEL (data);
-  GdkSurface *surface = GDK_SURFACE (toplevel);
+  ExportHandleData *data;
 
-  surface->shortcuts_inhibited = FALSE;
-  g_object_notify (G_OBJECT (toplevel), "shortcuts-inhibited");
-}
+  g_return_val_if_fail (GDK_IS_WAYLAND_TOPLEVEL (toplevel), FALSE);
 
-static const struct zwp_keyboard_shortcuts_inhibitor_v1_listener
-zwp_keyboard_shortcuts_inhibitor_listener = {
-  inhibitor_active,
-  inhibitor_inactive,
-};
+  data = g_new (ExportHandleData, 1);
+  data->callback = callback;
+  data->user_data = user_data;
+  data->destroy = destroy_func;
 
-static void
-gdk_wayland_toplevel_inhibit_system_shortcuts (GdkToplevel *toplevel,
-                                               GdkEvent    *event)
-{
-  struct zwp_keyboard_shortcuts_inhibitor_v1 *inhibitor;
-  GdkSurface *surface = GDK_SURFACE (toplevel);
-  GdkWaylandToplevel *wayland_toplevel = GDK_WAYLAND_TOPLEVEL (toplevel);
-  GdkSeat *gdk_seat;
+  gdk_toplevel_export_handle (toplevel, NULL, export_handle_done, data);
 
-  if (surface->shortcuts_inhibited)
-    return;
+  return TRUE;
+}
 
-  gdk_seat = gdk_surface_get_seat_from_event (surface, event);
-  gdk_wayland_surface_inhibit_shortcuts (surface, gdk_seat);
-  inhibitor = gdk_wayland_toplevel_get_inhibitor (wayland_toplevel, gdk_seat);
-  if (!inhibitor)
-    return;
+/**
+ * gdk_wayland_toplevel_unexport_handle:
+ * @toplevel: (type GdkWaylandToplevel): the `GdkToplevel` to unexport
+ *
+ * Destroys the handle that was obtained with
+ * gdk_wayland_toplevel_export_handle().
+ *
+ * It is an error to call this function on a surface that
+ * does not have a handle.
+ *
+ * Note that this API depends on an unstable Wayland protocol,
+ * and thus may require changes in the future.
+ */
+void
+gdk_wayland_toplevel_unexport_handle (GdkToplevel *toplevel)
+{
+  g_return_if_fail (GDK_IS_WAYLAND_TOPLEVEL (toplevel));
 
-  surface->current_shortcuts_inhibited_seat = gdk_seat;
-  zwp_keyboard_shortcuts_inhibitor_v1_add_listener
-    (inhibitor, &zwp_keyboard_shortcuts_inhibitor_listener, toplevel);
+  gdk_toplevel_unexport_handle (toplevel);
 }
 
 static void
-gdk_wayland_toplevel_restore_system_shortcuts (GdkToplevel *toplevel)
+unset_transient_for_exported (GdkSurface *surface)
 {
-  GdkSurface *surface = GDK_SURFACE (toplevel);
+  if (GDK_IS_WAYLAND_TOPLEVEL (surface))
+    {
+      GdkWaylandToplevel *toplevel = GDK_WAYLAND_TOPLEVEL (surface);
 
-  gdk_wayland_surface_restore_shortcuts (surface, surface->current_shortcuts_inhibited_seat);
-  surface->current_shortcuts_inhibited_seat = NULL;
-  surface->shortcuts_inhibited = FALSE;
-  g_object_notify (G_OBJECT (toplevel), "shortcuts-inhibited");
+      g_clear_pointer (&toplevel->imported_transient_for, zxdg_imported_v1_destroy);
+      g_clear_pointer (&toplevel->imported_transient_for_v2, zxdg_imported_v2_destroy);
+    }
 }
 
 static void
-xdg_exported_handle_v1 (void                    *data,
-                        struct zxdg_exported_v1 *zxdg_exported_v1,
-                        const char              *handle)
+xdg_imported_destroyed (void                    *data,
+                        struct zxdg_imported_v1 *imported)
 {
-  g_task_return_pointer (G_TASK (data), g_strdup (handle), g_free);
-  g_object_unref (data);
+  unset_transient_for_exported (GDK_SURFACE (data));
 }
 
-static const struct zxdg_exported_v1_listener xdg_exported_listener_v1 = {
-  xdg_exported_handle_v1
+static const struct zxdg_imported_v1_listener xdg_imported_listener = {
+  xdg_imported_destroyed,
 };
 
 static void
-xdg_exported_handle_v2 (void                    *data,
-                        struct zxdg_exported_v2 *zxdg_exported_v2,
-                        const char              *handle)
+xdg_imported_v2_destroyed (void                    *data,
+                           struct zxdg_imported_v2 *imported)
 {
-  g_task_return_pointer (G_TASK (data), g_strdup (handle), g_free);
-  g_object_unref (data);
+  unset_transient_for_exported (GDK_SURFACE (data));
 }
 
-static const struct zxdg_exported_v2_listener xdg_exported_listener_v2 = {
-  xdg_exported_handle_v2
+static const struct zxdg_imported_v2_listener xdg_imported_listener_v2 = {
+  xdg_imported_v2_destroyed,
 };
 
-static void
-gdk_wayland_toplevel_real_export_handle (GdkToplevel          *toplevel,
-                                         GCancellable         *cancellable,
-                                         GAsyncReadyCallback   callback,
-                                         gpointer              user_data)
+/**
+ * gdk_wayland_toplevel_set_transient_for_exported:
+ * @toplevel: (type GdkWaylandToplevel): the `GdkToplevel` to make as transient
+ * @parent_handle_str: an exported handle for a surface
+ *
+ * Marks @toplevel as transient for the surface to which the given
+ * @parent_handle_str refers.
+ *
+ * Typically, the handle will originate from a
+ * [method@GdkWayland.WaylandToplevel.export_handle] call in another process.
+ *
+ * Note that this API depends on an unstable Wayland protocol,
+ * and thus may require changes in the future.
+ *
+ * Return value: %TRUE if the surface has been marked as transient,
+ *   %FALSE if an error occurred.
+ */
+gboolean
+gdk_wayland_toplevel_set_transient_for_exported (GdkToplevel *toplevel,
+                                                 const char  *parent_handle_str)
 {
   GdkWaylandToplevel *wayland_toplevel = GDK_WAYLAND_TOPLEVEL (toplevel);
-  GdkSurface *surface = GDK_SURFACE (toplevel);
   GdkDisplay *display = gdk_surface_get_display (GDK_SURFACE (toplevel));
   GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display);
-  GTask *task;
 
-  task = g_task_new (toplevel, cancellable, callback, user_data);
+  g_return_val_if_fail (GDK_IS_WAYLAND_TOPLEVEL (toplevel), FALSE);
+  g_return_val_if_fail (GDK_IS_WAYLAND_DISPLAY (display), FALSE);
 
-  if (display_wayland->xdg_exporter_v2)
+  display_wayland = GDK_WAYLAND_DISPLAY (display);
+
+  if (!display_wayland->xdg_importer && !display_wayland->xdg_importer_v2)
     {
-      wayland_toplevel->xdg_exported_v2 =
-        zxdg_exporter_v2_export_toplevel (display_wayland->xdg_exporter_v2,
-                                          gdk_wayland_surface_get_wl_surface (surface));
-      zxdg_exported_v2_add_listener (wayland_toplevel->xdg_exported_v2,
-                                     &xdg_exported_listener_v2, task);
+      g_warning ("Server is missing xdg_foreign support");
+      return FALSE;
     }
-  else if (display_wayland->xdg_exporter)
+
+  gdk_wayland_toplevel_set_transient_for (wayland_toplevel, NULL);
+
+  if (display_wayland->xdg_importer)
     {
-      wayland_toplevel->xdg_exported =
-        zxdg_exporter_v1_export (display_wayland->xdg_exporter,
-                                 gdk_wayland_surface_get_wl_surface (surface));
-      zxdg_exported_v1_add_listener (wayland_toplevel->xdg_exported,
-                                     &xdg_exported_listener_v1, task);
+      wayland_toplevel->imported_transient_for =
+        zxdg_importer_v1_import (display_wayland->xdg_importer, parent_handle_str);
+      zxdg_imported_v1_add_listener (wayland_toplevel->imported_transient_for,
+                                     &xdg_imported_listener,
+                                     toplevel);
     }
   else
     {
-      g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED, "Exporting surface handles not supported");
-      g_object_unref (task);
-      return;
+      wayland_toplevel->imported_transient_for_v2 =
+        zxdg_importer_v2_import_toplevel (display_wayland->xdg_importer_v2, parent_handle_str);
+      zxdg_imported_v2_add_listener (wayland_toplevel->imported_transient_for_v2,
+                                     &xdg_imported_listener_v2,
+                                     toplevel);
     }
-}
-
-static char *
-gdk_wayland_toplevel_real_export_handle_finish (GdkToplevel   *toplevel,
-                                                GAsyncResult  *result,
-                                                GError       **error)
-{
-  return g_task_propagate_pointer (G_TASK (result), error);
-}
-
-static void
-gdk_wayland_toplevel_real_unexport_handle (GdkToplevel *toplevel)
-{
-  GdkWaylandToplevel *wayland_toplevel = GDK_WAYLAND_TOPLEVEL (toplevel);
-
-  g_return_if_fail (GDK_IS_WAYLAND_TOPLEVEL (toplevel));
-  g_return_if_fail (wayland_toplevel->xdg_exported_v2 || wayland_toplevel->xdg_exported);
 
-  g_clear_pointer (&wayland_toplevel->xdg_exported_v2, zxdg_exported_v2_destroy);
-  g_clear_pointer (&wayland_toplevel->xdg_exported, zxdg_exported_v1_destroy);
-}
+  gdk_wayland_toplevel_sync_parent_of_imported (wayland_toplevel);
 
-static void
-gdk_wayland_toplevel_iface_init (GdkToplevelInterface *iface)
-{
-  iface->present = gdk_wayland_toplevel_present;
-  iface->minimize = gdk_wayland_toplevel_minimize;
-  iface->lower = gdk_wayland_toplevel_lower;
-  iface->focus = gdk_wayland_toplevel_focus;
-  iface->show_window_menu = gdk_wayland_toplevel_show_window_menu;
-  iface->titlebar_gesture = gdk_wayland_toplevel_titlebar_gesture;
-  iface->supports_edge_constraints = gdk_wayland_toplevel_supports_edge_constraints;
-  iface->inhibit_system_shortcuts = gdk_wayland_toplevel_inhibit_system_shortcuts;
-  iface->restore_system_shortcuts = gdk_wayland_toplevel_restore_system_shortcuts;
-  iface->begin_resize = gdk_wayland_toplevel_begin_resize;
-  iface->begin_move = gdk_wayland_toplevel_begin_move;
-  iface->export_handle = gdk_wayland_toplevel_real_export_handle;
-  iface->export_handle_finish = gdk_wayland_toplevel_real_export_handle_finish;
-  iface->unexport_handle = gdk_wayland_toplevel_real_unexport_handle;
+  return TRUE;
 }
 
-
+/* }}} */
+/* vim:set foldmethod=marker expandtab: */