text: /*
    ==============================================================================
  
     This file is part of the JUCE library.
     Copyright (c) 2020 - Raw Material Software Limited
  
     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 6 End-User License
     Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
  
     End User License Agreement: www.juce.com/juce-6-licence
     Privacy Policy: www.juce.com/juce-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*);
  
  //==============================================================================
  ::Window juce_createKeyProxyWindow (ComponentPeer*);
  void juce_deleteKeyProxyWindow (::Window);
  
  //==============================================================================
  enum
  {
      maxXEmbedVersionToSupport = 0
  };
  
  enum
  {
      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 XEmbedComponent::Pimpl  : private ComponentListener
  {
  public:
      //==============================================================================
      struct SharedKeyWindow : public ReferenceCountedObject
      {
          SharedKeyWindow (ComponentPeer* peerToUse)
              : keyPeer (peerToUse),
                keyProxy (juce_createKeyProxyWindow (keyPeer))
          {}
  
          ~SharedKeyWindow()
          {
              juce_deleteKeyProxyWindow (keyProxy);
  
              auto& keyWindows = getKeyWindows();
              keyWindows.remove (keyPeer);
          }
  
          using Ptr = ReferenceCountedObjectPtr<SharedKeyWindow>;
  
          //==============================================================================
          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:
          //==============================================================================
          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),
            infoAtom (XWindowSystem::getInstance()->getAtoms().XembedInfo),
            messageTypeAtom (XWindowSystem::getInstance()->getAtoms().XembedMsgType),
            clientInitiated (isClientInitiated),
            wantsFocus (wantsKeyboardFocus),
            allowResize (shouldAllowResize)
      {
          getWidgets().add (this);
  
          createHostWindow();
  
          if (clientInitiated)
              setClient (x11Window, true);
  
          owner.setWantsKeyboardFocus (wantsFocus);
          owner.addComponentListener (this);
      }
  
      ~Pimpl() override
      {
          owner.removeComponentListener (this);
          setClient (0, true);
  
          if (host != 0)
          {
              auto dpy = getDisplay();
  
              X11Symbols::getInstance()->xDestroyWindow (dpy, host);
              X11Symbols::getInstance()->xSync (dpy, false);
  
              auto mask = NoEventMask | KeyPressMask | KeyReleaseMask
                        | EnterWindowMask | LeaveWindowMask | PointerMotionMask
                        | KeymapStateMask | ExposureMask | StructureNotifyMask
                        | FocusChangeMask;
  
              XEvent event;
              while (X11Symbols::getInstance()->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();
                  X11Symbols::getInstance()->xResizeWindow (dpy, client, static_cast<unsigned int> (newBounds.getWidth()),
                                                            static_cast<unsigned int> (newBounds.getHeight()));
              }
  
              auto eventMask = StructureNotifyMask | PropertyChangeMask | FocusChangeMask;
  
              XWindowAttributes clientAttr;
              X11Symbols::getInstance()->xGetWindowAttributes (dpy, client, &clientAttr);
  
              if ((eventMask & clientAttr.your_event_mask) != eventMask)
                  X11Symbols::getInstance()->xSelectInput (dpy, client, clientAttr.your_event_mask | eventMask);
  
              getXEmbedMappedFlag();
  
              if (shouldReparent)
                  X11Symbols::getInstance()->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;
      Atom infoAtom, messageTypeAtom;
  
      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 (X11Symbols::getInstance()->xGetWindowAttributes (dpy, host, &attr))
              {
                  Rectangle<int> currentBounds (attr.x, attr.y, attr.width, attr.height);
                  if (currentBounds != newBounds)
                  {
                      X11Symbols::getInstance()->xMoveResizeWindow (dpy, host, newBounds.getX(), newBounds.getY(),
                                                                    static_cast<unsigned int> (newBounds.getWidth()),
                                                                    static_cast<unsigned int> (newBounds.getHeight()));
                  }
              }
  
              if (client != 0 && X11Symbols::getInstance()->xGetWindowAttributes (dpy, client, &attr))
              {
                  Rectangle<int> currentBounds (attr.x, attr.y, attr.width, attr.height);
  
                  if ((currentBounds.getWidth() != newBounds.getWidth()
                       || currentBounds.getHeight() != newBounds.getHeight()))
                  {
                      X11Symbols::getInstance()->xMoveResizeWindow (dpy, client, 0, 0,
                                                                    static_cast<unsigned int> (newBounds.getWidth()),
                                                                    static_cast<unsigned int> (newBounds.getHeight()));
                  }
              }
          }
      }
  
      //==============================================================================
      void createHostWindow()
      {
          auto dpy = getDisplay();
          int defaultScreen = X11Symbols::getInstance()->xDefaultScreen (dpy);
          Window root = X11Symbols::getInstance()->xRootWindow (dpy, defaultScreen);
  
          XSetWindowAttributes swa;
          swa.border_pixel = 0;
          swa.background_pixmap = None;
          swa.override_redirect = True;
          swa.event_mask = SubstructureNotifyMask | StructureNotifyMask | FocusChangeMask;
  
          host = X11Symbols::getInstance()->xCreateWindow (dpy, root, 0, 0, 1, 1, 0, CopyFromParent,
                                                           InputOutput, CopyFromParent,
                                                           CWEventMask | CWBorderPixel | CWBackPixmap | CWOverrideRedirect,
                                                           &swa);
      }
  
      void removeClient()
      {
          if (client != 0)
          {
              auto dpy = getDisplay();
              X11Symbols::getInstance()->xSelectInput (dpy, client, 0);
  
              keyWindow = nullptr;
  
              int defaultScreen = X11Symbols::getInstance()->xDefaultScreen (dpy);
              Window root = X11Symbols::getInstance()->xRootWindow (dpy, defaultScreen);
  
              if (hasBeenMapped)
              {
                  X11Symbols::getInstance()->xUnmapWindow (dpy, client);
                  hasBeenMapped = false;
              }
  
              X11Symbols::getInstance()->xReparentWindow (dpy, client, root, 0, 0);
              client = 0;
  
              X11Symbols::getInstance()->xSync (dpy, False);
          }
      }
  
      void updateMapping()
      {
          if (client != 0)
          {
              const bool shouldBeMapped = getXEmbedMappedFlag();
  
              if (shouldBeMapped != hasBeenMapped)
              {
                  hasBeenMapped = shouldBeMapped;
  
                  if (shouldBeMapped)
                      X11Symbols::getInstance()->xMapWindow (getDisplay(), client);
                  else
                      X11Symbols::getInstance()->xUnmapWindow (getDisplay(), client);
              }
          }
      }
  
      Window getParentX11Window()
      {
          if (auto* peer = owner.getPeer())
              return reinterpret_cast<Window> (peer->getNativeHandle());
  
          return {};
      }
  
      Display* getDisplay()   { return XWindowSystem::getInstance()->getDisplay(); }
  
      //==============================================================================
      bool getXEmbedMappedFlag()
      {
          XWindowSystemUtilities::GetXProperty embedInfo (client, infoAtom, 0, 2, false, infoAtom);
  
          if (embedInfo.success && embedInfo.actualFormat == 32
               && embedInfo.numItems >= 2 && embedInfo.data != nullptr)
          {
              long version;
              memcpy (&version, embedInfo.data, sizeof (long));
  
              supportsXembed = true;
              xembedVersion = jmin ((int) maxXEmbedVersionToSupport, (int) version);
  
              long flags;
              memcpy (&flags, embedInfo.data + sizeof (long), sizeof (long));
  
              return ((flags & XEMBED_MAPPED) != 0);
          }
          else
          {
              supportsXembed = false;
              xembedVersion = maxXEmbedVersionToSupport;
          }
  
          return true;
      }
  
      //==============================================================================
      void propertyChanged (const Atom& a)
      {
          if (a == infoAtom)
              updateMapping();
      }
  
      void configureNotify()
      {
          XWindowAttributes attr;
          auto dpy = getDisplay();
  
          if (X11Symbols::getInstance()->xGetWindowAttributes (dpy, client, &attr))
          {
              XWindowAttributes hostAttr;
  
              if (X11Symbols::getInstance()->xGetWindowAttributes (dpy, host, &hostAttr))
                  if (attr.width != hostAttr.width || attr.height != hostAttr.height)
                      X11Symbols::getInstance()->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 ? peer->getPlatformScaleFactor()
                                                    : 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 = X11Symbols::getInstance()->xRootWindow (dpy, DefaultScreen (dpy));
              Rectangle<int> newBounds = getX11BoundsFromJuce();
  
              if (newPeer == nullptr)
                  X11Symbols::getInstance()->xUnmapWindow (dpy, host);
  
              Window newParent = (newPeer != nullptr ? getParentX11Window() : rootWindow);
              X11Symbols::getInstance()->xReparentWindow (dpy, host, newParent, newBounds.getX(), newBounds.getY());
  
              lastPeer = newPeer;
  
              if (newPeer != nullptr)
              {
                  if (wantsFocus)
                  {
                      keyWindow = SharedKeyWindow::getKeyWindowForPeer (newPeer);
                      updateKeyFocus();
                  }
  
                  componentMovedOrResized (owner, true, true);
                  X11Symbols::getInstance()->xMapWindow (dpy, host);
  
                  broughtToFront();
              }
          }
      }
  
      void updateKeyFocus()
      {
          if (lastPeer != nullptr && lastPeer->isFocused())
              X11Symbols::getInstance()->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;
  
              default:
                  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;
  
                  default:
                      break;
              }
          }
          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 == messageTypeAtom && 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;
  
                  default:
                      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 = messageTypeAtom;
          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;
  
          X11Symbols::getInstance()->xSendEvent (dpy, client, False, NoEventMask, (XEvent*) &msg);
          X11Symbols::getInstance()->xSync (dpy, False);
      }
  
      Rectangle<int> getX11BoundsFromJuce()
      {
          if (auto* peer = owner.getPeer())
          {
              auto r = peer->getComponent().getLocalArea (&owner, owner.getLocalBounds());
              return r * peer->getPlatformScaleFactor();
          }
  
          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(); }
  void XEmbedComponent::removeClient()                               { pimpl->setClient (0, true); }
  
  //==============================================================================
  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
  