From 5a12bcf7dbf1525919f9617a7fc216f5f1515636 Mon Sep 17 00:00:00 2001 From: Sunderland93 Date: Sat, 18 Oct 2025 16:37:34 +0400 Subject: [PATCH 1/4] xdg-shell/popup: Reset 'dismissed-by-client' state when remapping A popup surface can be remapped multiple times using the same wl_surface, if a new xdg_popup object is created. To properly handle this, we need to reset the 'dismissed_by_client' boolean to false, as otherwise we won't allow new buffer commits. --- src/wayland/meta-wayland-xdg-shell.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/wayland/meta-wayland-xdg-shell.c b/src/wayland/meta-wayland-xdg-shell.c index 1319c74e3..cd6663bef 100644 --- a/src/wayland/meta-wayland-xdg-shell.c +++ b/src/wayland/meta-wayland-xdg-shell.c @@ -1059,6 +1059,7 @@ finish_popup_setup (MetaWaylandXdgPopup *xdg_popup) xdg_popup->setup.parent_surface = NULL; xdg_popup->setup.grab_seat = NULL; + xdg_popup->dismissed_by_client = FALSE; if (!meta_wayland_surface_get_window (parent_surface)) { From 1ce35c0485709d968c01cd67a9751858e6e1ad90 Mon Sep 17 00:00:00 2001 From: Sunderland93 Date: Sat, 18 Oct 2025 16:45:15 +0400 Subject: [PATCH 2/4] window-actor/wayland: Remove subsurface actors on dispose Destroying the window actor also destroys all its children. Subsurfaces however may get reused. If the client did not unparent them before the window actor got destroyed, they will be left without actor which results in a crash. Unparent all actors of subsurfaces on dispose to avoid that. --- src/compositor/meta-window-actor-wayland.c | 25 ++++++++++++++++++++++ src/wayland/meta-wayland-subsurface.c | 4 +++- src/wayland/meta-wayland-surface.c | 16 +------------- 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/src/compositor/meta-window-actor-wayland.c b/src/compositor/meta-window-actor-wayland.c index 98f26fc6c..6afac865b 100644 --- a/src/compositor/meta-window-actor-wayland.c +++ b/src/compositor/meta-window-actor-wayland.c @@ -144,10 +144,33 @@ meta_window_actor_wayland_update_regions (MetaWindowActor *actor) { } +static void +meta_window_actor_wayland_dispose (GObject *object) +{ + MetaWindowActor *window_actor = META_WINDOW_ACTOR (object); + MetaSurfaceActor *surface_actor = + meta_window_actor_get_surface (window_actor); + GList *children; + GList *l; + + children = clutter_actor_get_children (CLUTTER_ACTOR (window_actor)); + for (l = children; l; l = l->next) + { + ClutterActor *child_actor = l->data; + + if (META_IS_SURFACE_ACTOR_WAYLAND (child_actor) && + child_actor != CLUTTER_ACTOR (surface_actor)) + clutter_actor_remove_child (CLUTTER_ACTOR (window_actor), child_actor); + } + + G_OBJECT_CLASS (meta_window_actor_wayland_parent_class)->dispose (object); +} + static void meta_window_actor_wayland_class_init (MetaWindowActorWaylandClass *klass) { MetaWindowActorClass *window_actor_class = META_WINDOW_ACTOR_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); window_actor_class->assign_surface_actor = meta_window_actor_wayland_assign_surface_actor; window_actor_class->frame_complete = meta_window_actor_wayland_frame_complete; @@ -157,6 +180,8 @@ meta_window_actor_wayland_class_init (MetaWindowActorWaylandClass *klass) window_actor_class->queue_destroy = meta_window_actor_wayland_queue_destroy; window_actor_class->set_frozen = meta_window_actor_wayland_set_frozen; window_actor_class->update_regions = meta_window_actor_wayland_update_regions; + + object_class->dispose = meta_window_actor_wayland_dispose; } static void diff --git a/src/wayland/meta-wayland-subsurface.c b/src/wayland/meta-wayland-subsurface.c index 24d5c0a53..542d97ca1 100644 --- a/src/wayland/meta-wayland-subsurface.c +++ b/src/wayland/meta-wayland-subsurface.c @@ -292,7 +292,8 @@ unparent_actor (MetaWaylandSurface *surface) return; parent_actor = clutter_actor_get_parent (actor); - clutter_actor_remove_child (parent_actor, actor); + if (parent_actor) + clutter_actor_remove_child (parent_actor, actor); } static void @@ -490,6 +491,7 @@ surface_handle_parent_surface_destroyed (struct wl_listener *listener, surface, sub.parent_destroy_listener); + g_node_unlink (surface->subsurface_branch_node); surface->sub.parent = NULL; } diff --git a/src/wayland/meta-wayland-surface.c b/src/wayland/meta-wayland-surface.c index ca6bd98cd..13d0f9184 100644 --- a/src/wayland/meta-wayland-surface.c +++ b/src/wayland/meta-wayland-surface.c @@ -1328,13 +1328,6 @@ meta_wayland_surface_notify_unmapped (MetaWaylandSurface *surface) g_signal_emit (surface, surface_signals[SURFACE_UNMAPPED], 0); } -static void -unlink_note (GNode *node, - gpointer data) -{ - g_node_unlink (node); -} - static void wl_surface_destructor (struct wl_resource *resource) { @@ -1383,14 +1376,7 @@ wl_surface_destructor (struct wl_resource *resource) if (surface->wl_subsurface) wl_resource_destroy (surface->wl_subsurface); - if (surface->subsurface_branch_node) - { - g_node_children_foreach (surface->subsurface_branch_node, - G_TRAVERSE_NON_LEAVES, - unlink_note, - NULL); - g_clear_pointer (&surface->subsurface_branch_node, g_node_destroy); - } + g_clear_pointer (&surface->subsurface_branch_node, g_node_destroy); g_hash_table_destroy (surface->shortcut_inhibited_seats); From 7216d3141db5787b5f35e8a21f43feb12423f936 Mon Sep 17 00:00:00 2001 From: Sunderland93 Date: Sat, 18 Oct 2025 16:51:17 +0400 Subject: [PATCH 3/4] wayland/xdg-shell: Send xdg_popup.popup_done when position invalid A client may provide a positioner that places the window outside of its parent. This isn't allowed, according to spec, so we hide the window and log a warning. This, however, leads these affected clients with an incorrect view of what is mapped or not, meaning it becomes harder to recover. Fix this by sending xdg_popup.done when we hide the popup due to an invalid position. Don't error out the client, let the bug slide, as that's a less jarring experience for existing applications that reproduce this than being disconnected, which practically feels like a crash. --- src/wayland/meta-wayland-xdg-shell.c | 39 +++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/src/wayland/meta-wayland-xdg-shell.c b/src/wayland/meta-wayland-xdg-shell.c index cd6663bef..bd1f8d59c 100644 --- a/src/wayland/meta-wayland-xdg-shell.c +++ b/src/wayland/meta-wayland-xdg-shell.c @@ -185,6 +185,12 @@ surface_from_xdg_toplevel_resource (struct wl_resource *resource) return surface_from_xdg_surface_resource (resource); } +static MetaWaylandXdgPopup * +meta_wayland_xdg_popup_from_surface (MetaWaylandSurface *surface) +{ + return META_WAYLAND_XDG_POPUP (surface->role); +} + static void meta_wayland_xdg_surface_reset (MetaWaylandXdgSurface *xdg_surface) { @@ -1129,6 +1135,37 @@ finish_popup_setup (MetaWaylandXdgPopup *xdg_popup) } } +static void +dismiss_invalid_popup (MetaWaylandXdgPopup *xdg_popup) +{ + if (xdg_popup->popup) + { + while (TRUE) + { + MetaWaylandSurface *top_popup_surface; + MetaWaylandXdgPopup *top_xdg_popup; + + top_popup_surface = + meta_wayland_popup_get_top_popup (xdg_popup->popup); + if (!top_popup_surface) + break; + + top_xdg_popup = meta_wayland_xdg_popup_from_surface (top_popup_surface); + + xdg_popup_send_popup_done (top_xdg_popup->resource); + meta_wayland_popup_destroy (top_xdg_popup->popup); + + if (top_xdg_popup == xdg_popup) + break; + } + } + else + { + xdg_popup_send_popup_done (xdg_popup->resource); + meta_wayland_xdg_popup_unmap (xdg_popup); + } +} + static void meta_wayland_xdg_popup_apply_state (MetaWaylandSurfaceRole *surface_role, MetaWaylandSurfaceState *pending) @@ -1209,7 +1246,7 @@ meta_wayland_xdg_popup_post_apply_state (MetaWaylandSurfaceRole *surface_role, { g_warning ("Buggy client caused popup to be placed outside of " "parent window"); - dismiss_popup (xdg_popup); + dismiss_invalid_popup (xdg_popup); } } From a7ec199913992f4801665608f82bfebd92757e0e Mon Sep 17 00:00:00 2001 From: Sunderland93 Date: Sat, 18 Oct 2025 16:59:17 +0400 Subject: [PATCH 4/4] wayland/xdg-shell: Bail from popup_configure if resource was destroyed This function gets called when a surface state transaction is applied. Applying a transaction can get delayed, so the Wayland resource may have already been destroyed when we get here. In that case we cannot send events, so there's nothing to do. --- src/wayland/meta-wayland-xdg-shell.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/wayland/meta-wayland-xdg-shell.c b/src/wayland/meta-wayland-xdg-shell.c index bd1f8d59c..b87cb9aee 100644 --- a/src/wayland/meta-wayland-xdg-shell.c +++ b/src/wayland/meta-wayland-xdg-shell.c @@ -1286,6 +1286,9 @@ meta_wayland_xdg_popup_configure (MetaWaylandShellSurface *shell_surface, int geometry_scale; int x, y; + if (!xdg_popup->resource) + return; + /* If the parent surface was destroyed, its window will be destroyed * before the popup receives the parent-destroy signal. This means that * the popup may potentially get temporary focus until itself is destroyed.