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?