diff --git a/modules/juce_gui_basics/native/juce_linux_X11.cpp b/modules/juce_gui_basics/native/juce_linux_X11.cpp index 09f377af92..a46035817f 100644 --- a/modules/juce_gui_basics/native/juce_linux_X11.cpp +++ b/modules/juce_gui_basics/native/juce_linux_X11.cpp @@ -279,6 +279,9 @@ Atoms::Atoms(::Display* display) XdndActionPrivate = getCreating (display, "XdndActionPrivate"); XdndActionDescription = getCreating (display, "XdndActionDescription"); + XembedMsgType = getCreating (display, "_XEMBED"); + XembedInfo = getCreating (display, "_XEMBED_INFO"); + allowedMimeTypes[0] = getCreating (display, "UTF8_STRING"); allowedMimeTypes[1] = getCreating (display, "text/plain;charset=utf-8"); allowedMimeTypes[2] = getCreating (display, "text/plain"); diff --git a/modules/juce_gui_basics/native/juce_linux_X11.h b/modules/juce_gui_basics/native/juce_linux_X11.h index 6efc9269e3..edb65ae810 100644 --- a/modules/juce_gui_basics/native/juce_linux_X11.h +++ b/modules/juce_gui_basics/native/juce_linux_X11.h @@ -103,6 +103,7 @@ struct Atoms XdndAware, XdndEnter, XdndLeave, XdndPosition, XdndStatus, XdndDrop, XdndFinished, XdndSelection, XdndTypeList, XdndActionList, XdndActionDescription, XdndActionCopy, XdndActionPrivate, + XembedMsgType, XembedInfo, allowedActions[5], allowedMimeTypes[4]; diff --git a/modules/juce_gui_basics/native/juce_linux_X11_Windowing.cpp b/modules/juce_gui_basics/native/juce_linux_X11_Windowing.cpp index 1ae5431600..c5be8a815d 100644 --- a/modules/juce_gui_basics/native/juce_linux_X11_Windowing.cpp +++ b/modules/juce_gui_basics/native/juce_linux_X11_Windowing.cpp @@ -26,6 +26,17 @@ #define JUCE_DEBUG_XERRORS 1 #endif +#if JUCE_MODULE_AVAILABLE_juce_gui_extra + #define JUCE_X11_SUPPORTS_XEMBED 1 +#else + #define JUCE_X11_SUPPORTS_XEMBED 0 +#endif + +#if JUCE_X11_SUPPORTS_XEMBED +bool juce_handleXEmbedEvent (ComponentPeer*, void*); +unsigned long juce_getCurrentFocusWindow (ComponentPeer*); +#endif + extern WindowMessageReceiveCallback dispatchWindowMessage; extern XContext windowHandleXContext; @@ -1456,8 +1467,8 @@ class LinuxComponentPeer : public ComponentPeer public: LinuxComponentPeer (Component& comp, const int windowStyleFlags, Window parentToAddTo) : ComponentPeer (comp, windowStyleFlags), - windowH (0), parentWindow (0), - fullScreen (false), mapped (false), + windowH (0), parentWindow (0), keyProxy (0), + fullScreen (false), mapped (false), focused (false), visual (nullptr), depth (0), isAlwaysOnTop (comp.isAlwaysOnTop()), currentScaleFactor (1.0) @@ -1484,6 +1495,10 @@ public: // it's dangerous to delete a window on a thread other than the message thread.. jassert (MessageManager::getInstance()->currentThreadHasLockedMessageManager()); + #if JUCE_X11_SUPPORTS_XEMBED + juce_handleXEmbedEvent (this, nullptr); + #endif + deleteIconPixmaps(); destroyWindow(); windowH = 0; @@ -1718,6 +1733,33 @@ public: return false; } + bool isParentWindowOf (Window possibleChild) const + { + if (windowH != 0 && possibleChild != 0) + { + if (possibleChild == windowH) + return true; + + Window* windowList = nullptr; + uint32 windowListSize = 0; + Window parent, root; + + ScopedXLock xlock (display); + if (XQueryTree (display, possibleChild, &root, &parent, &windowList, &windowListSize) != 0) + { + if (windowList != nullptr) + XFree (windowList); + + if (parent == root) + return false; + + return isParentWindowOf (parent); + } + } + + return false; + } + bool isFrontWindow() const { Window* windowList = nullptr; @@ -1845,7 +1887,19 @@ public: ScopedXLock xlock (display); XGetInputFocus (display, &focusedWindow, &revert); - return focusedWindow == windowH; + return isParentWindowOf (focusedWindow); + } + + Window getFocusWindow() + { + #if JUCE_X11_SUPPORTS_XEMBED + Window w = (Window) juce_getCurrentFocusWindow (this); + + if (w != 0) + return w; + #endif + + return windowH; } void grabFocus() override @@ -1858,7 +1912,7 @@ public: && atts.map_state == IsViewable && ! isFocused()) { - XSetInputFocus (display, windowH, RevertToParent, (::Time) getUserTime()); + XSetInputFocus (display, getFocusWindow(), RevertToParent, (::Time) getUserTime()); isActiveApplication = true; } } @@ -2255,15 +2309,23 @@ public: void handleFocusInEvent() { isActiveApplication = true; - if (isFocused()) + + if (isFocused() && ! focused) + { + focused = true; handleFocusGain(); + } } void handleFocusOutEvent() { - isActiveApplication = false; - if (! isFocused()) + if (! isFocused() && focused) + { + focused = false; + isActiveApplication = false; + handleFocusLoss(); + } } void handleExposeEvent (XExposeEvent& exposeEvent) @@ -2310,15 +2372,15 @@ public: // if the native title bar is dragged, need to tell any active menus, etc. if ((styleFlags & windowHasTitleBar) != 0 - && component.isCurrentlyBlockedByAnotherModalComponent()) + && component.isCurrentlyBlockedByAnotherModalComponent()) { - if (Component* const currentModalComp = Component::getCurrentlyModalComponent()) - currentModalComp->inputAttemptWhenModal(); + if (Component* const currentModalComp = Component::getCurrentlyModalComponent()) + currentModalComp->inputAttemptWhenModal(); } if (confEvent.window == windowH - && confEvent.above != 0 - && isFrontWindow()) + && confEvent.above != 0 + && isFrontWindow()) { handleBroughtToFront(); } @@ -2386,7 +2448,11 @@ public: && XGetWindowAttributes (display, clientMsg.window, &atts)) { if (atts.map_state == IsViewable) - XSetInputFocus (display, clientMsg.window, RevertToParent, (::Time) clientMsg.data.l[1]); + XSetInputFocus (display, + (clientMsg.window == windowH ? getFocusWindow () + : clientMsg.window), + RevertToParent, + (::Time) clientMsg.data.l[1]); } } } @@ -2485,6 +2551,51 @@ public: } } + //============================================================================== + unsigned long createKeyProxy() + { + jassert (keyProxy == 0 && windowH != 0); + + if (keyProxy == 0 && windowH != 0) + { + XSetWindowAttributes swa; + swa.event_mask = KeyPressMask | KeyReleaseMask | FocusChangeMask; + + keyProxy = XCreateWindow (display, windowH, + -1, -1, 1, 1, 0, 0, + InputOnly, CopyFromParent, + CWEventMask, + &swa); + + XMapWindow (display, keyProxy); + XSaveContext (display, (XID) keyProxy, windowHandleXContext, (XPointer) this); + } + + return keyProxy; + } + + void deleteKeyProxy() + { + jassert (keyProxy != 0); + + if (keyProxy != 0) + { + XPointer handlePointer; + + if (! XFindContext (display, (XID) keyProxy, windowHandleXContext, &handlePointer)) + XDeleteContext (display, (XID) keyProxy, windowHandleXContext); + + XDestroyWindow (display, keyProxy); + XSync (display, false); + + XEvent event; + while (XCheckWindowEvent (display, keyProxy, getAllEventsMask(), &event) == True) + {} + + keyProxy = 0; + } + } + //============================================================================== bool dontRepaint; @@ -2638,10 +2749,10 @@ private: ScopedPointer repainter; friend class LinuxRepaintManager; - Window windowH, parentWindow; + Window windowH, parentWindow, keyProxy; Rectangle bounds; Image taskbarImage; - bool fullScreen, mapped; + bool fullScreen, mapped, focused; Visual* visual; int depth; BorderSize windowBorder; @@ -2927,11 +3038,6 @@ private: CWBorderPixel | CWColormap | CWBackPixmap | CWEventMask | CWOverrideRedirect, &swa); - unsigned int buttonMask = EnterWindowMask | LeaveWindowMask | PointerMotionMask; - - if ((styleFlags & windowIgnoresMouseClicks) == 0) - buttonMask |= ButtonPressMask | ButtonReleaseMask; - // Set the window context to identify the window handle object if (XSaveContext (display, (XID) windowH, windowHandleXContext, (XPointer) this)) { @@ -2984,6 +3090,10 @@ private: ScopedXLock xlock (display); XPointer handlePointer; + + if (keyProxy != 0) + deleteKeyProxy(); + if (! XFindContext (display, (XID) windowH, windowHandleXContext, &handlePointer)) XDeleteContext (display, (XID) windowH, windowHandleXContext); @@ -3640,8 +3750,13 @@ namespace WindowingHelpers { { if (event.xany.window != None) { - if (LinuxComponentPeer* const peer = LinuxComponentPeer::getPeerFor (event.xany.window)) - peer->handleWindowMessage (event); + #if JUCE_X11_SUPPORTS_XEMBED + if (! juce_handleXEmbedEvent (nullptr, &event)) + #endif + { + if (LinuxComponentPeer* const peer = LinuxComponentPeer::getPeerFor (event.xany.window)) + peer->handleWindowMessage (event); + } } else if (event.xany.type == KeymapNotify) { @@ -3927,6 +4042,19 @@ void juce_LinuxRemoveRepaintListener (ComponentPeer* peer, Component* dummy) linuxPeer->removeOpenGLRepaintListener (dummy); } +unsigned long juce_createKeyProxyWindow (ComponentPeer* peer) +{ + if (LinuxComponentPeer* linuxPeer = dynamic_cast (peer)) + return linuxPeer->createKeyProxy(); + + return 0; +} + +void juce_deleteKeyProxyWindow (ComponentPeer* peer) +{ + if (LinuxComponentPeer* linuxPeer = dynamic_cast (peer)) + linuxPeer->deleteKeyProxy(); +} //============================================================================== #if JUCE_MODAL_LOOPS_PERMITTED void JUCE_CALLTYPE NativeMessageBox::showMessageBox (AlertWindow::AlertIconType iconType, diff --git a/modules/juce_gui_extra/embedding/juce_XEmbedComponent.h b/modules/juce_gui_extra/embedding/juce_XEmbedComponent.h new file mode 100644 index 0000000000..3c63faa5cb --- /dev/null +++ b/modules/juce_gui_extra/embedding/juce_XEmbedComponent.h @@ -0,0 +1,78 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#pragma once + +/** @internal */ +bool juce_handleXEmbedEvent (ComponentPeer*, void*); +/** @internal */ +unsigned long juce_getCurrentFocusWindow (ComponentPeer*); + +#if JUCE_LINUX || DOXYGEN + +//============================================================================== +/** + A Linux-specific class that can embed a foreign X11 widget. + + Use this class to embed a foreign X11 widget from other toolkits such as + GTK+ or QT. + + For GTK+, create a gtk_plug container and pass the plug's id + (gtk_plug_get_id) to the constructor of this class. + + For QT, use the QX11EmbedWidget class and pass the widget's + id (containerWinId()) to the constructor of this class. + + Other toolkits or raw X11 widgets should follow the X11 embed protocol: + https://specifications.freedesktop.org/xembed-spec/xembed-spec-latest.html +*/ +class XEmbedComponent : public Component +{ +public: + //============================================================================== + + /** Create a JUCE component wrapping the foreign widget with id wID */ + XEmbedComponent (unsigned long wID, bool wantsKeyboardFocus = true); + + /** Destructor. */ + ~XEmbedComponent(); + +protected: + //============================================================================== + /** @internal */ + void paint (Graphics&) override; + void focusGained (FocusChangeType) override; + void focusLost (FocusChangeType) override; + void broughtToFront() override; + +private: + friend bool juce::juce_handleXEmbedEvent (ComponentPeer*, void*); + friend unsigned long juce_getCurrentFocusWindow (ComponentPeer*); + + class Pimpl; + friend struct ContainerDeletePolicy; + ScopedPointer pimpl; +}; + +#endif diff --git a/modules/juce_gui_extra/juce_gui_extra.cpp b/modules/juce_gui_extra/juce_gui_extra.cpp index ebcfed767f..20614fa50d 100644 --- a/modules/juce_gui_extra/juce_gui_extra.cpp +++ b/modules/juce_gui_extra/juce_gui_extra.cpp @@ -123,6 +123,7 @@ namespace juce //============================================================================== #elif JUCE_LINUX + #include "native/juce_linux_XEmbedComponent.cpp" #if JUCE_WEB_BROWSER #include "native/juce_linux_X11_WebBrowserComponent.cpp" #endif diff --git a/modules/juce_gui_extra/juce_gui_extra.h b/modules/juce_gui_extra/juce_gui_extra.h index e7a4ac4a92..a4de28c1d0 100644 --- a/modules/juce_gui_extra/juce_gui_extra.h +++ b/modules/juce_gui_extra/juce_gui_extra.h @@ -86,6 +86,7 @@ namespace juce #include "embedding/juce_ActiveXControlComponent.h" #include "embedding/juce_NSViewComponent.h" #include "embedding/juce_UIViewComponent.h" +#include "embedding/juce_XEmbedComponent.h" #include "misc/juce_AppleRemote.h" #include "misc/juce_BubbleMessageComponent.h" #include "misc/juce_ColourSelector.h" diff --git a/modules/juce_gui_extra/native/juce_linux_XEmbedComponent.cpp b/modules/juce_gui_extra/native/juce_linux_XEmbedComponent.cpp new file mode 100644 index 0000000000..71df7f3e21 --- /dev/null +++ b/modules/juce_gui_extra/native/juce_linux_XEmbedComponent.cpp @@ -0,0 +1,700 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +//============================================================================== +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: + //============================================================================== + class Ref + { + public: + Ref() : keyWindow (nullptr) {} + Ref (Pimpl& p) { keyWindow = getKeyWindowForPeer (p.owner.getPeer()); } + ~Ref() { free(); } + + //============================================================================== + Ref (const Ref& o) : keyWindow (o.keyWindow) { if (keyWindow != nullptr) keyWindow->numRefs++; } + Ref (Ref && o) : keyWindow (o.keyWindow) { o.keyWindow = nullptr; } + Ref (std::nullptr_t) : keyWindow (nullptr) {} + + //============================================================================== + Ref& operator= (std::nullptr_t) { free(); return *this; } + Ref& operator= (const Ref& o) + { + free(); + keyWindow = o.keyWindow; + if (keyWindow != nullptr) + keyWindow->numRefs++; + + return *this; + } + + Ref& operator= (Ref && o) + { + if (keyWindow != o.keyWindow) + { + free(); + keyWindow = o.keyWindow; + } + + o.keyWindow = nullptr; + return *this; + } + + //============================================================================== + SharedKeyWindow& operator*() noexcept { return *keyWindow; } + SharedKeyWindow* operator->() noexcept { return keyWindow; } + + //============================================================================== + bool operator== (std::nullptr_t) const noexcept { return (keyWindow == nullptr); } + bool operator!= (std::nullptr_t) const noexcept { return (keyWindow != nullptr); } + private: + //============================================================================== + void free() + { + if (keyWindow != nullptr) + { + if (--keyWindow->numRefs == 0) + delete keyWindow; + + keyWindow = nullptr; + } + } + + SharedKeyWindow* keyWindow; + }; + + public: + //============================================================================== + Window getHandle() { return keyProxy; } + + static Window getCurrentFocusWindow (ComponentPeer* peerToLookFor) + { + if (keyWindows != nullptr && peerToLookFor != nullptr) + { + SharedKeyWindow* foundKeyWindow = (*keyWindows)[peerToLookFor]; + + if (foundKeyWindow != nullptr) + return foundKeyWindow->keyProxy; + } + + return (Window)0; + } + + private: + //============================================================================== + friend class Ref; + + SharedKeyWindow (ComponentPeer* peerToUse) + : keyPeer (peerToUse), + keyProxy (juce_createKeyProxyWindow (keyPeer)), + numRefs (1) + {} + + ~SharedKeyWindow() + { + juce_deleteKeyProxyWindow (keyPeer); + + if (keyWindows != nullptr) + { + keyWindows->remove (keyPeer); + if (keyWindows->size() == 0) + { + delete keyWindows; + keyWindows = nullptr; + } + } + } + + ComponentPeer* keyPeer; + Window keyProxy; + int numRefs; + + static SharedKeyWindow* getKeyWindowForPeer (ComponentPeer* peerToLookFor) + { + jassert (peerToLookFor != nullptr); + + if (keyWindows == nullptr) + keyWindows = new HashMap; + + SharedKeyWindow* foundKeyWindow = (*keyWindows)[peerToLookFor]; + if (foundKeyWindow == nullptr) + { + foundKeyWindow = new SharedKeyWindow (peerToLookFor); + keyWindows->set (peerToLookFor, foundKeyWindow); + } + + return foundKeyWindow; + } + + //============================================================================== + friend class Ref; + static HashMap* keyWindows; + }; + +public: + //============================================================================== + Pimpl (XEmbedComponent& parent, Window x11Window, bool wantsKeyboardFocus) + : owner (parent), atoms (x11display.get()), wantsFocus (wantsKeyboardFocus) + { + if (widgets == nullptr) + widgets = new Array; + + widgets->add (this); + + createHostWindow(); + setClient (x11Window); + + owner.setWantsKeyboardFocus (wantsFocus); + owner.addComponentListener (this); + } + + ~Pimpl() + { + owner.removeComponentListener (this); + setClient (0); + + if (host != 0) + { + Display* 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; + } + + if (widgets != nullptr) + { + widgets->removeAllInstancesOf (this); + + if (widgets->size() == 0) + { + delete widgets; + widgets = nullptr; + } + } + } + //============================================================================== + void setClient (Window xembedClient) + { + removeClient(); + + if (xembedClient != 0) + { + client = xembedClient; + + configureNotify(); + + Display* dpy = getDisplay(); + XSelectInput (dpy, client, StructureNotifyMask | PropertyChangeMask | FocusChangeMask); + getXEmbedMappedFlag(); + + 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); + } + +private: + //============================================================================== + XEmbedComponent& owner; + Window client = 0, host = 0; + + ScopedXDisplay x11display; + Atoms atoms; + + bool wantsFocus = false; + bool supportsXembed = false; + bool hasBeenMapped = false; + int xembedVersion = maxXEmbedVersionToSupport; + + ComponentPeer* lastPeer = nullptr; + SharedKeyWindow::Ref keyWindow; + + //============================================================================== + void componentParentHierarchyChanged (Component&) override { peerChanged (owner.getPeer()); } + void componentMovedOrResized (Component&, bool, bool) override + { + if (client != 0 && lastPeer != nullptr) + { + Display* dpy = getDisplay(); + Rectangle newBounds = getX11BoundsFromJuce(); + XWindowAttributes attr; + + if (XGetWindowAttributes (dpy, host, &attr)) + { + Rectangle currentBounds (attr.x, attr.y, attr.width, attr.height); + if (currentBounds != newBounds) + { + XMoveResizeWindow (dpy, host, newBounds.getX(), newBounds.getY(), + static_cast (newBounds.getWidth()), + static_cast (newBounds.getHeight())); + + if (currentBounds.getWidth() != newBounds.getWidth() + || currentBounds.getHeight() != newBounds.getHeight()) + XResizeWindow (dpy, client, + static_cast (newBounds.getWidth()), + static_cast (newBounds.getHeight())); + } + } + } + } + + //============================================================================== + void createHostWindow() + { + Display* 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 = StructureNotifyMask | FocusChangeMask; + + host = XCreateWindow (dpy, root, 0, 0, 1, 1, 0, CopyFromParent, + InputOutput, CopyFromParent, + CWEventMask | CWBorderPixel | CWBackPixmap | CWOverrideRedirect, + &swa); + } + + void removeClient() + { + if (client != 0) + { + Display* 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 (ComponentPeer* peer = owner.getPeer()) + return reinterpret_cast (peer->getNativeHandle()); + + return 0; + } + + Display* getDisplay() { return reinterpret_cast (x11display.get()); } + + //============================================================================== + bool getXEmbedMappedFlag() + { + GetXProperty embedInfo (x11display.get(), client, atoms.XembedInfo, 0, 2, false, atoms.XembedInfo); + if (embedInfo.success && embedInfo.actualFormat == 32 + && embedInfo.numItems >= 2 && embedInfo.data != nullptr) + { + long* 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; + Display* 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 :-( + const Desktop::Displays& displays = Desktop::getInstance().getDisplays(); + ComponentPeer* peer = owner.getPeer(); + const double scale = (peer != nullptr ? displays.getDisplayContaining (peer->getBounds().getCentre()) + : displays.getMainDisplay()).scale; + + Point topLeftInPeer + = (peer != nullptr ? peer->getComponent().getLocalPoint (&owner, Point (0, 0)) + : owner.getBounds().getTopLeft()); + + Rectangle newBounds (topLeftInPeer.getX(), topLeftInPeer.getY(), + static_cast (static_cast (attr.width) / scale), + static_cast (static_cast (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; + + Display* dpy = getDisplay(); + Window rootWindow = RootWindow (dpy, DefaultScreen (dpy)); + Rectangle 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::Ref (*this); + 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) + { + switch (e.type) + { + case PropertyNotify: + propertyChanged (e.xproperty.atom); + return true; + case ConfigureNotify: + configureNotify(); + return true; + } + } + else if (e.xany.window == host) + { + switch (e.type) + { + 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; + Display* 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 getX11BoundsFromJuce() + { + if (ComponentPeer* peer = owner.getPeer()) + { + Rectangle r + = peer->getComponent().getLocalArea (&owner, owner.getLocalBounds()); + + const double 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* widgets; + + static bool dispatchX11Event (ComponentPeer* p, const XEvent* eventArg) + { + if (widgets != nullptr) + { + if (eventArg != nullptr) + { + const XEvent& e = *eventArg; + Window w = e.xany.window; + + if (w == 0) return false; + + for (auto && widget : *widgets) + if (w == widget->host || w == widget->client) + return widget->handleX11Event (e); + } + else + { + for (auto && widget : *widgets) + { + if (widget->owner.getPeer() == p) + widget->peerChanged (nullptr); + } + } + } + + return false; + } + + static Window getCurrentFocusWindow (ComponentPeer* p) + { + if (widgets != nullptr && p != nullptr) + { + for (auto && widget : *widgets) + if (widget->owner.getPeer() == p && widget->owner.hasKeyboardFocus (false)) + return widget->client; + } + + return SharedKeyWindow::getCurrentFocusWindow (p); + } +}; + +//============================================================================== +Array* XEmbedComponent::Pimpl::widgets = nullptr; +HashMap* XEmbedComponent::Pimpl::SharedKeyWindow::keyWindows = nullptr; + +//============================================================================== +XEmbedComponent::XEmbedComponent (unsigned long wID, bool wantsKeyboardFocus) + : pimpl (new Pimpl (*this, wID, wantsKeyboardFocus)) +{ + 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(); } + +//============================================================================== +bool juce_handleXEmbedEvent (ComponentPeer* p, void* e) +{ + return ::XEmbedComponent::Pimpl::dispatchX11Event (p, reinterpret_cast (e)); +} + +unsigned long juce_getCurrentFocusWindow (ComponentPeer* peer) +{ + return (unsigned long) ::XEmbedComponent::Pimpl::getCurrentFocusWindow (peer); +}