AU plugin not passing keypress to Live

Hi Jules,

I was reminded about this issue because I received a couple more bug reports from Live users. Are you able to take a look at this?

Same problem here, got some bug reports too. Can someone tell me if this is fixed in the latest tip?

A bug that is probably related to this: I have a user complaining that my plugin hijacks the Enter/Return key in Logic when entering numeric values. Here’s a quote from their email:

[quote]It is crucial, at least for me, to be able to control the transport with key commands.
I remember that you were going to investigate this further with your Juce-pals. Have you had any answers on the problem?
I don’t know if it helps but i think that Andrew Simper @ cytomic.com also uses Juce for his plug-in: “The Glue”.
The plug-in itself uses the Enter/Return commands to enter values numerically but it passes those commands to Logic directly after the parameter is set. Maybe this can be of any help![/quote]

Has anyone figured out a solution to entering text into a text editor (such as used by the Slider class) that works on all hosts? I want to fix this bug for this user, but I don’t want to break functionality for other users. Aargh.

Sean Costello

Hi Sean,

I always pop up a new text editor window directly over the value to be edited. I’ve included my helper class below and give an example of how I use it. Juce still eats the “esc” key in Logic, but it is the best I’ve been able to come up with to pass as many keys to as many hosts as possible:

JucePluginCharacteristics - this is so you can track the mouse over events on mac:

#define JucePlugin_EditorRequiresKeyboardFocus  1

Then from inside each and every control on the gui:

setWantsKeyboardFocus (false);

Here is the class I use to popup a text editor:

[code] class PopupTextEditor;
class TextEditorModal;

class PopupTextEditorListener
{
public:
    //==============================================================================
    juce_UseDebuggingNewOperator

    virtual ~PopupTextEditorListener () {};
    virtual void textEditAccepted (PopupTextEditor* editor, const String& text) = 0;
    virtual void textEditRejected (PopupTextEditor* editor) = 0;
};

class PopupTextEditor
    : public TextEditorListener
    , public ComponentListener
{
public:
    //==============================================================================
    juce_UseDebuggingNewOperator

    class TextEditorModal : public TextEditor
    {
    public:
        TextEditorModal (const String name) : TextEditor (name) {};
        virtual void inputAttemptWhenModal ()
        {
            returnPressed();
            exitModalState (0);
        }
    };
    
    PopupTextEditor (const String& name, PopupTextEditorListener* listener);
    virtual ~PopupTextEditor ();

    const String& getName () const;
    void setColour (const int colourId, const Colour& colour);
    void setFont (const Font& font);
    void showOverWithText (Component* anchor, String& text);

    virtual void textEditorTextChanged (TextEditor& editor);
    virtual void textEditorReturnKeyPressed (TextEditor& editor);
    virtual void textEditorEscapeKeyPressed (TextEditor& editor);
    virtual void textEditorFocusLost (TextEditor& editor);

protected:
    String name;
    String text;
    PopupTextEditorListener* listener;
    TextEditorModal* texteditor;
};

PopupTextEditor::PopupTextEditor (const String& name_, PopupTextEditorListener* listener_)
: name (name_)
, listener (listener_)
{
jassert (listener);
texteditor = new TextEditorModal (name_);
texteditor->addListener (this);
jassert (texteditor);
}

PopupTextEditor::~PopupTextEditor ()
{
if (texteditor)
{
delete texteditor;
}
}

const String& PopupTextEditor::getName () const
{
return name;
}

void PopupTextEditor::setColour (const int colourId, const Colour& colour)
{
texteditor->setColour (colourId, colour);
}

void PopupTextEditor::setFont (const Font& font)
{
texteditor->setFont (font);
}

void PopupTextEditor::showOverWithText (Component* anchor, String& text)
{
ModalComponentManager::Callback* userCallback = 0;
ScopedPointerModalComponentManager::Callback userCallbackDeleter (userCallback);

Component::SafePointer<Component> prevFocused (Component::getCurrentlyFocusedComponent());
Component::SafePointer<Component> prevTopLevel ((prevFocused != 0) ? prevFocused->getTopLevelComponent() : 0);
texteditor->setWantsKeyboardFocus (true);
Rectangle<int> sr (anchor->getBounds ());
sr.setPosition (anchor->getScreenX(), anchor->getScreenY());
int fontheight = texteditor->getFont().getHeight() + 4;
if (sr.getHeight() > fontheight)
{
    sr.translate (0, (sr.getHeight() - fontheight)/2);
    sr.setHeight (fontheight);
}
texteditor->setAlwaysOnTop (true);
texteditor->setBounds (sr);
texteditor->setText (text);
texteditor->setHighlightedRegion (Range <int> (0, text.length ()));
texteditor->setVisible (true);
texteditor->grabKeyboardFocus();

texteditor->addToDesktop (ComponentPeer::windowIsTemporary, 0);

texteditor->addListener (this);
texteditor->enterModalState (true, userCallbackDeleter.release());
texteditor->toFront (false);  // need to do this after making it modal, or it could
                              // be stuck behind other comps that are already modal..

const int result = texteditor->runModalLoop();

{
    if (prevTopLevel != 0)
        prevTopLevel->toFront (true);

    if (prevFocused != 0)
        prevFocused->grabKeyboardFocus();
}

}

void PopupTextEditor::textEditorTextChanged (TextEditor& editor)
{
// do nothing until the return or escape key is pressed
}

void PopupTextEditor::textEditorReturnKeyPressed (TextEditor& editor)
{
jassert (listener);
String text = editor.getText ();
listener->textEditAccepted (this, text);
texteditor->exitModalState(1);
}

void PopupTextEditor::textEditorEscapeKeyPressed (TextEditor& editor)
{
jassert (listener);
listener->textEditRejected (this);
texteditor->exitModalState(0);
}

void PopupTextEditor::textEditorFocusLost (TextEditor& editor)
{
texteditor->exitModalState(0);
}

void CytomicEditor::textEditAccepted (PopupTextEditor* editor, const String& text)
{
if (editor->getName ().startsWithIgnoreCase (“Edit_”))
{
if (text.length() <= 0) return;
String controlname = editor->getName ().fromFirstOccurrenceOf(“Edit_”, false, true);
TRC("-controlname " << controlname);
// find the control that is being edited
const int paramcount = getFilter ()->getNumParameters ();
for (int index=0; index<paramcount; index++)
{
CySlider slider = sliderctrl[index];
if (slider)
{
if (controlname.equalsIgnoreCase (slider->getName ()))
{
// we have found the control being edited
CytomicFilter
filter = getFilter ();
float value = filter->getParameterValueFromText (index, text);
getFilter()->getCurrentPreset ().SetValue (index, value);
getFilter()->param[index]->SetFromGui (value);
sliderctrl[index]->setValue (value, false);
getFilter()->beginParameterChangeGesture (index);
getFilter()->setParameterNotifyingHost (index, value);
getFilter()->endParameterChangeGesture (index);
}
}
}
}
}
void CytomicEditor::textEditRejected (PopupTextEditor* editor)
{
if (editor->getName ().equalsIgnoreCase (“Rename”))
{
buttonpreset->setVisible (true);
}
else if (editor->getName ().startsWithIgnoreCase (“Edit_”))
{
// do nothing
}
}

[/code]

[quote=“andrewsimper”]Hi Sean,

I always pop up a new text editor window directly over the value to be edited. I’ve included my helper class below and give an example of how I use it. Juce still eats the “esc” key in Logic, but it is the best I’ve been able to come up with to pass as many keys to as many hosts as possible:
[/quote]

Thanks for this, Andrew! Would you be able to provide an example of how you call the CytomicEditor::textEditAccepted function in your editor code?

Sean Costello

The “textEditorAccepted” function gets called by the popup text editor as a listener that is passed into the constructor of the class. Below is the code that gets called on a double click on a control, I have added a member to the filter base class which does ValueFromText and TextFromValue which I use in the au version of the plugin as well:

void CytomicEditor::CommandEditParameterText (CySlider* slider) { int id = sliderctrl.GetIndex (slider); const int paramcount = getFilter ()->getNumParameters (); if (id >= 0 && id < paramcount) { String name = "Edit_" + slider->getName (); ScopedPointer <PopupTextEditor> textedit (new PopupTextEditor (name, this)); textedit->setColour (anything you want); textedit->setFont (anythig you want); CytomicFilter* filter = getFilter (); String text (filter->getParameterTextFromValue(id, filter->param[id]->GetFromGui ())); textedit->showOverWithText (slider, text); } }

This thread kinda got hijacked. Not a problem but I don’t care about text editors for the moment. I’m restating the original goal of getting the keypresses that trigger Live’s keyboard notes through to Live. These include the space bar, letters and numbers. I can not find a way to pass these keys through to Live from Juce. Talked with Ableton tech support who think of this as a JUCE bug, because most non-Juce plugins don’t exhibit this behavior with Live.

Any news?

DP 7 also has this issue. Please let me know if you find a solution as it is really annoying for me in Live.

I’ve tested the Demo Plugin AU built from Juce 1.53.96 in Live 8.2.1 and it also steals all keyboard events, and it doesn’t matter what I set the following to:

#define JucePlugin_EditorRequiresKeyboardFocus 0

It also doesn’t matter which SDK I target, from 10.4, to 10.5, to 10.6 all behave the same way. Funnily enough the VST Mac version works fine in Live 8.2.1, no keyboard stolen at all.

To reproduce the issue:

  1. compile the juce demo au plugin from juce 1.53.96
  2. load live 8.2.1
  3. load the juce demo plugin onto an audio track (the interface will appear)
  4. drag on the “throughput level” knob radially to make it move to any value
  5. press spacebar to start “play” in live and nothing will happen

Any ideas?

Bit of a bump of a slightly older thread, but I too was recently informed of this by a customer. Which surprised me, because I use the Audio Units in Live on a daily basis and never have had this problem. To cut a long story short, after a little investigation it seems to work fine on Mac OS X 10.5, but not on Mac OS X 10.6 . No solution, unfortunately, but at least it’s a little additional information.

The AU version of my plugins still block all keystrokes to Live. I am really annoyed with this myself. The VST version works fine. Any ideas anyone?

I’ve given up on Jules looking at this, and it’s the main source of customer complaints for me. I’m going to devote some serious time to this issue before the next release of my plugin.

Yeah, this is a problem for me as well.

Sean Costello

I have found a solution that works for Cocoa windows not sat on top of a carbon one, which I hacked in by hard coding: #undef BUILD_AU_CARBON_UI

It seems that live isn’t paying attention to “acceptsFirstResponder” of a JuceNSView (in file “juce_mac_NSViewComponentPeer.mm”). Instead first of all becomesFirstResponder is being called directly, and the return value of that used to determine if keyboard events are accepted or not. I couldn’t actually debug into any of this cocoa stuff, I had to hack in my own lock free debug tracing system:


- (BOOL) becomeFirstResponder 
{
    // hack
    bool ret = owner != nullptr && owner->canBecomeKeyWindow();
    TRC ("becomeFirstResponder = " << ret << "\n");
    if (ret)
    {
        if (owner != nullptr) owner->viewFocusGain();
    }
    return ret;
}
- (BOOL) resignFirstResponder
{
    // hack
    bool ret = owner != nullptr && owner->canBecomeKeyWindow();
    TRC ("resignFirstResponder = " << ret << "\n");
    if (ret)
    {
        if (owner != nullptr) owner->viewFocusLoss();
    }
    return ret;
}

- (BOOL) acceptsFirstResponder
{
    // hack
    bool ret = owner != nullptr && owner->canBecomeKeyWindow();
    TRC ("acceptsFirstResponder = " << ret << "\n");
    return ret;
}

With the previous post Live does get keypresses, but the plugin no longer receives any mouse move messages. For it to work you also need to set:

#define JucePlugin_EditorRequiresKeyboardFocus 0

[quote=“andrewsimper”]I have found a solution that works for Cocoa windows not sat on top of a carbon one, which I hacked in by hard coding: #undef BUILD_AU_CARBON_UI

It seems that live isn’t paying attention to “acceptsFirstResponder” of a JuceNSView (in file “juce_mac_NSViewComponentPeer.mm”). Instead first of all becomesFirstResponder is being called directly, and the return value of that used to determine if keyboard events are accepted or not. I couldn’t actually debug into any of this cocoa stuff, I had to hack in my own lock free debug tracing system:

[code]

  • (BOOL) becomeFirstResponder
    {
    // hack
    bool ret = owner != nullptr && owner->canBecomeKeyWindow();
    TRC ("becomeFirstResponder = " << ret << “\n”);
    if (ret)
    {
    if (owner != nullptr) owner->viewFocusGain();
    }
    return ret;
    }

  • (BOOL) resignFirstResponder
    {
    // hack
    bool ret = owner != nullptr && owner->canBecomeKeyWindow();
    TRC ("resignFirstResponder = " << ret << “\n”);
    if (ret)
    {
    if (owner != nullptr) owner->viewFocusLoss();
    }
    return ret;
    }

  • (BOOL) acceptsFirstResponder
    {
    // hack
    bool ret = owner != nullptr && owner->canBecomeKeyWindow();
    TRC ("acceptsFirstResponder = " << ret << “\n”);
    return ret;
    }
    [/code][/quote]

Thanks for sharing the code. Had no luck until now. Do i have to #undef BUILD_AU_CARBON_UI too. Does that disable carbon for the whole plugin? Is the plugin still compatible with all common hosts after that? @ jules: Is it possible to merge a solution into the tip?

Live 9 is about to be released.

Maybe we should create an online petition asking them to stop doing all this keystroke stealing.

Live don’t steal the keystrokes, it is a problem with double layer focus in the gui wrapper in Juce and then something odd going on with cocoa stuff. I’ve got keystrokes working perfectly now with Juce, check out the trial of The Glue to see if it works for you. I don’t have time at the moment to post the solution as I’ve got to get on with releasing a new product.

Hi Andrew,

I can verify that kypresses are getting to Live just fine through The Glue! Are you willing to share your solution?

[quote=“madronalabs”]Hi Andrew,

I can verify that kypresses are getting to Live just fine through The Glue! Are you willing to share your solution?[/quote]
+1

Would be great if this solution find its way into to tip. This is not an Ableton Live related issue. I could reproduce it in Reaper too.