From 5f72d112dd598cda36bcf6908c9d3e973c86a2a6 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Wed, 15 Oct 2025 04:24:25 +0000 Subject: [PATCH] Fix #144919: Opening windows hangs on GNOME-49 with Wayland Opening "normal" windows (non-maximized) windows was hanging. Blender was waiting for a "configure" event with a valid size, which stopped being sent in GNOME-49. Even though GNOME-49.1 will resolve the problem, according to the GNOME developers Blender's use of Wayland was incorrect. Resolve the issue with the following changes: - Don't wait for the `xdg_toplevel` to be when creating new windows. Instead, defer setting the window state using logic that was already used for Vulkan. - Set new window's pending size - used if no size is received from LIBDECOR's "configure" callback. - When the window is "configured" always set the window "state" even if the size is not yet known. Ref !148104 --- intern/ghost/intern/GHOST_WindowWayland.cc | 105 +++++++++++---------- 1 file changed, 57 insertions(+), 48 deletions(-) diff --git a/intern/ghost/intern/GHOST_WindowWayland.cc b/intern/ghost/intern/GHOST_WindowWayland.cc index 2e39b4c79ec..ca54d228220 100644 --- a/intern/ghost/intern/GHOST_WindowWayland.cc +++ b/intern/ghost/intern/GHOST_WindowWayland.cc @@ -110,6 +110,10 @@ static libdecor_configuration *ghost_wl_libdecor_configuration_copy( static void ghost_wl_libdecor_configuration_free(libdecor_configuration *configuration); #endif +static bool gwl_window_state_set_for_xdg(xdg_toplevel *toplevel, + const GHOST_TWindowState state, + const GHOST_TWindowState state_current); + static const xdg_activation_token_v1_listener *xdg_activation_listener_get(); static constexpr size_t base_dpi = 96; @@ -173,6 +177,16 @@ static void gwl_libdecor_window_destroy(GWL_LibDecor_Window *decor) libdecor_frame_unref(decor->frame); delete decor; } + +static void gwl_libdecor_window_initial_configure_state_set(GWL_LibDecor_Window *decor, + const GHOST_TWindowState state_current) +{ + xdg_toplevel *toplevel = libdecor_frame_get_xdg_toplevel(decor->frame); + GHOST_ASSERT(toplevel, "Expected to be valid!"); + gwl_window_state_set_for_xdg(toplevel, decor->initial_configure_state.value(), state_current); + decor->initial_configure_state = std::nullopt; +} + #endif /* WITH_GHOST_WAYLAND_LIBDECOR */ struct GWL_XDG_Decor_Window { @@ -1019,12 +1033,8 @@ static void gwl_window_frame_update_from_pending_no_lock(GWL_Window *win) if (decor.initial_configure_seen == false) { decor.initial_configure_seen = true; - if (decor.initial_configure_state) { - xdg_toplevel *toplevel = libdecor_frame_get_xdg_toplevel(decor.frame); - gwl_window_state_set_for_xdg( - toplevel, decor.initial_configure_state.value(), gwl_window_state_get(win)); - decor.initial_configure_state = std::nullopt; + gwl_libdecor_window_initial_configure_state_set(&decor, gwl_window_state_get(win)); } } @@ -1421,6 +1431,9 @@ static void libdecor_frame_handle_configure(libdecor_frame *frame, /* Set the size. */ int size_next[2] = {0, 0}; + /* Perform a "final" commit. */ + bool surface_needs_commit_finally = false; + { GWL_Window *win = static_cast(data); const int fractional_scale = win->frame.fractional_scale; @@ -1521,6 +1534,19 @@ static void libdecor_frame_handle_configure(libdecor_frame *frame, decor.pending.configuration_needs_free = false; /* Wait until we have a valid size. */ decor.pending.ack_configure = false; + + /* It's important to set this when `ack_configure` is disabled, + * otherwise it's possible the window is never called with a valid size `ack_configure` + * is never set to true and the `decor.initial_configure_state` is never applied. + * + * So set the state here, and commit the surface. + * Then LIBDECOR is responsible for applying the state. */ + if (decor.initial_configure_state) { + gwl_libdecor_window_initial_configure_state_set(&decor, gwl_window_state_get(win)); + /* Without the final commit, popup windows such as the preferences wont + * update the window frame and the window wont be clickable. */ + surface_needs_commit_finally = true; + } } # endif /* USE_LIBDECOR_CONFIG_COPY_QUEUE */ } @@ -1538,6 +1564,19 @@ static void libdecor_frame_handle_configure(libdecor_frame *frame, gwl_window_frame_update_from_pending_no_lock(win); } } + + if (surface_needs_commit_finally) { + GWL_Window *win = static_cast(data); +# ifdef USE_EVENT_BACKGROUND_THREAD + if (!is_main_thread) { + gwl_window_pending_actions_tag(win, PENDING_WINDOW_SURFACE_COMMIT); + } + else +# endif + { + wl_surface_commit(win->wl.surface); + } + } } static void libdecor_frame_handle_close(libdecor_frame * /*frame*/, void *data) @@ -2001,14 +2040,6 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system, } #endif -#ifdef WITH_GHOST_WAYLAND_LIBDECOR -# ifdef WITH_VULKAN_BACKEND - const bool libdecor_wait_for_window_init = (type == GHOST_kDrawingContextTypeVulkan); -# else - const bool libdecor_wait_for_window_init = false; -# endif -#endif - /* Drawing context. */ if (setDrawingContextType(type) == GHOST_kFailure) { /* This can happen when repeatedly creating windows, see #123096. @@ -2025,7 +2056,7 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system, } else #ifdef WITH_GHOST_WAYLAND_LIBDECOR - if (use_libdecor && libdecor_wait_for_window_init) + if (use_libdecor) { /* Ensuring the XDG window has been created is *not* supported by VULKAN. * @@ -2043,43 +2074,21 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system, * This can be removed if CSD are implemented, see: #113795. */ GWL_LibDecor_Window &decor = *window_->libdecor; decor.initial_configure_state = state; - } - else if (use_libdecor) { - /* Commit needed so the top-level callbacks run (and `toplevel` can be accessed). */ - wl_surface_commit(window_->wl.surface); - GWL_LibDecor_Window &decor = *window_->libdecor; - /* Additional round-trip is needed to ensure `xdg_toplevel` is set. */ - wl_display_roundtrip(display); - - /* NOTE: LIBDECOR requires the window to be created & configured before the state can be set. - * Workaround this by using the underlying `xdg_toplevel` */ - - /* Failure exits with an error, simply prevent an eternal loop. */ - while (!decor.initial_configure_seen && !ghost_wl_display_report_error_if_set(display)) { - wl_display_flush(display); - wl_display_dispatch(display); - } - - xdg_toplevel *toplevel = libdecor_frame_get_xdg_toplevel(decor.frame); - gwl_window_state_set_for_xdg(toplevel, state, gwl_window_state_get(window_)); - - /* NOTE(@ideasman42): Round trips are necessary with LIBDECOR on GNOME - * because resizing later on and redrawing does *not* update as it should, see #119871. + /* Set the pending size now, this is an imperfect solution, + * it's needed for the following reasons. * - * Without the round-trip here: - * - The window will be created and this function will return using the requested buffer size, - * instead of the window size which ends up being used (causing a visible flicker). - * This has the down side that Blender's internal window state has the outdated size - * which then gets immediately resized, causing a noticeable glitch. - * - The window decorations will be displayed at the wrong size before refreshing - * at the new size. - * - On GNOME-Shell 46 shows the previous buffer-size under some conditions. + * - New windows won't apply their configuration + * (when #GWL_LibDecor_Window::ack_configure is true) + * *unless* there is a valid size. + * - In some cases (GNOME 49.0 maybe other versions) the window never gets a valid size. * - * In principle this could be used with XDG too however it causes problems with KDE - * and some WLROOTS based compositors. - */ - wl_display_roundtrip(display); + * So set a size here, it will be used if the window is configured without a size. + * Note that this may not match the size used by LIBDECOR, showing a visible + * difference between the window and it's frame. This mainly happens when attempting + * to use small window sizes (which may be clamped to a larger size). */ + decor.pending.size[0] = window_->frame.size[0] / window_->frame.buffer_scale; + decor.pending.size[1] = window_->frame.size[1] / window_->frame.buffer_scale; } else #endif /* WITH_GHOST_WAYLAND_LIBDECOR */