diff --git a/src/wine-host/bridges/clap-impls/host-proxy.cpp b/src/wine-host/bridges/clap-impls/host-proxy.cpp index d83bbe9b..7e5d12d0 100644 --- a/src/wine-host/bridges/clap-impls/host-proxy.cpp +++ b/src/wine-host/bridges/clap-impls/host-proxy.cpp @@ -218,6 +218,19 @@ bool CLAP_ABI clap_host_proxy::ext_gui_request_resize(const clap_host_t* host, assert(host && host->host_data); auto self = static_cast(host->host_data); + // HACK: Surge XT/the CLAP JUCE Extensions get stuck in a resize loop when + // the host tries to resize the window. It will send + // `clap_host_gui::request_resize()` in response to + // `clap_plugin_gui::set_size()` with the same size it has just set. + // We'll need to filter these calls out to prevent this from causing + // issues. + if (const std::optional current_size = + self->bridge_.editor_size(self->owner_instance_id()); + current_size && current_size->width == width && + current_size->height == height) { + return true; + } + const bool result = self->bridge_.send_mutually_recursive_main_thread_message( clap::ext::gui::host::RequestResize{ diff --git a/src/wine-host/bridges/clap.cpp b/src/wine-host/bridges/clap.cpp index 699726ca..4402ebc1 100644 --- a/src/wine-host/bridges/clap.cpp +++ b/src/wine-host/bridges/clap.cpp @@ -508,15 +508,23 @@ void ClapBridge::run() { [&, plugin = instance.plugin.get(), gui = instance.extensions.gui, &editor = instance.editor]() { + assert(editor); + + // HACK: We need to resize the editor window before + // setting the size on the plugin. Surge XT and + // presumably other CLAP JUCE Extensions plugins + // will request a resize to the same size that was + // just set. This causes a resize loop, so we'll + // try to prevent resizes to the same size. + const Size old_size = editor->size(); + editor->resize(request.width, request.height); + if (gui->set_size(plugin, request.width, request.height)) { - // Also resize the editor window. We do the same - // thing when the plugin requests a resize. - assert(editor); - editor->resize(request.width, request.height); - return true; } else { + editor->resize(old_size.width, old_size.height); + return false; } }); @@ -745,6 +753,16 @@ bool ClapBridge::maybe_resize_editor(size_t instance_id, } } +std::optional ClapBridge::editor_size(size_t instance_id) { + const auto& [instance, _] = get_instance(instance_id); + + if (instance.editor) { + return instance.editor->size(); + } else { + return std::nullopt; + } +} + void ClapBridge::close_sockets() { sockets_.close(); } diff --git a/src/wine-host/bridges/clap.h b/src/wine-host/bridges/clap.h index ea11fcd3..b97a791d 100644 --- a/src/wine-host/bridges/clap.h +++ b/src/wine-host/bridges/clap.h @@ -219,6 +219,12 @@ class ClapBridge : public HostBridge { uint16_t width, uint16_t height); + /** + * Get the plugin instance's current editor size, if it has an active + * editor. + */ + std::optional editor_size(size_t instance_id); + protected: void close_sockets() override; diff --git a/src/wine-host/editor.cpp b/src/wine-host/editor.cpp index 5c8373e8..685e51c3 100644 --- a/src/wine-host/editor.cpp +++ b/src/wine-host/editor.cpp @@ -270,6 +270,7 @@ Editor::Editor(MainContext& main_context, x11_connection_(xcb_connect(nullptr, nullptr), xcb_disconnect), dnd_proxy_handle_(WineXdndProxy::get_handle()), client_area_(get_maximum_screen_dimensions(*x11_connection_)), + wrapper_window_size_({128, 128}), // Create a window without any decoratiosn for easy embedding. The // combination of `WS_EX_TOOLWINDOW` and `WS_POPUP` causes the window to // be drawn without any decorations (making resizes behave as you'd @@ -319,7 +320,8 @@ Editor::Editor(MainContext& main_context, parent_window_(parent_window_handle), wrapper_window_( x11_connection_, - [parent_window = parent_window_]( + [parent_window = parent_window_, + wrapper_window_size = wrapper_window_size_]( std::shared_ptr x11_connection, xcb_window_t window) { xcb_generic_error_t* error = nullptr; @@ -330,10 +332,11 @@ Editor::Editor(MainContext& main_context, &error)); THROW_X11_ERROR(error); - xcb_create_window(x11_connection.get(), XCB_COPY_FROM_PARENT, - window, query_reply->root, 0, 0, 128, 128, 0, - XCB_WINDOW_CLASS_INPUT_OUTPUT, - XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_create_window( + x11_connection.get(), XCB_COPY_FROM_PARENT, window, + query_reply->root, 0, 0, wrapper_window_size.width, + wrapper_window_size.height, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, + XCB_COPY_FROM_PARENT, 0, nullptr); }), wine_window_(get_x11_handle(win32_window_.handle_)), host_window_(find_host_window(*x11_connection_, @@ -429,6 +432,12 @@ void Editor::resize(uint16_t width, uint16_t height) { value_mask, values.data()); xcb_flush(x11_connection_.get()); + // NOTE: This lets us skip resize requests in CLAP plugins when the plugin + // tries to resize to its current size. This fixes resize loops when + // using the CLAP JUCE Extensions. + wrapper_window_size_.width = width; + wrapper_window_size_.height = height; + // When the `editor_coordinate_hack` option is enabled, we will make sure // that the window is actually placed at (0, 0) coordinates. Otherwise some // plugins that rely on screen coordinates, like the Soundtoys plugins and diff --git a/src/wine-host/editor.h b/src/wine-host/editor.h index a3f130ea..93d975f2 100644 --- a/src/wine-host/editor.h +++ b/src/wine-host/editor.h @@ -261,6 +261,11 @@ class Editor { */ void run_timer_proc(); + /** + * Get the editor's (or, the wrapper window's) current size. + */ + inline Size size() const noexcept { return wrapper_window_size_; } + /** * Whether to reposition `win32_window_` to (0, 0) every time the window * resizes. This can help with buggy plugins that use the (top level) @@ -361,12 +366,17 @@ class Editor { * will be set to a size that's large enough to be able to enter full screen * on a single display. This is more of a theoretical maximum size, as the * plugin will only use a portion of this window to draw to. Because we're - * not changing the size of the Wine window and simply letting the user or - * the host resize the X11 parent window it's been embedded in instead, - * resizing will feel smooth and native. + * not changing the size of the Wine window and only resize the wrapper + * window it's been embedded in, resizing will feel smooth and native. */ const Size client_area_; + /** + * The size of the wrapper window. We'll prevent CLAP resize requests when + * the wrapper window is already at the correct size. + */ + Size wrapper_window_size_; + /** * The handle for the window created through Wine that the plugin uses to * embed itself in.