VST plugin isn't getting keystrokes

Hi!

Sounds good to me, now where is that PM link… ah, here it is…!

Pete

This is my first post here so hello to all :slight_smile:

I’ve read this topic and also tried to do something to deal with this problem.
After reading that keyboard focus works good in Text Editors placed in Alert Window I tried to fix this issue on my own needs in similar way.

Here is the code of sample class named ModalTextEditor. It has been written very quickly so please don’t blame on me :wink:

ModalTextEditor.h

#ifndef __ModalTextEditor_h__
#define __ModalTextEditor_h__

class ModalTextEditor;

/*------------------------------------------------------------------------------
 * ModalTextEditorListener class
 **/

class JUCE_API  ModalTextEditorListener
{
public:
  /** Destructor. */
  virtual ~ModalTextEditorListener()  {}

  /** Called when the user changed and accepted the new text. */
  virtual void modalTextEditorTextChanged (ModalTextEditor& editor) = 0;
};

/*------------------------------------------------------------------------------
 * ModalTextEditor class
 **/

class ModalTextEditor : public TextEditor, 
                        public TextEditorListener, 
                        public AsyncUpdater
{
public:
  ModalTextEditor (const String &componentName);
  ~ModalTextEditor ();
  
  void addModalTextEditorListener (ModalTextEditorListener* const newListener) throw();
  void removeModalTextEditorListener (ModalTextEditorListener* const listenerToRemove) throw();

  void setFocusedTextColour (const Colour& colour);
  void setTextColour (const Colour& colour);
  
protected:
  void updateTextColour ();
  
  virtual void focusGained (FocusChangeType cause);
  virtual void focusLost (FocusChangeType cause);

  /* TextEditorListener methods */ 
  void textEditorTextChanged (TextEditor& editor) {};
  void textEditorReturnKeyPressed (TextEditor& editor);
  void textEditorEscapeKeyPressed (TextEditor& editor);
  void textEditorFocusLost (TextEditor& editor) {}

  virtual void handleAsyncUpdate();

private:
  Colour focusedTextColour;
  Colour textColour;
  VoidArray listeners;
  Component *oldParent;
  Rectangle oldBounds;
  String oldContent;
};

#endif

and the ModalTextEditor.cpp

#include "ModalTextEditor.h"

/*------------------------------------------------------------------------------
 * ModalTextEditor class
 **/

ModalTextEditor::ModalTextEditor (const String& componentName)
  : TextEditor (componentName), 
    oldParent (0)
{
  setPopupMenuEnabled (false);
  setSelectAllWhenFocused (true);
  setOpaque (true);
  addListener (this);
}

//------------------------------------------------------------------------------
ModalTextEditor::~ModalTextEditor ()
{
}

//------------------------------------------------------------------------------
void ModalTextEditor::addModalTextEditorListener (ModalTextEditorListener* const newListener) throw()
{
  jassert (newListener != 0)

  if (newListener != 0)
    listeners.add (newListener);
}

//------------------------------------------------------------------------------
void ModalTextEditor::removeModalTextEditorListener (ModalTextEditorListener* const listenerToRemove) throw()
{
  listeners.removeValue (listenerToRemove);
}

//------------------------------------------------------------------------------
void ModalTextEditor::setFocusedTextColour (const Colour& colour)
{
  focusedTextColour = colour;
  updateTextColour();
}

//------------------------------------------------------------------------------
void ModalTextEditor::setTextColour (const Colour& colour)
{
  textColour = colour;
  updateTextColour();
}

//------------------------------------------------------------------------------
void ModalTextEditor::updateTextColour ()
{
  setColour (TextEditor::textColourId, isOnDesktop() ? focusedTextColour : textColour);
  applyFontToAllText (getFont());
}

//------------------------------------------------------------------------------
void ModalTextEditor::focusGained (FocusChangeType cause)
{
  if (!isOnDesktop())
  {
    oldBounds = getBounds();
    oldParent = getParentComponent();
    oldContent = getText();
    addToDesktop (ComponentPeer::windowIsTemporary);
    updateTextColour();
    grabKeyboardFocus();
  }

  repaint();
}

//------------------------------------------------------------------------------
void ModalTextEditor::focusLost (FocusChangeType cause)
{
  if (isOnDesktop() && oldParent != 0)
  {
    oldParent->addChildComponent (this);
    updateTextColour();
    setBounds (oldBounds);
    moveKeyboardFocusToSibling (true);
    setHighlightedRegion (0, 0);
  }

  if (oldContent != getText())
    triggerAsyncUpdate();

  repaint ();
}

//------------------------------------------------------------------------------
void ModalTextEditor::textEditorReturnKeyPressed (TextEditor& editor)
{
  focusLost (focusChangedDirectly);
}

//------------------------------------------------------------------------------
void ModalTextEditor::textEditorEscapeKeyPressed (TextEditor& editor)
{
  setText (oldContent);
  focusLost (focusChangedDirectly);
}

//------------------------------------------------------------------------------
void ModalTextEditor::handleAsyncUpdate()
{
  for (int i = listeners.size(); --i >= 0;)
  {
    ((ModalTextEditorListener*) listeners.getUnchecked (i))->modalTextEditorTextChanged (*this);
    i = jmin (i, listeners.size());
  }
}

As you can see it’s simply derived from TextEditor. The most important part of code is when it gains the focus it simply switches to become a desktop’s component. When it lost the focus it returns to the ‘old’ parent component.

The rest of the code is just fitted for my needs … eg. I wanted only to know when the text is changed - it happens when Return is pressed or the focus is lost - and cancel any changes when Esc is pressed. This is and few other minor changes are not very important for the the sense of this topic.

Anyway as I can see it works good in Live, Cubase and some other hosts.

I’m not sure if anything has been fixed in new Live 7 (will check it tomorrow) and of course it would be the best if host deals properly with the keyboard messages but I doubt that we can rely on it (at least with Cubase I don’t believe in it ;))

I wondered why the similar class in VSTGUI works good and after checking the sources I noticed that it runs in very common way. The separate window is created with new proc window and while it’s modal every message is caught.

Still it would be good if we can to deal with this for entire UI because the same problems occurs eg. with MidiKeyobard component and probably all which needs keyboard messages to work.

And the last thing is that as far as I know the isKeyboardRequired() method is deprecated in VST SDK when we use strict 2.4 version. So no matter how we set JucePlugin_EditorRequiresKeyboardFocus flag in the plugin characteristics header file I guess.

Sorry for that long post and cheers…

wow, and wat a nice first post it is!
thanks Bld! Looks like a very useful solution

Nice one, thanks bld!

Unfortunately Live 7 on PC has nothing changed in this matter :frowning:

Cheers,
Przemek

You’d have thought that the various tool manufacturers would have tested-out keyboard handling for hosted plug-ins, wouldn’t you? :frowning:

yeah it’s apparently not high on their priority list

if we could only turn plugin-keyboard-friendliness into a feature checkmark on their commercial datasheets, then there would be a competitive argument to do something about it :slight_smile:

i would be interested for sure how other plugins work around this problem - I doubt we are we really the first or only ones bugged by it…

We’re definitely not the first, but maybe there are not so many who use Juce TextEditor in the same way we do, or they solved it somehow and didn’t share the solution :wink:

From my experiences with getting keys in hosted plugin it has been always a bit problematic.

In the older VST SDKs /* v.2.1 I guess */ there were some methods like onKeyDown() and onKeyUp() in the AEffEditor class. But most hosts nerver called them :smiley:

It’s understandable because if the plugin can do all we want on the editor’s side (I mean that there are no strict restrictions on how the UI editor have to work) the host may even not be able to receive keys eg. when editor run modal dialog window.

The TextEdit class from VSTGUI deal with it by creating modal window receiving all keyboard messages properly. /* The window was created when user clicked on the control and destroyed when it lost focus - I tried to recreate similar behavior in ModalTextEditor with pure Juce code */ Anyway if the same kind of solution were applied in other situations the plugin could do all it wanted with the keys I believe. I’m not sure about it because all I always wanted was working text editor :slight_smile:

I’m not Juce expert (work with it for less than 2 weeks) but I hope that this kind of solution could be adopted for whole editor window. I mean that some modal (borderless and even transparent) window could be created on demand, covering all editor’s area. All mouse and keyboard messages which this window receives could be ‘rerouted’ to proper editor’s component below this modal window. In this way not only TextEditor could receive keys but also other key-dependent components without deriving new class from them.
If we knew whether any component below have such kind of keyboard focus then we can decide if the keys should go to this component or should be send back that host could receive them and response in its own way.

I would like to know if it’s feasible solution :wink:

Would be great without any ‘native code hacks’, deriving own components to deal with the keystroke problem or waiting if and when some PC hosts will work more plugin-friendly :wink:

Cheers,
Przemek

Hi!

With noatikl, we’ve suggested for now that users, if having problem with text entry in the noatikl VSTi under Sonar, to copy/paste into a separate text editor and copy/paste their text into the text editor fields in the noatikl plug-in when done! At least it works this way, I guess… but not a pretty solution! :slight_smile:

:roll:

Pete

fyi: Ableton Live now passes keystrokes to plugins (!) :

from the release notes of v7.02:

Haven’t had time yet to test it though…

for more info see: http://www.ableton.com/forum/viewtopic.php?t=84434

Cubase SX3 for example DOES call these functions and they are the only way to go, since the hosts will not pass the keys as WM_KEYDOWN’s.
It’s true that some hosts do implement these functions and others not. It’s a big problem on Windows. The only workaround is using modal windows.

a friend of mine just found an open source VST that does get keystrokes.
i have “dig” into it and find out that the code programmer is using
the “vstcontrols.h” classes . in that way he gets the keyboard focus text label.

does anyone have an idea how to “cut” this code and using it in a juce plugin ?

the code:
http://www.niallmoody.com/ndcplugs/fragmental.htm

Here’s the source CLICKABLELABEL.H for a label that can be edited in all hosts. You’ll have to tell the ClickableLabel a ClickableLabelListener with setListener(). In the listener’s clickableLabelMouseDoubleClick() I just call the ClickableLabel’s edit() function and there you go… EDIT: THIS DOES NOT FIX THE PROBLEM IN LIVE 6, SORRY GUYS.

But this still does not fix the problem of VST’s not getting keystrokes. One would really have to fix the VstWrapper code to make it react to effKeyDown or effKeyUp in the dispatcher. EDIT: EVEN THAT WOULD NOT HELP IN LIVE 6, BECAUSE LIVE DOES NOT PASS EFFKEYDOWN/EFFKEYUP.

[code]#pragma once
#include “juce.h”

class ClickableLabel;
class ClickableLabelListener
{
public:
virtual void clickableLabelMouseDown(ClickableLabel *label, const MouseEvent &e)=0;
virtual void clickableLabelMouseDoubleClick(ClickableLabel *label, const MouseEvent &e)=0;
};

class ClickableLabel:public Component, public TextEditorListener
{
public:
ClickableLabel()
{
id=0;
listener=0;
setRepaintsOnMouseActivity(true);
}

void setText(const String &text_)
{
	text=text_;
	repaint();
}

const String &getText()
{
	return text;
}

void setListener(ClickableLabelListener *listener_)
{
	listener=listener_;
}

void setId(int id_)
{
	id=id_;
}

int getId()
{
	return id;
}

// pop ups a modal text editor
void edit()
{
	TextEditor *ed=new TextEditor();
	if (ed==0) return;
            ed->setText(getText(),false);
	ed->setBounds(0, 0, getWidth(), getHeight());
	addAndMakeVisible(ed);
	ed->addListener(this);
	ed->runModalLoop();
	setText(ed->getText());
	deleteAndZero(ed);
}

private:
String text;
int id;
ClickableLabelListener *listener;

void paint(Graphics &g)
{
	if (!isMouseOver()) g.fillAll(Colours::black);
	else g.fillAll(Colours::orange);

	g.setColour(Colours::white);
	g.drawText(text, 0, 0, getWidth(), getHeight(), Justification::centred, false);
}

void mouseDown(const MouseEvent &e)
{
	if (listener!=0)
		listener->clickableLabelMouseDown(this, e);
}

void mouseDoubleClick(const MouseEvent &e)
{
	if (listener!=0)
		listener->clickableLabelMouseDoubleClick(this, e);
}

void  textEditorTextChanged (TextEditor &editor)
{

}

void  textEditorReturnKeyPressed (TextEditor &editor)
{
	editor.exitModalState(0);
}

void  textEditorEscapeKeyPressed (TextEditor &editor)
{
	editor.exitModalState(0);
}

void  textEditorFocusLost (TextEditor &editor)
{
	editor.exitModalState(0);
}

};[/code]

To do it properly, one would to have something like the following in the dispatcher of the juce_VstWrapper.cpp:

[code]VstIntPtr dispatcher (VstInt32 opCode, VstInt32 index, VstIntPtr value, void* ptr, float opt)
{
if (hasShutdown)
return 0;

	// ADDED BY ME 
	if (opCode == effEditKeyDown || opCode == effEditKeyUp)
	{

#if JUCE_WIN32
if (editorComp!=0)
{
int vkey=0;
ComponentPeer *peer=editorComp->getPeer();
if (peer!=0)
peer->handleKeyPress ((int) index, 0); // index=ASCII key code

			return 1; // 1 means: key was handled by plugin, 0 means key may be used by host
		}

#endif

	}
	else ...[/code]

But this code does not work, because I don’t know how exactly to pass the key to the Component so that the keyDown() is called later. Jules, could you help me out a little bit?

I’ve also no idea how the VST keycodes work, they probably date back to OS9 support… Surely those callbacks are deprecated like the old mouseDown rubbish has been?

No, they aren’t outdated, they are still used in Cubase SX 3! How could I send the ASCII value that is contained in the variable “index” to the JUCE messaging system, so it’s gonna be picked up and so it’s gonna result in a corresponding keyDown() / keyUp() in the component that has the focus? If you tell me that, I can do the rest for you…

Well, you’re doing the right thing by passing it to the ComponentPeer::handleKeyPress() method, though you’d need to make sure the keycode is either a printable ascii char, or one of the values defined in the KeyPress class.

Ok, got it working in Cubase SX3. JUCE TextEditors now magically come to life! :slight_smile: Also “normal” components receive the key messages now, which is absolutely amazing because I can now navigate with keys and spacebar in my step sequencer VSTi !! Lovely :smiley:

In Ableton Live 6, it does not work, but that’s an Ableton problem, not ours. One could solve it by using a native Windows Text Input Box like for instance in that plugin Fractal (see aviram2k’s post). But in Ableton 7 everything works using that -_EnsureKeyMessagesForPlugins in the Options.txt. Come on over, everybody switch to Ableton 7!

So, we finally got JUCE keyboard support in all major hosts!!

Put this code somewhere on the top of the VST wrapper - it is the code responsible for converting VST Key Codes into JUCE Keycodes:

[code]int vstVirtualKeyCodeConversionTable[]=
{
VKEY_BACK, KeyPress::backspaceKey,
VKEY_TAB, KeyPress::tabKey,
VKEY_CLEAR, 0,
VKEY_RETURN, KeyPress::returnKey,
VKEY_PAUSE, 0,
VKEY_ESCAPE, KeyPress::escapeKey,
VKEY_SPACE, KeyPress::spaceKey,
VKEY_NEXT, 0,
VKEY_END, KeyPress::endKey,
VKEY_HOME, KeyPress::homeKey,

	VKEY_LEFT, KeyPress::leftKey,
	VKEY_UP, KeyPress::upKey,
	VKEY_RIGHT, KeyPress::rightKey,
	VKEY_DOWN, KeyPress::downKey,
	VKEY_PAGEUP, KeyPress::pageUpKey,
	VKEY_PAGEDOWN, KeyPress::pageDownKey,

	VKEY_SELECT, 0,
	VKEY_PRINT, 0,
	VKEY_ENTER, KeyPress::returnKey,
	VKEY_SNAPSHOT, 0,
	VKEY_INSERT, KeyPress::insertKey,
	VKEY_DELETE, KeyPress::deleteKey,
	VKEY_HELP, 0,
	VKEY_NUMPAD0, KeyPress::numberPad0,
	VKEY_NUMPAD1, KeyPress::numberPad1, 
	VKEY_NUMPAD2, KeyPress::numberPad2,
	VKEY_NUMPAD3, KeyPress::numberPad3,
	VKEY_NUMPAD4, KeyPress::numberPad4,
	VKEY_NUMPAD5, KeyPress::numberPad5,
	VKEY_NUMPAD6, KeyPress::numberPad6,
	VKEY_NUMPAD7, KeyPress::numberPad7,
	VKEY_NUMPAD8, KeyPress::numberPad8,
	VKEY_NUMPAD9, KeyPress::numberPad9,
	VKEY_MULTIPLY, KeyPress::numberPadMultiply,
	VKEY_ADD, KeyPress::numberPadAdd,
	VKEY_SEPARATOR, KeyPress::numberPadSeparator,
	VKEY_SUBTRACT, KeyPress::numberPadSubtract,
	VKEY_DECIMAL, KeyPress::numberPadDecimalPoint,
	VKEY_DIVIDE, KeyPress::numberPadDivide,
	VKEY_F1, KeyPress::F1Key,
	VKEY_F2, KeyPress::F2Key,
	VKEY_F3, KeyPress::F3Key,
	VKEY_F4, KeyPress::F4Key,
	VKEY_F5, KeyPress::F5Key,
	VKEY_F6, KeyPress::F6Key,
	VKEY_F7, KeyPress::F7Key,
	VKEY_F8, KeyPress::F8Key,
	VKEY_F9, KeyPress::F9Key,
	VKEY_F10, KeyPress::F10Key,
	VKEY_F11, KeyPress::F11Key,
	VKEY_F12, KeyPress::F12Key,
	VKEY_NUMLOCK, 0,
	VKEY_SCROLL, 0,

	VKEY_SHIFT, 0,
	VKEY_CONTROL, 0,
	VKEY_ALT, 0,

	VKEY_EQUALS, 0

};

bool isShiftDown=false;
void convertVstKeyInputToKeyCodeAndTextCharacter(int opCode, int value, int index, int &keyCode, int &textCharacter)
{
keyCode=0;
textCharacter=0;

if (opCode==effEditKeyDown)
{
	if (value==VKEY_SHIFT) isShiftDown=true;
	else 
		for (int i=0; i<sizeof(vstVirtualKeyCodeConversionTable)/(2*sizeof(int));i++)
			if (value==vstVirtualKeyCodeConversionTable[i*2])
			{
				keyCode=vstVirtualKeyCodeConversionTable[i*2+1];
				break;
			}
	
	textCharacter=index; // ascii
	if (isShiftDown && textCharacter>=97 && textCharacter<=122) textCharacter-=(97-65); // A-Z -> a-z
}
else if (opCode==effEditKeyUp)
{
	if (value==VKEY_SHIFT) isShiftDown=false;
}

}[/code]

And change the VST wrapper like this:

[code]VstIntPtr dispatcher (VstInt32 opCode, VstInt32 index, VstIntPtr value, void* ptr, float opt)
{
if (hasShutdown)
return 0;

	// ADDED BY ME 
	if (opCode == effEditKeyDown || opCode == effEditKeyUp)
	{

#if JUCE_WIN32
if (editorComp!=0)
{
int keyCode, textCharacter;
convertVstKeyInputToKeyCodeAndTextCharacter(opCode, value, index, keyCode, textCharacter);

			ComponentPeer *peer=editorComp->getPeer();
			if (peer!=0)
				if (keyCode!=0 || textCharacter!=0)
					peer->handleKeyPress (keyCode, textCharacter);

			return 1;
		}

#endif
return 0;
}
else

    if (opCode == effEditIdle)
    {
        doIdleCallback();
        ....
        ....[/code]

Note that my code will only “eat” keys when the editor component exists (generally it is open too then). So when the editor is closed all keys are still handled by the host, which is a good thing.

PS: As you can see in the conversion table, there are some Key Codes that exist in VST where I could not find any counterpart in JUCE. Maybe I overlooked them, or they don’t exist?

Cool stuff!

But what about hosts where keypresses do work, like tracktion? I’d have thought this would cause the keypresses to go through twice…

Apart from that, there’s one other thing you might want to add, which is to use the bool result of the handleKeyPress() call to dictate the return code, as this bool tells you whether the app actually wanted the keypress or not. That’d let you pass the unused ones through to the host.

No the keypresses will not go twice, because hosts that actually do use effEditKeyDown and effEditKeyUp will receive a return value (0 or 1) that tells them that the key was used or not and they won´t send the key message to the window again (to its message queue).

Also for the problem in Tracktion: since Tracktion does not use effEditKeyDown, it will never get called; so why should the problem you stated arise in that particular host?

Yes, one could return the bool code indeed - the only problem I see is that only the keydown´s are sent to the handleKeyPress() function; so for them one knows if they were used or not, whereas the keyUp´s are not sent to JUCE, what would one return for them? Always 0 (not used) ? Does that make sense?

Yes, good point about tracktion!

The way the normal key routines handle a key up is that they make a keyPressed call on a key down, and a handleKeyStateChanged call on both an up and down. The app can then use isKeyDown to figure out which ones have changed. But in this case, I’d say just ignore the key-up. It’s only useful for apps where they need to handle multiple simultaneous key-presses, and in a VST you have to lower your expectations of what’s possible slightly!