BUG? Popup Menu Background and Logic Silicon

After a bit more investigation, I’ve managed to trigger a similar issue in GarageBand 10.3 and Logic 10.6 with macOS 10.15.7 (intel).

I created a new “app” project in Xcode, then added an Audio Unit extension target to the project.

In the generated swift view controller I added the following function:

    public override func viewDidAppear() {
        for (i, alpha) in [0.7, 0.8].enumerated() {
            let w = NSWindow(contentRect: NSRect(x: 100 + 50 * i,
                                                 y: 100 + 50 * i,
                                                 width: 200,
                                                 height: 200),
                             styleMask: NSWindow.StyleMask.borderless,
                             backing: NSWindow.BackingStoreType.buffered,
                             defer: true)
            w.isOpaque = false
            w.backgroundColor = NSColor.cyan.withAlphaComponent(CGFloat(alpha))
            w.hasShadow = true
            w.level = NSWindow.Level.popUpMenu
            w.isReleasedWhenClosed = true
            w.isExcludedFromWindowsMenu = true
            w.ignoresMouseEvents = false
            w.setIsVisible(true)
            windows.append(w)
        }
    }

Here, we’re creating two cyan windows, one with an alpha of 0.7 and another with an alpha of 0.8. We set some other properties on the windows, and then make them visible.

When I open the generated AUv3 in GarageBand, I see the following:

The window with an alpha of 0.7 is displayed in pink, whereas the window with an alpha of 0.8 renders properly. It seems that any window with an alpha value which is below a certain threshold renders in pink instead, which is why JUCE’s completely clear popupmenus don’t work as expected.

Given that I can produce the above error in a completely JUCE-free app, I’m confident that this is an issue in GB/Logic’s hosting.

Interestingly, on this setup, transparent menus in a JUCE AU plugin work properly. I also tried downloading Apple’s demo AU project, and adding the extra windows to that project - again, both windows rendered in the correct colour. After a bit of debugging, I realised that the working plugins (Apple’s demo, and JUCE AUv2 plugins) were being loaded in-process, while the broken plugin was an AUv3 being loaded out-of-process. If I updated my broken AUv3 plugin to support in-process loading instead, then it also started to render correctly.

To sum up: it looks like rendering of transparent windows is broken in both Logic and GB, specifically for plugins loaded out-of-process.

It’s probably best to report the issue to apple via the offical feedback pages for GarageBand and Logic.

Edit: I tried running GarageBand on a DTK while also viewing hierarchically-grouped processes in the Activity Monitor. If I add a JUCE AU with broken popup rendering (not an AUv3, just an AU) to a track, GarageBand starts a new “AUHostingService” process, and when I remove the plugin, that process quits. This is consistent with the theory that this bug affects only plugins which are loaded out-of-process.

Could be worth emailing this guy? JUCE and macOS 11 / ARM - #43 by mfritze

Seems at least he works in exactly the right team!

If you’re looking for workarounds, one possiblity might be to override getParentComponentForMenuOptions to return a pointer to your top-level component in your look and feel.

Taking the LookAndFeel example above, adjust it like so:

class TestLookAndFeel : public LookAndFeel_V4
{
public:
    explicit TestLookAndFeel (juce::Component& tlc)
        : topLevelComponent (tlc)
    {
        const Colour newBgColour (53, 53, 53);
        const Colour newFgColour (220, 220, 220);
        setColour (PopupMenu::highlightedBackgroundColourId, newFgColour);
        setColour (PopupMenu::highlightedTextColourId, newBgColour.withAlpha (0.9f));
        setColour (PopupMenu::backgroundColourId, newBgColour.withAlpha (0.9f));
        setColour (PopupMenu::textColourId, newFgColour);
    }

    juce::Component* getParentComponentForMenuOptions (const PopupMenu::Options&) override
    {
        return &topLevelComponent;
    }

    void drawPopupMenuBackground (Graphics& g, int width, int height) override
    {
        Rectangle<int> area (0, 0, width, height);

        g.setColour (findColour (PopupMenu::backgroundColourId));
        g.fillRoundedRectangle (area.toFloat().reduced (0.5f), 4.0f);
    }

    juce::Component& topLevelComponent;
};

When constructing the LookAndFeel, pass in a reference to your plugin’s AudioProcessorEditor.

Now, the popupmenu will be added to your main editor window, rather than creating a whole new floating window. It should render correctly, although the size of the popup menu will be constrained to the size of the main editor window.

4 Likes

I see, perhaps a bit overkill to do it just for Logic Pro. I’ve sent the bug report, hopefully, it’s going to be fixed soon. Thanks again!

That LookAndFeel mod works fine. Tested with the same project and now it renders correctly. I need to test it further on other DAWs on Silicon.

Thanks @reuk

Any feedback from Apple regarding that bug?

There is the same issue with the sliders’ popups (cf setPopupDisplayEnabled()).

I’ve sent two bug reports over the months. One through an online form, one through Feedback Assistant. No answer so far. I had a contact at Apple suggesting me to use Feedback Assistant, but he’s also not replying anymore, so not sure where to go from here.

Small update, I’ve just updated to Logic Pro 10.6.3 on Big Sur 11.3 and the bug is still there. I’ve contacted another person at Apple and the feedback I submitted was moved to macOS. This is their response:

We found the feedback, it’s on the macOS side – we already assumed that it had nothing to do with Logic. My guess would be: it happens, because Logic is built against a recent SDK, which changes some behaviors inside macOS.

Ok, I finally have a full answer and will update the ProAudio Seed Release Notes accordingly. Please check them regularly.

Windows opened by the plugin might have a subtle magenta tinting. This is security feature, not a bug. It avoids that malicious code can open a window which overlays system windows. There are a couple of ways to avoid this tinting:

  • Make the window less transparent (75% or more opacity will avoid this tinting)
  • Call -[NSWindow addChildWindow:…]. This will confine the magenta to the remote view, which spawns the child.
  • Use a standard window to obtain rounded corners without using a transparent background color.
3 Likes

Hi Markus, thank you for the update!
Reducing the transparency unfortunately is not great with rounded corners.
Regarding the other solutions, I’m not sure if JUCE can implement them directly? I hope so.

2 Likes

Well, there is another really crazy option, I haven’t even mentioned: you could use standard macOS popup menus…

1 Like

@reuk any thoughts on that?

The odd thing as mentioned before, Logic Pro is the only DAW affected by this (afaik). So, creating a workaround for just one DAW seems a bit extreme. But still…

Unfortunately, popup menus are also used in ComboBoxes to display the popup list of values, and having a native menu appearing under a custom designed widget would feel strange.

And also, It’s not just an issue with popup menus: the same magenta coloring is overlaid to rows of lists when they are dragged around, for example. And I suppose also to the semi-transparent drag image that appears when a drag gesture is started with DragAndDropContainer::startDragging()

Unfortunately I don’t think any of the suggestions would resolve the problem.

This won’t allow popup windows to have completely transparent rounded corners.

This initially seemed like a viable solution, but it seems that any part of the child window that is not on top of the parent window is still painted with a magenta background. To test, I applied this patch:

childwindow.patch (2.9 KB)

Then, I built the DSPModulePluginDemo AU and loaded it in Logic on an M1 mac. Popup menus which are not entirely contained by the editor window sill have partially magenta backgrounds.

Screenshot 2021-06-02 at 13.11.24

This is not a general solution, it only fixes the case where the desired corner radius matches that set by the OS. It doesn’t fix the issue for windows with a translucent background.

This would force the menu to use OS styling, which may not be acceptable to some developers. It would also only provide a solution for PopupMenus. This solution would not work for arbitrary floating widgets such as dragged items, tooltips and so on.

All this is to say that I still think the solution that will yield the most consistent results across platforms is to avoid opening new top-level windows entirely, and to draw widgets such as menus, tooltips, and dialogs directly into the editor window wherever possible.

5 Likes

Thank you. This is ok for small menus, but it’s awful for bigger ones. We’ll need to rewrite a few things to make them look at least decent.
I’m thinking of adding this workaround just for Logic.

I was able to repro with AudioPluginHost built for M1 with an AU plugin built for Intel. Most likely if you can’t repro this you’re launching an Intel app on an M1.

It’s been a while since this post. Is there an actual solution?

No. As mentioned earlier, this is due to the cross-process hosting mechanism introduced on ARM macs. Apple is not planning to add a workaround, as far as I know.

The best solution is still the one I mentioned above: avoid opening new top-level windows entirely, and draw widgets such as menus, tooltips, and dialogs directly into the editor window.

2 Likes

Ok so, I need to rewrite my look and feel implementation as using @reuk suggestion “topLevelComponent” won’t work when the look and feel is implemented as a SharedResourcePointer:

struct MainLookAndFeel
{
    MainLookAndFeel()  { LookAndFeel::setDefaultLookAndFeel (&lnf);    }
    ~MainLookAndFeel() { LookAndFeel::setDefaultLookAndFeel (nullptr); }
    MyLookAndFeel lnf;
};

SharedResourcePointer<MainLookAndFeel> mainLookAndFeel;

The first instance of the plugin will be ok, but the second will override the topLevelComponent, causing the popups from the first plugin to be opened in the second plugin.

Any suggestion on what would be the proper way of implementing something like that?

getParentComponentForMenuOptions (const PopupMenu::Options&) allows you to discover the popup menu’s target component. From there, you can call getTopLevelComponent() to find the outermost component.

1 Like