fix: show plugin GUI in-process (inside Ableton window) not from Helper
Browse filesROOT CAUSE: Moving GUI to Helper process broke it. macOS treats Helper
as separate app → opens in different Space when fullscreen.
SOLUTION: PluginBridge.vst3 (in-process) loads a GUI-only plugin instance.
This instance doesn't process audio — it's only for displaying the editor.
Ableton manages it as a normal plugin window (same as Pro-Q 4 native).
Parameter sync: GUI instance ↔ Helper via IPC (bidirectional).
- User moves knob on GUI → notify Helper → Helper updates real instance
- MCP/automation changes param on Helper → notify Plugin → update GUI instance
Helper no longer shows any GUI window.
Source/Helper/HelperPluginHost.cpp
CHANGED
|
@@ -4,66 +4,6 @@
|
|
| 4 |
#include <pthread.h>
|
| 5 |
#include <sched.h>
|
| 6 |
|
| 7 |
-
#if JUCE_MAC
|
| 8 |
-
#include <objc/objc.h>
|
| 9 |
-
#include <objc/message.h>
|
| 10 |
-
#endif
|
| 11 |
-
|
| 12 |
-
// Configure window for floating-panel behavior that works in fullscreen
|
| 13 |
-
// MUST be called BEFORE the window is made visible
|
| 14 |
-
static void configureAsOverlayPanel(juce::Component* window)
|
| 15 |
-
{
|
| 16 |
-
#if JUCE_MAC
|
| 17 |
-
if (auto* peer = window->getPeer())
|
| 18 |
-
{
|
| 19 |
-
auto* nsWindow = (id)peer->getNativeHandle();
|
| 20 |
-
|
| 21 |
-
// --- COLLECTION BEHAVIOR (MUST be set before visible) ---
|
| 22 |
-
// canJoinAllSpaces = 1 << 0 (appear on all desktops/spaces)
|
| 23 |
-
// fullScreenAuxiliary = 1 << 8 (can appear over fullscreen apps)
|
| 24 |
-
// stationary = 1 << 4 (don't move with space switches)
|
| 25 |
-
// ignoresCycle = 1 << 6 (skip in Cmd+` cycling)
|
| 26 |
-
long behavior = (1 << 0) | (1 << 8) | (1 << 4) | (1 << 6);
|
| 27 |
-
((void(*)(id, SEL, long))objc_msgSend)(nsWindow, sel_registerName("setCollectionBehavior:"), behavior);
|
| 28 |
-
|
| 29 |
-
// --- WINDOW LEVEL ---
|
| 30 |
-
// Level 25 = NSStatusWindowLevel (above fullscreen apps)
|
| 31 |
-
((void(*)(id, SEL, long))objc_msgSend)(nsWindow, sel_registerName("setLevel:"), 25);
|
| 32 |
-
|
| 33 |
-
// Don't hide when the helper app loses focus
|
| 34 |
-
((void(*)(id, SEL, int))objc_msgSend)(nsWindow, sel_registerName("setHidesOnDeactivate:"), 0);
|
| 35 |
-
}
|
| 36 |
-
#endif
|
| 37 |
-
}
|
| 38 |
-
|
| 39 |
-
// Make the window appear without stealing focus (call AFTER visible)
|
| 40 |
-
static void showWithoutFocusSteal(juce::Component* window)
|
| 41 |
-
{
|
| 42 |
-
#if JUCE_MAC
|
| 43 |
-
if (auto* peer = window->getPeer())
|
| 44 |
-
{
|
| 45 |
-
auto* nsWindow = (id)peer->getNativeHandle();
|
| 46 |
-
((void(*)(id, SEL))objc_msgSend)(nsWindow, sel_registerName("orderFrontRegardless"));
|
| 47 |
-
}
|
| 48 |
-
#endif
|
| 49 |
-
}
|
| 50 |
-
|
| 51 |
-
// Custom window — no native title bar close issues with accessory policy
|
| 52 |
-
class PluginGuiWindow : public juce::DocumentWindow
|
| 53 |
-
{
|
| 54 |
-
public:
|
| 55 |
-
PluginGuiWindow(const juce::String& name)
|
| 56 |
-
: DocumentWindow(name, juce::Colour(0xff1a1a2e), DocumentWindow::allButtons, true)
|
| 57 |
-
{
|
| 58 |
-
setUsingNativeTitleBar(true);
|
| 59 |
-
}
|
| 60 |
-
|
| 61 |
-
void closeButtonPressed() override
|
| 62 |
-
{
|
| 63 |
-
setVisible(false);
|
| 64 |
-
}
|
| 65 |
-
};
|
| 66 |
-
|
| 67 |
HelperPluginHost::HelperPluginHost()
|
| 68 |
{
|
| 69 |
#if JUCE_PLUGINHOST_VST3
|
|
@@ -77,7 +17,6 @@ HelperPluginHost::HelperPluginHost()
|
|
| 77 |
HelperPluginHost::~HelperPluginHost()
|
| 78 |
{
|
| 79 |
stopProcessing();
|
| 80 |
-
hideGui();
|
| 81 |
unloadPlugin();
|
| 82 |
}
|
| 83 |
|
|
@@ -137,7 +76,6 @@ bool HelperPluginHost::loadPlugin(const juce::String& path, double sampleRate, i
|
|
| 137 |
|
| 138 |
void HelperPluginHost::unloadPlugin()
|
| 139 |
{
|
| 140 |
-
hideGui();
|
| 141 |
if (plugin)
|
| 142 |
{
|
| 143 |
plugin->releaseResources();
|
|
@@ -282,54 +220,19 @@ bool HelperPluginHost::setParams(const std::map<int, float>& values)
|
|
| 282 |
}
|
| 283 |
|
| 284 |
// --- GUI ---
|
|
|
|
|
|
|
|
|
|
| 285 |
|
| 286 |
void HelperPluginHost::showGui()
|
| 287 |
{
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
juce::MessageManager::callAsync([this]()
|
| 291 |
-
{
|
| 292 |
-
if (!plugin) return;
|
| 293 |
-
|
| 294 |
-
// If window exists but hidden, just show it
|
| 295 |
-
if (guiWindow)
|
| 296 |
-
{
|
| 297 |
-
// Re-apply overlay config before showing (in case Space changed)
|
| 298 |
-
configureAsOverlayPanel(guiWindow.get());
|
| 299 |
-
guiWindow->setVisible(true);
|
| 300 |
-
showWithoutFocusSteal(guiWindow.get());
|
| 301 |
-
return;
|
| 302 |
-
}
|
| 303 |
-
|
| 304 |
-
auto* editor = plugin->createEditor();
|
| 305 |
-
if (!editor) return;
|
| 306 |
-
|
| 307 |
-
guiWindow = std::make_unique<PluginGuiWindow>(plugin->getName());
|
| 308 |
-
guiWindow->setContentOwned(editor, true);
|
| 309 |
-
guiWindow->setResizable(true, true);
|
| 310 |
-
guiWindow->centreWithSize(editor->getWidth(), editor->getHeight());
|
| 311 |
-
|
| 312 |
-
// CRITICAL: configure overlay behavior BEFORE making visible
|
| 313 |
-
configureAsOverlayPanel(guiWindow.get());
|
| 314 |
-
|
| 315 |
-
// NOW make visible
|
| 316 |
-
guiWindow->setVisible(true);
|
| 317 |
-
|
| 318 |
-
// Show without stealing focus from Ableton
|
| 319 |
-
showWithoutFocusSteal(guiWindow.get());
|
| 320 |
-
});
|
| 321 |
}
|
| 322 |
|
| 323 |
void HelperPluginHost::hideGui()
|
| 324 |
{
|
| 325 |
-
|
| 326 |
-
{
|
| 327 |
-
juce::MessageManager::callAsync([this]()
|
| 328 |
-
{
|
| 329 |
-
if (guiWindow)
|
| 330 |
-
guiWindow->setVisible(false);
|
| 331 |
-
});
|
| 332 |
-
}
|
| 333 |
}
|
| 334 |
|
| 335 |
// --- State ---
|
|
|
|
| 4 |
#include <pthread.h>
|
| 5 |
#include <sched.h>
|
| 6 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
HelperPluginHost::HelperPluginHost()
|
| 8 |
{
|
| 9 |
#if JUCE_PLUGINHOST_VST3
|
|
|
|
| 17 |
HelperPluginHost::~HelperPluginHost()
|
| 18 |
{
|
| 19 |
stopProcessing();
|
|
|
|
| 20 |
unloadPlugin();
|
| 21 |
}
|
| 22 |
|
|
|
|
| 76 |
|
| 77 |
void HelperPluginHost::unloadPlugin()
|
| 78 |
{
|
|
|
|
| 79 |
if (plugin)
|
| 80 |
{
|
| 81 |
plugin->releaseResources();
|
|
|
|
| 220 |
}
|
| 221 |
|
| 222 |
// --- GUI ---
|
| 223 |
+
// GUI is NO LONGER handled by the Helper process.
|
| 224 |
+
// It's now handled in-process by PluginBridgeEditor (inside Ableton).
|
| 225 |
+
// These stubs remain for IPC compatibility but do nothing.
|
| 226 |
|
| 227 |
void HelperPluginHost::showGui()
|
| 228 |
{
|
| 229 |
+
// No-op — GUI is shown in-process by PluginBridgeEditor
|
| 230 |
+
DBG("HelperPluginHost::showGui() — no-op, GUI is in-process now");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 231 |
}
|
| 232 |
|
| 233 |
void HelperPluginHost::hideGui()
|
| 234 |
{
|
| 235 |
+
// No-op
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 236 |
}
|
| 237 |
|
| 238 |
// --- State ---
|