VST3 Keystroke handling

Unfortunately, all the host application I’ve tested seem to swallow keystrokes when the command key is pressed on Mac. Circumventing this is pretty easy by implementing IPlugView::onKeyDown along the lines of the code below (“keydefines.h” from the VST3 SDK must be included for it to compile):

        tresult onKeyDown (char16 key, int16 keyCode, int16 modifiers) override
        {
            auto peer = component->getPeer();

            if ((peer != nullptr) && (component->hasKeyboardFocus (true)))
            {
                int juceModifierFlags = 0;
                if (modifiers & KeyModifier::kShiftKey)
                    juceModifierFlags |= ModifierKeys::shiftModifier;
                if (modifiers & KeyModifier::kAlternateKey)
                    juceModifierFlags |= ModifierKeys::altModifier;
                if (modifiers & KeyModifier::kControlKey)
                    juceModifierFlags |= ModifierKeys::ctrlModifier;
                if (modifiers & KeyModifier::kCommandKey)
                    juceModifierFlags |= ModifierKeys::commandModifier;

                int juceKeyCode = 0;
                switch (keyCode)
                {
                    case 0: // No virtual key
                        juceKeyCode = key;
                        break;
                    case VirtualKeyCodes::KEY_BACK:
                        juceKeyCode = KeyPress::backspaceKey;
                        break;
                    case VirtualKeyCodes::KEY_TAB:
                        juceKeyCode = KeyPress::tabKey;
                        break;
                    case VirtualKeyCodes::KEY_RETURN:
                        juceKeyCode = KeyPress::returnKey;
                        break;
                    case VirtualKeyCodes::KEY_ESCAPE:
                        juceKeyCode = KeyPress::escapeKey;
                        break;
                    case VirtualKeyCodes::KEY_SPACE:
                        juceKeyCode = KeyPress::spaceKey;
                        break;
                    case VirtualKeyCodes::KEY_HOME:
                        juceKeyCode = KeyPress::homeKey;
                        break;
                    case VirtualKeyCodes::KEY_END:
                        juceKeyCode = KeyPress::endKey;
                        break;
                    case VirtualKeyCodes::KEY_LEFT:
                        juceKeyCode = KeyPress::leftKey;
                        break;
                    case VirtualKeyCodes::KEY_UP:
                        juceKeyCode = KeyPress::upKey;
                        break;
                    case VirtualKeyCodes::KEY_RIGHT:
                        juceKeyCode = KeyPress::rightKey;
                        break;
                    case VirtualKeyCodes::KEY_DOWN:
                        juceKeyCode = KeyPress::downKey;
                        break;
                    case VirtualKeyCodes::KEY_PAGEUP:
                        juceKeyCode = KeyPress::pageUpKey;
                        break;
                    case VirtualKeyCodes::KEY_PAGEDOWN:
                        juceKeyCode = KeyPress::pageDownKey;
                        break;
                    case VirtualKeyCodes::KEY_ENTER:
                        juceKeyCode = KeyPress::returnKey;
                        break;
                    case VirtualKeyCodes::KEY_INSERT:
                        juceKeyCode = KeyPress::insertKey;
                        break;
                    case VirtualKeyCodes::KEY_DELETE:
                        juceKeyCode = KeyPress::deleteKey;
                        break;
                    case VirtualKeyCodes::KEY_NUMPAD0:
                        juceKeyCode = KeyPress::numberPad0;
                        break;
                    case VirtualKeyCodes::KEY_NUMPAD1:
                        juceKeyCode = KeyPress::numberPad1;
                        break;
                    case VirtualKeyCodes::KEY_NUMPAD2:
                        juceKeyCode = KeyPress::numberPad2;
                        break;
                    case VirtualKeyCodes::KEY_NUMPAD3:
                        juceKeyCode = KeyPress::numberPad3;
                        break;
                    case VirtualKeyCodes::KEY_NUMPAD4:
                        juceKeyCode = KeyPress::numberPad4;
                        break;
                    case VirtualKeyCodes::KEY_NUMPAD5:
                        juceKeyCode = KeyPress::numberPad5;
                        break;
                    case VirtualKeyCodes::KEY_NUMPAD6:
                        juceKeyCode = KeyPress::numberPad6;
                        break;
                    case VirtualKeyCodes::KEY_NUMPAD7:
                        juceKeyCode = KeyPress::numberPad7;
                        break;
                    case VirtualKeyCodes::KEY_NUMPAD8:
                        juceKeyCode = KeyPress::numberPad8;
                        break;
                    case VirtualKeyCodes::KEY_NUMPAD9:
                        juceKeyCode = KeyPress::numberPad9;
                        break;
                    case VirtualKeyCodes::KEY_MULTIPLY:
                        juceKeyCode = KeyPress::numberPadMultiply;
                        break;
                    case VirtualKeyCodes::KEY_ADD:
                        juceKeyCode = KeyPress::numberPadAdd;
                        break;
                    case VirtualKeyCodes::KEY_SEPARATOR:
                        juceKeyCode = KeyPress::numberPadSeparator;
                        break;
                    case VirtualKeyCodes::KEY_SUBTRACT:
                        juceKeyCode = KeyPress::numberPadSubtract;
                        break;
                    case VirtualKeyCodes::KEY_DECIMAL:
                        juceKeyCode = KeyPress::numberPadDecimalPoint;
                        break;
                    case VirtualKeyCodes::KEY_DIVIDE:
                        juceKeyCode = KeyPress::numberPadDivide;
                        break;
                    case VirtualKeyCodes::KEY_F1:
                        juceKeyCode = KeyPress::F1Key;
                        break;
                    case VirtualKeyCodes::KEY_F2:
                        juceKeyCode = KeyPress::F2Key;
                        break;
                    case VirtualKeyCodes::KEY_F3:
                        juceKeyCode = KeyPress::F3Key;
                        break;
                    case VirtualKeyCodes::KEY_F4:
                        juceKeyCode = KeyPress::F4Key;
                        break;
                    case VirtualKeyCodes::KEY_F5:
                        juceKeyCode = KeyPress::F5Key;
                        break;
                    case VirtualKeyCodes::KEY_F6:
                        juceKeyCode = KeyPress::F6Key;
                        break;
                    case VirtualKeyCodes::KEY_F7:
                        juceKeyCode = KeyPress::F7Key;
                        break;
                    case VirtualKeyCodes::KEY_F8:
                        juceKeyCode = KeyPress::F8Key;
                        break;
                    case VirtualKeyCodes::KEY_F9:
                        juceKeyCode = KeyPress::F9Key;
                        break;
                    case VirtualKeyCodes::KEY_F10:
                        juceKeyCode = KeyPress::F10Key;
                        break;
                    case VirtualKeyCodes::KEY_F11:
                        juceKeyCode = KeyPress::F11Key;
                        break;
                    case VirtualKeyCodes::KEY_F12:
                        juceKeyCode = KeyPress::F12Key;
                        break;
                    default:
                        return kResultFalse;
                }
                return peer->handleKeyPress (KeyPress (juceKeyCode, juceModifierFlags, key)) ? kResultTrue : kResultFalse;
            }
            return kResultFalse;
        }

Is this something you would consider to add to the official JUCE?

Could you provide an example of keystroke being swallowed in a particular host?

I’ve tried a few hosts but it’s not clear when onKeyDown would make a difference.

Thanks for looking into this! All keystrokes with the cmd-modifier are swallowed by Cubase, Nuendo and REAPER here.

Doesn’t happen to me, in Cubase, at least. We capture cmd+Z for Undo in our plugin’s graph, and it works fine.

Hmm, that’s strange. I should have mentioned that this is using the JUCE_ARA fork, so it might be related to that. However, I saw another similar post regarding VST3 keystroke (Cmd+Z is intercepted by plugin host), so I assumed that it is a general problem.

I also use the JUCE_ARA fork, so it’s not that. Maybe you need to check “Plugin editors always on top” in the Plug-Ins section of the Cubase Preferences? Or maybe you need to give it focus first, such as by clicking somewhere inside your plugin window? I know that if I click on a Cubase window, then keystrokes won’t be seen in my plugin until I do so. But it’s not eating my Cmd+Z (or other Cmd+whatever) keystrokes.

Since the plug-in is an ARA plug-in, it appears in the bottom window pane in Cubase. Cubase indicates focus with a thicker frame and only keystrokes with the Cmd modifier are affected. I did fix this in our own JUCE fork by overriding IPlugView::onKeyDown and responding only to keystrokes with the Cmd modifier. Other keystrokes are handled through the JUCE keyboard handler.

I gave things a try in REAPER, but I couldn’t find any examples where I could recover a swallowed key.

Cmd-p wasn’t swallowed and things like Cmd-a couldn’t be un-swallowed.

I’ll have a look at Cubase.

1 Like

Ah, it’s an embedded ARA instance. That may be the culprit in Cubase. I am using that JUCE_ARA SDK, but not currently allowing our plug-in to instantiate in anything but Studio One. (Haven’t yet figured out how our plug-in’s current design can possibly work with multiple Processor/Editor instances.) As a regular plug-in in Cubase, it works as expected. Haven’t tried Reaper.

Thanks!

I see, it’s probably related to being an ARA instance, then. It took me a while to understand the ARA concepts, but it is really nice once you get the hang of it. It opens up so many interesting use cases.

Is there an easier way to reproduce swallowed keystrokes that doesn’t involve JUCE_ARA? Otherwise, could you provide a project that demonstrates the behaviour?

I still have this problem even with v.7.0.4 of JUCE.
Only when launching Cubase on macOS with the ARA extension, keystrokes including the Cmd key are taken by the Cubase side.
This does not occur on Windows.
The problem does not occur with normal plug-in startup on macOS either.

Is there any way to solve this problem, preferably without modifying the JUCE code?
Or will JUCE fix this in the near future?

We have the same problem now that we’re allowing our plugin to run in Cubase as ARA. Command+Z no longer goes to our plugin. I just got that bug report last week, and passed it on to Steinberg. Works fine in a non-ARA window, but not with an ARA instance.

1 Like

Thank you for sharing the information.
Has there been any further updates from Steinberg?