I’m developing a Windows desktop JUCE software which loads VST3 plugins.
When I create a window and add VST3 plugins GUI into it, keyboard listener of my application stops receiving keyboard messages. I have to click with mouse any other part of the screen except the VST3 window to get my software to respond to keyboard presses again.
I have put the following lines into my Window class which shows the VST3 GUI:
Do you see the same behaviour in the JUCE examples, e.g. in the AudioPluginHost? If so, this may be a bug in JUCE. Otherwise, I’d recommend checking the AudioPluginHost source to see how it differs from your code.
Hard to say for sure. In my software I’m reading all sorts of keys when VST3 plugin windows are open and in focus on screen, so this is easy to test.
But in AudioPluginHost I could only think of pressing Ctrl-S which normally opens up the save dialog window. When any plugin window is in focus (be it made with JUCE or not), that save dialog window does not pop on screen, even though that would IMHO be expected behavior. I don’t know what would be a better test to try in AudioPluginHost, though.
Also when I try to load Reveal Sound Spire VST3 plugin, AudioPluginHost crashes. I first thought this was just happening in my own JUCE software, but AudioPluginHost also seems to have trouble with that plugin.
Bug fixes would be welcome in the near future as this limits the compatibility of VST3 plugins in my software
It looks like there is a bug in the AudioPluginHost, where key presses don’t reach the application command target when a plugin window has the keyboard focus. The fix looks simple: we can add the application command target as a KeyListener of each PluginWindow. After doing so, the keyboard shortcuts work as expected when a plugin window has focus (ctrl+a opens the audio settings, ctrl+p opens the plugin list window, etc.). We’ll likely publish this change shortly. Perhaps you can use a similar approach in your program.
Note that this pattern of keyboard handling requires the plugin to correctly report whether or not each key press is consumed by the plugin editor. In my testing, FabFilter Pro-Q 4 seemed to do this correctly, while Xfer OTT 1.37 did not.
I can reproduce this behaviour. Unfortunately, the crash seems to happen somewhere inside the plugin itself during the call to setActive(true) on the VST3. On the console, I see that the error code 0xc0000374 is logged during this call, which indicates heap corruption, so I think the plugin itself is buggy. I recommend filing a bug report with Reveal Sound.
Did you try the keyboard fix also with plugins which were not made with JUCE? For example Native Instruments Battery does presented issues, while Valhalla plugins worked fine in my software.
I tried the latest commit of develop branch and the problem persists at least with plugins not made with JUCE. I tried opening Native Instruments Battery 4 and then pressing Ctrl+S and nothing happened while the Battery window was in focus. Only when I clicked with mouse the AudioPluginHost window, then I was able to get the save dialog window on screen by pressing Ctrl+S.
How to get it working on plugins which don’t report the consumed keys? Ableton Live and other DAWs seem to be able to read keyboard correctly even with those plugins which cause trouble in AudioPluginHost and my software.
As long as the problem persists, simply having many of the common plugin GUIs open on screen + tweaking their knobs results in inability to easily save project file with Ctrl+S, or to start/stop playback by pressing Spacebar, etc. That’s a big issue in my book. I hope there is a fix coming soon. Or even a workaround of some sort.
One work around I think not 100% using a midi keyboard component should capture keypresses .
I had this issue a long time ago trying to make it act like Ableton style keypress to midi notes . I never resolved it, but it definitely does work with native windows . Maybe try to wrap just that portion that displays the plugins in native windows for each platform .
You should definitely disable that when it’s loaded in a daw as the keystrokes may collide .
So instead of using JUCE’s own windows, I would call Windows / macOS own native methods to create the window and read the keyboard messages that go through them?
Does all that mean that I need to create a new thread for my own native window and have my own message thread in there? Then I need to interact with JUCE’s own message thread whatever way I see fit?
I’m not sure juce is internally using native windows but something about how the events are handled makes the plugin windows collide key presses or lock into GUI knobs.
Why you have to click outside the plugin and they start working again
The core of the issue is that JUCE handles the VST3 plugin loading and then I have access to the VST3 plugin’s juce::Component. I would need to somehow add that juce::Component into a native Windows window. So I assume I would somehow need to create all the Component handling around it too, as Components don’t give out HWNDs? Is this a massive and futile undertaking or is there actually a realistic way of doing this?
Here’s the code so far:
LRESULT CALLBACK MyWndProc(
HWND hwnd, // handle to window
UINT uMsg, // message identifier
WPARAM wParam, // first message parameter
LPARAM lParam) // second message parameter
{
switch (uMsg)
{
case WM_CREATE:
// Initialize the window.
return 0;
case WM_PAINT:
// Paint the window's client area.
return 0;
case WM_SIZE:
// Set the size and position of the window.
return 0;
case WM_DESTROY:
// Clean up window-specific data objects.
return 0;
//
// Process other messages.
//
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 0;
}
void DEBUG_Create_Native_Test_Window(void* __stdcall _hinstance)
{
// Register the window class.
const LPCSTR CLASS_NAME("Sample Window Class");
WNDCLASS wc = { };
HINSTANCE hInstance = (HINSTANCE) _hinstance;
wc.lpfnWndProc = MyWndProc;
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME;
RegisterClass(&wc);
// Create the window.
HWND hwnd = CreateWindowEx(
0, // Optional window styles.
CLASS_NAME, // Window class
"Learn to Program Windows", // Window text
WS_OVERLAPPEDWINDOW, // Window style
// Size and position
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, // Parent window
NULL, // Menu
hInstance, // Instance handle
NULL // Additional application data
);
if (hwnd == NULL)
return;
// NOW WHAT? HOW TO ADD juce::Component INTO THIS WINDOW?
}
The addToDesktop seems to work. At least the following short code adds a juce::Label successfully into the native window using HWND as the void* pointer:
I also changed the native windows creation to the following:
HWND hwnd = CreateWindowEx(
0, // Optional window styles.
CLASS_NAME, // Window class
"Learn to Program Windows", // Window text
WS_OVERLAPPEDWINDOW, // Window style
// Size and position
200, 100, 500, 300,
(HWND) parent_hwnd,// Parent window
NULL, // Menu
hInstance, // Instance handle
NULL // Additional application data
);
Namely, I gave that method the MainComponent HWND as a parameter for the parent window, which I got using the Component::getWindowHandle() as you suggested. Now the window stays nicely on top of the JUCE application window.
The next stop is to figure out how to handle the messages / screen updates so that the main JUCE application also updates its own visuals while this new native window is in focus / alive. This seems to happen even when I click the main JUCE app window. I.e. the application window doesn’t update itself unless I am dragging the native window around. Why does this happen? Does this mean that all the messages automatically go to the native window, or is there some other cause for this behavior?