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
{

#if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
template <> struct ContainerDeletePolicy<NSObject<UIApplicationDelegate, UNUserNotificationCenterDelegate>> { static void destroy (NSObject* o) { [o release]; } };
#endif

namespace PushNotificationsDelegateDetails
{
    //==============================================================================
    NSArray* varArrayToNSArray (const var& varToParse);

    NSDictionary* varObjectToNSDictionary (const var& varToParse)
    {
        NSMutableDictionary* dictionary = [NSMutableDictionary dictionary];

        if (varToParse.isObject())
        {
            auto* dynamicObject = varToParse.getDynamicObject();

            auto& properties = dynamicObject->getProperties();

            for (int i = 0; i < properties.size(); ++i)
            {
                NSString* keyString = juceStringToNS (properties.getName (i).toString());

                const var& valueVar = properties.getValueAt (i);

                if (valueVar.isObject())
                {
                    NSDictionary* valueDictionary = varObjectToNSDictionary (valueVar);

                    [dictionary setObject: valueDictionary forKey: keyString];
                }
                else if (valueVar.isArray())
                {
                    NSArray* valueArray = varArrayToNSArray (valueVar);

                    [dictionary setObject: valueArray forKey: keyString];
                }
                else
                {
                    NSString* valueString = juceStringToNS (valueVar.toString());

                    [dictionary setObject: valueString forKey: keyString];
                }
            }
        }

        return dictionary;
    }

    NSArray* varArrayToNSArray (const var& varToParse)
    {
        jassert (varToParse.isArray());

        if (! varToParse.isArray())
            return nil;

        const auto* varArray = varToParse.getArray();

        NSMutableArray* array = [NSMutableArray arrayWithCapacity: (NSUInteger) varArray->size()];

        for (const auto& aVar : *varArray)
        {
            if (aVar.isObject())
            {
                NSDictionary* valueDictionary = varObjectToNSDictionary (aVar);

                [array addObject: valueDictionary];
            }
            else if (aVar.isArray())
            {
                NSArray* valueArray = varArrayToNSArray (aVar);

                [array addObject: valueArray];
            }
            else
            {
                NSString* valueString = juceStringToNS (aVar.toString());

                [array addObject: valueString];
            }
        }

        return array;
    }

    using Action   = PushNotifications::Settings::Action;
    using Category = PushNotifications::Settings::Category;

    void* actionToNSAction (const Action& a, bool iOSEarlierThan10)
    {
        if (iOSEarlierThan10)
        {
            UIMutableUserNotificationAction* action = [[UIMutableUserNotificationAction alloc] init];

            action.identifier     = juceStringToNS (a.identifier);
            action.title          = juceStringToNS (a.title);
            action.behavior       = a.style == Action::text ? UIUserNotificationActionBehaviorTextInput
                                                            : UIUserNotificationActionBehaviorDefault;
            action.parameters     = varObjectToNSDictionary (a.parameters);
            action.activationMode = a.triggerInBackground ? UIUserNotificationActivationModeBackground
                                                          : UIUserNotificationActivationModeForeground;
            action.destructive    = (bool) a.destructive;

            [action autorelease];

            return action;
        }
        else
        {
          #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
            if (a.style == Action::text)
            {
                return [UNTextInputNotificationAction actionWithIdentifier: juceStringToNS (a.identifier)
                                                                     title: juceStringToNS (a.title)
                                                                   options: NSUInteger (a.destructive << 1 | (! a.triggerInBackground) << 2)
                                                      textInputButtonTitle: juceStringToNS (a.textInputButtonText)
                                                      textInputPlaceholder: juceStringToNS (a.textInputPlaceholder)];
            }

            return [UNNotificationAction actionWithIdentifier: juceStringToNS (a.identifier)
                                                        title: juceStringToNS (a.title)
                                                      options: NSUInteger (a.destructive << 1 | (! a.triggerInBackground) << 2)];
          #else
            return nullptr;
          #endif
        }
    }

    void* categoryToNSCategory (const Category& c, bool iOSEarlierThan10)
    {
        if (iOSEarlierThan10)
        {
            UIMutableUserNotificationCategory* category = [[UIMutableUserNotificationCategory alloc] init];
            category.identifier = juceStringToNS (c.identifier);

            NSMutableArray* actions = [NSMutableArray arrayWithCapacity: (NSUInteger) c.actions.size()];

            for (const auto& a : c.actions)
            {
                UIUserNotificationAction* action = (UIUserNotificationAction*) actionToNSAction (a, iOSEarlierThan10);
                [actions addObject: action];
            }

            [category setActions: actions forContext: UIUserNotificationActionContextDefault];
            [category setActions: actions forContext: UIUserNotificationActionContextMinimal];

            [category autorelease];

            return category;
        }
        else
        {
          #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
            NSMutableArray* actions = [NSMutableArray arrayWithCapacity: (NSUInteger) c.actions.size()];

            for (const auto& a : c.actions)
            {
                UNNotificationAction* action = (UNNotificationAction*) actionToNSAction (a, iOSEarlierThan10);
                [actions addObject: action];
            }

            return [UNNotificationCategory categoryWithIdentifier: juceStringToNS (c.identifier)
                                                          actions: actions
                                                intentIdentifiers: @[]
                                                          options: c.sendDismissAction ? UNNotificationCategoryOptionCustomDismissAction : 0];
          #else
            return nullptr;
          #endif
        }
    }

    //==============================================================================
    UILocalNotification* juceNotificationToUILocalNotification (const PushNotifications::Notification& n)
    {
        UILocalNotification* notification = [[UILocalNotification alloc] init];

        notification.alertTitle = juceStringToNS (n.title);
        notification.alertBody  = juceStringToNS (n.body);
        notification.category   = juceStringToNS (n.category);
        notification.applicationIconBadgeNumber = n.badgeNumber;

        auto triggerTime = Time::getCurrentTime() + RelativeTime (n.triggerIntervalSec);
        notification.fireDate   = [NSDate dateWithTimeIntervalSince1970: triggerTime.toMilliseconds() / 1000.];
        notification.userInfo   = PushNotificationsDelegateDetails::varObjectToNSDictionary (n.properties);

        auto soundToPlayString = n.soundToPlay.toString (true);

        if (soundToPlayString == "default_os_sound")
            notification.soundName = UILocalNotificationDefaultSoundName;
        else if (soundToPlayString.isNotEmpty())
            notification.soundName = juceStringToNS (soundToPlayString);

        return notification;
    }

  #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
    UNNotificationRequest* juceNotificationToUNNotificationRequest (const PushNotifications::Notification& n)
    {
        // content
        UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init];

        content.title              = juceStringToNS (n.title);
        content.subtitle           = juceStringToNS (n.subtitle);
        content.threadIdentifier   = juceStringToNS (n.groupId);
        content.body               = juceStringToNS (n.body);
        content.categoryIdentifier = juceStringToNS (n.category);
        content.badge              = [NSNumber numberWithInt: n.badgeNumber];

        auto soundToPlayString = n.soundToPlay.toString (true);

        if (soundToPlayString == "default_os_sound")
            content.sound = [UNNotificationSound defaultSound];
        else if (soundToPlayString.isNotEmpty())
            content.sound = [UNNotificationSound soundNamed: juceStringToNS (soundToPlayString)];

        NSMutableDictionary* propsDict = (NSMutableDictionary*) PushNotificationsDelegateDetails::varObjectToNSDictionary (n.properties);
        [propsDict setObject: juceStringToNS (soundToPlayString) forKey: nsStringLiteral ("com.juce.soundName")];
        content.userInfo = propsDict;

        // trigger
        UNTimeIntervalNotificationTrigger* trigger = nil;

        if (std::abs (n.triggerIntervalSec) >= 0.001)
        {
            BOOL shouldRepeat = n.repeat && n.triggerIntervalSec >= 60;
            trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval: n.triggerIntervalSec repeats: shouldRepeat];
        }

        // request
        // each notification on iOS 10 needs to have an identifer, otherwise it will not show up
        jassert (n.identifier.isNotEmpty());
        UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier: juceStringToNS (n.identifier)
                                                                              content: content
                                                                              trigger: trigger];

        [content autorelease];

        return request;
    }
  #endif

    var nsArrayToVar (NSArray* array);

    var nsDictionaryToVar (NSDictionary* dictionary)
    {
        DynamicObject::Ptr dynamicObject = new DynamicObject();

        for (NSString* key in dictionary)
        {
            const auto keyString = nsStringToJuce (key);

            id value = dictionary[key];

            if ([value isKindOfClass: [NSString class]])
                dynamicObject->setProperty (keyString, nsStringToJuce ((NSString*) value));
            else if ([value isKindOfClass: [NSNumber class]])
                dynamicObject->setProperty (keyString, nsStringToJuce ([(NSNumber*) value stringValue]));
            else if ([value isKindOfClass: [NSDictionary class]])
                dynamicObject->setProperty (keyString, nsDictionaryToVar ((NSDictionary*) value));
            else if ([value isKindOfClass: [NSArray class]])
                dynamicObject->setProperty (keyString, nsArrayToVar ((NSArray*) value));
            else
                jassertfalse; // Unsupported yet, add here!
        }

        return var (dynamicObject);
    }

    var nsArrayToVar (NSArray* array)
    {
        Array<var> resultArray;

        for (id value in array)
        {
            if ([value isKindOfClass: [NSString class]])
                resultArray.add (var (nsStringToJuce ((NSString*) value)));
            else if ([value isKindOfClass: [NSNumber class]])
                resultArray.add (var (nsStringToJuce ([(NSNumber*) value stringValue])));
            else if ([value isKindOfClass: [NSDictionary class]])
                resultArray.add (nsDictionaryToVar ((NSDictionary*) value));
            else if ([value isKindOfClass: [NSArray class]])
                resultArray.add (nsArrayToVar ((NSArray*) value));
            else
                jassertfalse; // Unsupported yet, add here!
        }

        return var (resultArray);
    }

    String getUserResponseFromNSDictionary (NSDictionary* dictionary)
    {
        if (dictionary == nil || dictionary.count == 0)
            return {};

        jassert (dictionary.count == 1);

        for (NSString* key in dictionary)
        {
            const auto keyString = nsStringToJuce (key);

            id value = dictionary[key];

            if ([value isKindOfClass: [NSString class]])
                return nsStringToJuce ((NSString*) value);
        }

        jassertfalse;
        return {};
    }

    //==============================================================================
    var getNotificationPropertiesFromDictionaryVar (const var& dictionaryVar)
    {
        DynamicObject* dictionaryVarObject = dictionaryVar.getDynamicObject();

        if (dictionaryVarObject == nullptr)
            return {};

        const auto& properties = dictionaryVarObject->getProperties();

        DynamicObject::Ptr propsVarObject = new DynamicObject();

        for (int i = 0; i < properties.size(); ++i)
        {
            auto propertyName = properties.getName (i).toString();

            if (propertyName == "aps")
                continue;

            propsVarObject->setProperty (propertyName, properties.getValueAt (i));
        }

        return var (propsVarObject);
    }

    //==============================================================================
  #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
    double getIntervalSecFromUNNotificationTrigger (UNNotificationTrigger* t)
    {
        if (t != nil)
        {
            if ([t isKindOfClass: [UNTimeIntervalNotificationTrigger class]])
            {
                UNTimeIntervalNotificationTrigger* trigger = (UNTimeIntervalNotificationTrigger*) t;
                return trigger.timeInterval;
            }
            else if ([t isKindOfClass: [UNCalendarNotificationTrigger class]])
            {
                UNCalendarNotificationTrigger* trigger = (UNCalendarNotificationTrigger*) t;
                NSDate* date    = [trigger.dateComponents date];
                NSDate* dateNow = [NSDate date];
                return [dateNow timeIntervalSinceDate: date];
            }
        }

        return 0.;
    }

    PushNotifications::Notification unNotificationRequestToJuceNotification (UNNotificationRequest* r)
    {
        PushNotifications::Notification n;

        n.identifier = nsStringToJuce (r.identifier);
        n.title      = nsStringToJuce (r.content.title);
        n.subtitle   = nsStringToJuce (r.content.subtitle);
        n.body       = nsStringToJuce (r.content.body);
        n.groupId    = nsStringToJuce (r.content.threadIdentifier);
        n.category   = nsStringToJuce (r.content.categoryIdentifier);
        n.badgeNumber = r.content.badge.intValue;

        auto userInfoVar = PushNotificationsDelegateDetails::nsDictionaryToVar (r.content.userInfo);

        if (auto* object = userInfoVar.getDynamicObject())
        {
            static const Identifier soundName ("com.juce.soundName");
            n.soundToPlay = URL (object->getProperty (soundName).toString());
            object->removeProperty (soundName);
        }

        n.properties = userInfoVar;

        n.triggerIntervalSec = getIntervalSecFromUNNotificationTrigger (r.trigger);
        n.repeat = r.trigger != nil && r.trigger.repeats;

        return n;
    }

    PushNotifications::Notification unNotificationToJuceNotification (UNNotification* n)
    {
        return unNotificationRequestToJuceNotification (n.request);
    }
  #endif

    PushNotifications::Notification uiLocalNotificationToJuceNotification (UILocalNotification* n)
    {
        PushNotifications::Notification notif;

        notif.title       = nsStringToJuce (n.alertTitle);
        notif.body        = nsStringToJuce (n.alertBody);

        if (n.fireDate != nil)
        {
            NSDate* dateNow = [NSDate date];
            notif.triggerIntervalSec = [dateNow timeIntervalSinceDate: n.fireDate];
        }

        notif.soundToPlay = URL (nsStringToJuce (n.soundName));
        notif.badgeNumber = (int) n.applicationIconBadgeNumber;
        notif.category    = nsStringToJuce (n.category);
        notif.properties  = nsDictionaryToVar (n.userInfo);

        return notif;
    }

    Action uiUserNotificationActionToAction (UIUserNotificationAction* a)
    {
        Action action;

        action.identifier = nsStringToJuce (a.identifier);
        action.title = nsStringToJuce (a.title);
        action.style = a.behavior == UIUserNotificationActionBehaviorTextInput
                     ? Action::text
                     : Action::button;

        action.triggerInBackground = a.activationMode == UIUserNotificationActivationModeBackground;
        action.destructive = a.destructive;
        action.parameters = nsDictionaryToVar (a.parameters);

        return action;
    }

    Category uiUserNotificationCategoryToCategory (UIUserNotificationCategory* c)
    {
        Category category;
        category.identifier = nsStringToJuce (c.identifier);

        for (UIUserNotificationAction* a in [c actionsForContext: UIUserNotificationActionContextDefault])
            category.actions.add (uiUserNotificationActionToAction (a));

        return category;
    }

  #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
    Action unNotificationActionToAction (UNNotificationAction* a)
    {
        Action action;

        action.identifier = nsStringToJuce (a.identifier);
        action.title      = nsStringToJuce (a.title);
        action.triggerInBackground = ! (a.options & UNNotificationActionOptionForeground);
        action.destructive         =    a.options & UNNotificationActionOptionDestructive;

        if ([a isKindOfClass: [UNTextInputNotificationAction class]])
        {
            UNTextInputNotificationAction* textAction = (UNTextInputNotificationAction*)a;

            action.style = Action::text;
            action.textInputButtonText  = nsStringToJuce (textAction.textInputButtonTitle);
            action.textInputPlaceholder = nsStringToJuce (textAction.textInputPlaceholder);
        }
        else
        {
            action.style = Action::button;
        }

        return action;
    }

    Category unNotificationCategoryToCategory (UNNotificationCategory* c)
    {
        Category category;

        category.identifier = nsStringToJuce (c.identifier);
        category.sendDismissAction = c.options & UNNotificationCategoryOptionCustomDismissAction;

        for (UNNotificationAction* a in c.actions)
            category.actions.add (unNotificationActionToAction (a));

        return category;
    }
  #endif

    PushNotifications::Notification nsDictionaryToJuceNotification (NSDictionary* dictionary)
    {
        const var dictionaryVar = nsDictionaryToVar (dictionary);

        const var apsVar = dictionaryVar.getProperty ("aps", {});

        if (! apsVar.isObject())
            return {};

        var alertVar = apsVar.getProperty ("alert", {});

        const var titleVar = alertVar.getProperty ("title", {});
        const var bodyVar  = alertVar.isObject() ? alertVar.getProperty ("body", {}) : alertVar;

        const var categoryVar = apsVar.getProperty ("category", {});
        const var soundVar    = apsVar.getProperty ("sound", {});
        const var badgeVar    = apsVar.getProperty ("badge", {});
        const var threadIdVar = apsVar.getProperty ("thread-id", {});

        PushNotifications::Notification notification;

        notification.title       = titleVar   .toString();
        notification.body        = bodyVar    .toString();
        notification.groupId     = threadIdVar.toString();
        notification.category    = categoryVar.toString();
        notification.soundToPlay = URL (soundVar.toString());
        notification.badgeNumber = (int) badgeVar;
        notification.properties  = getNotificationPropertiesFromDictionaryVar (dictionaryVar);

        return notification;
    }
}

//==============================================================================
struct PushNotificationsDelegate
{
    PushNotificationsDelegate() : delegate ([getClass().createInstance() init])
    {
        Class::setThis (delegate, this);

        id<UIApplicationDelegate> appDelegate = [[UIApplication sharedApplication] delegate];

        SEL selector = NSSelectorFromString (@"setPushNotificationsDelegateToUse:");

        if ([appDelegate respondsToSelector: selector])
            [appDelegate performSelector: selector withObject: delegate];
    }

    virtual ~PushNotificationsDelegate() {}

    virtual void didRegisterUserNotificationSettings (UIUserNotificationSettings* notificationSettings) = 0;

    virtual void registeredForRemoteNotifications (NSData* deviceToken) = 0;

    virtual void failedToRegisterForRemoteNotifications (NSError* error) = 0;

    virtual void didReceiveRemoteNotification (NSDictionary* userInfo) = 0;

    virtual void didReceiveRemoteNotificationFetchCompletionHandler (NSDictionary* userInfo,
                                                                     void (^completionHandler)(UIBackgroundFetchResult result)) = 0;

    virtual void handleActionForRemoteNotificationCompletionHandler (NSString* actionIdentifier,
                                                                     NSDictionary* userInfo,
                                                                     NSDictionary* responseInfo,
                                                                     void (^completionHandler)()) = 0;

    virtual void didReceiveLocalNotification (UILocalNotification* notification) = 0;

    virtual void handleActionForLocalNotificationCompletionHandler (NSString* actionIdentifier,
                                                                    UILocalNotification* notification,
                                                                    void (^completionHandler)()) = 0;

    virtual void handleActionForLocalNotificationWithResponseCompletionHandler (NSString* actionIdentifier,
                                                                                UILocalNotification* notification,
                                                                                NSDictionary* responseInfo,
                                                                                void (^completionHandler)()) = 0;

  #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
    virtual void willPresentNotificationWithCompletionHandler (UNNotification* notification,
                                                               void (^completionHandler)(UNNotificationPresentationOptions options)) = 0;

    virtual void didReceiveNotificationResponseWithCompletionHandler (UNNotificationResponse* response,
                                                                      void (^completionHandler)()) = 0;
  #endif

protected:
  #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
    ScopedPointer<NSObject<UIApplicationDelegate, UNUserNotificationCenterDelegate>> delegate;
  #else
    ScopedPointer<NSObject<UIApplicationDelegate>> delegate;
  #endif

private:
    //==============================================================================
  #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
    struct Class    : public ObjCClass<NSObject<UIApplicationDelegate, UNUserNotificationCenterDelegate>>
    {
        Class() : ObjCClass<NSObject<UIApplicationDelegate, UNUserNotificationCenterDelegate>> ("JucePushNotificationsDelegate_")
  #else
    struct Class    : public ObjCClass<NSObject<UIApplicationDelegate>>
    {
        Class() : ObjCClass<NSObject<UIApplicationDelegate>> ("JucePushNotificationsDelegate_")
  #endif
        {
            addIvar<PushNotificationsDelegate*> ("self");

            addMethod (@selector (application:didRegisterUserNotificationSettings:),                                                 didRegisterUserNotificationSettings,                            "v@:@@");
            addMethod (@selector (application:didRegisterForRemoteNotificationsWithDeviceToken:),                                    registeredForRemoteNotifications,                               "v@:@@");
            addMethod (@selector (application:didFailToRegisterForRemoteNotificationsWithError:),                                    failedToRegisterForRemoteNotifications,                         "v@:@@");
            addMethod (@selector (application:didReceiveRemoteNotification:),                                                        didReceiveRemoteNotification,                                   "v@:@@");
            addMethod (@selector (application:didReceiveRemoteNotification:fetchCompletionHandler:),                                 didReceiveRemoteNotificationFetchCompletionHandler,             "v@:@@@");
            addMethod (@selector (application:handleActionWithIdentifier:forRemoteNotification:withResponseInfo:completionHandler:), handleActionForRemoteNotificationCompletionHandler,             "v@:@@@@@");
            addMethod (@selector (application:didReceiveLocalNotification:),                                                         didReceiveLocalNotification,                                    "v@:@@");
            addMethod (@selector (application:handleActionWithIdentifier:forLocalNotification:completionHandler:),                   handleActionForLocalNotificationCompletionHandler,              "v@:@@@@");
            addMethod (@selector (application:handleActionWithIdentifier:forLocalNotification:withResponseInfo:completionHandler:),  handleActionForLocalNotificationWithResponseCompletionHandler,  "v@:@@@@@");

          #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
            addMethod (@selector (userNotificationCenter:willPresentNotification:withCompletionHandler:),                            willPresentNotificationWithCompletionHandler,                   "v@:@@@");
            addMethod (@selector (userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:),                     didReceiveNotificationResponseWithCompletionHandler,            "v@:@@@");
          #endif

            registerClass();
        }

        //==============================================================================
        static PushNotificationsDelegate& getThis (id self)         { return *getIvar<PushNotificationsDelegate*> (self, "self"); }
        static void setThis (id self, PushNotificationsDelegate* d) { object_setInstanceVariable (self, "self", d); }

        //==============================================================================
        static void didRegisterUserNotificationSettings                           (id self, SEL, UIApplication*,
                                                                                UIUserNotificationSettings* settings)                        { getThis (self).didRegisterUserNotificationSettings (settings); }
        static void registeredForRemoteNotifications                              (id self, SEL, UIApplication*,
                                                                                NSData* deviceToken)                                         { getThis (self).registeredForRemoteNotifications (deviceToken); }

        static void failedToRegisterForRemoteNotifications                        (id self, SEL, UIApplication*,
                                                                                NSError* error)                                              { getThis (self).failedToRegisterForRemoteNotifications (error); }

        static void didReceiveRemoteNotification                                  (id self, SEL, UIApplication*,
                                                                                NSDictionary* userInfo)                                      { getThis (self).didReceiveRemoteNotification (userInfo); }

        static void didReceiveRemoteNotificationFetchCompletionHandler            (id self, SEL, UIApplication*,
                                                                                NSDictionary* userInfo,
                                                                                void (^completionHandler)(UIBackgroundFetchResult result))   { getThis (self).didReceiveRemoteNotificationFetchCompletionHandler (userInfo, completionHandler); }

        static void handleActionForRemoteNotificationCompletionHandler            (id self, SEL, UIApplication*,
                                                                                NSString* actionIdentifier,
                                                                                NSDictionary* userInfo,
                                                                                NSDictionary* responseInfo,
                                                                                void (^completionHandler)())                                 { getThis (self).handleActionForRemoteNotificationCompletionHandler (actionIdentifier, userInfo, responseInfo, completionHandler); }

        static void didReceiveLocalNotification                                   (id self, SEL, UIApplication*,
                                                                                UILocalNotification* notification)                           { getThis (self).didReceiveLocalNotification (notification); }

        static void handleActionForLocalNotificationCompletionHandler             (id self, SEL, UIApplication*,
                                                                                NSString* actionIdentifier,
                                                                                UILocalNotification* notification,
                                                                                void (^completionHandler)())                                 { getThis (self).handleActionForLocalNotificationCompletionHandler (actionIdentifier, notification, completionHandler); }

        static void handleActionForLocalNotificationWithResponseCompletionHandler (id self, SEL, UIApplication*,
                                                                                NSString* actionIdentifier,
                                                                                UILocalNotification* notification,
                                                                                NSDictionary* responseInfo,
                                                                                void (^completionHandler)())                                 { getThis (self). handleActionForLocalNotificationWithResponseCompletionHandler (actionIdentifier, notification, responseInfo, completionHandler); }

      #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
        static void willPresentNotificationWithCompletionHandler        (id self, SEL, UNUserNotificationCenter*,
                                                                         UNNotification* notification,
                                                                         void (^completionHandler)(UNNotificationPresentationOptions options))  { getThis (self).willPresentNotificationWithCompletionHandler (notification, completionHandler); }

        static void didReceiveNotificationResponseWithCompletionHandler (id self, SEL, UNUserNotificationCenter*,
                                                                         UNNotificationResponse* response,
                                                                         void (^completionHandler)())                                           { getThis (self).didReceiveNotificationResponseWithCompletionHandler (response, completionHandler); }
      #endif
    };

    //==============================================================================
    static Class& getClass()
    {
        static Class c;
        return c;
    }
};

//==============================================================================
bool PushNotifications::Notification::isValid() const noexcept
{
    const bool iOSEarlierThan10 = std::floor (NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max;

    if (iOSEarlierThan10)
        return title.isNotEmpty() && body.isNotEmpty() && category.isNotEmpty();

    return title.isNotEmpty() && body.isNotEmpty() && identifier.isNotEmpty() && category.isNotEmpty();
}

//==============================================================================
struct PushNotifications::Pimpl : private PushNotificationsDelegate
{
    Pimpl (PushNotifications& p)
        : owner (p)
    {
    }

    void requestPermissionsWithSettings (const PushNotifications::Settings& settingsToUse)
    {
        settings = settingsToUse;

        NSMutableSet* categories = [NSMutableSet setWithCapacity: (NSUInteger) settings.categories.size()];

        if (iOSEarlierThan10)
        {
            for (const auto& c : settings.categories)
            {
                UIUserNotificationCategory* category = (UIUserNotificationCategory*) PushNotificationsDelegateDetails::categoryToNSCategory (c, iOSEarlierThan10);
                [categories addObject: category];
            }

            UIUserNotificationType type = NSUInteger ((bool)settings.allowBadge << 0
                                                    | (bool)settings.allowSound << 1
                                                    | (bool)settings.allowAlert << 2);

            UIUserNotificationSettings* s = [UIUserNotificationSettings settingsForTypes: type categories: categories];
            [[UIApplication sharedApplication] registerUserNotificationSettings: s];
        }
       #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
        else
        {
            for (const auto& c : settings.categories)
            {
                UNNotificationCategory* category = (UNNotificationCategory*) PushNotificationsDelegateDetails::categoryToNSCategory (c, iOSEarlierThan10);
                [categories addObject: category];
            }

            UNAuthorizationOptions authOptions = NSUInteger ((bool)settings.allowBadge << 0
                                                           | (bool)settings.allowSound << 1
                                                           | (bool)settings.allowAlert << 2);

            [[UNUserNotificationCenter currentNotificationCenter] setNotificationCategories: categories];
            [[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions: authOptions
                                                                                completionHandler: ^(BOOL /*granted*/, NSError* /*error*/)
                                                                                {
                                                                                requestSettingsUsed();
                                                                                }];
        }
       #endif

        [[UIApplication sharedApplication] registerForRemoteNotifications];

        initialised = true;
    }

    void requestSettingsUsed()
    {
        if (iOSEarlierThan10)
        {
            UIUserNotificationSettings* s = [UIApplication sharedApplication].currentUserNotificationSettings;

            settings.allowBadge = s.types & UIUserNotificationTypeBadge;
            settings.allowSound = s.types & UIUserNotificationTypeSound;
            settings.allowAlert = s.types & UIUserNotificationTypeAlert;

            for (UIUserNotificationCategory *c in s.categories)
                settings.categories.add (PushNotificationsDelegateDetails::uiUserNotificationCategoryToCategory (c));

            owner.listeners.call (&PushNotifications::Listener::notificationSettingsReceived, settings);
        }
       #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
        else
        {

            [[UNUserNotificationCenter currentNotificationCenter] getNotificationSettingsWithCompletionHandler:
             ^(UNNotificationSettings* s)
             {
                 [[UNUserNotificationCenter currentNotificationCenter] getNotificationCategoriesWithCompletionHandler:
                  ^(NSSet<UNNotificationCategory*>* categories)
                  {
                      settings.allowBadge = s.badgeSetting == UNNotificationSettingEnabled;
                      settings.allowSound = s.soundSetting == UNNotificationSettingEnabled;
                      settings.allowAlert = s.alertSetting == UNNotificationSettingEnabled;

                      for (UNNotificationCategory* c in categories)
                          settings.categories.add (PushNotificationsDelegateDetails::unNotificationCategoryToCategory (c));

                      owner.listeners.call (&PushNotifications::Listener::notificationSettingsReceived, settings);
                  }
                 ];

             }];
        }
       #endif
    }

    bool areNotificationsEnabled() const { return true; }

    void sendLocalNotification (const Notification& n)
    {
        if (iOSEarlierThan10)
        {
            auto* notification = PushNotificationsDelegateDetails::juceNotificationToUILocalNotification (n);

            [[UIApplication sharedApplication] scheduleLocalNotification: notification];
            [notification release];
        }
       #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
        else
        {

            UNNotificationRequest* request = PushNotificationsDelegateDetails::juceNotificationToUNNotificationRequest (n);

            [[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest: request
                                                                   withCompletionHandler: ^(NSError* error)
                                                                                {
                                                                                jassert (error == nil);

                                                                                if (error != nil)
                                                                                NSLog (nsStringLiteral ("addNotificationRequest error: %@"), error);
                                                                                }];
        }
       #endif
    }

    void getDeliveredNotifications() const
    {
        if (iOSEarlierThan10)
        {
            // Not supported on this platform
            jassertfalse;
            owner.listeners.call (&Listener::deliveredNotificationsListReceived, {});
        }
       #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
        else
        {
            [[UNUserNotificationCenter currentNotificationCenter] getDeliveredNotificationsWithCompletionHandler:
             ^(NSArray<UNNotification*>* notifications)
             {
                Array<PushNotifications::Notification> notifs;

                for (UNNotification* n in notifications)
                    notifs.add (PushNotificationsDelegateDetails::unNotificationToJuceNotification (n));

                owner.listeners.call (&Listener::deliveredNotificationsListReceived, notifs);
             }];
        }
       #endif
    }

    void removeAllDeliveredNotifications()
    {
        if (iOSEarlierThan10)
        {
            // Not supported on this platform
            jassertfalse;
        }
        else
       #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
        {

            [[UNUserNotificationCenter currentNotificationCenter] removeAllDeliveredNotifications];
        }
       #endif
    }

    void removeDeliveredNotification (const String& identifier)
    {
        if (iOSEarlierThan10)
        {
            ignoreUnused (identifier);
            // Not supported on this platform
            jassertfalse;
        }
       #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
        else
        {

            NSArray<NSString*>* identifiers = [NSArray arrayWithObject: juceStringToNS (identifier)];

            [[UNUserNotificationCenter currentNotificationCenter] removeDeliveredNotificationsWithIdentifiers: identifiers];
        }
       #endif
    }

    void setupChannels (const Array<ChannelGroup>& groups, const Array<Channel>& channels)
    {
        ignoreUnused (groups, channels);
    }

    void getPendingLocalNotifications() const
    {
        if (iOSEarlierThan10)
        {
            Array<PushNotifications::Notification> notifs;

            for (UILocalNotification* n in [UIApplication sharedApplication].scheduledLocalNotifications)
                notifs.add (PushNotificationsDelegateDetails::uiLocalNotificationToJuceNotification (n));

            owner.listeners.call (&PushNotifications::Listener::pendingLocalNotificationsListReceived, notifs);
        }
       #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
        else
        {

            [[UNUserNotificationCenter currentNotificationCenter] getPendingNotificationRequestsWithCompletionHandler:
             ^(NSArray<UNNotificationRequest*>* requests)
             {
                 Array<PushNotifications::Notification> notifs;

                 for (UNNotificationRequest* r : requests)
                     notifs.add (PushNotificationsDelegateDetails::unNotificationRequestToJuceNotification (r));

                 owner.listeners.call (&PushNotifications::Listener::pendingLocalNotificationsListReceived, notifs);
             }
            ];
        }
       #endif
    }

    void removePendingLocalNotification (const String& identifier)
    {
        if (iOSEarlierThan10)
        {
            // Not supported on this platform
            jassertfalse;
        }
       #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
        else
        {

            NSArray<NSString*>* identifiers = [NSArray arrayWithObject: juceStringToNS (identifier)];

            [[UNUserNotificationCenter currentNotificationCenter] removePendingNotificationRequestsWithIdentifiers: identifiers];
        }
       #endif
    }

    void removeAllPendingLocalNotifications()
    {
        if (iOSEarlierThan10)
        {
            [[UIApplication sharedApplication] cancelAllLocalNotifications];
        }
       #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
        else
        {
            [[UNUserNotificationCenter currentNotificationCenter] removeAllPendingNotificationRequests];
        }
       #endif
    }

    String getDeviceToken()
    {
        // You need to call requestPermissionsWithSettings() first.
        jassert (initialised);

        return deviceToken;
    }

    //==============================================================================
    //PushNotificationsDelegate
    void didRegisterUserNotificationSettings (UIUserNotificationSettings*) override
    {
        requestSettingsUsed();
    }

    void registeredForRemoteNotifications (NSData* deviceTokenToUse) override
    {
        NSString* deviceTokenString = [[[[deviceTokenToUse description]
                                          stringByReplacingOccurrencesOfString: nsStringLiteral ("<") withString: nsStringLiteral ("")]
                                          stringByReplacingOccurrencesOfString: nsStringLiteral (">") withString: nsStringLiteral ("")]
                                          stringByReplacingOccurrencesOfString: nsStringLiteral (" ") withString: nsStringLiteral ("")];

        deviceToken = nsStringToJuce (deviceTokenString);

        owner.listeners.call (&PushNotifications::Listener::deviceTokenRefreshed, deviceToken);
    }

    void failedToRegisterForRemoteNotifications (NSError* error) override
    {
        ignoreUnused (error);

        deviceToken.clear();
    }

    void didReceiveRemoteNotification (NSDictionary* userInfo) override
    {
        auto n = PushNotificationsDelegateDetails::nsDictionaryToJuceNotification (userInfo);

        owner.listeners.call (&PushNotifications::Listener::handleNotification, false, n);
    }

    void didReceiveRemoteNotificationFetchCompletionHandler (NSDictionary* userInfo,
                                                             void (^completionHandler)(UIBackgroundFetchResult result)) override
    {
        auto n = PushNotificationsDelegateDetails::nsDictionaryToJuceNotification (userInfo);

        owner.listeners.call (&PushNotifications::Listener::handleNotification, false, n);

        completionHandler (UIBackgroundFetchResultNewData);
    }

    void handleActionForRemoteNotificationCompletionHandler (NSString* actionIdentifier,
                                                             NSDictionary* userInfo,
                                                             NSDictionary* responseInfo,
                                                             void (^completionHandler)()) override
    {
        auto n = PushNotificationsDelegateDetails::nsDictionaryToJuceNotification (userInfo);
        auto actionString = nsStringToJuce (actionIdentifier);
        auto response = PushNotificationsDelegateDetails::getUserResponseFromNSDictionary (responseInfo);

        owner.listeners.call (&PushNotifications::Listener::handleNotificationAction, false, n, actionString, response);

        completionHandler();
    }

    void didReceiveLocalNotification (UILocalNotification* notification) override
    {
        auto n = PushNotificationsDelegateDetails::uiLocalNotificationToJuceNotification (notification);

        owner.listeners.call (&PushNotifications::Listener::handleNotification, true, n);
    }

    void handleActionForLocalNotificationCompletionHandler (NSString* actionIdentifier,
                                                            UILocalNotification* notification,
                                                            void (^completionHandler)()) override
    {
        handleActionForLocalNotificationWithResponseCompletionHandler (actionIdentifier,
                                                                       notification,
                                                                       nil,
                                                                       completionHandler);
    }

    void handleActionForLocalNotificationWithResponseCompletionHandler (NSString* actionIdentifier,
                                                                        UILocalNotification* notification,
                                                                        NSDictionary* responseInfo,
                                                                        void (^completionHandler)()) override
    {
        auto n = PushNotificationsDelegateDetails::uiLocalNotificationToJuceNotification (notification);
        auto actionString = nsStringToJuce (actionIdentifier);
        auto response = PushNotificationsDelegateDetails::getUserResponseFromNSDictionary (responseInfo);

        owner.listeners.call (&PushNotifications::Listener::handleNotificationAction, true, n, actionString, response);

        completionHandler();
    }

  #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
    void willPresentNotificationWithCompletionHandler (UNNotification* notification,
                                                       void (^completionHandler)(UNNotificationPresentationOptions options)) override
    {
        NSUInteger options = NSUInteger ((int)settings.allowBadge << 0
                                       | (int)settings.allowSound << 1
                                       | (int)settings.allowAlert << 2);

        ignoreUnused (notification);

        completionHandler (options);
    }

    void didReceiveNotificationResponseWithCompletionHandler (UNNotificationResponse* response,
                                                              void (^completionHandler)()) override
    {
        const bool remote = [response.notification.request.trigger isKindOfClass: [UNPushNotificationTrigger class]];

        auto actionString = nsStringToJuce (response.actionIdentifier);

        if (actionString == "com.apple.UNNotificationDefaultActionIdentifier")
            actionString.clear();
        else if (actionString == "com.apple.UNNotificationDismissActionIdentifier")
            actionString = "com.juce.NotificationDeleted";

        auto n = PushNotificationsDelegateDetails::unNotificationToJuceNotification (response.notification);

        String responseString;

        if ([response isKindOfClass: [UNTextInputNotificationResponse class]])
        {
            UNTextInputNotificationResponse* textResponse = (UNTextInputNotificationResponse*)response;
            responseString = nsStringToJuce (textResponse.userText);
        }

        owner.listeners.call (&PushNotifications::Listener::handleNotificationAction, ! remote, n, actionString, responseString);

        completionHandler();
    }
  #endif

    void subscribeToTopic (const String& topic)     { ignoreUnused (topic); }
    void unsubscribeFromTopic (const String& topic) { ignoreUnused (topic); }

    void sendUpstreamMessage (const String& serverSenderId,
                              const String& collapseKey,
                              const String& messageId,
                              const String& messageType,
                              int timeToLive,
                              const StringPairArray& additionalData)
    {
        ignoreUnused (serverSenderId, collapseKey, messageId, messageType);
        ignoreUnused (timeToLive, additionalData);
    }

private:
    PushNotifications& owner;

    const bool iOSEarlierThan10 = std::floor (NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max;

    bool initialised = false;
    String deviceToken;

    PushNotifications::Settings settings;
};

} // 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
{

bool juce_handleXEmbedEvent (ComponentPeer*, void*);
Window juce_getCurrentFocusWindow (ComponentPeer*);

//==============================================================================
unsigned long juce_createKeyProxyWindow (ComponentPeer*);
void juce_deleteKeyProxyWindow (ComponentPeer*);

//==============================================================================
class XEmbedComponent::Pimpl  : private ComponentListener
{
public:
    enum
    {
        maxXEmbedVersionToSupport = 0
    };

    enum Flags
    {
        XEMBED_MAPPED  = (1<<0)
    };

    enum
    {
        XEMBED_EMBEDDED_NOTIFY        = 0,
        XEMBED_WINDOW_ACTIVATE        = 1,
        XEMBED_WINDOW_DEACTIVATE      = 2,
        XEMBED_REQUEST_FOCUS          = 3,
        XEMBED_FOCUS_IN               = 4,
        XEMBED_FOCUS_OUT              = 5,
        XEMBED_FOCUS_NEXT             = 6,
        XEMBED_FOCUS_PREV             = 7,
        XEMBED_MODALITY_ON            = 10,
        XEMBED_MODALITY_OFF           = 11,
        XEMBED_REGISTER_ACCELERATOR   = 12,
        XEMBED_UNREGISTER_ACCELERATOR = 13,
        XEMBED_ACTIVATE_ACCELERATOR   = 14
    };

    enum
    {
        XEMBED_FOCUS_CURRENT = 0,
        XEMBED_FOCUS_FIRST   = 1,
        XEMBED_FOCUS_LAST    = 2
    };

    //==============================================================================
    class SharedKeyWindow : public ReferenceCountedObject
    {
    public:
        typedef ReferenceCountedObjectPtr<SharedKeyWindow> Ptr;

        //==============================================================================
        Window getHandle()    { return keyProxy; }

        static Window getCurrentFocusWindow (ComponentPeer* peerToLookFor)
        {
            auto& keyWindows = getKeyWindows();

            if (peerToLookFor != nullptr)
                if (auto* foundKeyWindow = keyWindows[peerToLookFor])
                    return foundKeyWindow->keyProxy;

            return {};
        }

        static SharedKeyWindow::Ptr getKeyWindowForPeer (ComponentPeer* peerToLookFor)
        {
            jassert (peerToLookFor != nullptr);

            auto& keyWindows = getKeyWindows();
            auto foundKeyWindow = keyWindows[peerToLookFor];

            if (foundKeyWindow == nullptr)
            {
                foundKeyWindow = new SharedKeyWindow (peerToLookFor);
                keyWindows.set (peerToLookFor, foundKeyWindow);
            }

            return foundKeyWindow;
        }

    private:
        //==============================================================================
        friend struct ContainerDeletePolicy<SharedKeyWindow>;

        SharedKeyWindow (ComponentPeer* peerToUse)
            : keyPeer (peerToUse),
              keyProxy (juce_createKeyProxyWindow (keyPeer))
        {}

        ~SharedKeyWindow()
        {
            juce_deleteKeyProxyWindow (keyPeer);

            auto& keyWindows = getKeyWindows();
            keyWindows.remove (keyPeer);
        }

        ComponentPeer* keyPeer;
        Window keyProxy;

        static HashMap<ComponentPeer*, SharedKeyWindow*>& getKeyWindows()
        {
            // store a weak reference to the shared key windows
            static HashMap<ComponentPeer*, SharedKeyWindow*> keyWindows;
            return keyWindows;
        }
    };

public:
    //==============================================================================
    Pimpl (XEmbedComponent& parent, Window x11Window,
           bool wantsKeyboardFocus, bool isClientInitiated, bool shouldAllowResize)
        : owner (parent), atoms (x11display.display), clientInitiated (isClientInitiated),
          wantsFocus (wantsKeyboardFocus), allowResize (shouldAllowResize)
    {
        getWidgets().add (this);

        createHostWindow();

        if (clientInitiated)
            setClient (x11Window, true);

        owner.setWantsKeyboardFocus (wantsFocus);
        owner.addComponentListener (this);
    }

    ~Pimpl()
    {
        owner.removeComponentListener (this);
        setClient (0, true);

        if (host != 0)
        {
            auto dpy = getDisplay();
            XDestroyWindow (dpy, host);
            XSync (dpy, false);

            const long mask = NoEventMask | KeyPressMask | KeyReleaseMask
                            | EnterWindowMask | LeaveWindowMask | PointerMotionMask
                            | KeymapStateMask | ExposureMask | StructureNotifyMask
                            | FocusChangeMask;

            XEvent event;
            while (XCheckWindowEvent (dpy, host, mask, &event) == True)
            {}

            host = 0;
        }

        getWidgets().removeAllInstancesOf (this);
    }

    //==============================================================================
    void setClient (Window xembedClient, bool shouldReparent)
    {
        removeClient();

        if (xembedClient != 0)
        {
            auto dpy = getDisplay();

            client = xembedClient;

            // if the client has initiated the component then keep the clients size
            // otherwise the client should use the host's window' size
            if (clientInitiated)
            {
                configureNotify();
            }
            else
            {
                auto newBounds = getX11BoundsFromJuce();
                XResizeWindow (dpy, client, static_cast<unsigned int> (newBounds.getWidth()),
                                            static_cast<unsigned int> (newBounds.getHeight()));
            }

            XSelectInput (dpy, client, StructureNotifyMask | PropertyChangeMask | FocusChangeMask);
            getXEmbedMappedFlag();

            if (shouldReparent)
                XReparentWindow (dpy, client, host, 0, 0);

            if (supportsXembed)
                sendXEmbedEvent (CurrentTime, XEMBED_EMBEDDED_NOTIFY, 0, (long) host, xembedVersion);

            updateMapping();
        }
    }

    void focusGained (FocusChangeType changeType)
    {
        if (client != 0 && supportsXembed && wantsFocus)
        {
            updateKeyFocus();
            sendXEmbedEvent (CurrentTime, XEMBED_FOCUS_IN,
                             (changeType == focusChangedByTabKey ? XEMBED_FOCUS_FIRST : XEMBED_FOCUS_CURRENT));
        }
    }

    void focusLost (FocusChangeType)
    {
        if (client != 0 && supportsXembed && wantsFocus)
        {
            sendXEmbedEvent (CurrentTime, XEMBED_FOCUS_OUT);
            updateKeyFocus();
        }
    }

    void broughtToFront()
    {
        if (client != 0 && supportsXembed)
            sendXEmbedEvent (CurrentTime, XEMBED_WINDOW_ACTIVATE);
    }

    unsigned long getHostWindowID()
    {
        // You are using the client initiated version of the protocol. You cannot
        // retrieve the window id of the host. Please read the documentation for
        // the XEmebedComponent class.
        jassert (! clientInitiated);

        return host;
    }

private:
    //==============================================================================
    XEmbedComponent& owner;
    Window client = 0, host = 0;

    ScopedXDisplay x11display;
    Atoms atoms;

    bool clientInitiated;
    bool wantsFocus        = false;
    bool allowResize       = false;
    bool supportsXembed    = false;
    bool hasBeenMapped     = false;
    int xembedVersion      = maxXEmbedVersionToSupport;

    ComponentPeer* lastPeer = nullptr;
    SharedKeyWindow::Ptr keyWindow;

    //==============================================================================
    void componentParentHierarchyChanged (Component&) override   { peerChanged (owner.getPeer()); }
    void componentMovedOrResized (Component&, bool, bool) override
    {
        if (host != 0 && lastPeer != nullptr)
        {
            auto dpy = getDisplay();
            auto newBounds = getX11BoundsFromJuce();
            XWindowAttributes attr;

            if (XGetWindowAttributes (dpy, host, &attr))
            {
                Rectangle<int> currentBounds (attr.x, attr.y, attr.width, attr.height);
                if (currentBounds != newBounds)
                {
                    XMoveResizeWindow (dpy, host, newBounds.getX(), newBounds.getY(),
                                       static_cast<unsigned int> (newBounds.getWidth()),
                                       static_cast<unsigned int> (newBounds.getHeight()));
                }
            }

            if (client != 0 && XGetWindowAttributes (dpy, client, &attr))
            {
                Rectangle<int> currentBounds (attr.x, attr.y, attr.width, attr.height);

                if ((currentBounds.getWidth() != newBounds.getWidth()
                     || currentBounds.getHeight() != newBounds.getHeight()))
                {
                    XMoveResizeWindow (dpy, client, 0, 0,
                                       static_cast<unsigned int> (newBounds.getWidth()),
                                       static_cast<unsigned int> (newBounds.getHeight()));
                }
            }
        }
    }

    //==============================================================================
    void createHostWindow()
    {
        auto dpy = getDisplay();
        int defaultScreen = XDefaultScreen (dpy);
        Window root = RootWindow (dpy, defaultScreen);

        XSetWindowAttributes swa;
        swa.border_pixel = 0;
        swa.background_pixmap = None;
        swa.override_redirect = True;
        swa.event_mask = SubstructureNotifyMask | StructureNotifyMask | FocusChangeMask;

        host = XCreateWindow (dpy, root, 0, 0, 1, 1, 0, CopyFromParent,
                              InputOutput, CopyFromParent,
                              CWEventMask | CWBorderPixel | CWBackPixmap | CWOverrideRedirect,
                              &swa);
    }

    void removeClient()
    {
        if (client != 0)
        {
            auto dpy = getDisplay();
            XSelectInput (dpy, client, 0);

            keyWindow = nullptr;

            int defaultScreen = XDefaultScreen (dpy);
            Window root = RootWindow (dpy, defaultScreen);

            if (hasBeenMapped)
            {
                XUnmapWindow (dpy, client);
                hasBeenMapped = false;
            }

            XReparentWindow (dpy, client, root, 0, 0);
            client = 0;
        }
    }

    void updateMapping()
    {
        if (client != 0)
        {
            const bool shouldBeMapped = getXEmbedMappedFlag();

            if (shouldBeMapped != hasBeenMapped)
            {
                hasBeenMapped = shouldBeMapped;

                if (shouldBeMapped)
                    XMapWindow (getDisplay(), client);
                else
                    XUnmapWindow (getDisplay(), client);
            }
        }
    }

    Window getParentX11Window()
    {
        if (auto peer = owner.getPeer())
            return reinterpret_cast<Window> (peer->getNativeHandle());

        return {};
    }

    Display* getDisplay()   { return reinterpret_cast<Display*> (x11display.display); }

    //==============================================================================
    bool getXEmbedMappedFlag()
    {
        GetXProperty embedInfo (x11display.display, client, atoms.XembedInfo, 0, 2, false, atoms.XembedInfo);

        if (embedInfo.success && embedInfo.actualFormat == 32
             && embedInfo.numItems >= 2 && embedInfo.data != nullptr)
        {
            auto* buffer = (long*) embedInfo.data;

            supportsXembed = true;
            xembedVersion = jmin ((int) maxXEmbedVersionToSupport, (int) buffer[0]);

            return ((buffer[1] & XEMBED_MAPPED) != 0);
        }
        else
        {
            supportsXembed = false;
            xembedVersion = maxXEmbedVersionToSupport;
        }

        return true;
    }

    //==============================================================================
    void propertyChanged (const Atom& a)
    {
        if (a == atoms.XembedInfo)
            updateMapping();
    }

    void configureNotify()
    {
        XWindowAttributes attr;
        auto dpy = getDisplay();

        if (XGetWindowAttributes (dpy, client, &attr))
        {
            XWindowAttributes hostAttr;

            if (XGetWindowAttributes (dpy, host, &hostAttr))
                if (attr.width != hostAttr.width || attr.height != hostAttr.height)
                    XResizeWindow (dpy, host, (unsigned int) attr.width, (unsigned int) attr.height);

            // as the client window is not on any screen yet, we need to guess
            // on which screen it might appear to get a scaling factor :-(
            auto& displays = Desktop::getInstance().getDisplays();
            auto* peer = owner.getPeer();
            const double scale = (peer != nullptr ? displays.getDisplayContaining (peer->getBounds().getCentre())
                                  : displays.getMainDisplay()).scale;

            Point<int> topLeftInPeer
                = (peer != nullptr ? peer->getComponent().getLocalPoint (&owner, Point<int> (0, 0))
                   : owner.getBounds().getTopLeft());

            Rectangle<int> newBounds (topLeftInPeer.getX(), topLeftInPeer.getY(),
                                      static_cast<int> (static_cast<double> (attr.width)  / scale),
                                      static_cast<int> (static_cast<double> (attr.height) / scale));

            if (peer != nullptr)
                newBounds = owner.getLocalArea (&peer->getComponent(), newBounds);

            jassert (newBounds.getX() == 0 && newBounds.getY() == 0);

            if (newBounds != owner.getLocalBounds())
                owner.setSize (newBounds.getWidth(), newBounds.getHeight());
        }
    }

    void peerChanged (ComponentPeer* newPeer)
    {
        if (newPeer != lastPeer)
        {
            if (lastPeer != nullptr)
                keyWindow = nullptr;

            auto dpy = getDisplay();
            Window rootWindow = RootWindow (dpy, DefaultScreen (dpy));
            Rectangle<int> newBounds = getX11BoundsFromJuce();

            if (newPeer == nullptr)
                XUnmapWindow (dpy, host);

            Window newParent = (newPeer != nullptr ? getParentX11Window() : rootWindow);
            XReparentWindow (dpy, host, newParent, newBounds.getX(), newBounds.getY());

            lastPeer = newPeer;

            if (newPeer != nullptr)
            {
                if (wantsFocus)
                {
                    keyWindow = SharedKeyWindow::getKeyWindowForPeer (newPeer);
                    updateKeyFocus();
                }

                componentMovedOrResized (owner, true, true);
                XMapWindow (dpy, host);

                broughtToFront();
            }
        }
    }

    void updateKeyFocus()
    {
        if (lastPeer != nullptr && lastPeer->isFocused())
            XSetInputFocus (getDisplay(), getCurrentFocusWindow (lastPeer), RevertToParent, CurrentTime);
    }

    //==============================================================================
    void handleXembedCmd (const ::Time& /*xTime*/, long opcode, long /*detail*/, long /*data1*/, long /*data2*/)
    {
        switch (opcode)
        {
            case XEMBED_REQUEST_FOCUS:
                if (wantsFocus)
                    owner.grabKeyboardFocus();
                break;

            case XEMBED_FOCUS_NEXT:
                if (wantsFocus)
                    owner.moveKeyboardFocusToSibling (true);
                break;

            case XEMBED_FOCUS_PREV:
                if (wantsFocus)
                    owner.moveKeyboardFocusToSibling (false);
                break;
        }
    }

    bool handleX11Event (const XEvent& e)
    {
        if (e.xany.window == client && client != 0)
        {
            switch (e.type)
            {
                case PropertyNotify:
                    propertyChanged (e.xproperty.atom);
                    return true;

                case ConfigureNotify:
                    if (allowResize)
                        configureNotify();
                    else
                        MessageManager::callAsync([this] () {componentMovedOrResized (owner, true, true);});

                    return true;
            }
        }
        else if (e.xany.window == host && host != 0)
        {
            switch (e.type)
            {
                case ReparentNotify:
                    if (e.xreparent.parent == host && e.xreparent.window != client)
                    {
                        setClient (e.xreparent.window, false);
                        return true;
                    }
                    break;

                case CreateNotify:
                    if (e.xcreatewindow.parent != e.xcreatewindow.window && e.xcreatewindow.parent == host && e.xcreatewindow.window != client)
                    {
                        setClient (e.xcreatewindow.window, false);
                        return true;
                    }
                    break;

                case GravityNotify:
                    componentMovedOrResized (owner, true, true);
                    return true;

                case ClientMessage:
                    if (e.xclient.message_type == atoms.XembedMsgType && e.xclient.format == 32)
                    {
                        handleXembedCmd ((::Time) e.xclient.data.l[0], e.xclient.data.l[1],
                                         e.xclient.data.l[2], e.xclient.data.l[3],
                                         e.xclient.data.l[4]);

                        return true;
                    }
                    break;
            }
        }

        return false;
    }

    void sendXEmbedEvent (const ::Time& xTime, long opcode,
                          long opcodeMinor = 0, long data1 = 0, long data2 = 0)
    {
        XClientMessageEvent msg;
        auto dpy = getDisplay();

        ::memset (&msg, 0, sizeof (XClientMessageEvent));
        msg.window = client;
        msg.type = ClientMessage;
        msg.message_type = atoms.XembedMsgType;
        msg.format = 32;
        msg.data.l[0] = (long) xTime;
        msg.data.l[1] = opcode;
        msg.data.l[2] = opcodeMinor;
        msg.data.l[3] = data1;
        msg.data.l[4] = data2;

        XSendEvent (dpy, client, False, NoEventMask, (XEvent*) &msg);
        XSync (dpy, False);
    }

    Rectangle<int> getX11BoundsFromJuce()
    {
        if (auto* peer = owner.getPeer())
        {
            auto r = peer->getComponent().getLocalArea (&owner, owner.getLocalBounds());
            auto scale = Desktop::getInstance().getDisplays().getDisplayContaining (peer->localToGlobal (r.getCentre())).scale;

            return r * scale;
        }

        return owner.getLocalBounds();
    }

    //==============================================================================
    friend bool juce::juce_handleXEmbedEvent (ComponentPeer*, void*);
    friend unsigned long juce::juce_getCurrentFocusWindow (ComponentPeer*);

    static Array<Pimpl*>& getWidgets()
    {
        static Array<Pimpl*> i;
        return i;
    }

    static bool dispatchX11Event (ComponentPeer* p, const XEvent* eventArg)
    {
        if (eventArg != nullptr)
        {
            auto& e = *eventArg;

            if (auto w = e.xany.window)
                for (auto* widget : getWidgets())
                    if (w == widget->host || w == widget->client)
                        return widget->handleX11Event (e);
        }
        else
        {
            for (auto* widget : getWidgets())
                if (widget->owner.getPeer() == p)
                    widget->peerChanged (nullptr);
        }

        return false;
    }

    static Window getCurrentFocusWindow (ComponentPeer* p)
    {
        if (p != nullptr)
        {
            for (auto* widget : getWidgets())
                if (widget->owner.getPeer() == p && widget->owner.hasKeyboardFocus (false))
                    return widget->client;
        }

        return SharedKeyWindow::getCurrentFocusWindow (p);
    }
};

//==============================================================================
XEmbedComponent::XEmbedComponent (bool wantsKeyboardFocus, bool allowForeignWidgetToResizeComponent)
    : pimpl (new Pimpl (*this, 0, wantsKeyboardFocus, false, allowForeignWidgetToResizeComponent))
{
    setOpaque (true);
}

XEmbedComponent::XEmbedComponent (unsigned long wID, bool wantsKeyboardFocus, bool allowForeignWidgetToResizeComponent)
    : pimpl (new Pimpl (*this, wID, wantsKeyboardFocus, true, allowForeignWidgetToResizeComponent))
{
    setOpaque (true);
}

XEmbedComponent::~XEmbedComponent() {}

void XEmbedComponent::paint (Graphics& g)
{
    g.fillAll (Colours::lightgrey);
}

void XEmbedComponent::focusGained (FocusChangeType changeType)     { pimpl->focusGained (changeType); }
void XEmbedComponent::focusLost   (FocusChangeType changeType)     { pimpl->focusLost   (changeType); }
void XEmbedComponent::broughtToFront()                             { pimpl->broughtToFront(); }
unsigned long XEmbedComponent::getHostWindowID()                   { return pimpl->getHostWindowID(); }

//==============================================================================
bool juce_handleXEmbedEvent (ComponentPeer* p, void* e)
{
    return XEmbedComponent::Pimpl::dispatchX11Event (p, reinterpret_cast<const XEvent*> (e));
}

unsigned long juce_getCurrentFocusWindow (ComponentPeer* peer)
{
    return (unsigned long) XEmbedComponent::Pimpl::getCurrentFocusWindow (peer);
}

} // 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.

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

namespace juce
{

extern int64 getMouseEventTime();

JUCE_DECLARE_UUID_GETTER (IOleObject,       "00000112-0000-0000-C000-000000000046")
JUCE_DECLARE_UUID_GETTER (IOleWindow,       "00000114-0000-0000-C000-000000000046")
JUCE_DECLARE_UUID_GETTER (IOleInPlaceSite,  "00000119-0000-0000-C000-000000000046")

namespace ActiveXHelpers
{
    //==============================================================================
    struct JuceIStorage   : public ComBaseClassHelper<IStorage>
    {
        JuceIStorage() {}

        JUCE_COMRESULT CreateStream (const WCHAR*, DWORD, DWORD, DWORD, IStream**)           { return E_NOTIMPL; }
        JUCE_COMRESULT OpenStream (const WCHAR*, void*, DWORD, DWORD, IStream**)             { return E_NOTIMPL; }
        JUCE_COMRESULT CreateStorage (const WCHAR*, DWORD, DWORD, DWORD, IStorage**)         { return E_NOTIMPL; }
        JUCE_COMRESULT OpenStorage (const WCHAR*, IStorage*, DWORD, SNB, DWORD, IStorage**)  { return E_NOTIMPL; }
        JUCE_COMRESULT CopyTo (DWORD, IID const*, SNB, IStorage*)                            { return E_NOTIMPL; }
        JUCE_COMRESULT MoveElementTo (const OLECHAR*,IStorage*, const OLECHAR*, DWORD)       { return E_NOTIMPL; }
        JUCE_COMRESULT Commit (DWORD)                                                        { return E_NOTIMPL; }
        JUCE_COMRESULT Revert()                                                              { return E_NOTIMPL; }
        JUCE_COMRESULT EnumElements (DWORD, void*, DWORD, IEnumSTATSTG**)                    { return E_NOTIMPL; }
        JUCE_COMRESULT DestroyElement (const OLECHAR*)                                       { return E_NOTIMPL; }
        JUCE_COMRESULT RenameElement (const WCHAR*, const WCHAR*)                            { return E_NOTIMPL; }
        JUCE_COMRESULT SetElementTimes (const WCHAR*, FILETIME const*, FILETIME const*, FILETIME const*)    { return E_NOTIMPL; }
        JUCE_COMRESULT SetClass (REFCLSID)                                                   { return S_OK; }
        JUCE_COMRESULT SetStateBits (DWORD, DWORD)                                           { return E_NOTIMPL; }
        JUCE_COMRESULT Stat (STATSTG*, DWORD)                                                { return E_NOTIMPL; }
    };

    //==============================================================================
    struct JuceOleInPlaceFrame   : public ComBaseClassHelper<IOleInPlaceFrame>
    {
        JuceOleInPlaceFrame (HWND hwnd)   : window (hwnd) {}

        JUCE_COMRESULT GetWindow (HWND* lphwnd)                                 { *lphwnd = window; return S_OK; }
        JUCE_COMRESULT ContextSensitiveHelp (BOOL)                              { return E_NOTIMPL; }
        JUCE_COMRESULT GetBorder (LPRECT)                                       { return E_NOTIMPL; }
        JUCE_COMRESULT RequestBorderSpace (LPCBORDERWIDTHS)                     { return E_NOTIMPL; }
        JUCE_COMRESULT SetBorderSpace (LPCBORDERWIDTHS)                         { return E_NOTIMPL; }
        JUCE_COMRESULT SetActiveObject (IOleInPlaceActiveObject* a, LPCOLESTR)  { activeObject = a; return S_OK; }
        JUCE_COMRESULT InsertMenus (HMENU, LPOLEMENUGROUPWIDTHS)                { return E_NOTIMPL; }
        JUCE_COMRESULT SetMenu (HMENU, HOLEMENU, HWND)                          { return S_OK; }
        JUCE_COMRESULT RemoveMenus (HMENU)                                      { return E_NOTIMPL; }
        JUCE_COMRESULT SetStatusText (LPCOLESTR)                                { return S_OK; }
        JUCE_COMRESULT EnableModeless (BOOL)                                    { return S_OK; }
        JUCE_COMRESULT TranslateAccelerator (LPMSG, WORD)                       { return E_NOTIMPL; }

        HRESULT OfferKeyTranslation (LPMSG lpmsg)
        {
            if (activeObject != nullptr)
                return activeObject->TranslateAcceleratorW (lpmsg);

            return S_FALSE;
        }

        HWND window;
        ComSmartPtr<IOleInPlaceActiveObject> activeObject;
    };

    //==============================================================================
    struct JuceIOleInPlaceSite   : public ComBaseClassHelper<IOleInPlaceSite>
    {
        JuceIOleInPlaceSite (HWND hwnd)
            : window (hwnd),
              frame (new JuceOleInPlaceFrame (window))
        {}

        ~JuceIOleInPlaceSite()
        {
            frame->Release();
        }

        JUCE_COMRESULT GetWindow (HWND* lphwnd)      { *lphwnd = window; return S_OK; }
        JUCE_COMRESULT ContextSensitiveHelp (BOOL)   { return E_NOTIMPL; }
        JUCE_COMRESULT CanInPlaceActivate()          { return S_OK; }
        JUCE_COMRESULT OnInPlaceActivate()           { return S_OK; }
        JUCE_COMRESULT OnUIActivate()                { return S_OK; }

        JUCE_COMRESULT GetWindowContext (LPOLEINPLACEFRAME* lplpFrame, LPOLEINPLACEUIWINDOW* lplpDoc, LPRECT, LPRECT, LPOLEINPLACEFRAMEINFO lpFrameInfo)
        {
            /* Note: if you call AddRef on the frame here, then some types of object (e.g. web browser control) cause leaks..
               If you don't call AddRef then others crash (e.g. QuickTime).. Bit of a catch-22, so letting it leak is probably preferable.
            */
            if (lplpFrame != nullptr) { frame->AddRef(); *lplpFrame = frame; }
            if (lplpDoc != nullptr)   *lplpDoc = nullptr;
            lpFrameInfo->fMDIApp = FALSE;
            lpFrameInfo->hwndFrame = window;
            lpFrameInfo->haccel = 0;
            lpFrameInfo->cAccelEntries = 0;
            return S_OK;
        }

        JUCE_COMRESULT Scroll (SIZE)                 { return E_NOTIMPL; }
        JUCE_COMRESULT OnUIDeactivate (BOOL)         { return S_OK; }
        JUCE_COMRESULT OnInPlaceDeactivate()         { return S_OK; }
        JUCE_COMRESULT DiscardUndoState()            { return E_NOTIMPL; }
        JUCE_COMRESULT DeactivateAndUndo()           { return E_NOTIMPL; }
        JUCE_COMRESULT OnPosRectChange (LPCRECT)     { return S_OK; }

        LRESULT offerEventToActiveXControl (::MSG& msg)
        {
            if (frame != nullptr)
                return frame->OfferKeyTranslation (&msg);

            return S_FALSE;
        }

        HWND window;
        JuceOleInPlaceFrame* frame;
    };

    //==============================================================================
    struct JuceIOleClientSite  : public ComBaseClassHelper<IOleClientSite>
    {
        JuceIOleClientSite (HWND window)  : inplaceSite (new JuceIOleInPlaceSite (window))
        {}

        ~JuceIOleClientSite()
        {
            inplaceSite->Release();
        }

        JUCE_COMRESULT QueryInterface (REFIID type, void** result)
        {
            if (type == __uuidof (IOleInPlaceSite))
            {
                inplaceSite->AddRef();
                *result = static_cast<IOleInPlaceSite*> (inplaceSite);
                return S_OK;
            }

            return ComBaseClassHelper <IOleClientSite>::QueryInterface (type, result);
        }

        JUCE_COMRESULT SaveObject()                                  { return E_NOTIMPL; }
        JUCE_COMRESULT GetMoniker (DWORD, DWORD, IMoniker**)         { return E_NOTIMPL; }
        JUCE_COMRESULT GetContainer (LPOLECONTAINER* ppContainer)    { *ppContainer = nullptr; return E_NOINTERFACE; }
        JUCE_COMRESULT ShowObject()                                  { return S_OK; }
        JUCE_COMRESULT OnShowWindow (BOOL)                           { return E_NOTIMPL; }
        JUCE_COMRESULT RequestNewObjectLayout()                      { return E_NOTIMPL; }

        LRESULT offerEventToActiveXControl (::MSG& msg)
        {
            if (inplaceSite != nullptr)
                return inplaceSite->offerEventToActiveXControl (msg);

            return S_FALSE;
        }

        JuceIOleInPlaceSite* inplaceSite;
    };

    //==============================================================================
    static Array<ActiveXControlComponent*> activeXComps;

    static inline HWND getHWND (const ActiveXControlComponent* const component)
    {
        HWND hwnd = {};
        const IID iid = __uuidof (IOleWindow);

        if (auto* window = (IOleWindow*) component->queryInterface (&iid))
        {
            window->GetWindow (&hwnd);
            window->Release();
        }

        return hwnd;
    }

    static inline void offerActiveXMouseEventToPeer (ComponentPeer* peer, HWND hwnd, UINT message, LPARAM lParam)
    {
        switch (message)
        {
            case WM_MOUSEMOVE:
            case WM_LBUTTONDOWN:
            case WM_MBUTTONDOWN:
            case WM_RBUTTONDOWN:
            case WM_LBUTTONUP:
            case WM_MBUTTONUP:
            case WM_RBUTTONUP:
            {
                RECT activeXRect, peerRect;
                GetWindowRect (hwnd, &activeXRect);
                GetWindowRect ((HWND) peer->getNativeHandle(), &peerRect);

                peer->handleMouseEvent (MouseInputSource::InputSourceType::mouse,
                                        { (float) (GET_X_LPARAM (lParam) + activeXRect.left - peerRect.left),
                                          (float) (GET_Y_LPARAM (lParam) + activeXRect.top  - peerRect.top) },
                                        ModifierKeys::getCurrentModifiersRealtime(),
                                        MouseInputSource::invalidPressure,
                                        MouseInputSource::invalidOrientation,
                                        getMouseEventTime());
                break;
            }

            default:
                break;
        }
    }
}

//==============================================================================
class ActiveXControlComponent::Pimpl  : public ComponentMovementWatcher
{
public:
    Pimpl (HWND hwnd, ActiveXControlComponent& activeXComp)
        : ComponentMovementWatcher (&activeXComp),
          owner (activeXComp),
          storage (new ActiveXHelpers::JuceIStorage()),
          clientSite (new ActiveXHelpers::JuceIOleClientSite (hwnd))
    {
    }

    ~Pimpl()
    {
        if (control != nullptr)
        {
            control->Close (OLECLOSE_NOSAVE);
            control->Release();
        }

        clientSite->Release();
        storage->Release();
    }

    void setControlBounds (Rectangle<int> newBounds) const
    {
        if (controlHWND != 0)
            MoveWindow (controlHWND, newBounds.getX(), newBounds.getY(), newBounds.getWidth(), newBounds.getHeight(), TRUE);
    }

    void setControlVisible (bool shouldBeVisible) const
    {
        if (controlHWND != 0)
            ShowWindow (controlHWND, shouldBeVisible ? SW_SHOWNA : SW_HIDE);
    }

    //==============================================================================
    void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) override
    {
        if (auto* peer = owner.getTopLevelComponent()->getPeer())
            setControlBounds (peer->getAreaCoveredBy(owner));
    }

    void componentPeerChanged() override
    {
        componentMovedOrResized (true, true);
    }

    void componentVisibilityChanged() override
    {
        setControlVisible (owner.isShowing());
        componentPeerChanged();
    }

    // intercepts events going to an activeX control, so we can sneakily use the mouse events
    static LRESULT CALLBACK activeXHookWndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
        for (auto* ax : ActiveXHelpers::activeXComps)
        {
            if (ax->control != nullptr && ax->control->controlHWND == hwnd)
            {
                switch (message)
                {
                    case WM_MOUSEMOVE:
                    case WM_LBUTTONDOWN:
                    case WM_MBUTTONDOWN:
                    case WM_RBUTTONDOWN:
                    case WM_LBUTTONUP:
                    case WM_MBUTTONUP:
                    case WM_RBUTTONUP:
                    case WM_LBUTTONDBLCLK:
                    case WM_MBUTTONDBLCLK:
                    case WM_RBUTTONDBLCLK:
                        if (ax->isShowing())
                        {
                            if (auto* peer = ax->getPeer())
                            {
                                ActiveXHelpers::offerActiveXMouseEventToPeer (peer, hwnd, message, lParam);

                                if (! ax->areMouseEventsAllowed())
                                    return 0;
                            }
                        }
                        break;

                    default:
                        break;
                }

                return CallWindowProc (ax->control->originalWndProc, hwnd, message, wParam, lParam);
            }
        }

        return DefWindowProc (hwnd, message, wParam, lParam);
    }

    ActiveXControlComponent& owner;
    HWND controlHWND = {};
    IStorage* storage = nullptr;
    ActiveXHelpers::JuceIOleClientSite* clientSite = nullptr;
    IOleObject* control = nullptr;
    WNDPROC originalWndProc = 0;
};

//==============================================================================
ActiveXControlComponent::ActiveXControlComponent()
{
    ActiveXHelpers::activeXComps.add (this);
}

ActiveXControlComponent::~ActiveXControlComponent()
{
    deleteControl();
    ActiveXHelpers::activeXComps.removeFirstMatchingValue (this);
}

void ActiveXControlComponent::paint (Graphics& g)
{
    if (control == nullptr)
        g.fillAll (Colours::lightgrey);
}

bool ActiveXControlComponent::createControl (const void* controlIID)
{
    deleteControl();

    if (auto* peer = getPeer())
    {
        auto controlBounds = peer->getAreaCoveredBy (*this);
        auto hwnd = (HWND) peer->getNativeHandle();

        ScopedPointer<Pimpl> newControl (new Pimpl (hwnd, *this));

        HRESULT hr = OleCreate (*(const IID*) controlIID, __uuidof (IOleObject), 1 /*OLERENDER_DRAW*/, 0,
                                newControl->clientSite, newControl->storage,
                                (void**) &(newControl->control));

        if (hr == S_OK)
        {
            newControl->control->SetHostNames (L"JUCE", 0);

            if (OleSetContainedObject (newControl->control, TRUE) == S_OK)
            {
                RECT rect;
                rect.left   = controlBounds.getX();
                rect.top    = controlBounds.getY();
                rect.right  = controlBounds.getRight();
                rect.bottom = controlBounds.getBottom();

                if (newControl->control->DoVerb (OLEIVERB_SHOW, 0, newControl->clientSite, 0, hwnd, &rect) == S_OK)
                {
                    control = newControl;
                    control->controlHWND = ActiveXHelpers::getHWND (this);

                    if (control->controlHWND != 0)
                    {
                        control->setControlBounds (controlBounds);

                        control->originalWndProc = (WNDPROC) GetWindowLongPtr ((HWND) control->controlHWND, GWLP_WNDPROC);
                        SetWindowLongPtr ((HWND) control->controlHWND, GWLP_WNDPROC, (LONG_PTR) Pimpl::activeXHookWndProc);
                    }

                    return true;
                }
            }
        }
    }
    else
    {
        // the component must have already been added to a real window when you call this!
        jassertfalse;
    }

    return false;
}

void ActiveXControlComponent::deleteControl()
{
    control = nullptr;
}

void* ActiveXControlComponent::queryInterface (const void* iid) const
{
    void* result = nullptr;

    if (control != nullptr && control->control != nullptr
         && SUCCEEDED (control->control->QueryInterface (*(const IID*) iid, &result)))
        return result;

    return nullptr;
}

void ActiveXControlComponent::setMouseEventsAllowed (const bool eventsCanReachControl)
{
    mouseEventsAllowed = eventsCanReachControl;
}

intptr_t ActiveXControlComponent::offerEventToActiveXControl (void* ptr)
{
    if (control != nullptr && control->clientSite != nullptr)
        return (intptr_t) control->clientSite->offerEventToActiveXControl (*reinterpret_cast<::MSG*> (ptr));

    return S_FALSE;
}

intptr_t ActiveXControlComponent::offerEventToActiveXControlStatic (void* ptr)
{
    for (auto* ax : ActiveXHelpers::activeXComps)
    {
        auto result = ax->offerEventToActiveXControl (ptr);

        if (result != S_FALSE)
            return result;
    }

    return S_FALSE;
}

LRESULT juce_offerEventToActiveXControl (::MSG& msg)
{
    if (msg.message >= WM_KEYFIRST && msg.message <= WM_KEYLAST)
        return ActiveXControlComponent::offerEventToActiveXControlStatic (&msg);

    return S_FALSE;
}

} // 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
{

WebBrowserComponent::WebBrowserComponent (const bool unloadPageWhenBrowserIsHidden_)
    : browser (nullptr),
      blankPageShown (false),
      unloadPageWhenBrowserIsHidden (unloadPageWhenBrowserIsHidden_)
{
    // Unfortunately, WebBrowserComponent is not implemented for Android yet!
    // This is just a stub implementation without any useful functionality.
    jassertfalse;

    setOpaque (true);
}

WebBrowserComponent::~WebBrowserComponent()
{
}

//==============================================================================
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;
}

void WebBrowserComponent::stop()
{
}

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

}

void WebBrowserComponent::goForward()
{
    lastURL.clear();

}

void WebBrowserComponent::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()
{
}

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

void WebBrowserComponent::focusGained (FocusChangeType)
{
}

void WebBrowserComponent::clearCookies()
{
}

} // namespace juce