I’ll be posting my solution soon, just need to get a few more things tidied up with my latest release.
Here is my current workaround for AU plugin keypresses. It still steals the “tab” key in Live and one other key in Logic (sorry I can’t remember which), but most of them get through. I’m using Juce v1.54.27
In juce_AU_Wrapper.mm:
//==============================================================================
@interface JuceUIViewClass : NSView
{
AudioProcessor* filter;
JuceAU* au;
EditorCompHolder* editorComp;
}
- (JuceUIViewClass*) initWithFilter: (AudioProcessor*) filter
withAU: (JuceAU*) au
withComponent: (AudioProcessorEditor*) editorComp;
- (void) dealloc;
- (void) shutdown;
- (void) applicationWillTerminate: (NSNotification*) aNotification;
- (void) viewDidMoveToWindow;
- (BOOL) mouseDownCanMoveWindow;
- (void) filterBeingDeleted: (JuceAU*) au_;
- (void) deleteEditor;
- (BOOL) acceptsFirstResponder;
- (BOOL) becomeFirstResponder;
- (BOOL) resignFirstResponder;
- (BOOL) canBecomeKeyWindow;
- (BOOL) acceptsFirstMouse: (NSEvent*) ev;
- (void) keyDown: (NSEvent*) ev;
- (void) keyUp: (NSEvent*) ev;
@end
//==============================================================================
@implementation JuceUIViewClass
- (JuceUIViewClass*) initWithFilter: (AudioProcessor*) filter_
withAU: (JuceAU*) au_
withComponent: (AudioProcessorEditor*) editorComp_
{
filter = filter_;
au = au_;
editorComp = new EditorCompHolder (editorComp_);
[super initWithFrame: NSMakeRect (0, 0, editorComp_->getWidth(), editorComp_->getHeight())];
[self setHidden: NO];
[self setPostsFrameChangedNotifications: YES];
[[NSNotificationCenter defaultCenter] addObserver: self
selector: @selector (applicationWillTerminate:)
name: NSApplicationWillTerminateNotification
object: nil];
activeUIs.add (self);
editorComp->setOpaque (true);
#if JucePlugin_EditorRequiresKeyboardFocus
editorComp->addToDesktop (0, (void*)self);
editorComp->setWantsKeyboardFocus (true);
#else
editorComp->addToDesktop (ComponentPeer::windowIgnoresKeyPresses, (void*)self);
editorComp->setWantsKeyboardFocus (false);
#endif
editorComp->setVisible (true);
return self;
}
- (BOOL) acceptsFirstResponder
{
BOOL ret;
#if JucePlugin_EditorRequiresKeyboardFocus
ret = YES;
#else
ret = NO;
#endif
return ret;
}
- (BOOL) becomeFirstResponder
{
BOOL ret = [self acceptsFirstResponder];
return ret;
}
- (BOOL) resignFirstResponder
{
BOOL ret = [self acceptsFirstResponder];
return ret;
}
- (BOOL) canBecomeKeyWindow
{
BOOL ret = [self acceptsFirstResponder];
return ret;
}
- (BOOL) acceptsFirstMouse: (NSEvent*) ev
{
return YES;
}
- (void) keyDown: (NSEvent*) ev
{
[[self window] makeFirstResponder: self];
[super keyDown: ev];
// the esc key can delete the editor so check if editorComp still exists and if it does set the focus back to it
if (editorComp)
{
NSView* view = (NSView*)(editorComp->getPeer ()->getNativeHandle());
[[self window] makeFirstResponder: view];
}
}
- (void) keyUp: (NSEvent*) ev
{
[[self window] makeFirstResponder: self];
[super keyUp: ev];
// the esc key can delete the editor so check if editorComp still exists and if it does set the focus back to it
if (editorComp)
{
NSView* view = (NSView*)(editorComp->getPeer ()->getNativeHandle());
[[self window] makeFirstResponder: view];
}
}
and in juce_mac_NSViewComponentPeer.mm:
- (BOOL) acceptsFirstResponder
{
bool ret = owner != nullptr && owner->canBecomeKeyWindow();
return ret;
}
- (BOOL) becomeFirstResponder
{
bool ret = [self acceptsFirstResponder];
if (ret)
{
if (owner != nullptr) owner->viewFocusGain();
}
return ret;
}
- (BOOL) resignFirstResponder
{
bool ret = [self acceptsFirstResponder];
if (ret)
{
if (owner != nullptr) owner->viewFocusLoss();
}
return ret;
}
Please let me know if that does the trick, I may have missed something but I’ve made so many changes to the wrapper classes it’s hard to spot just the keyboard ones in a diff.
Hi Andrew.
Thanks for sharing. This does the trick! I also had to change JucePlugin_EditorRequiresKeyboardFocus to 1 in JucePluginCharacteristics.h. Some Aalto users will be very happy.
I made this work by patching your changes into my own fork of the Juce AU wrapper, which is be a lot like 1.54. I see that in Juce 2 Jules has made the wrapper code more C++ like, which involves a lot of changes. I can try to make your changes work with the tip after my current release is done.
We had the same issue in our plugs. Big thanks Andrew for posting your solution! I came up with a simpler solution (inspired by yours) that involves fewer changes:
void forwardCurrentKeyEventToHost_AU (Component* comp)
{
NSView* view = (NSView*) comp->getWindowHandle();
NSView* superview = [view superview]; // The superview is the NSView defined in AU wrapper
[[superview window] makeFirstResponder: superview];
NSEvent* event = [NSApp currentEvent];
[superview keyDown:event]; // Let the superview handle the event, this allows the host to handle it.
[[superview window] makeFirstResponder: view]; // Make the component firstResponder again so it gets future events.
}
Just call this function from your editor component’s keyPressed override. You’ll notice the similarity to how the VST wrapper handles this problem.
Background:
juce_VST_Wrapper.mm defines a function forwardCurrentKeyEventToHost() that passes focus to the plugin’s native window’s parent (a window owned by the host), and then re-posts the current event from the NSApplication event queue. This is required for 32-bit VSTs because of the Cocoa-on-Carbon fun and I’m sure you’re all using it. In the 64-Bit case, and the AU case (including 32-bit because Cocoa support exists there), keyPressed simply returns false, which should be enough to let the host handle the event–but clearly that doesn’t work in Ableton Live.
asomers; you’re a god amongst men. I haven’t fully tested this on enough hosts to ensure that it doesn’t cause any other issues (although, looking at the change, I think that’s unlikely), but I can confirm that your fix works with Live and JUCE 2. A much appreciated change, thanks!
Bad news. Looks like my version of the fix doesn’t work in 10.8. Also, looks like Andrew’s version works, but when the plugin is in focus (i.e. after you’ved clicked on it) Live does not get the first keypress, but does get subsequent keypresses. Can someone please confirm this behavior?
This can be made to work on 10.8 by calling [superview keyDown:event] twice in my previous snippet. Alternatively, you can return false from the component’s keyPressed method after the obj-c stuff. Logically, it makes no sense to return false here, and the effect up the chain in the event manager is probably the same as just calling keyDown twice. We decided to go with returning false. This can be fixed in Juce with this addition:
in juce_AU_Wrapper.cpp, EditorCompHolder class:
bool keyPressed(const KeyPress& kp)
{
NSView* view = (NSView*) getWindowHandle();
NSView* superview = [view superview];
[[superview window] makeFirstResponder: superview];
NSEvent* event = [NSApp currentEvent];
[superview keyDown:event];
[[superview window] makeFirstResponder: view];
return false;
}
[quote=“asomers”]This can be made to work on 10.8 by calling [superview keyDown:event] twice in my previous snippet. Alternatively, you can return false from the component’s keyPressed method after the obj-c stuff. Logically, it makes no sense to return false here, and the effect up the chain in the event manager is probably the same as just calling keyDown twice. We decided to go with returning false. This can be fixed in Juce with this addition:
in juce_AU_Wrapper.cpp, EditorCompHolder class:
bool keyPressed(const KeyPress& kp)
{
NSView* view = (NSView*) getWindowHandle();
NSView* superview = [view superview];
[[superview window] makeFirstResponder: superview];
NSEvent* event = [NSApp currentEvent];
[superview keyDown:event];
[[superview window] makeFirstResponder: view];
return false;
}
[/quote]
Awesome stuff!
So is the general consensus that this approach works well enough for me to add it to the library?
On 10.5 and 10.6, yes. I’m just setting up a 10.8 test machine with Ableton Live, so if you give me until tomorrow, I can give you confirmation on this additional tweak.
(And by the way, jules, nice job with Tracktion 4. Versions 1 and 2 were my host of choice, so I’m eager to give T4 a work-out!)
Sadly it looks like the “return false” bit messes up Logic’s transport control. This fix is not required at all for Logic, so we are going to make it Live-specific. This will be going through a round of QA on multiple OSes and DAWs. I’ll post back if there are any more gotchas.
Right, I think I have a general solution for JUCE 2. One which works for Live without borking Logic’s transport. I just need to run it through a few final tests, but I will endeavour to get a patch (or an admittance of defeat!) posted tonight…
This appears to work in every scenario I’ve tested, be it Mac OS X 10.6 or 10.8, Live, or Logic. Full disclosure: I’ve not tried with the most recent tip, but I am on a relatively up-to-date JUCE 2 branch.
So simply add this method to juce_AU_Wrapper.mm’s EditorCompHolder:
bool keyPressed(const KeyPress& kp)
{
// If we have an unused keypress, move the key-focus to a host window and re-inject the event..
static NSTimeInterval lastEventTime = 0; // check we're not recursively sending the same event
static PluginHostType pluginHostType;
if (pluginHostType.isAbletonLive())
{
if (lastEventTime != [[NSApp currentEvent] timestamp])
{
lastEventTime = [[NSApp currentEvent] timestamp];
Component* editor = getChildComponent(0);
NSView* view = (NSView*) editor->getWindowHandle();
NSView* superview = [view superview]; // The superview is the NSView defined in AU wrapper
[[superview window] makeFirstResponder: superview];
NSEvent* event = [NSApp currentEvent];
[superview keyDown:event]; // Let the superview handle the event, this allows the host to handle it.
[[superview window] makeFirstResponder: view]; // Make the component firstResponder again so it gets future events.
}
}
return false;
}
Space now controls the transport, as expected. Text Editor boxes still work and handle space presses correctly too, so I think this sorts it.
@CPB: I’m puzzled about why you seem to have deliberately added
Component* editor = getChildComponent(0);
NSView* view = (NSView*) editor->getWindowHandle();
instead of just
NSView* view = (NSView*) getWindowHandle();
?
AFAICT your change would make no difference (unless the editor is missing, in which case your version will crash). Or am I missing something?
Oops, sorry, yes, change that; those few lines were a leftover from when I was trying various things out. It works perfectly well with your suggested change.
Ok, thanks. Will check something in shortly…
The tab key switches between the arrange view and the mixer view in Live. If the Juce plugin has focus and I press the tab key then nothing happens with this most recent fix. Has anyone got the tab key working yet?
Juce uses tab key to move focus between controls AFAIK, so it steals it from Live.
It may explains what you’re seeing. no ?
[quote=“otristan”]Juce uses tab key to move focus between controls AFAIK, so it steals it from Live.
It may explains what you’re seeing. no ?[/quote]
Juce can use the tab key but not in this case. I have disabled support for tabbing between components and have traced the tab keypress all the way back to calling the keyPressed function as listed above by CPB. The fix thus far does the identical thing to what I already posted but in a much more concise form. I also could trick the tab key to get passed back up the chain.
All this is going on because Juce creates a new sub window and adds it to the window that is returned to Live, and although this should work ok in theory it doesn’t actually work in practice for whatever reason. BTW this is in Juce x86, I haven’t tested Juce x64 for a while, but I’ll do so and report back here.
A customer just wrote in about this problem coming up for a few JUCE plugins on his 10.8 machine. He says it happens with both the AU and VST versions. Haven’t had a chance to check it out yet, but if it’s affecting the VST as well that’s probably pertinent info for anyone working on this. Mac OS X 10.8.3 + Live 9.0.2 (64), as well as Live 8.4.
This happens on OS 10.6.8 Live 8/9 32/64 VST/AU. I also tried switching off JucePlugin_EditorRequiresKeyboardFocus but there is no difference either, the tab key never makes it back to Live no matter what I’ve tried thus far.