From 3349b97987bcaad4679667aaec6c8500038b3203 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Mon, 13 Oct 2025 09:47:07 +0000 Subject: [PATCH] Core: add --no-window-frame command line option Add a command line option to load Blender without a window frame. Currently this is only used on Wayland, enabled for WITH_UI_TESTS since attempting to load LIBDECOR caused the tests to crash on start on Fedora. For tests there is no need to use LIBDECOR, so disable the window frame. This can also be used by users who don't want to use the X11 fallback if LIBDECOR can't be found. Ref !147716 --- intern/ghost/GHOST_C-api.h | 7 ++++++ intern/ghost/GHOST_ISystem.hh | 12 ++++++++++ intern/ghost/intern/GHOST_C-api.cc | 5 ++++ intern/ghost/intern/GHOST_ISystem.cc | 15 +++++++++++- intern/ghost/intern/GHOST_SystemWayland.cc | 24 ++++++++++++++++--- intern/ghost/intern/GHOST_SystemWayland.hh | 4 +++- intern/ghost/intern/GHOST_WindowWayland.cc | 6 ++++- source/blender/windowmanager/WM_api.hh | 2 ++ .../windowmanager/intern/wm_playanim.cc | 1 + .../blender/windowmanager/intern/wm_window.cc | 12 ++++++++++ source/creator/creator_args.cc | 11 +++++++++ tests/python/CMakeLists.txt | 4 ++++ 12 files changed, 97 insertions(+), 6 deletions(-) diff --git a/intern/ghost/GHOST_C-api.h b/intern/ghost/GHOST_C-api.h index 2dea63ef0db..a5225eaeedf 100644 --- a/intern/ghost/GHOST_C-api.h +++ b/intern/ghost/GHOST_C-api.h @@ -1007,6 +1007,13 @@ extern GHOST_TCapabilityFlag GHOST_GetCapabilities(void); */ extern void GHOST_SetBacktraceHandler(GHOST_TBacktraceFn backtrace_fn); +/** + * When `use_window_frame` is false, don't show window frames. + * + * \note This must run before the system is created. + */ +extern void GHOST_UseWindowFrame(bool use_window_frame); + /** * Focus window after opening, or put them in the background. */ diff --git a/intern/ghost/GHOST_ISystem.hh b/intern/ghost/GHOST_ISystem.hh index 810b4d0418e..cc018139146 100644 --- a/intern/ghost/GHOST_ISystem.hh +++ b/intern/ghost/GHOST_ISystem.hh @@ -150,6 +150,9 @@ class GHOST_ISystem { static GHOST_TBacktraceFn getBacktraceFn(); static void setBacktraceFn(GHOST_TBacktraceFn backtrace_fn); + static bool getUseWindowFrame(); + static void setUseWindowFrame(bool use_window_frame); + protected: /** * Constructor. @@ -541,5 +544,14 @@ class GHOST_ISystem { /** Function to call that sets the back-trace. */ static GHOST_TBacktraceFn backtrace_fn_; + /** + * When false, don't use window frame. + * + * \note This needs to be set before system initialization + * to avoid loading LIBDECOR libraries (which can crash). + * If LIBDECOR is removed, this could be set on window creation instead. + */ + static bool use_window_frame_; + MEM_CXX_CLASS_ALLOC_FUNCS("GHOST:GHOST_ISystem") }; diff --git a/intern/ghost/intern/GHOST_C-api.cc b/intern/ghost/intern/GHOST_C-api.cc index 913e0f069d4..325cb02d41c 100644 --- a/intern/ghost/intern/GHOST_C-api.cc +++ b/intern/ghost/intern/GHOST_C-api.cc @@ -965,6 +965,11 @@ void GHOST_SetBacktraceHandler(GHOST_TBacktraceFn backtrace_fn) GHOST_ISystem::setBacktraceFn(backtrace_fn); } +void GHOST_UseWindowFrame(bool use_window_frame) +{ + GHOST_ISystem::setUseWindowFrame(use_window_frame); +} + void GHOST_UseWindowFocus(bool use_focus) { GHOST_ISystem *system = GHOST_ISystem::getSystem(); diff --git a/intern/ghost/intern/GHOST_ISystem.cc b/intern/ghost/intern/GHOST_ISystem.cc index 83bb833fbdc..81a340335e3 100644 --- a/intern/ghost/intern/GHOST_ISystem.cc +++ b/intern/ghost/intern/GHOST_ISystem.cc @@ -39,6 +39,8 @@ const char *GHOST_ISystem::system_backend_id_ = nullptr; GHOST_TBacktraceFn GHOST_ISystem::backtrace_fn_ = nullptr; +bool GHOST_ISystem::use_window_frame_ = true; + GHOST_TSuccess GHOST_ISystem::createSystem(bool verbose, [[maybe_unused]] bool background) { @@ -53,12 +55,13 @@ GHOST_TSuccess GHOST_ISystem::createSystem(bool verbose, [[maybe_unused]] bool b GHOST_TSuccess success; if (!system_) { + const bool use_window_frame = GHOST_ISystem::getUseWindowFrame(); #if defined(WITH_HEADLESS) /* Pass. */ #elif defined(WITH_GHOST_WAYLAND) # if defined(WITH_GHOST_WAYLAND_DYNLOAD) - const bool has_wayland_libraries = ghost_wl_dynload_libraries_init(); + const bool has_wayland_libraries = ghost_wl_dynload_libraries_init(use_window_frame); # else const bool has_wayland_libraries = true; # endif @@ -261,3 +264,13 @@ void GHOST_ISystem::setBacktraceFn(GHOST_TBacktraceFn backtrace_fn) { GHOST_ISystem::backtrace_fn_ = backtrace_fn; } + +bool GHOST_ISystem::getUseWindowFrame() +{ + return GHOST_ISystem::use_window_frame_; +} + +void GHOST_ISystem::setUseWindowFrame(bool use_window_frame) +{ + GHOST_ISystem::use_window_frame_ = use_window_frame; +} diff --git a/intern/ghost/intern/GHOST_SystemWayland.cc b/intern/ghost/intern/GHOST_SystemWayland.cc index 1221a57e34f..10fb059df8b 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.cc +++ b/intern/ghost/intern/GHOST_SystemWayland.cc @@ -1548,6 +1548,11 @@ struct GWL_Display { */ bool background = false; + /** + * Show window decorations, otherwise all windows are frame-less. + */ + bool use_window_frame = false; + /* Threaded event handling. */ #ifdef USE_EVENT_BACKGROUND_THREAD /** @@ -7609,8 +7614,12 @@ GHOST_SystemWayland::GHOST_SystemWayland(const bool background) wl_log_set_handler_client(background ? ghost_wayland_log_handler_background : ghost_wayland_log_handler); + const bool use_window_frame = GHOST_ISystem::getUseWindowFrame(); + display_->system = this; display_->background = background; + display_->use_window_frame = use_window_frame; + /* Connect to the Wayland server. */ display_->wl.display = wl_display_connect(nullptr); @@ -7641,7 +7650,7 @@ GHOST_SystemWayland::GHOST_SystemWayland(const bool background) #ifdef WITH_GHOST_WAYLAND_LIBDECOR bool libdecor_required = false; - { + if (use_window_frame) { const char *xdg_current_desktop = [] { /* Account for VSCode overriding this value (TSK!), see: #133921. */ const char *key = "ORIGINAL_XDG_CURRENT_DESKTOP"; @@ -9359,6 +9368,11 @@ GHOST_TimerManager *GHOST_SystemWayland::ghost_timer_manager() } #endif +bool GHOST_SystemWayland::use_window_frame_get() +{ + return display_->use_window_frame; +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -9870,7 +9884,7 @@ bool GHOST_SystemWayland::use_libdecor_runtime() #endif #ifdef WITH_GHOST_WAYLAND_DYNLOAD -bool ghost_wl_dynload_libraries_init() +bool ghost_wl_dynload_libraries_init(const bool use_window_frame) { # ifdef WITH_GHOST_X11 /* When running in WAYLAND, let the user know when a missing library is the only reason @@ -9891,7 +9905,11 @@ bool ghost_wl_dynload_libraries_init() ) { # ifdef WITH_GHOST_WAYLAND_LIBDECOR - has_libdecor = wayland_dynload_libdecor_init(verbose); /* `libdecor-0`. */ + if (use_window_frame) { + has_libdecor = wayland_dynload_libdecor_init(verbose); /* `libdecor-0`. */ + } +# else + (void)use_window_frame; # endif return true; } diff --git a/intern/ghost/intern/GHOST_SystemWayland.hh b/intern/ghost/intern/GHOST_SystemWayland.hh index 42a840c8ce1..a60698c1832 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.hh +++ b/intern/ghost/intern/GHOST_SystemWayland.hh @@ -86,7 +86,7 @@ int gwl_window_scale_int_from(const GWL_WindowScaleParams &scale_params, int val * Return true when all required WAYLAND libraries are present, * Performs dynamic loading when `WITH_GHOST_WAYLAND_DYNLOAD` is in use. */ -bool ghost_wl_dynload_libraries_init(); +bool ghost_wl_dynload_libraries_init(bool use_window_frame); void ghost_wl_dynload_libraries_exit(); #endif @@ -280,6 +280,8 @@ class GHOST_SystemWayland : public GHOST_System { bool completed) const; void ime_end(const GHOST_WindowWayland *win) const; + bool use_window_frame_get(); + static const char *xdg_app_id_get(); /* WAYLAND utility functions. */ diff --git a/intern/ghost/intern/GHOST_WindowWayland.cc b/intern/ghost/intern/GHOST_WindowWayland.cc index 2e39b4c79ec..f51c9fca75f 100644 --- a/intern/ghost/intern/GHOST_WindowWayland.cc +++ b/intern/ghost/intern/GHOST_WindowWayland.cc @@ -1867,12 +1867,16 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system, GWL_XDG_Decor_Window &decor = *window_->xdg_decor; if (system_->xdg_decor_manager_get()) { + const bool use_window_frame = system_->use_window_frame_get(); decor.toplevel_decor = zxdg_decoration_manager_v1_get_toplevel_decoration( system_->xdg_decor_manager_get(), decor.toplevel); zxdg_toplevel_decoration_v1_add_listener( decor.toplevel_decor, &xdg_toplevel_decoration_v1_listener, window_); + /* Request client side decorations as a way of disabling decorations. */ zxdg_toplevel_decoration_v1_set_mode(decor.toplevel_decor, - ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); + use_window_frame ? + ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE : + ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE); } /* Commit needed to so configure callback runs. */ diff --git a/source/blender/windowmanager/WM_api.hh b/source/blender/windowmanager/WM_api.hh index bb7ab80a4eb..ec22bd092ef 100644 --- a/source/blender/windowmanager/WM_api.hh +++ b/source/blender/windowmanager/WM_api.hh @@ -103,6 +103,8 @@ void WM_init_state_normal_set(); void WM_init_state_maximized_set(); void WM_init_state_start_with_console_set(bool value); void WM_init_window_focus_set(bool do_it); +bool WM_init_window_frame_get(); +void WM_init_window_frame_set(bool do_it); void WM_init_native_pixels(bool do_it); void WM_init_input_devices(); diff --git a/source/blender/windowmanager/intern/wm_playanim.cc b/source/blender/windowmanager/intern/wm_playanim.cc index bf885d49ef1..bb219a82477 100644 --- a/source/blender/windowmanager/intern/wm_playanim.cc +++ b/source/blender/windowmanager/intern/wm_playanim.cc @@ -1862,6 +1862,7 @@ static std::optional wm_main_playanim_intern(int argc, const char **argv, P /* Init GHOST and open window. */ GHOST_SetBacktraceHandler((GHOST_TBacktraceFn)BLI_system_backtrace); + GHOST_UseWindowFrame(WM_init_window_frame_get()); ps.ghost_data.system = GHOST_CreateSystem(); if (UNLIKELY(ps.ghost_data.system == nullptr)) { diff --git a/source/blender/windowmanager/intern/wm_window.cc b/source/blender/windowmanager/intern/wm_window.cc index 184f3388e4d..8c360da2619 100644 --- a/source/blender/windowmanager/intern/wm_window.cc +++ b/source/blender/windowmanager/intern/wm_window.cc @@ -132,6 +132,7 @@ static struct WMInitStruct { GHOST_TWindowState windowstate = GHOST_WINDOW_STATE_DEFAULT; eWinOverrideFlag override_flag; + bool window_frame = true; bool window_focus = true; bool native_pixels = true; } wm_init_state; @@ -2116,6 +2117,7 @@ void wm_ghost_init(bContext *C) consumer = GHOST_CreateEventConsumer(ghost_event_proc, C); GHOST_SetBacktraceHandler((GHOST_TBacktraceFn)BLI_system_backtrace); + GHOST_UseWindowFrame(wm_init_state.window_frame); g_system = GHOST_CreateSystem(); GPU_backend_ghost_system_set(g_system); @@ -2814,6 +2816,16 @@ void WM_init_state_maximized_set() wm_init_state.override_flag |= WIN_OVERRIDE_WINSTATE; } +bool WM_init_window_frame_get() +{ + return wm_init_state.window_frame; +} + +void WM_init_window_frame_set(bool do_it) +{ + wm_init_state.window_frame = do_it; +} + void WM_init_window_focus_set(bool do_it) { wm_init_state.window_focus = do_it; diff --git a/source/creator/creator_args.cc b/source/creator/creator_args.cc index 817276d3446..28c8486bbad 100644 --- a/source/creator/creator_args.cc +++ b/source/creator/creator_args.cc @@ -702,6 +702,7 @@ static void print_help(bArgs *ba, bool all) BLI_args_print_arg_doc(ba, "--window-geometry"); BLI_args_print_arg_doc(ba, "--start-console"); BLI_args_print_arg_doc(ba, "--no-native-pixels"); + BLI_args_print_arg_doc(ba, "--no-window-frame"); BLI_args_print_arg_doc(ba, "--no-window-focus"); PRINT("\n"); @@ -1889,6 +1890,15 @@ static int arg_handle_window_maximized(int /*argc*/, const char ** /*argv*/, voi return 0; } +static const char arg_handle_no_window_frame_doc[] = + "\n\t" + "Disable all window decorations (Wayland only)."; +static int arg_handle_no_window_frame(int /*argc*/, const char ** /*argv*/, void * /*data*/) +{ + WM_init_window_frame_set(false); + return 0; +} + static const char arg_handle_no_window_focus_doc[] = "\n\t" "Open behind other windows and without taking focus."; @@ -3087,6 +3097,7 @@ void main_args_setup(bContext *C, bArgs *ba, bool all) BLI_args_add(ba, "-w", "--window-border", CB(arg_handle_window_border), nullptr); BLI_args_add(ba, "-W", "--window-fullscreen", CB(arg_handle_window_fullscreen), nullptr); BLI_args_add(ba, "-M", "--window-maximized", CB(arg_handle_window_maximized), nullptr); + BLI_args_add(ba, nullptr, "--no-window-frame", CB(arg_handle_no_window_frame), nullptr); BLI_args_add(ba, nullptr, "--no-window-focus", CB(arg_handle_no_window_focus), nullptr); BLI_args_add(ba, "-con", "--start-console", CB(arg_handle_start_with_console), nullptr); BLI_args_add(ba, "-r", "--register", CB(arg_handle_register_extension), nullptr); diff --git a/tests/python/CMakeLists.txt b/tests/python/CMakeLists.txt index 9dabaccff6b..27dda2c6bad 100644 --- a/tests/python/CMakeLists.txt +++ b/tests/python/CMakeLists.txt @@ -121,6 +121,10 @@ if(WITH_UI_TESTS) # while this could be investigated, use windowed mode instead. # Use a window size that balances software GPU rendering with enough room to use the UI. --factory-startup + # Used so GHOST/Wayland doesn't attempt to load LIBDECOR + # which can fail on some systems, causing tests to fail. + # On other systems this is harmless. + --no-window-frame -p 0 0 800 600 "${EXE_PARAMS}" "${ARGN}"