Skip to content

Implement IPlugView::onKeyDown with Editor::on_key_down hook#267

Open
paravozz wants to merge 1 commit intorobbert-vdh:masterfrom
paravozz:fix/vst3-on-key-down
Open

Implement IPlugView::onKeyDown with Editor::on_key_down hook#267
paravozz wants to merge 1 commit intorobbert-vdh:masterfrom
paravozz:fix/vst3-on-key-down

Conversation

@paravozz
Copy link
Copy Markdown

Summary

Implements IPlugView::onKeyDown in the VST3 wrapper, forwarding to a new Editor::on_key_down(Option<char>) -> bool trait method. The editor returns true when it consumes the key (e.g. a text input has focus); the wrapper then returns kResultOk and the host skips its own accelerator. Otherwise the wrapper returns kResultFalse and the host processes the key normally.

Motivation

On macOS REAPER, pressing space while a nih-plug plugin's text input has focus toggles REAPER's transport instead of inserting a space. This is because REAPER calls IPlugView::onKeyDown first for keys bound to its accelerator table; if the plugin returns kResultTrue REAPER skips the accelerator, if it returns kNotImplemented (nih-plug's current default) REAPER runs it.

This is a long-standing issue in JUCE for the same reason — JUCE's VST3 wrapper also returns kNotImplemented. Commercial plugins that work in REAPER either use VSTGUI (which implements onKeyDown) or ship patched JUCE forks.

I verified the fix shape end-to-end with a probe JUCE plugin (applied the same override to JuceVST3Editor, observed space typed into a juce::TextEditor, no transport toggle, letters unaffected) before porting to nih-plug.

Design notes

  • New trait method has a default impl returning false so existing editors remain source-compatible.
  • Only keys with key_code != 0 are forwarded. VST3 populates key_code with a virtual key code for non-character keys (space → KEY_SPACE=7, return → KEY_RETURN=2, etc.) and leaves it zero for plain character keys. Character keys already reach the native view's keyDown: and are handled via NSTextInputContext — an earlier iteration that returned kResultOk on key_code == 0 dropped all letter input.
  • Returns kResultOk on consume. vst3-sys re-exports only kResultOk; the VST3 spec treats kResultTrue and kResultOk as identical.
  • Single-char signature is sufficient for the common case. The trait could later gain virtual-key-code + modifiers for editors that want to handle arrow-key navigation, but Option<char> is enough to close the space-bar gap.

Test

  • Built a vizia-plug-backed plugin with a companion patch wiring Editor::on_key_down into the focused textbox.
  • Loaded in REAPER 7.x on macOS 15 (Apple Silicon).
  • Space with textbox focused → inserted, transport unchanged.
  • Space with no textbox focus → transport toggles as before.
  • Letter input → unchanged with and without textbox focus.
  • Escape / arrows with no text focus → still reach the host.

Companion work

A vizia-plug implementation of Editor::on_key_down lives at paravozz/vizia-plug#fix/vst3-on-key-down. I'll open it as a PR against vizia/vizia-plug once this trait method lands so it has a real API to implement against.

Previously the VST3 wrapper's `IPlugView::onKeyDown` returned
`kNotImplemented` unconditionally. In REAPER on macOS this causes the
host to intercept keys it has bound as accelerators — most visibly the
space bar toggles transport instead of inserting a space in focused
text inputs, even when the plugin editor has keyboard focus.

Add a new `Editor::on_key_down(Option<char>) -> bool` trait method
(default impl returns `false`, backwards compatible) and call it from
the VST3 wrapper for keys with a non-zero VST3 virtual key code. Keys
without a virtual key code (letters, digits, symbols) continue to flow
through AppKit's normal `keyDown:` path so NSTextInputContext handles
them — consuming them here would break letter input.

This matches the mechanism VSTGUI already implements (and that Serum
relies on), and the fix shape documented on the JUCE forum for the
equivalent JUCE bug.
@paravozz
Copy link
Copy Markdown
Author

FYI, also filed on the new maintained fork at BillyDM/nih-plug#9 (Codeberg), since vizia/vizia-plug switched its dependency there today. Keeping this one open too, happy for it to land wherever works.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant