Logic Pro Silicon and plugin visibility

As already discussed few times on forums, Logic Pro on Silicon doesn’t always destroy editor when plugin window is closed. I don’t know why this is happening, but for example, using Big Sur and Logic 10.7, editor is deleted every other time, otherwise plugin window is just hidden. Using Ventura and Logic 10.7.8, editor is deleted every time window is closed as expected. So it’s pretty unpredictable behaviour. My guess is, that Logic decides about it depending on some system parameters or configuration, maybe there are also settings, which I am not aware of.

For example, I have this “misbehaviour” on 1 of my 3 macOS systems.


I have top-level-window component inside my plugin editor. It is added using addToDesktop() method, using my plugin editor window as a parent component. So I am creating heavy-weight window as a child of my plugin window. I need top-level-window because of technical reasons. So far so good.

topLevelWindow->addToDesktop (TopLevelWindow::getDesktopWindowStyleFlags(), getWindowHandle());

The problem no. 1

When Logic is hiding my plugin editor (not destroying it) on plugin close, this top-level-window is not restored when reopening plugin editor. Top-level-window is not destroyed as I can tell, it still exists in memory, it’s just not visible anymore. It’s worth to mention, that if I set “parent” to nullptr, top-level-window is not lost (!), but its screen position is not correct.

Now, I could solve this by checking visibility of my plugin and manually restoring missing top-level-window. Here comes …

The problem no. 2

In this particular situation, none of the JUCE methods (using version 6.1.6), which checks visibility of the component, work (including isShowing() method, which is meant to be used in situations like this). So I can’t get any relevant info about visibility of my plugin and top-level-window component. Everything seems like my plugin window and its components are is visible, but they are not! No matter what I do, I always get info that my plugin is visible. It seems, that JUCE is completely unaware of this unusual situation.

So if anyone has any slightest idea how to solve this, it would be greatly appreciated. My plugin works properly in all other hosts. True, there are some other hosts, which don’t always delete editor, but correct information about plugin visibility is always available.

Thank you!

There was an issue in macOS which prevented the viewWillDisappear callbacks from firing in the view controller. Any other visibility related properties or methods would also report the view as always being visible. I reported the bug and the issue is now fixed in macOS Ventura. This was a general bug with AUv3 (not only Logic). I also think this happens with any out-of-process AUv3, not only Apple Silicon. It’s just that logic will always load AUs out-of-process on Apple silicon where it will prefer to load AUv3s in-process on Intel if the AUv3 is capable of being loaded in-porcess.

There is a workaround I managed to finally discover on pre Ventura. When the AUv3 window is closed, the window level will always be reset to 0 (which is the default). You can use this fact to detect when the window was closed by setting the window level to something non-zero (window levels are ignored by AUv3s anyways) and observing the property with a KVO observer to see when it’s set to zero.

- (void)viewDidAppear {
    [super viewDidAppear];

    if (_requiresWorkaround) {
        UIWindow* topLevel = [self topLevelWindow];
        
        if (topLevel != nil) {
            NSWindowLevel currentLevel = topLevel.level;
            
            // more dirty hacks: an out-of-process loaded appex doesn't
            // react to the window level at all, so we can set it to whatever
            // we want. Luckily the window level is always reset to 0 when
            // the parent window in the host process is closed. Hence,
            // we need to avoid the 0 level when we are sure that the window
            // is onscreen (which it will be after viewDidAppear was called).
            if (currentLevel == 0)
                topLevel.level = 1;
        }
        
        // TODO: only do this dirty hack when loaded out-of-process
        if (topLevel != _lastTopLevelWindow) {
            if (_lastTopLevelWindow != nil) {
                [_lastTopLevelWindow removeObserver:self forKeyPath:@"level" context:nil];
                _lastTopLevelWindow = nil;
            }
            
            [topLevel addObserver:self forKeyPath:@"level"
                          options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld)
                          context:nil];
            _lastTopLevelWindow = topLevel;
        }
    }

    // inform your code that the view is now visible
    [self setMyViewIsNowReallyActuallyVisible:YES];
}

-(void)viewWillDisappear {
    if (! _requiresWorkaround)
        [self setMyViewIsNowReallyActuallyVisible:NO];
}

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary<NSKeyValueChangeKey, id>*)change
                       context:(void *)context {
    if ([keyPath isEqualToString:@"level"]) {
        __weak AUViewController* weakSelf = self;
        dispatch_async(dispatch_get_main_queue(), ^{
            __strong AUViewController* _self = weakSelf;
            if (_self != nil && _self.isViewLoaded) {
                UIWindow* topLevel = [_self topLevelWindow];
                
                if (object == topLevel) {
                    int newLevel = [((NSNumber*)change[NSKeyValueChangeNewKey]) intValue];
                    
                    if (newLevel >= 0 && (! _self->_isVisible))
                    {
                         // inform your code that the view is now visible
                         [self setMyViewIsNowReallyActuallyVisible:YES];
                        
                        if (newLevel == 0)
                            topLevel.level = 1;
                    } else if (newLevel <= 0 && _self->_isVisible) {
                        // inform your code that the view is no longer visible
                         [self setMyViewIsNowReallyActuallyVisible:NO];
                        
                        if (newLevel == 0)
                            topLevel.level = -1;
                    }
                }
            }
        });
        
        return;
    }
    [super observeValueForKeyPath:keyPath
                         ofObject:object
                           change:change
                          context:context];
}

-(UIWindow*)topLevelWindow {
    if (! self.isViewLoaded)
        return nil;
    
    UIWindow* window = [self.view window];
    while (window != nil && window.parentWindow != nil)
        window = window.parentWindow;
    
    return window;
}
#endif

-(void)dealloc
{
   #if TARGET_OS_OSX
    if (_lastTopLevelWindow != nil) {
        [_lastTopLevelWindow removeObserver:self forKeyPath:@"level" context:nil];
        _lastTopLevelWindow = nil;
    }
   #endif
}

EDIT: To be clear _requiresWorkaround should only be set to true on pre-Ventura and only if your plug-in was loaded out-of-process.

1 Like

Wow, thanks … my plugin is classic AU (not v3) - does this fix also applies?

Hmmm that’s a good question. I must admit, I forget how views work under-the-hood with AUv2s. If it also uses a ViewController then I think it would also apply. But I’m not sure.

Have you tested your AUv2 under macOS ventura? The bug shouldn’t occur there anymore.

Yes, it works on Ventura for me, there is no problem at all, because create/destroy editor sequence is working properly. Now that I checked again, all users with issues have Monterey - this makes perfect sense!

1 Like

Maybe my workaround above is something the JUCE team could integrate. It’s especially a problem if your AUv3 has resource-hungry OpenGL rendering etc. Currently, even when the view is closed, the OpenGL rendering code will continue to consume resources.

But I’m not sure it’s worth it as Ventura adoption rises.

1 Like

Hm, i can see this is very AUv3 specific code, can’t find traces of ViewController in AUv2 wrapper :frowning: Anything that can be done in AUv2 to fix this issue by your opinion?

Thx!

@attila Polite bump, thx for checking it! :slight_smile: