text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

extern int juce_gtkWebkitMain (int argc, const char* argv[]);

class CommandReceiver
{
public:
    struct Responder
    {
        virtual ~Responder() {}

        virtual void handleCommand (const String& cmd, const var& param) = 0;
        virtual void receiverHadError() = 0;
    };

    CommandReceiver (Responder* responderToUse, int inputChannelToUse)
        : responder (responderToUse), inChannel (inputChannelToUse)
    {
        setBlocking (inChannel, false);
    }

    static void setBlocking (int fd, bool shouldBlock)
    {
        int flags = fcntl (fd, F_GETFL);
        fcntl (fd, F_SETFL, (shouldBlock ? (flags & ~O_NONBLOCK)
                             : (flags | O_NONBLOCK)));
    }

    int getFd() const     { return inChannel; }

    void tryNextRead()
    {
        while (true)
        {
            size_t len = (receivingLength ? sizeof (size_t) : bufferLength.len);

            if (! receivingLength)
                buffer.realloc (len);

            char* dst  = (receivingLength ? bufferLength.data : buffer.getData());

            ssize_t actual = read (inChannel, &dst[pos], static_cast<size_t> (len - pos));

            if (actual < 0)
            {
                if (errno == EINTR)
                    continue;

                break;
            }

            pos += static_cast<size_t> (actual);

            if (pos == len)
            {
                pos = 0;

                if (! receivingLength)
                    parseJSON (String (buffer.getData(), bufferLength.len));

                receivingLength = (! receivingLength);
            }
        }

        if (errno != EAGAIN && errno != EWOULDBLOCK && responder != nullptr)
            responder->receiverHadError();
    }

    static void sendCommand (int outChannel, const String& cmd, const var& params)
    {
        DynamicObject::Ptr obj = new DynamicObject;

        obj->setProperty (getCmdIdentifier(), cmd);

        if (! params.isVoid())
            obj->setProperty (getParamIdentifier(), params);

        String json (JSON::toString (var (obj)));

        size_t jsonLength = static_cast<size_t> (json.length());
        size_t len        = sizeof (size_t) + jsonLength;

        HeapBlock<char> buffer (len);
        char* dst = buffer.getData();

        memcpy (dst, &jsonLength, sizeof (size_t));
        dst += sizeof (size_t);

        memcpy (dst, json.toRawUTF8(), jsonLength);

        ssize_t ret;

        do
        {
            ret = write (outChannel, buffer.getData(), len);
        } while (ret == -1 && errno == EINTR);
    }

private:
    void parseJSON (const String& json)
    {
        var object (JSON::fromString (json));

        if (! object.isVoid())
        {
            String cmd (object.getProperty (getCmdIdentifier(),   var()).toString());
            var params (object.getProperty (getParamIdentifier(), var()));

            if (responder != nullptr)
                responder->handleCommand (cmd, params);
        }
    }

    static Identifier getCmdIdentifier()    { static Identifier Id ("cmd");    return Id; }
    static Identifier getParamIdentifier()  { static Identifier Id ("params"); return Id; }

    Responder* responder;
    int inChannel;
    size_t pos = 0;
    bool receivingLength = true;
    union { char data [sizeof (size_t)]; size_t len; } bufferLength;
    HeapBlock<char> buffer;
};

//==============================================================================
class GtkChildProcess : private CommandReceiver::Responder
{
public:
    //==============================================================================
    GtkChildProcess (int inChannel, int outChannelToUse)
        : outChannel (outChannelToUse), receiver (this, inChannel)
    {}

    typedef void (*SetHardwareAcclPolicyFunctionPtr) (WebKitSettings*, int);

    int entry()
    {
        CommandReceiver::setBlocking (outChannel,      true);

        gtk_init (nullptr, nullptr);

        WebKitSettings* settings = webkit_settings_new();

        // webkit_settings_set_hardware_acceleration_policy was only added recently to webkit2
        // but is needed when running a WebBrowserComponent in a Parallels VM with 3D acceleration enabled
        auto setHardwarePolicy
            = reinterpret_cast<SetHardwareAcclPolicyFunctionPtr> (dlsym (RTLD_DEFAULT, "webkit_settings_set_hardware_acceleration_policy"));

        if (setHardwarePolicy != nullptr)
            setHardwarePolicy (settings, 2 /*WEBKIT_HARDWARE_ACCELERATION_POLICY_NEVER*/);

        GtkWidget *plug;

        plug = gtk_plug_new(0);
        GtkWidget* container;
        container = gtk_scrolled_window_new (nullptr, nullptr);

        GtkWidget* webviewWidget = webkit_web_view_new_with_settings (settings);
        webview = WEBKIT_WEB_VIEW (webviewWidget);

        gtk_container_add (GTK_CONTAINER (container), webviewWidget);
        gtk_container_add (GTK_CONTAINER (plug),      container);

        webkit_web_view_load_uri (webview, "about:blank");

        g_signal_connect (webview, "decide-policy",
                          G_CALLBACK (decidePolicyCallback), this);

        g_signal_connect (webview, "load-changed",
                          G_CALLBACK (loadChangedCallback), this);

        g_signal_connect (webview, "load-failed",
                          G_CALLBACK (loadFailedCallback), this);

        gtk_widget_show_all (plug);
        unsigned long wID = (unsigned long) gtk_plug_get_id (GTK_PLUG (plug));

        ssize_t ret;

        do {
            ret = write (outChannel, &wID, sizeof (wID));
        } while (ret == -1 && errno == EINTR);

        g_unix_fd_add (receiver.getFd(), G_IO_IN, pipeReadyStatic, this);
        receiver.tryNextRead();

        gtk_main();
        return 0;
    }

    void goToURL (const var& params)
    {
        static Identifier urlIdentifier ("url");
        String url (params.getProperty (urlIdentifier, var()).toString());

        webkit_web_view_load_uri (webview, url.toRawUTF8());
    }

    void handleDecisionResponse (const var& params)
    {
        WebKitPolicyDecision* decision
            = (WebKitPolicyDecision*) ((int64) params.getProperty ("decision_id", var (0)));
        bool allow = params.getProperty ("allow", var (false));

        if (decision != nullptr && decisions.contains (decision))
        {
            if (allow)
                webkit_policy_decision_use (decision);
            else
                webkit_policy_decision_ignore (decision);

            decisions.removeAllInstancesOf (decision);
            g_object_unref (decision);
        }
    }

    //==============================================================================
    void handleCommand (const String& cmd, const var& params) override
    {
        if      (cmd == "quit")      quit();
        else if (cmd == "goToURL")   goToURL (params);
        else if (cmd == "goBack")    webkit_web_view_go_back      (webview);
        else if (cmd == "goForward") webkit_web_view_go_forward   (webview);
        else if (cmd == "refresh")   webkit_web_view_reload       (webview);
        else if (cmd == "stop")      webkit_web_view_stop_loading (webview);
        else if (cmd == "decision")  handleDecisionResponse (params);
    }

    void receiverHadError() override
    {
        exit (-1);
    }

    //==============================================================================
    bool pipeReady (gint fd, GIOCondition)
    {
        if (fd == receiver.getFd())
        {
            receiver.tryNextRead();
            return true;
        }

        return false;
    }

    void quit()
    {
        gtk_main_quit();
    }

    bool onNavigation (String frameName,
                       WebKitNavigationAction* action,
                       WebKitPolicyDecision* decision)
    {
        if (decision != nullptr && frameName.isEmpty())
        {
            g_object_ref (decision);
            decisions.add (decision);

            DynamicObject::Ptr params = new DynamicObject;

            params->setProperty ("url", String (webkit_uri_request_get_uri (webkit_navigation_action_get_request (action))));
            params->setProperty ("decision_id", (int64) decision);
            CommandReceiver::sendCommand (outChannel, "pageAboutToLoad", var (params));

            return true;
        }

        return false;
    }

    bool onNewWindow (String /*frameName*/,
                      WebKitNavigationAction* action,
                      WebKitPolicyDecision* decision)
    {
        if (decision != nullptr)
        {
            DynamicObject::Ptr params = new DynamicObject;

            params->setProperty ("url", String (webkit_uri_request_get_uri (webkit_navigation_action_get_request (action))));
            CommandReceiver::sendCommand (outChannel, "newWindowAttemptingToLoad", var (params));

            // never allow new windows
            webkit_policy_decision_ignore (decision);

            return true;
        }

        return false;
    }

    void onLoadChanged (WebKitLoadEvent loadEvent)
    {
        if (loadEvent == WEBKIT_LOAD_FINISHED)
        {
            DynamicObject::Ptr params = new DynamicObject;

            params->setProperty ("url", String (webkit_web_view_get_uri (webview)));
            CommandReceiver::sendCommand (outChannel, "pageFinishedLoading", var (params));
        }
    }

    bool onDecidePolicy (WebKitPolicyDecision*    decision,
                         WebKitPolicyDecisionType decisionType)
    {
        switch (decisionType)
        {
        case WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION:
            {
                WebKitNavigationPolicyDecision* navigationDecision = WEBKIT_NAVIGATION_POLICY_DECISION (decision);
                const char* frameName = webkit_navigation_policy_decision_get_frame_name (navigationDecision);

                return onNavigation (String (frameName != nullptr ? frameName : ""),
                                     webkit_navigation_policy_decision_get_navigation_action (navigationDecision),
                                     decision);
            }
            break;
        case WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION:
            {
                WebKitNavigationPolicyDecision* navigationDecision = WEBKIT_NAVIGATION_POLICY_DECISION (decision);
                const char* frameName = webkit_navigation_policy_decision_get_frame_name (navigationDecision);

                return onNewWindow  (String (frameName != nullptr ? frameName : ""),
                                     webkit_navigation_policy_decision_get_navigation_action (navigationDecision),
                                     decision);
            }
            break;
        case WEBKIT_POLICY_DECISION_TYPE_RESPONSE:
            {
                WebKitResponsePolicyDecision *response = WEBKIT_RESPONSE_POLICY_DECISION (decision);

                // for now just always allow response requests
                ignoreUnused (response);
                webkit_policy_decision_use (decision);
                return true;
            }
            break;
        default:
            break;
        }

        return false;
    }

    void onLoadFailed (GError* error)
    {
        DynamicObject::Ptr params = new DynamicObject;

        params->setProperty ("error", String (error != nullptr ? error->message : "unknown error"));
        CommandReceiver::sendCommand (outChannel, "pageLoadHadNetworkError", var (params));
    }

private:
    static gboolean pipeReadyStatic (gint fd, GIOCondition condition, gpointer user)
    {
        return (reinterpret_cast<GtkChildProcess*> (user)->pipeReady (fd, condition) ? TRUE : FALSE);
    }

    static gboolean decidePolicyCallback (WebKitWebView*,
                                          WebKitPolicyDecision*    decision,
                                          WebKitPolicyDecisionType decisionType,
                                          gpointer user)
    {
        GtkChildProcess& owner = *reinterpret_cast<GtkChildProcess*> (user);
        return (owner.onDecidePolicy (decision, decisionType) ? TRUE : FALSE);
    }

    static void loadChangedCallback (WebKitWebView*,
                                     WebKitLoadEvent loadEvent,
                                     gpointer        user)
    {
        GtkChildProcess& owner = *reinterpret_cast<GtkChildProcess*> (user);
        owner.onLoadChanged (loadEvent);
    }

    static void loadFailedCallback (WebKitWebView*,
                                    WebKitLoadEvent /*loadEvent*/,
                                    gchar*          /*failing_uri*/,
                                    GError*         error,
                                    gpointer        user)
    {
        GtkChildProcess& owner = *reinterpret_cast<GtkChildProcess*> (user);
        owner.onLoadFailed (error);
    }

    int outChannel;
    CommandReceiver receiver;
    WebKitWebView* webview = nullptr;
    Array<WebKitPolicyDecision*> decisions;
};

//==============================================================================
class WebBrowserComponent::Pimpl : private Thread, private CommandReceiver::Responder
{
public:
    Pimpl (WebBrowserComponent& parent)
        : Thread ("Webview"), owner (parent)
    {}

    ~Pimpl()
    {
        quit();
    }

    //==============================================================================
    void init()
    {
        launchChild();

        int ret = pipe (threadControl);

        ignoreUnused (ret);
        jassert (ret == 0);

        CommandReceiver::setBlocking (inChannel,        true);
        CommandReceiver::setBlocking (outChannel,       true);
        CommandReceiver::setBlocking (threadControl[0], false);
        CommandReceiver::setBlocking (threadControl[1], true);

        unsigned long windowHandle;
        ssize_t actual = read (inChannel, &windowHandle, sizeof (windowHandle));
        if (actual != sizeof (windowHandle))
        {
            killChild();
            return;
        }

        receiver = new CommandReceiver (this, inChannel);
        startThread();

        xembed = new XEmbedComponent (windowHandle);
        owner.addAndMakeVisible (xembed);
    }

    void quit()
    {
        if (isThreadRunning())
        {
            signalThreadShouldExit();

            char ignore = 0;
            ssize_t ret;

            do
            {
                ret = write (threadControl[1], &ignore, 1);
            } while (ret == -1 && errno == EINTR);

            waitForThreadToExit (-1);
            receiver = nullptr;
        }

        if (childProcess != 0)
        {
            CommandReceiver::sendCommand (outChannel, "quit", var());
            killChild();
        }
    }

    //==============================================================================
    void goToURL (const String& url, const StringArray* headers, const MemoryBlock* postData)
    {
        DynamicObject::Ptr params = new DynamicObject;

        params->setProperty ("url", url);

        if (headers != nullptr)
            params->setProperty ("headers", var (*headers));

        if (postData != nullptr)
            params->setProperty ("postData", var (*postData));

        CommandReceiver::sendCommand (outChannel, "goToURL", var (params));
    }

    void goBack()      { CommandReceiver::sendCommand (outChannel, "goBack",    var()); }
    void goForward()   { CommandReceiver::sendCommand (outChannel, "goForward", var()); }
    void refresh()     { CommandReceiver::sendCommand (outChannel, "refresh",   var()); }
    void stop()        { CommandReceiver::sendCommand (outChannel, "stop",      var()); }

    void resized()
    {
        if (xembed != nullptr)
            xembed->setBounds (owner.getLocalBounds());
    }
private:
    //==============================================================================
    void killChild()
    {
        if (childProcess != 0)
        {
            xembed = nullptr;

            int status = 0, result;

            result = waitpid (childProcess, &status, WNOHANG);
            for (int i = 0; i < 15 && (! WIFEXITED(status) || result != childProcess); ++i)
            {
                Thread::sleep (100);
                result = waitpid (childProcess, &status, WNOHANG);
            }

            // clean-up any zombies
            status = 0;
            if (! WIFEXITED(status) || result != childProcess)
            {
                do
                {
                    kill (childProcess, SIGTERM);
                    waitpid (childProcess, &status, 0);
                } while (! WIFEXITED(status));
            }

            childProcess = 0;
        }
    }

    void launchChild()
    {
        int ret;
        int inPipe[2], outPipe[2];

        ret = pipe (inPipe);
        ignoreUnused (ret); jassert (ret == 0);

        ret = pipe (outPipe);
        ignoreUnused (ret); jassert (ret == 0);

        int pid = fork();
        if (pid == 0)
        {
            close (inPipe[0]);
            close (outPipe[1]);

            HeapBlock<const char*> argv (5);
            StringArray arguments;

            arguments.add (File::getSpecialLocation (File::currentExecutableFile).getFullPathName());
            arguments.add ("--juce-gtkwebkitfork-child");
            arguments.add (String (outPipe[0]));
            arguments.add (String (inPipe [1]));

            for (int i = 0; i < arguments.size(); ++i)
                argv[i] = arguments[i].toRawUTF8();

            argv[4] = nullptr;

           #if JUCE_STANDALONE_APPLICATION
            execv (arguments[0].toRawUTF8(), (char**) argv.getData());
           #else
            juce_gtkWebkitMain (4, (const char**) argv.getData());
           #endif
            exit (0);
        }

        close (inPipe[1]);
        close (outPipe[0]);

        inChannel  = inPipe[0];
        outChannel = outPipe[1];

        childProcess = pid;
    }

    void run() override
    {
        while (! threadShouldExit())
        {
            if (shouldExit())
                return;

            receiver->tryNextRead();

            fd_set set;
            FD_ZERO (&set);
            FD_SET (threadControl[0],  &set);
            FD_SET (receiver->getFd(), &set);

            int max_fd = jmax (threadControl[0], receiver->getFd());

            int result = 0;

            while (result == 0 || (result < 0 && errno == EINTR))
                result = select (max_fd + 1, &set, NULL, NULL, NULL);

            if (result < 0)
                break;
        }
    }

    bool shouldExit()
    {
        char ignore;
        ssize_t result = read (threadControl[0], &ignore, 1);

        return (result != -1 || (errno != EAGAIN && errno != EWOULDBLOCK));
    }

    //==============================================================================
    void handleCommandOnMessageThread (const String& cmd, const var& params)
    {
        String url (params.getProperty ("url", var()).toString());

        if      (cmd == "pageAboutToLoad")           handlePageAboutToLoad (url, params);
        else if (cmd == "pageFinishedLoading")       owner.pageFinishedLoading (url);
        else if (cmd == "windowCloseRequest")        owner.windowCloseRequest();
        else if (cmd == "newWindowAttemptingToLoad") owner.newWindowAttemptingToLoad (url);
        else if (cmd == "pageLoadHadNetworkError")   handlePageLoadHadNetworkError (params);

        threadBlocker.signal();
    }

    void handlePageAboutToLoad (const String& url, const var& inputParams)
    {
        int64 decision_id = inputParams.getProperty ("decision_id", var (0));

        if (decision_id != 0)
        {
            DynamicObject::Ptr params = new DynamicObject;

            params->setProperty ("decision_id", decision_id);
            params->setProperty ("allow", owner.pageAboutToLoad (url));

            CommandReceiver::sendCommand (outChannel, "decision", var (params));
        }
    }

    void handlePageLoadHadNetworkError (const var& params)
    {
        String error = params.getProperty ("error", "Unknown error");

        if (owner.pageLoadHadNetworkError (error))
            goToURL (String ("data:text/plain,") + error, nullptr, nullptr);
    }

    void handleCommand (const String& cmd, const var& params) override
    {
        threadBlocker.reset();

        (new HandleOnMessageThread (this, cmd, params))->post();

        // wait until the command has executed on the message thread
        // this ensures that Pimpl can never be deleted while the
        // message has not been executed yet
        threadBlocker.wait (-1);
    }

    void receiverHadError() override {}

    //==============================================================================
    struct HandleOnMessageThread : public CallbackMessage
    {
        HandleOnMessageThread (Pimpl* pimpl, const String& cmdToUse, const var& params)
            : owner (pimpl), cmdToSend (cmdToUse), paramsToSend (params)
        {}

        void messageCallback() override
        {
            owner->handleCommandOnMessageThread (cmdToSend, paramsToSend);
        }

        Pimpl* owner;
        String cmdToSend;
        var paramsToSend;
    };

private:
    WebBrowserComponent& owner;
    ScopedPointer<CommandReceiver> receiver;
    int childProcess = 0, inChannel = 0, outChannel = 0;
    int threadControl[2];
    ScopedPointer<XEmbedComponent> xembed;
    WaitableEvent threadBlocker;
};

//==============================================================================
WebBrowserComponent::WebBrowserComponent (const bool unloadPageWhenBrowserIsHidden_)
    : browser (new Pimpl (*this)),
      blankPageShown (false),
      unloadPageWhenBrowserIsHidden (unloadPageWhenBrowserIsHidden_)
{
    setOpaque (true);

    browser->init();
}

WebBrowserComponent::~WebBrowserComponent()
{
    if (browser != nullptr)
    {
        delete browser;
        browser = nullptr;
    }
}

//==============================================================================
void WebBrowserComponent::goToURL (const String& url,
                                   const StringArray* headers,
                                   const MemoryBlock* postData)
{
    lastURL = url;

    if (headers != nullptr)
        lastHeaders = *headers;
    else
        lastHeaders.clear();

    if (postData != nullptr)
        lastPostData = *postData;
    else
        lastPostData.reset();

    blankPageShown = false;

    browser->goToURL (url, headers, postData);
}

void WebBrowserComponent::stop()
{
    browser->stop();
}

void WebBrowserComponent::goBack()
{
    lastURL.clear();
    blankPageShown = false;

    browser->goBack();
}

void WebBrowserComponent::goForward()
{
    lastURL.clear();
    browser->goForward();
}

void WebBrowserComponent::refresh()
{
    browser->refresh();
}

//==============================================================================
void WebBrowserComponent::paint (Graphics& g)
{
    g.fillAll (Colours::white);
}

void WebBrowserComponent::checkWindowAssociation()
{
}

void WebBrowserComponent::reloadLastURL()
{
    if (lastURL.isNotEmpty())
    {
        goToURL (lastURL, &lastHeaders, &lastPostData);
        lastURL.clear();
    }
}

void WebBrowserComponent::parentHierarchyChanged()
{
    checkWindowAssociation();
}

void WebBrowserComponent::resized()
{
    if (browser != nullptr)
        browser->resized();
}

void WebBrowserComponent::visibilityChanged()
{
    checkWindowAssociation();
}

void WebBrowserComponent::focusGained (FocusChangeType)
{
}

void WebBrowserComponent::clearCookies()
{
    // Currently not implemented on linux as WebBrowserComponent currently does not
    // store cookies on linux
    jassertfalse;
}

int juce_gtkWebkitMain (int argc, const char* argv[])
{
    if (argc != 4) return -1;

    GtkChildProcess child (String (argv[2]).getIntValue(),
                           String (argv[3]).getIntValue());
    return child.entry();
}

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

namespace MouseCursorHelpers
{
    extern NSImage* createNSImage (const Image&, float scaleFactor = 1.f);
}

extern NSMenu* createNSMenu (const PopupMenu&, const String& name, int topLevelMenuId,
                             int topLevelIndex, bool addDelegate);

class SystemTrayIconComponent::Pimpl  : private Timer
{
public:
    Pimpl (SystemTrayIconComponent& iconComp, const Image& im)
        : owner (iconComp), statusIcon (MouseCursorHelpers::createNSImage (im))
    {
        static SystemTrayViewClass cls;
        view = [cls.createInstance() init];
        SystemTrayViewClass::setOwner (view, this);
        SystemTrayViewClass::setImage (view, statusIcon);

        setIconSize();

        statusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength: NSSquareStatusItemLength] retain];
        [statusItem setView: view];

        SystemTrayViewClass::frameChanged (view, SEL(), nullptr);

        [[NSNotificationCenter defaultCenter]  addObserver: view
                                                  selector: @selector (frameChanged:)
                                                      name: NSWindowDidMoveNotification
                                                    object: nil];
    }

    ~Pimpl()
    {
        [[NSNotificationCenter defaultCenter]  removeObserver: view];
        [[NSStatusBar systemStatusBar] removeStatusItem: statusItem];
        SystemTrayViewClass::setOwner (view, nullptr);
        SystemTrayViewClass::setImage (view, nil);
        [statusItem release];
        [view release];
        [statusIcon release];
    }

    void updateIcon (const Image& newImage)
    {
        [statusIcon release];
        statusIcon = MouseCursorHelpers::createNSImage (newImage);
        setIconSize();
        SystemTrayViewClass::setImage (view, statusIcon);
        [statusItem setView: view];
    }

    void setHighlighted (bool shouldHighlight)
    {
        isHighlighted = shouldHighlight;
        [view setNeedsDisplay: true];
    }

    void handleStatusItemAction (NSEvent* e)
    {
        NSEventType type = [e type];

        const bool isLeft  = (type == NSEventTypeLeftMouseDown  || type == NSEventTypeLeftMouseUp);
        const bool isRight = (type == NSEventTypeRightMouseDown || type == NSEventTypeRightMouseUp);

        if (owner.isCurrentlyBlockedByAnotherModalComponent())
        {
            if (isLeft || isRight)
                if (auto* current = Component::getCurrentlyModalComponent())
                    current->inputAttemptWhenModal();
        }
        else
        {
            auto eventMods = ModifierKeys::getCurrentModifiersRealtime();

            if (([e modifierFlags] & NSEventModifierFlagCommand) != 0)
                eventMods = eventMods.withFlags (ModifierKeys::commandModifier);

            auto now = Time::getCurrentTime();
            auto mouseSource = Desktop::getInstance().getMainMouseSource();
            auto pressure = (float) e.pressure;

            if (isLeft || isRight)  // Only mouse up is sent by the OS, so simulate a down/up
            {
                setHighlighted (true);
                startTimer (150);

                owner.mouseDown (MouseEvent (mouseSource, {},
                                             eventMods.withFlags (isLeft ? ModifierKeys::leftButtonModifier
                                                                         : ModifierKeys::rightButtonModifier),
                                             pressure, MouseInputSource::invalidOrientation, MouseInputSource::invalidRotation,
                                             MouseInputSource::invalidTiltX, MouseInputSource::invalidTiltY,
                                             &owner, &owner, now, {}, now, 1, false));

                owner.mouseUp (MouseEvent (mouseSource, {}, eventMods.withoutMouseButtons(), pressure,
                                           MouseInputSource::invalidOrientation, MouseInputSource::invalidRotation,
                                           MouseInputSource::invalidTiltX, MouseInputSource::invalidTiltY,
                                           &owner, &owner, now, {}, now, 1, false));
            }
            else if (type == NSEventTypeMouseMoved)
            {
                owner.mouseMove (MouseEvent (mouseSource, {}, eventMods, pressure,
                                             MouseInputSource::invalidOrientation, MouseInputSource::invalidRotation,
                                             MouseInputSource::invalidTiltX, MouseInputSource::invalidTiltY,
                                             &owner, &owner, now, {}, now, 1, false));
            }
        }
    }

    void showMenu (const PopupMenu& menu)
    {
        if (NSMenu* m = createNSMenu (menu, "MenuBarItem", -2, -3, true))
        {
            setHighlighted (true);
            stopTimer();
            [statusItem popUpStatusItemMenu: m];
            startTimer (1);
        }
    }

    SystemTrayIconComponent& owner;
    NSStatusItem* statusItem = nil;

private:
    NSImage* statusIcon = nil;
    NSControl* view = nil;
    bool isHighlighted = false;

    void setIconSize()
    {
        [statusIcon setSize: NSMakeSize (20.0f, 20.0f)];
    }

    void timerCallback() override
    {
        stopTimer();
        setHighlighted (false);
    }

    struct SystemTrayViewClass : public ObjCClass<NSControl>
    {
        SystemTrayViewClass()  : ObjCClass<NSControl> ("JUCESystemTrayView_")
        {
            addIvar<Pimpl*> ("owner");
            addIvar<NSImage*> ("image");

            addMethod (@selector (mouseDown:),      handleEventDown, "v@:@");
            addMethod (@selector (rightMouseDown:), handleEventDown, "v@:@");
            addMethod (@selector (drawRect:),       drawRect,        "v@:@");
            addMethod (@selector (frameChanged:),   frameChanged,    "v@:@");

            registerClass();
        }

        static Pimpl* getOwner (id self)                { return getIvar<Pimpl*> (self, "owner"); }
        static NSImage* getImage (id self)              { return getIvar<NSImage*> (self, "image"); }
        static void setOwner (id self, Pimpl* owner)    { object_setInstanceVariable (self, "owner", owner); }
        static void setImage (id self, NSImage* image)  { object_setInstanceVariable (self, "image", image); }

        static void frameChanged (id self, SEL, NSNotification*)
        {
            if (auto* owner = getOwner (self))
            {
                NSRect r = [[[owner->statusItem view] window] frame];
                NSRect sr = [[[NSScreen screens] objectAtIndex: 0] frame];
                r.origin.y = sr.size.height - r.origin.y - r.size.height;
                owner->owner.setBounds (convertToRectInt (r));
            }
        }

    private:
        static void handleEventDown (id self, SEL, NSEvent* e)
        {
            if (auto* owner = getOwner (self))
                owner->handleStatusItemAction (e);
        }

        static void drawRect (id self, SEL, NSRect)
        {
            NSRect bounds = [self bounds];

            if (auto* owner = getOwner (self))
                [owner->statusItem drawStatusBarBackgroundInRect: bounds
                                                   withHighlight: owner->isHighlighted];

            if (NSImage* const im = getImage (self))
            {
                NSSize imageSize = [im size];

                [im drawInRect: NSMakeRect (bounds.origin.x + ((bounds.size.width  - imageSize.width)  / 2.0f),
                                            bounds.origin.y + ((bounds.size.height - imageSize.height) / 2.0f),
                                            imageSize.width, imageSize.height)
                      fromRect: NSZeroRect
                     operation: NSCompositingOperationSourceOver
                      fraction: 1.0f];
            }
        }
    };

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
};

//==============================================================================
void SystemTrayIconComponent::setIconImage (const Image& newImage)
{
    if (newImage.isValid())
    {
        if (pimpl == nullptr)
            pimpl = new Pimpl (*this, newImage);
        else
            pimpl->updateIcon (newImage);
    }
    else
    {
        pimpl = nullptr;
    }
}

void SystemTrayIconComponent::setIconTooltip (const String&)
{
    // xxx not yet implemented!
}

void SystemTrayIconComponent::setHighlighted (bool highlight)
{
    if (pimpl != nullptr)
        pimpl->setHighlighted (highlight);
}

void SystemTrayIconComponent::showInfoBubble (const String& /*title*/, const String& /*content*/)
{
    // xxx Not implemented!
}

void SystemTrayIconComponent::hideInfoBubble()
{
    // xxx Not implemented!
}

void* SystemTrayIconComponent::getNativeHandle() const
{
    return pimpl != nullptr ? pimpl->statusItem : nullptr;
}

void SystemTrayIconComponent::showDropdownMenu (const PopupMenu& menu)
{
    if (pimpl != nullptr)
        pimpl->showMenu (menu);
}

} // namespace juce

repo_name: fedden/RenderMan

mime_type: text/x-c++


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

#if JUCE_MAC

namespace juce
{

struct WebViewKeyEquivalentResponder : public ObjCClass<WebView>
{
    WebViewKeyEquivalentResponder() : ObjCClass<WebView> ("WebViewKeyEquivalentResponder_")
    {
        addMethod (@selector (performKeyEquivalent:), performKeyEquivalent, @encode (BOOL), "@:@");
        registerClass();
    }

private:
    static BOOL performKeyEquivalent (id self, SEL selector, NSEvent* event)
    {
        NSResponder* first = [[self window] firstResponder];

       #if (defined (MAC_OS_X_VERSION_10_12) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_12)
        if (([event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask) == NSEventModifierFlagCommand)
       #else
        if (([event modifierFlags] & NSDeviceIndependentModifierFlagsMask) == NSCommandKeyMask)
       #endif
        {
            if ([[event charactersIgnoringModifiers] isEqualToString:@"x"]) return [NSApp sendAction:@selector(cut:)       to:first from:self];
            if ([[event charactersIgnoringModifiers] isEqualToString:@"c"]) return [NSApp sendAction:@selector(copy:)      to:first from:self];
            if ([[event charactersIgnoringModifiers] isEqualToString:@"v"]) return [NSApp sendAction:@selector(paste:)     to:first from:self];
            if ([[event charactersIgnoringModifiers] isEqualToString:@"a"]) return [NSApp sendAction:@selector(selectAll:) to:first from:self];
        }

        objc_super s = { self, [WebView class] };
        return ObjCMsgSendSuper<BOOL, NSEvent*> (&s, selector, event);
    }
};

struct DownloadClickDetectorClass  : public ObjCClass<NSObject>
{
    DownloadClickDetectorClass()  : ObjCClass<NSObject> ("JUCEWebClickDetector_")
    {
        addIvar<WebBrowserComponent*> ("owner");

        addMethod (@selector (webView:decidePolicyForNavigationAction:request:frame:decisionListener:),
                   decidePolicyForNavigationAction, "v@:@@@@@");
        addMethod (@selector (webView:decidePolicyForNewWindowAction:request:newFrameName:decisionListener:),
                   decidePolicyForNewWindowAction, "v@:@@@@@");
        addMethod (@selector (webView:didFinishLoadForFrame:), didFinishLoadForFrame, "v@:@@");
        addMethod (@selector (webView:didFailLoadWithError:forFrame:),  didFailLoadWithError,  "v@:@@@");
        addMethod (@selector (webView:didFailProvisionalLoadWithError:forFrame:),  didFailLoadWithError,  "v@:@@@");
        addMethod (@selector (webView:willCloseFrame:), willCloseFrame, "v@:@@");
        addMethod (@selector (webView:runOpenPanelForFileButtonWithResultListener:allowMultipleFiles:), runOpenPanel, "v@:@@", @encode (BOOL));

        registerClass();
    }

    static void setOwner (id self, WebBrowserComponent* owner)   { object_setInstanceVariable (self, "owner", owner); }
    static WebBrowserComponent* getOwner (id self)               { return getIvar<WebBrowserComponent*> (self, "owner"); }

private:
    static String getOriginalURL (NSDictionary* actionInformation)
    {
        if (NSURL* url = [actionInformation valueForKey: nsStringLiteral ("WebActionOriginalURLKey")])
            return nsStringToJuce ([url absoluteString]);

        return {};
    }

    static void decidePolicyForNavigationAction (id self, SEL, WebView*, NSDictionary* actionInformation,
                                                 NSURLRequest*, WebFrame*, id<WebPolicyDecisionListener> listener)
    {
        if (getOwner (self)->pageAboutToLoad (getOriginalURL (actionInformation)))
            [listener use];
        else
            [listener ignore];
    }

    static void decidePolicyForNewWindowAction (id self, SEL, WebView*, NSDictionary* actionInformation,
                                                NSURLRequest*, NSString*, id<WebPolicyDecisionListener> listener)
    {
        getOwner (self)->newWindowAttemptingToLoad (getOriginalURL (actionInformation));
        [listener ignore];
    }

    static void didFinishLoadForFrame (id self, SEL, WebView* sender, WebFrame* frame)
    {
        if ([frame isEqual: [sender mainFrame]])
        {
            NSURL* url = [[[frame dataSource] request] URL];
            getOwner (self)->pageFinishedLoading (nsStringToJuce ([url absoluteString]));
        }
    }

    static void didFailLoadWithError (id self, SEL, WebView* sender, NSError* error, WebFrame* frame)
    {
        if ([frame isEqual: [sender mainFrame]] && error != nullptr && [error code] != NSURLErrorCancelled)
        {
            String errorString (nsStringToJuce ([error localizedDescription]));

            bool proceedToErrorPage = getOwner (self)->pageLoadHadNetworkError (errorString);

            // WebKit doesn't have an internal error page, so make a really simple one ourselves
            if (proceedToErrorPage)
                getOwner(self)->goToURL (String ("data:text/plain;charset=UTF-8,") + errorString);
        }
    }

    static void willCloseFrame (id self, SEL, WebView*, WebFrame*)
    {
        getOwner (self)->windowCloseRequest();
    }

    static void runOpenPanel (id, SEL, WebView*, id<WebOpenPanelResultListener> resultListener, BOOL allowMultipleFiles)
    {
       #if JUCE_MODAL_LOOPS_PERMITTED
        FileChooser chooser (TRANS("Select the file you want to upload..."),
                             File::getSpecialLocation (File::userHomeDirectory), "*");

        if (allowMultipleFiles ? chooser.browseForMultipleFilesToOpen()
                               : chooser.browseForFileToOpen())
        {
            for (auto& f : chooser.getResults())
                [resultListener chooseFilename: juceStringToNS (f.getFullPathName())];
        }
       #else
        ignoreUnused (resultListener, allowMultipleFiles);
        jassertfalse; // Can't use this without modal loops being enabled!
       #endif
    }
};

#else

//==============================================================================
@interface WebViewTapDetector  : NSObject<UIGestureRecognizerDelegate>
{
}

- (BOOL) gestureRecognizer: (UIGestureRecognizer*) gestureRecognizer
         shouldRecognizeSimultaneouslyWithGestureRecognizer: (UIGestureRecognizer*) otherGestureRecognizer;
@end

@implementation WebViewTapDetector

- (BOOL) gestureRecognizer: (UIGestureRecognizer*) gestureRecognizer
         shouldRecognizeSimultaneouslyWithGestureRecognizer: (UIGestureRecognizer*) otherGestureRecognizer
{
    juce::ignoreUnused (gestureRecognizer, otherGestureRecognizer);
    return YES;
}

@end

//==============================================================================
@interface WebViewURLChangeDetector : NSObject<UIWebViewDelegate>
{
    juce::WebBrowserComponent* ownerComponent;
}

- (WebViewURLChangeDetector*) initWithWebBrowserOwner: (juce::WebBrowserComponent*) ownerComponent;
- (BOOL) webView: (UIWebView*) webView shouldStartLoadWithRequest: (NSURLRequest*) request
                                                   navigationType: (UIWebViewNavigationType) navigationType;
- (void) webViewDidFinishLoad: (UIWebView*) webView;
@end

@implementation WebViewURLChangeDetector

- (WebViewURLChangeDetector*) initWithWebBrowserOwner: (juce::WebBrowserComponent*) ownerComp
{
    [super init];
    ownerComponent = ownerComp;
    return self;
}

- (BOOL) webView: (UIWebView*) webView shouldStartLoadWithRequest: (NSURLRequest*) request
                                                   navigationType: (UIWebViewNavigationType) navigationType
{
    juce::ignoreUnused (webView, navigationType);
    return ownerComponent->pageAboutToLoad (juce::nsStringToJuce (request.URL.absoluteString));
}

- (void) webViewDidFinishLoad: (UIWebView*) webView
{
    ownerComponent->pageFinishedLoading (juce::nsStringToJuce (webView.request.URL.absoluteString));
}
@end

namespace juce
{

#endif

//==============================================================================
class WebBrowserComponent::Pimpl
                                   #if JUCE_MAC
                                    : public NSViewComponent
                                   #else
                                    : public UIViewComponent
                                   #endif
{
public:
    Pimpl (WebBrowserComponent* owner)
    {
       #if JUCE_MAC
        static WebViewKeyEquivalentResponder webviewClass;
        webView = (WebView*) webviewClass.createInstance();

        webView = [webView initWithFrame: NSMakeRect (0, 0, 100.0f, 100.0f)
                               frameName: nsEmptyString()
                               groupName: nsEmptyString()];
        setView (webView);

        static DownloadClickDetectorClass cls;
        clickListener = [cls.createInstance() init];
        DownloadClickDetectorClass::setOwner (clickListener, owner);
        [webView setPolicyDelegate: clickListener];
        [webView setFrameLoadDelegate: clickListener];
        [webView setUIDelegate: clickListener];
       #else
        webView = [[UIWebView alloc] initWithFrame: CGRectMake (0, 0, 1.0f, 1.0f)];
        setView (webView);

        tapDetector = [[WebViewTapDetector alloc] init];
        urlDetector = [[WebViewURLChangeDetector alloc] initWithWebBrowserOwner: owner];
        gestureRecogniser = nil;
        webView.delegate = urlDetector;
       #endif
    }

    ~Pimpl()
    {
       #if JUCE_MAC
        [webView setPolicyDelegate: nil];
        [webView setFrameLoadDelegate: nil];
        [webView setUIDelegate: nil];
        [clickListener release];
       #else
        webView.delegate = nil;
        [webView removeGestureRecognizer: gestureRecogniser];
        [gestureRecogniser release];
        [tapDetector release];
        [urlDetector release];
       #endif

        setView (nil);
    }

    void goToURL (const String& url,
                  const StringArray* headers,
                  const MemoryBlock* postData)
    {
        stop();

        if (url.trimStart().startsWithIgnoreCase ("javascript:"))
        {
            [webView stringByEvaluatingJavaScriptFromString:
                juceStringToNS (url.fromFirstOccurrenceOf (":", false, false))];
        }
        else
        {
            NSString* urlString = juceStringToNS (url);

           #if (JUCE_MAC && (defined (__MAC_OS_X_VERSION_MIN_REQUIRED) && defined (__MAC_10_9) && __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_9)) || (JUCE_IOS && (defined (__IPHONE_OS_VERSION_MIN_REQUIRED) && defined (__IPHONE_7_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0))
            urlString = [urlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
           #else
            urlString = [urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
           #endif
            NSMutableURLRequest* r
                = [NSMutableURLRequest requestWithURL: [NSURL URLWithString: urlString]
                                          cachePolicy: NSURLRequestUseProtocolCachePolicy
                                      timeoutInterval: 30.0];

            if (postData != nullptr && postData->getSize() > 0)
            {
                [r setHTTPMethod: nsStringLiteral ("POST")];
                [r setHTTPBody: [NSData dataWithBytes: postData->getData()
                                               length: postData->getSize()]];
            }

            if (headers != nullptr)
            {
                for (int i = 0; i < headers->size(); ++i)
                {
                    const String headerName  ((*headers)[i].upToFirstOccurrenceOf (":", false, false).trim());
                    const String headerValue ((*headers)[i].fromFirstOccurrenceOf (":", false, false).trim());

                    [r setValue: juceStringToNS (headerValue)
                       forHTTPHeaderField: juceStringToNS (headerName)];
                }
            }

           #if JUCE_MAC
            [[webView mainFrame] loadRequest: r];
           #else
            [webView loadRequest: r];
           #endif

           #if JUCE_IOS
            [webView setScalesPageToFit:YES];
           #endif
        }
    }

    void goBack()       { [webView goBack]; }
    void goForward()    { [webView goForward]; }

   #if JUCE_MAC
    void stop()         { [webView stopLoading: nil]; }
    void refresh()      { [webView reload: nil]; }
   #else
    void stop()         { [webView stopLoading]; }
    void refresh()      { [webView reload]; }
   #endif

    void mouseMove (const MouseEvent&)
    {
        // WebKit doesn't capture mouse-moves itself, so it seems the only way to make
        // them work is to push them via this non-public method..
        if ([webView respondsToSelector: @selector (_updateMouseoverWithFakeEvent)])
            [webView performSelector:    @selector (_updateMouseoverWithFakeEvent)];
    }

private:
   #if JUCE_MAC
    WebView* webView;
    id clickListener;
   #else
    UIWebView* webView;
    WebViewTapDetector* tapDetector;
    WebViewURLChangeDetector* urlDetector;
    UITapGestureRecognizer* gestureRecogniser;
   #endif
};

//==============================================================================
WebBrowserComponent::WebBrowserComponent (const bool unloadWhenHidden)
    : browser (nullptr),
      blankPageShown (false),
      unloadPageWhenBrowserIsHidden (unloadWhenHidden)
{
    setOpaque (true);

    addAndMakeVisible (browser = new Pimpl (this));
}

WebBrowserComponent::~WebBrowserComponent()
{
    deleteAndZero (browser);
}

//==============================================================================
void WebBrowserComponent::goToURL (const String& url,
                                   const StringArray* headers,
                                   const MemoryBlock* postData)
{
    lastURL = url;

    if (headers != nullptr)
        lastHeaders = *headers;
    else
        lastHeaders.clear();

    if (postData != nullptr)
        lastPostData = *postData;
    else
        lastPostData.reset();

    blankPageShown = false;

    browser->goToURL (url, headers, postData);
}

void WebBrowserComponent::stop()
{
    browser->stop();
}

void WebBrowserComponent::goBack()
{
    lastURL.clear();
    blankPageShown = false;
    browser->goBack();
}

void WebBrowserComponent::goForward()
{
    lastURL.clear();
    browser->goForward();
}

void WebBrowserComponent::refresh()
{
    browser->refresh();
}

//==============================================================================
void WebBrowserComponent::paint (Graphics&)
{
}

void WebBrowserComponent::checkWindowAssociation()
{
    if (isShowing())
    {
        reloadLastURL();

        if (blankPageShown)
            goBack();
    }
    else
    {
        if (unloadPageWhenBrowserIsHidden && ! blankPageShown)
        {
            // when the component becomes invisible, some stuff like flash
            // carries on playing audio, so we need to force it onto a blank
            // page to avoid this, (and send it back when it's made visible again).

            blankPageShown = true;
            browser->goToURL ("about:blank", 0, 0);
        }
    }
}

void WebBrowserComponent::reloadLastURL()
{
    if (lastURL.isNotEmpty())
    {
        goToURL (lastURL, &lastHeaders, &lastPostData);
        lastURL.clear();
    }
}

void WebBrowserComponent::parentHierarchyChanged()
{
    checkWindowAssociation();
}

void WebBrowserComponent::resized()
{
    browser->setSize (getWidth(), getHeight());
}

void WebBrowserComponent::visibilityChanged()
{
    checkWindowAssociation();
}

void WebBrowserComponent::focusGained (FocusChangeType)
{
}

void WebBrowserComponent::clearCookies()
{
    NSHTTPCookieStorage* storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];

    if (NSArray* cookies = [storage cookies])
    {
        const NSUInteger n = [cookies count];

        for (NSUInteger i = 0; i < n; ++i)
            [storage deleteCookie: [cookies objectAtIndex: i]];
    }

    [[NSUserDefaults standardUserDefaults] synchronize];
}

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

//==============================================================================
/**
    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),
          carbonWindow (nil),
          keepPluginWindowWhenHidden (false),
          wrapperWindow (nil),
          embeddedView (0),
          recursiveResize (false),
          repaintChildOnCreation (true)
    {
    }

    ~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 handleMouseDown (int, int) {}
    virtual void handlePaint() {}

    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 == nil)
        {
            Rect r;
            r.left   = (short) getScreenX();
            r.top    = (short) getScreenY();
            r.right  = (short) (r.left + getWidth());
            r.bottom = (short) (r.top + getHeight());

            CreateNewWindow (kDocumentWindowClass,
                             (WindowAttributes) (kWindowStandardHandlerAttribute | kWindowCompositingAttribute
                                                  | kWindowNoShadowAttribute | kWindowNoTitleBarAttribute),
                             &r, &wrapperWindow);

            jassert (wrapperWindow != 0);
            if (wrapperWindow == 0)
                return;

            carbonWindow = [[NSWindow alloc] initWithWindowRef: wrapperWindow];

            [getOwnerWindow() addChildWindow: carbonWindow
                                     ordered: NSWindowAbove];

            embeddedView = attachView (wrapperWindow, HIViewGetRoot (wrapperWindow));

            // Check for the plugin creating its own floating window, and if there is one,
            // we need to reparent it to make it visible..
            if (carbonWindow.childWindows.count > 0)
                if (NSWindow* floatingChildWindow = [[carbonWindow childWindows] objectAtIndex: 0])
                    [getOwnerWindow() addChildWindow: floatingChildWindow
                                             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);

            setOurSizeToEmbeddedViewSize();
            setEmbeddedWindowToOurSize();

            creationTime = Time::getCurrentTime();
        }
    }

    void deleteWindow()
    {
        removeView (embeddedView);
        embeddedView = 0;

        if (wrapperWindow != nil)
        {
            NSWindow* ownerWindow = getOwnerWindow();

            if ([[ownerWindow childWindows] count] > 0)
            {
                [ownerWindow removeChildWindow: carbonWindow];
                [carbonWindow close];
            }

            RemoveEventHandler (eventHandlerRef);
            DisposeWindow (wrapperWindow);
            wrapperWindow = nil;
        }
    }

    //==============================================================================
    void setOurSizeToEmbeddedViewSize()
    {
        int w, h;
        if (getEmbeddedViewSize (w, h))
        {
            if (w != getWidth() || h != getHeight())
            {
                startTimer (50);
                setSize (w, h);

                if (Component* p = getParentComponent())
                    p->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 != nil)
            {
                jassert (getTopLevelComponent()->getDesktopScaleFactor() == 1.0f);
                Rectangle<int> screenBounds (getScreenBounds() * Desktop::getInstance().getGlobalScaleFactor());

                Rect wr;
                wr.left   = (short) screenBounds.getX();
                wr.top    = (short) screenBounds.getY();
                wr.right  = (short) screenBounds.getRight();
                wr.bottom = (short) screenBounds.getBottom();

                SetWindowBounds (wrapperWindow, kWindowContentRgn, &wr);

                // This group stuff is mainly a workaround for Mackie plugins like FinalMix..
                WindowGroupRef group = GetWindowGroup (wrapperWindow);
                WindowRef attachedWindow;

                if (GetIndexedWindow (group, 2, kWindowGroupContentsReturnWindows, &attachedWindow) == noErr)
                {
                    SelectWindow (attachedWindow);
                    ActivateWindow (attachedWindow, TRUE);
                    HideWindow (wrapperWindow);
                }

                ShowWindow (wrapperWindow);
            }

            recursiveResize = false;
        }
    }

    void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) override
    {
        setEmbeddedWindowToOurSize();
    }

    // (overridden to intercept movements of the top-level window)
    void componentMovedOrResized (Component& component, bool wasMoved, bool wasResized) override
    {
        ComponentMovementWatcher::componentMovedOrResized (component, wasMoved, wasResized);

        if (&component == getTopLevelComponent())
            setEmbeddedWindowToOurSize();
    }

    void componentPeerChanged() override
    {
        deleteWindow();
        createWindow();
    }

    void componentVisibilityChanged() override
    {
        if (isShowing())
            createWindow();
        else if (! keepPluginWindowWhenHidden)
            deleteWindow();

        setEmbeddedWindowToOurSize();
    }

    static void recursiveHIViewRepaint (HIViewRef view)
    {
        HIViewSetNeedsDisplay (view, true);
        HIViewRef child = HIViewGetFirstSubview (view);

        while (child != 0)
        {
            recursiveHIViewRepaint (child);
            child = HIViewGetNextView (child);
        }
    }

    void timerCallback() override
    {
        if (isShowing())
        {
            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 (repaintChildOnCreation && (Time::getCurrentTime() - creationTime).inMilliseconds() < 1000)
                recursiveHIViewRepaint (HIViewGetRoot (wrapperWindow));
        }
    }

    void setRepaintsChildHIViewWhenCreated (bool b) noexcept
    {
        repaintChildOnCreation = b;
    }

    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);

                if (embeddedView != 0)
                    HIViewSetNeedsDisplay (embeddedView, true);

                return noErr;
            }
        }

        return eventNotHandledErr;
    }

    static pascal OSStatus carbonEventCallback (EventHandlerCallRef nextHandlerRef, EventRef event, void* userData)
    {
        return ((CarbonViewWrapperComponent*) userData)->carbonEventHandler (nextHandlerRef, event);
    }

    NSWindow* carbonWindow;
    bool keepPluginWindowWhenHidden;

protected:
    WindowRef wrapperWindow;
    HIViewRef embeddedView;
    bool recursiveResize, repaintChildOnCreation;
    Time creationTime;

    EventHandlerRef eventHandlerRef;

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

//==============================================================================
// Non-public utility function that hosts can use if they need to get hold of the
// internals of a carbon wrapper window..
void* getCarbonWindow (Component* possibleCarbonComponent)
{
    if (CarbonViewWrapperComponent* cv = dynamic_cast<CarbonViewWrapperComponent*> (possibleCarbonComponent))
        return cv->carbonWindow;

    return nullptr;
}

} // namespace juce