-
-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Issue with Vestige + Helm #3918
Comments
Tagging @DomClark -- our resident expert with VSTs. :) |
Successfully reproduced. I've found one other host where this problem occurs: Hermann Seib's VSTHost when the plugin is running with a different number of bits to that of the host, in which case it is loaded in a separate bridge process, like LMMS does. Presumably having the parent window in a different process messes something up somewhere. I'm quite busy with university work currently, but I'll take a look when I have time. In the meantime, the value can be changed by hovering over the menu and scrolling. |
The bug appears to be in JUCE. Through some internal logic while opening a popup menu, execution reaches if (! window.doesAnyJuceCompHaveFocus())
{
if (timeNow > window.lastFocusedTime + 10)
{
PopupMenuSettings::menuWasHiddenBecauseOfAppChange = true;
window.dismissMenu (nullptr);
// Note: this object may have been deleted by the previous call..
}
}
bool doesAnyJuceCompHaveFocus()
{
bool anyFocused = Process::isForegroundProcess();
if (anyFocused && Component::getCurrentlyFocusedComponent() == nullptr)
{
// ...
}
return anyFocused;
} And finally the Win32 version of bool JUCE_CALLTYPE Process::isForegroundProcess()
{
if (auto fg = GetForegroundWindow())
{
DWORD processID = 0;
GetWindowThreadProcessId (fg, &processID);
return processID == GetCurrentProcessId();
}
return true;
}
The problem here is the assumption in I can't think of any obvious way we can work around this on our end; somebody should probably just file an issue with JUCE. |
Here is a pair of Helm builds with |
Maybe this is a job for the Helm devs? |
@DomClark excellent investigation. Can you please open a bug at JUCE describing the issue? |
Can be reproduced on Windows, but not on Linux. |
@DomClark Can you please open an issue with JUCE? Also, bumping helm @mtytel |
Ah sorry, I completely forgot about this! I'll open an issue there soon. |
Seem to be a problem with Dexed as well (forum comment). |
The lines just ahead of this code recently changed. Don't know if it applies to this case though. |
I've found a workaround here: if one clears the |
Fine, but please open an issue up-stream. I can't do this myself out of lack of competence on the matter. |
I've had a look at what some other DAWs do and it turns out FL also uses the |
As there seem to be no action in WeAreROLI/JUCE#401 maybe it's best to fix this on our side as you've suggested? |
Sure. Although I think this should go into master, not stable-1.2, since Lukas-W wants #2826 in master which seems a more minor change than this. |
I agree. |
Milestoned for 1.3. In the meantime please use "No embedding" for the "Plugin embedding" option as a workaround. |
This issue has been cleaned from off topic posts |
Hi I'm also impacted jpcima/ADLplug#53 and so is JuceOPLVSTi. What's the advice for a plugin developer to work around this? |
@jpcima This bug should manifest itself in any host which bridges plugins and embeds plugin UIs in the host's main window, unless it specifically works around the issue (e.g. FL Studio). Another such host is VSTHost (when the plugin architecture differs from that of VSTHost), and there may be more that I'm not aware of. We intend to add a workaround, but it won't land in our next stable release. For a workaround to use in your plugin, you can patch |
The following patch works on Windows, but I haven't tested it on Linux yet. PhysSong reports that the bug doesn't occur on Linux, so it may not need to be enabled there anyway. I haven't found any undesirable side effects yet, unlike the approach of not setting diff --git a/plugins/vst_base/RemoteVstPlugin.cpp b/plugins/vst_base/RemoteVstPlugin.cpp
index 942c9e960..121f04b00 100644
--- a/plugins/vst_base/RemoteVstPlugin.cpp
+++ b/plugins/vst_base/RemoteVstPlugin.cpp
@@ -282,6 +282,8 @@ public:
// has to be called as soon as input- or output-count changes
int updateInOutCount();
+ HWND pluginHwnd() const { return m_window; }
+
inline void lockShm()
{
m_shmLock.lock();
@@ -435,6 +437,55 @@ private:
} ;
+// Workaround for https://github.com/juce-framework/JUCE/issues/401.
+// JUCE tests if it has focus by comparing its process ID to that of the process
+// to which the currently active window belongs. This is incorrect for bridged,
+// embedded plugins; when the host's window is active, the bridge's process ID
+// won't the match the active window's process ID, i.e. that of the host.
+// We work around this by patching `GetWindowThreadProcessId` to return the
+// bridge's process ID when given a handle to the host's top level window.
+DWORD WINAPI GetWindowThreadProcessIdPatch(HWND hWnd, LPDWORD lpdwProcessId)
+{
+ // Is `hWnd` the top level window that contains the plugin?
+ if (GetAncestor(__plugin->pluginHwnd(), GA_ROOT) == hWnd)
+ {
+ // If so, pretend that it belongs to the current process.
+ if(lpdwProcessId) { *lpdwProcessId = GetCurrentProcessId(); }
+ return GetCurrentThreadId();
+ }
+ // Otherwise use the default behavior.
+ return GetWindowThreadProcessId(hWnd, lpdwProcessId);
+}
+
+// Walk the import table of the plugin and replace `GetWindowThreadProcessId`
+// with our own version.
+void injectJuceMenuWorkaround(HINSTANCE pluginLibrary)
+{
+ const auto base = reinterpret_cast<std::intptr_t>(pluginLibrary);
+ const auto dosHeader = reinterpret_cast<PIMAGE_DOS_HEADER>(base);
+ const auto ntHeaders = reinterpret_cast<PIMAGE_NT_HEADERS>(base + dosHeader->e_lfanew);
+ const auto& importDir = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
+ for(auto importDesc = reinterpret_cast<PIMAGE_IMPORT_DESCRIPTOR>(base + importDir.VirtualAddress);
+ importDesc->Name != 0; ++importDesc)
+ {
+ const auto libraryName = reinterpret_cast<LPCSTR>(base + importDesc->Name);
+ if(stricmp(libraryName, "user32.dll") != 0) { continue; }
+ for(auto originalFirstThunk = reinterpret_cast<PIMAGE_THUNK_DATA>(base + importDesc->OriginalFirstThunk),
+ firstThunk = reinterpret_cast<PIMAGE_THUNK_DATA>(base + importDesc->FirstThunk);
+ originalFirstThunk->u1.AddressOfData != 0; ++originalFirstThunk, ++firstThunk)
+ {
+ const auto functionName = reinterpret_cast<PIMAGE_IMPORT_BY_NAME>(
+ base + originalFirstThunk->u1.AddressOfData);
+ if(strcmp(functionName->Name, "GetWindowThreadProcessId") != 0) { continue; }
+ DWORD oldProtect = 0;
+ using FuncType = decltype(firstThunk->u1.Function);
+ VirtualProtect(&firstThunk->u1.Function, sizeof(FuncType), PAGE_READWRITE, &oldProtect);
+ firstThunk->u1.Function = reinterpret_cast<FuncType>(&GetWindowThreadProcessIdPatch);
+ VirtualProtect(&firstThunk->u1.Function, sizeof(FuncType), oldProtect, &oldProtect);
+ return;
+ }
+ }
+}
#ifdef SYNC_WITH_SHM_FIFO
@@ -882,6 +933,8 @@ bool RemoteVstPlugin::load( const std::string & _plugin_file )
return false;
}
+ injectJuceMenuWorkaround(m_libInst);
+
m_plugin = mainEntry( hostCallback );
if( m_plugin == NULL )
{ |
I tested Sylenth1 some more. I was able to determine that it uses JUCE, and that its menu bug is caused by the same buggy JUCE code. However, this new patch does not work with it due to the obfuscation used, which makes it impossible to overwrite its import address table (when unpacked in memory, I'm not sure it even meaningfully has one). There are other approaches, such as overwriting the code of |
JUCE have fixed the bug on their end: juce-framework/JUCE#401 (comment). Do we still want to try and work around this, or should we assume most plugins will update and embedding can be turned off as a workaround for the few that are still broken? |
No. Helm will probably not update because it has been left by the developer for other projects. I don't know about other projects but if there is a possibility to have a workaround for lmms-1.3 I think it could still be worth it if it's not too much work involved. Even a simple fix that would not work with some plugins as the case with Sylenth1 above. I have unfortunately not had time to test your patch. |
Turn off embedding of VST's in the settings |
This should be fixed in Dexed which used juce 7.0.1 (juce 6.1.0 introduced the fixe). |
I'm leaning toward closing this as Last Helm release was seven years ago and it's maybe better that someone fork and update it instead of us providing workarounds. The other projects we know the JUCE version they build against seem to have updated if they're still active. |
Seems reasonable to me. I hope to do more work on embedding bugs in the future, and it's possible this may be fixed as a side effect, but it doesn't need to be a target. |
I've noticed that when trying to use Helm as a VST under Windows, running on LMMS v1.2.0rc4, attempting to open a menu in Helm does not work.
Expected Behavior: Clicking on Distortion menu in the Helm synth opens the Distortion menu. Right clicking opens the right click menu.
Actual behavior: These menus open, but then close immediately, too quickly to interact with anything in it. Very rarely, it will work, which I notice causes Helm's animations to stop.
I believe this is an issue with LMMS (specifically vestige?) as the standalone Helm application does not have this issue on Windows, nor does this issue occur in other DAWs.
The text was updated successfully, but these errors were encountered: