From d961dd7491a6d04aa749eee9d5faaa433ef2bd20 Mon Sep 17 00:00:00 2001 From: Will Temple Date: Mon, 23 Mar 2026 10:36:33 -0400 Subject: [PATCH] Bound zxdg_decoration_manager_v1 for compositors that support server-side decorations. Fixed a resize deadlock. Fixed scroll box list rendering. --- lib/ui/src/layout.rs | 8 +++++++- lib/ui_platform_wayland/src/lib.rs | 28 ++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/lib/ui/src/layout.rs b/lib/ui/src/layout.rs index e9637cb..45ccba2 100644 --- a/lib/ui/src/layout.rs +++ b/lib/ui/src/layout.rs @@ -577,7 +577,13 @@ fn layout_element( text_system, perf_stats, layout_cache, - clip_rect, + // Do NOT propagate clip_rect into children: the scroll-box PushClip already clips + // them visually, and propagating the clip causes the layout cache to bake in + // culling results for a specific scroll position. When the scroll changes, the + // same cache entry would replay with stale "empty" children that were culled at + // the previous offset. Culling applies only at the direct-child level of the + // scroll box (which passes Some(viewport_rect) to layout_container_children). + None, ); if pushed_clip { diff --git a/lib/ui_platform_wayland/src/lib.rs b/lib/ui_platform_wayland/src/lib.rs index b6ba4c0..030e0ff 100644 --- a/lib/ui_platform_wayland/src/lib.rs +++ b/lib/ui_platform_wayland/src/lib.rs @@ -40,6 +40,9 @@ use wayland_client::{ use wayland_protocols::wp::cursor_shape::v1::client::{ wp_cursor_shape_device_v1, wp_cursor_shape_manager_v1, }; +use wayland_protocols::xdg::decoration::zv1::client::{ + zxdg_decoration_manager_v1, zxdg_toplevel_decoration_v1, +}; use wayland_protocols::wp::primary_selection::zv1::client::{ zwp_primary_selection_device_manager_v1, zwp_primary_selection_device_v1, zwp_primary_selection_offer_v1, zwp_primary_selection_source_v1, @@ -156,6 +159,8 @@ struct State { clipboard_device: Option, cursor_shape_manager: Option, cursor_shape_device: Option, + /// Server-side decoration handle. Kept alive so the compositor maintains decorations. + _decoration: Option, primary_selection_manager: Option, primary_selection_device: Option, @@ -318,6 +323,8 @@ impl WaylandWindow { manager.get_device(&seat, &qh, ()) }, ); + let decoration_manager: Option = + globals.bind(&qh, 1..=1, ()).ok(); let surface = compositor.create_surface(&qh, ()); let xdg_surface = wm_base.get_xdg_surface(&surface, &qh, ()); let toplevel = xdg_surface.get_toplevel(&qh, ()); @@ -325,6 +332,11 @@ impl WaylandWindow { if let Some(app_id) = spec.app_id.as_ref() { toplevel.set_app_id(app_id.clone()); } + let decoration = decoration_manager.as_ref().map(|dm| { + let deco = dm.get_toplevel_decoration(&toplevel, &qh, ()); + deco.set_mode(zxdg_toplevel_decoration_v1::Mode::ServerSide); + deco + }); apply_size_constraints(&toplevel, &spec); if spec.maximized { @@ -395,6 +407,7 @@ impl WaylandWindow { last_selection_serial: None, last_pointer_enter_serial: None, cursor_icon: CursorIcon::Default, + _decoration: decoration, }, }) } @@ -1355,6 +1368,19 @@ fn spawn_window_worker( ); state_ref.pending_viewport = Some(current_viewport); state_ref.pending_viewport_since.get_or_insert_with(Instant::now); + // Deadlock prevention: if the app responded to the + // configure we sent (scene matches in_flight) but the + // viewport has since moved on, clear the in_flight + // marker so we can immediately send the next configure. + // Without this, viewport_request_in_flight stays set + // to the old size forever and the app is never told + // about the new viewport. + if state_ref.viewport_request_in_flight + == Some(scene.logical_size) + { + state_ref.viewport_request_in_flight = None; + maybe_request_pending_viewport(&mut state_ref); + } state_ref.window.request_redraw(); } else if !state_ref.window.presentation_ready() { // Correct scene is ready but the compositor @@ -1582,6 +1608,8 @@ delegate_noop!( State: ignore zwp_primary_selection_device_manager_v1::ZwpPrimarySelectionDeviceManagerV1 ); +delegate_noop!(State: ignore zxdg_decoration_manager_v1::ZxdgDecorationManagerV1); +delegate_noop!(State: ignore zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1); impl Dispatch for State { fn event( state: &mut Self,