From 983cbdc4414c944cff58b591b3706c27153fa637 Mon Sep 17 00:00:00 2001 From: reuk Date: Tue, 28 Oct 2025 17:56:53 +0000 Subject: [PATCH] VST Host: Use a "wrapper" window on Linux so that the client does not have to be responsible for window position Previously, on Linux, client plugin editors were embedded directly into the peer displaying the client's AudioProcessorEditor. Although this approach is simple and lightweight, it means that plugin editors are able to reposition themselves over controls in the parent window just by calling XMoveResizeWindow or similar on their own widget. It's more desirable that the client editor should be clipped if it attempts to draw outside the area of the AudioProcessorEditor. --- .../format_types/juce_VSTPluginFormat.cpp | 57 ++++++++++--------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp b/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp index 123a51eade..e3949b70bb 100644 --- a/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp +++ b/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp @@ -42,8 +42,6 @@ namespace juce #if JUCE_LINUX || JUCE_BSD -using EventProcPtr = void (*)(XEvent*); - static Window getChildWindow (Window windowToCheck) { Window rootWindow, parentWindow; @@ -106,6 +104,8 @@ public: #if JUCE_WINDOWS addAndMakeVisible (embeddedComponent); + #elif JUCE_LINUX || JUCE_BSD + addAndMakeVisible (xembedComponent); #endif } @@ -182,23 +182,7 @@ public: void paint (Graphics& g) override { - #if JUCE_LINUX || JUCE_BSD - if (isOpen) - { - if (pluginWindow != 0) - { - auto clip = componentToVstRect (*this, g.getClipBounds().toNearestInt()); - - X11Symbols::getInstance()->xClearArea (display, pluginWindow, clip.getX(), clip.getY(), - static_cast (clip.getWidth()), - static_cast (clip.getHeight()), True); - } - } - else - #endif - { - g.fillAll (Colours::black); - } + g.fillAll (Colours::black); } void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) override @@ -213,17 +197,11 @@ public: #if JUCE_WINDOWS embeddedComponent.setBounds (getLocalBounds()); #elif JUCE_LINUX || JUCE_BSD - const auto pos = componentToVstRect (*this, getLocalBounds()); + xembedComponent.setBounds (getLocalBounds()); if (pluginWindow != 0) { auto* symbols = X11Symbols::getInstance(); - symbols->xMoveResizeWindow (display, - pluginWindow, - pos.getX(), - pos.getY(), - (unsigned int) pos.getWidth(), - (unsigned int) pos.getHeight()); symbols->xMapRaised (display, pluginWindow); symbols->xFlush (display); } @@ -431,6 +409,8 @@ private: #if JUCE_WINDOWS auto* handle = embeddedComponent.getHWND(); + #elif JUCE_LINUX || JUCE_BSD + auto* handle = desktopComponent.getPeer()->getNativeHandle(); #else auto* handle = getWindowHandle(); #endif @@ -722,8 +702,33 @@ private: void* originalWndProc = {}; int sizeCheckCount = 0; #elif JUCE_LINUX || JUCE_BSD + // This is to provide a consistent X11 window handle with the same lifetime as the + // VSTPluginWindow. Using the VSTPluginWindow's peer directly would mean that the X11 window + // handle could change if the same VST window instance is repeatedly added/removed from the + // desktop. + // We then XEmbed this stable window into the VSTPluginFormat, and also use the stable window + // as the parent for the client VST window. + // We're not XEmbedding the client VST window directly, because it's not clear that VST + // hosts & clients expect to use the XEmbed protocol. + class DesktopComponent : public Component + { + public: + DesktopComponent() + { + setOpaque (true); + addToDesktop (0); + } + + void paint (Graphics& g) override + { + g.fillAll (Colours::black); + } + }; + ::Display* display = XWindowSystem::getInstance()->getDisplay(); Window pluginWindow = 0; + DesktopComponent desktopComponent; + XEmbedComponent xembedComponent { reinterpret_cast (desktopComponent.getPeer()->getNativeHandle()), true, false }; #endif #else static constexpr auto nativeScaleFactor = 1.0f;