VST Plugin(mac) gui being painted white

VST Plugins (mac) are having their gui overwritten when built using:

OSX 10.7.3
XCode 4.3.1
Juce modules 2.0.18

I’ve compiled both a host and a vst plugin with the tools above, whose projects were both created with the new introjucer. For both host and plugin I elected to not copy the modules into the individual projects but use the ‘master copy of modules’.

The host can display the gui’s of store-bought plugins but neither mine, nor the juce plugin demo gui’s display properly. The symptom is that the gui is briefly drawn (a flicker) and then immediately overwritten with the color white. When I enable repaint debugging I do not see the vst gui changing colors, indicating a repaint by juce.

I used the introjucer to first create new host/vst projects, then copied my existing sources into the new projects. They appear to work properly other than the vst gui issue.

This behavior occurs even when the plugin is loaded into the stock PluginHostDemo (freshly built).

Is anybody else having this problem?

Yes, this happened to me too yesterday.
I was sure it’s a specific plug-in issue since this happened only for V-station and it’s a problematic plugin anyway.

I just tried to reproduce this but failed to do so.

From what I remember most of the time it was opened completely white, then flickered for a while and then became OK.
In a few cases It stayed white with a few of it’s knobs painted.

My app’s GUI was OK the whole time.
I’m using Lion with Xcode 4.2.1 and Juce tip from March 2.

I can verify that the vst plugin builds of the exact same source code work correctly on the PC.
The problem still exists on the mac/xcode 4.3, and is a real showstopper for me.

Can anybody else confirm this behavior?, I’m trying to figure out if it’s isolated to my dev environment.
Shlomi, In my case the ‘whitescreen’ is solid, the controls underneath never reappear once hidden.
My plugin’s gui is pure white.

I’m on Snow Leopard but was having some problems with white backgrounds and only a knob showing up, that kind of thing. I think it was just for AU’s but I made some changes to the Carbon view wrapper which seemed to fix the problem. It’s not well tested or documented but you may want to give it a shot and see what happens.

/*
 ==============================================================================
 
 This file is part of the JUCE library - "Jules' Utility Class Extensions"
 Copyright 2004-11 by Raw Material Software Ltd.
 
 ------------------------------------------------------------------------------
 
 JUCE can be redistributed and/or modified under the terms of the GNU General
 Public License (Version 2), as published by the Free Software Foundation.
 A copy of the license is included in the JUCE distribution, or can be found
 online at www.gnu.org/licenses.
 
 JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
 
 ------------------------------------------------------------------------------
 
 To release a closed-source product which uses JUCE, commercial licenses are
 available: visit www.rawmaterialsoftware.com/juce for more information.
 
 ==============================================================================
 */

#ifndef __JUCE_MAC_CARBONVIEWWRAPPERCOMPONENT_JUCEHEADER__
#define __JUCE_MAC_CARBONVIEWWRAPPERCOMPONENT_JUCEHEADER__


//==============================================================================
/**
 Creates a floating carbon window that can be used to hold a carbon UI.
 
 This is a handy class that's designed to be inlined where needed, e.g.
 in the audio plugin hosting code.
 */
class CarbonViewWrapperComponent  : public Component,
public ComponentMovementWatcher,
public Timer
{
public:
    CarbonViewWrapperComponent()
	: ComponentMovementWatcher (this),
	wrapperWindow (0),
	carbonWindow (0),
	embeddedView (0),
	recursiveResize (false)
    {
    }
	
    virtual ~CarbonViewWrapperComponent()
    {
        jassert (embeddedView == 0); // must call deleteWindow() in the subclass's destructor!
    }
	
    virtual HIViewRef attachView (WindowRef windowRef, HIViewRef rootView) = 0;
    virtual void removeView (HIViewRef embeddedView) = 0;
    virtual void mouseDown (int, int) {}
    virtual void paint() {}
	
    virtual bool getEmbeddedViewSize (int& w, int& h)
    {
        if (embeddedView == 0)
            return false;
		
        HIRect bounds;
        HIViewGetBounds (embeddedView, &bounds);
        w = jmax (1, roundToInt (bounds.size.width));
        h = jmax (1, roundToInt (bounds.size.height));
        return true;
    }
	
    void createWindow()
    {
        if (wrapperWindow == 0)
        {
            Rect r;
            r.left   = 100;//this->getScreenX();
            r.top    = 100;//this->getScreenY();
            r.right  = 100;//this->getWidth();
            r.bottom = 100;//this->getHeight();
			
            CreateNewWindow (kDocumentWindowClass,
                             (WindowAttributes) (kWindowStandardHandlerAttribute | kWindowCompositingAttribute
												 | kWindowNoShadowAttribute | kWindowNoTitleBarAttribute),
                             &r, &wrapperWindow);
			
            jassert (wrapperWindow != 0);
            if (wrapperWindow == 0)
                return;
			
						
			//CSR HALL DEACTIVATES PARENT WINDOW WHEN CLICKED, CAUSES PAINTING ISSUES
			SetWindowActivationScope (wrapperWindow, kWindowActivationScopeNone);
			carbonWindow = [[NSWindow alloc] initWithWindowRef: wrapperWindow];

			embeddedView = attachView (wrapperWindow, HIViewGetRoot (wrapperWindow));
			setOurSizeToEmbeddedViewSize();
			
			[getOwnerWindow() addChildWindow: carbonWindow
                                     ordered: NSWindowAbove];
			
			/*
			EventTypeSpec windowEventTypes[] =
            {
                { kEventClassWindow, kEventWindowGetClickActivation },
                { kEventClassWindow, kEventWindowHandleDeactivate },
                { kEventClassWindow, kEventWindowBoundsChanging },
                { kEventClassMouse,  kEventMouseDown },
                { kEventClassMouse,  kEventMouseMoved },
                { kEventClassMouse,  kEventMouseDragged },
                { kEventClassMouse,  kEventMouseUp},
                { kEventClassWindow, kEventWindowDrawContent },
                { kEventClassWindow, kEventWindowShown },
                { kEventClassWindow, kEventWindowHidden }
            };
			
            EventHandlerUPP upp = NewEventHandlerUPP (carbonEventCallback);
            InstallWindowEventHandler (wrapperWindow, upp,
                                       sizeof (windowEventTypes) / sizeof (EventTypeSpec),
                                       windowEventTypes, this, &eventHandlerRef);
			*/
			
			//EventHandlerRef carbonEventHandlerRef;
			EventTypeSpec   controlEventTypes[] = {
				{kEventClassControl, kEventControlBoundsChanged }
			}; 
			
			verify_noerr (InstallControlEventHandler(HIViewGetRoot (wrapperWindow), NewEventHandlerUPP(carbonEventCallback),
													 sizeof(controlEventTypes) / sizeof(EventTypeSpec),
													 controlEventTypes, carbonWindow,
													 &eventHandlerRef));
			
			creationTime = Time::getCurrentTime();
        }
    }
	
    void deleteWindow()
    {
        removeView (embeddedView);
        embeddedView = 0;
		
        if (wrapperWindow != 0)
        {
            NSWindow* ownerWindow = getOwnerWindow();
			
            if ([[ownerWindow childWindows] count] > 0)
            {
                [ownerWindow removeChildWindow: carbonWindow];
                [carbonWindow close];
            }
			
            RemoveEventHandler (eventHandlerRef);
            DisposeWindow (wrapperWindow);
            wrapperWindow = 0;
        }
    }
	
    //==============================================================================
    void setOurSizeToEmbeddedViewSize()
    {
        int w, h;
        if (getEmbeddedViewSize (w, h))
        {
            if (w != getWidth() || h != getHeight())
            {
                startTimer (50);
				
                setSize (w, h);
                if (getParentComponent() != nullptr)
                    getParentComponent()->setSize (w, h);
            }
            else
            {
				 startTimer (jlimit (50, 500, getTimerInterval() + 20));
            }
        }
        else
        {
            stopTimer();
        }
    }
	
    void setEmbeddedWindowToOurSize()
    {
        if (! recursiveResize)
        {
            recursiveResize = true;
			
            if (embeddedView != 0)
            {
                HIRect r;
                r.origin.x = 0;
                r.origin.y = 0;
                r.size.width  = (float) getWidth();
                r.size.height = (float) getHeight();
                HIViewSetFrame (embeddedView, &r);
            }
			
            if (wrapperWindow != 0)
            {
                Rect wr;
                wr.left   = getScreenX();
                wr.top    = getScreenY();
                wr.right  = wr.left + getWidth();
                wr.bottom = wr.top + getHeight();
				
                SetWindowBounds (wrapperWindow, kWindowContentRgn, &wr);
                ShowWindow (wrapperWindow);
            }
			
            recursiveResize = false;
        }
    }
	
    void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/)
    {
		//deleteWindow();
        //createWindow();

		setEmbeddedWindowToOurSize();
    }
	
    void componentPeerChanged()
    {
		
        deleteWindow();
        createWindow();
		//ShowWindow(wrapperWindow);
		//ActivateWindow (wrapperWindow, TRUE);
    }
	
    void componentVisibilityChanged()
    {
		/*
		 if (isShowing()) {
		 createWindow();
		 //ShowWindow(wrapperWindow);
		 //ActivateWindow (wrapperWindow, TRUE);
		 }
		 else
		 deleteWindow();
		 */
		setOurSizeToEmbeddedViewSize();
		setEmbeddedWindowToOurSize();
		
		
    }
	
    static void recursiveHIViewRepaint (HIViewRef view)
    {
		/*
		 HIViewSetNeedsDisplay (view, true);
		 HIViewRef child = HIViewGetFirstSubview (view);
		 
		 while (child != 0)
		 {
		 recursiveHIViewRepaint (child);
		 child = HIViewGetNextView (child);
		 }
		 
		*/
    }
	
    void timerCallback()
    {
	//	setEmbeddedWindowToOurSize();
	//	 setOurSizeToEmbeddedViewSize();
		/* 
		 // To avoid strange overpainting problems when the UI is first opened, we'll
		 // repaint it a few times during the first second that it's on-screen..
		 if ((Time::getCurrentTime() - creationTime).inMilliseconds() < 1000)
		 recursiveHIViewRepaint (HIViewGetRoot (wrapperWindow));
		 */
    }
	
    OSStatus carbonEventHandler (EventHandlerCallRef /*nextHandlerRef*/, EventRef event)
    {
        switch (GetEventKind (event))
        {
				/*
            case kEventWindowHandleDeactivate:
                ActivateWindow (wrapperWindow, TRUE);
                return noErr;
				
            case kEventWindowGetClickActivation:
            {
                getTopLevelComponent()->toFront (false);
                [carbonWindow makeKeyAndOrderFront: nil];
				
                ClickActivationResult howToHandleClick = kActivateAndHandleClick;
				
                SetEventParameter (event, kEventParamClickActivation, typeClickActivationResult,
                                   sizeof (ClickActivationResult), &howToHandleClick);
				
                HIViewSetNeedsDisplay (embeddedView, true);
                return noErr;
            }
				 */
				
			case kEventControlBoundsChanged:
				//this->setOurSizeToEmbeddedViewSize();
				return noErr;
				
        }
		
        return eventNotHandledErr;
    }
	
    static pascal OSStatus carbonEventCallback (EventHandlerCallRef nextHandlerRef, EventRef event, void* userData)
    {
        return ((CarbonViewWrapperComponent*) userData)->carbonEventHandler (nextHandlerRef, event);
    }
	
protected:
    WindowRef wrapperWindow;
    NSWindow* carbonWindow;
    HIViewRef embeddedView;
    bool recursiveResize;
    Time creationTime;

    EventHandlerRef eventHandlerRef;

    NSWindow* getOwnerWindow() const    { return [((NSView*) getWindowHandle()) window]; }
};

#endif   // __JUCE_MAC_CARBONVIEWWRAPPERCOMPONENT_JUCEHEADER__

Thanks Graeme, I’ll try that in just a moment, I’m in the middle of a debugging run at the moment and wanted to report that I’ve localized the whiteout to the [window makeKeyAndOrderFront: nil]; call in NSViewComponentPeer, which is in the file named juce_mac_NSViewComponentPeer.mm

void NSViewComponentPeer::toFront (bool makeActiveWindow)
{
    if (isSharedWindow)
        [[view superview] addSubview: view
                          positioned: NSWindowAbove
                          relativeTo: nil];

    if (window != nil && component->isVisible())
    {
        if (makeActiveWindow)
           [window makeKeyAndOrderFront: nil];
        else
            [window orderFront: nil];

        if (! recursiveToFrontCall)
        {
            recursiveToFrontCall = true;
            Desktop::getInstance().getMainMouseSource().forceMouseCursorUpdate();
            handleBroughtToFront();
            recursiveToFrontCall = false;
        }
    }
}

Graeme, that didn’t work per-se, the vst appears to use the other file but I’m examining your changes now to see what I can try/apply in my case. But I’m way out of my expertise level here and am afraid I’ll do more damage than good.

It’s official, I’m out of my depth and stuck, I’ll move onto other tasks in the hopes that some kind soul can shed some light on why that ‘window orderFront’ call (or something in the close vicinity is covering up the gui. I can’t just comment it out because other windows (like the main window) seem to need it! - I suspect something happens before this in order to setup the conditions required for failure.

the host-shell window is coming up to front, and hides the vst window. What is calling the “to front” call?

I had a similar issues when i used native borders for my host-windows

http://rawmaterialsoftware.com/viewtopic.php?f=8&t=8519

Thanks, I think you’re correct.

The toFront is being issued from a setVisible(true) call, from the PluginWindow class per the Plugin Host demo. And what you said makes a lot of sense because when I’m debugging I first see the plugin gui drawn WITHOUT any kind of window frame. Then, further on down the line the window frame appears, wrapped around where the plugin gui should be - obliterating it. I’m not making any ‘use native titlebar calls’ though, I’m letting juce do it. I just tried both ways, neither title bar setting fixes the problem.

It acts like the sequence of drawing calls is out of order. Currently it’s drawing content first, then the frame.

Jules, when you get a moment could you eyeball this behavior, see if happens to you etc. I’m off working on other features at the moment but a crucial part of my project will be the addition of plugins, and right now the mac is behaving kind of weird! The pc version is fine however.

Thanks for the help

I’ll put it at the end of my very long to-do-list… This is the kind of problem that could easily end up taking me hours, so it’ll have to wait until I’ve got more time.

Understood, and thanks for getting it on the list.

But know that my application consists of both a host, and a crucial midi plugin. Without this plugin I can’t release for the mac.

It would be very useful for me to know if other people using osx 10.7 are having this problem. Again, the problem occurs with the stock host and plugin demo, if somebody else out there could build those two projects on 10.7/xcode 4.3 , run them and confirm the problem it would really help. I don’t know yet which portion of the equation is the problem - Osx? Library? XCode? For all I know this issue is related to my dev system in particular.

Thanks,
Kurt

Hi Folks, I’m still unable to create vst plugins on the mac, due to the ‘whiteout’ issue - has anybody else been able to confirm this behavior?

Hello !

I have faced the same issue on MacOSX 10.7 only.
This occurred with one of our software hosting VST’s and the juce host demo.

I added a call to toFront() in EditorCompWrapper::handleAsyncUpdate().
This call is done once after the class EditorCompWrapper is built then skipped (thanks to bool member).

This is not a very proper solution since the plug-in still flickers when showing, but it works.

Thank you, I’ll give that a try - and report back.

Did this help for you, kurt?

Hi, sorry for the late reply but no it didn’t - If I’m not mistaken this would only have affected the au’s gui??? I need to generate a vst plugin as it’s the only kind I could convince to give one 1 midi in, 1 midi out, and no audio’s - I applied the fix, set a breakpoint but it never triggered and the gui was still in whiteout.

Sounds like you’re looking in the wrong place… He’s talking about the EditorCompWrapper class, in juce_VST_Wrapper.cpp.

I believe I found the spot, and did get a breakpoint to trigger in handleAsyncUpdate, but the fix didn’t work for me, the gui still gets painted white. When the plugin didn’t work for me I took the opportunity to catch up on a customers project, and I will spend some time tomorrow making sure I am modifying the correct project, and generally re-familiarize myself with the code. While trying to get it working I ended up generating about four projects, the original, another I generated from new jucer and moved my source code into, another with old jucer and non-modularized version etc. I’ll try to find an hour or two tomorrow to straighten up the clutter and try this again. Thanks for the help though and I’ll report back tomorrow.

Hello !

Encountered issue with this solution:
when moved the window is painted white again, so it does not work properly.

Will investigate,
Cheers

Hi Guys, The fix isn’t working at all for me, still see the gui flash into existence only to be painted (and stays) white milliseconds thereafter…I’m still dead in the water on this one.