|  | /*
  ==============================================================================
   This file is part of the JUCE library.
   Copyright (c) 2022 - 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 7 End-User License
   Agreement and JUCE Privacy Policy.
   End User License Agreement: www.juce.com/juce-7-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
{
static Cursor createDraggingHandCursor();
ComponentPeer* getPeerFor (::Window);
//==============================================================================
class X11DragState
{
public:
    X11DragState() = default;
    //==============================================================================
    bool isDragging() const noexcept
    {
        return dragging;
    }
    //==============================================================================
    void handleExternalSelectionClear()
    {
        if (dragging)
            externalResetDragAndDrop();
    }
    void handleExternalSelectionRequest (const XEvent& evt)
    {
        auto targetType = evt.xselectionrequest.target;
        XEvent s;
        s.xselection.type      = SelectionNotify;
        s.xselection.requestor = evt.xselectionrequest.requestor;
        s.xselection.selection = evt.xselectionrequest.selection;
        s.xselection.target    = targetType;
        s.xselection.property  = None;
        s.xselection.time      = evt.xselectionrequest.time;
        auto* display = getDisplay();
        if (allowedTypes.contains (targetType))
        {
            s.xselection.property = evt.xselectionrequest.property;
            X11Symbols::getInstance()->xChangeProperty (display, evt.xselectionrequest.requestor, evt.xselectionrequest.property,
                                                        targetType, 8, PropModeReplace,
                                                        reinterpret_cast<const unsigned char*> (textOrFiles.toRawUTF8()),
                                                        (int) textOrFiles.getNumBytesAsUTF8());
        }
        X11Symbols::getInstance()->xSendEvent (display, evt.xselectionrequest.requestor, True, 0, &s);
    }
    void handleExternalDragAndDropStatus (const XClientMessageEvent& clientMsg)
    {
        if (expectingStatus)
        {
            expectingStatus = false;
            canDrop         = false;
            silentRect      = {};
            const auto& atoms = getAtoms();
            if ((clientMsg.data.l[1] & 1) != 0
                && ((Atom) clientMsg.data.l[4] == atoms.XdndActionCopy
                    || (Atom) clientMsg.data.l[4] == atoms.XdndActionPrivate))
            {
                if ((clientMsg.data.l[1] & 2) == 0) // target requests silent rectangle
                    silentRect.setBounds ((int) clientMsg.data.l[2] >> 16, (int) clientMsg.data.l[2] & 0xffff,
                                          (int) clientMsg.data.l[3] >> 16, (int) clientMsg.data.l[3] & 0xffff);
                canDrop = true;
            }
        }
    }
    void handleExternalDragButtonReleaseEvent()
    {
        if (dragging)
            X11Symbols::getInstance()->xUngrabPointer (getDisplay(), CurrentTime);
        if (canDrop)
        {
            sendExternalDragAndDropDrop();
        }
        else
        {
            sendExternalDragAndDropLeave();
            externalResetDragAndDrop();
        }
    }
    void handleExternalDragMotionNotify()
    {
        auto* display = getDisplay();
        auto newTargetWindow = externalFindDragTargetWindow (X11Symbols::getInstance()
                                                               ->xRootWindow (display,
                                                                              X11Symbols::getInstance()->xDefaultScreen (display)));
        if (targetWindow != newTargetWindow)
        {
            if (targetWindow != None)
                sendExternalDragAndDropLeave();
            canDrop = false;
            silentRect = {};
            if (newTargetWindow == None)
                return;
            xdndVersion = getDnDVersionForWindow (newTargetWindow);
            if (xdndVersion == -1)
                return;
            targetWindow = newTargetWindow;
            sendExternalDragAndDropEnter();
        }
        if (! expectingStatus)
            sendExternalDragAndDropPosition();
    }
    void handleDragAndDropPosition (const XClientMessageEvent& clientMsg, ComponentPeer* peer)
    {
        if (dragAndDropSourceWindow == 0)
            return;
        dragAndDropSourceWindow = (::Window) clientMsg.data.l[0];
        if (windowH == 0)
            windowH = (::Window) peer->getNativeHandle();
        const auto displays = Desktop::getInstance().getDisplays();
        const auto logicalPos = displays.physicalToLogical (Point<int> ((int) clientMsg.data.l[2] >> 16,
                                                                        (int) clientMsg.data.l[2] & 0xffff));
        const auto dropPos = detail::ScalingHelpers::screenPosToLocalPos (peer->getComponent(), logicalPos.toFloat()).roundToInt();
        const auto& atoms = getAtoms();
        auto targetAction = atoms.XdndActionCopy;
        for (int i = numElementsInArray (atoms.allowedActions); --i >= 0;)
        {
            if ((Atom) clientMsg.data.l[4] == atoms.allowedActions[i])
            {
                targetAction = atoms.allowedActions[i];
                break;
            }
        }
        sendDragAndDropStatus (true, targetAction);
        if (dragInfo.position != dropPos)
        {
            dragInfo.position = dropPos;
            if (dragInfo.isEmpty())
                updateDraggedFileList (clientMsg, (::Window) peer->getNativeHandle());
            if (! dragInfo.isEmpty())
                peer->handleDragMove (dragInfo);
        }
    }
    void handleDragAndDropDrop (const XClientMessageEvent& clientMsg, ComponentPeer* peer)
    {
        if (dragInfo.isEmpty())
        {
            // no data, transaction finished in handleDragAndDropSelection()
            finishAfterDropDataReceived = true;
            updateDraggedFileList (clientMsg, (::Window) peer->getNativeHandle());
        }
        else
        {
            handleDragAndDropDataReceived();  // data was already received
        }
    }
    void handleDragAndDropEnter (const XClientMessageEvent& clientMsg, ComponentPeer* peer)
    {
        dragInfo.clear();
        srcMimeTypeAtomList.clear();
        dragAndDropCurrentMimeType = 0;
        auto dndCurrentVersion = (static_cast<unsigned long> (clientMsg.data.l[1]) & 0xff000000) >> 24;
        if (dndCurrentVersion < 3 || dndCurrentVersion > XWindowSystemUtilities::Atoms::DndVersion)
        {
            dragAndDropSourceWindow = 0;
            return;
        }
        const auto& atoms = getAtoms();
        dragAndDropSourceWindow = (::Window) clientMsg.data.l[0];
        if ((clientMsg.data.l[1] & 1) != 0)
        {
            XWindowSystemUtilities::ScopedXLock xLock;
            XWindowSystemUtilities::GetXProperty prop (getDisplay(),
                                                       dragAndDropSourceWindow,
                                                       atoms.XdndTypeList,
                                                       0,
                                                       0x8000000L,
                                                       false,
                                                       XA_ATOM);
            if (prop.success && prop.actualType == XA_ATOM && prop.actualFormat == 32 && prop.numItems != 0)
            {
                auto* types = prop.data;
                for (unsigned long i = 0; i < prop.numItems; ++i)
                {
                    unsigned long type;
                    memcpy (&type, types, sizeof (unsigned long));
                    if (type != None)
                        srcMimeTypeAtomList.add (type);
                    types += sizeof (unsigned long);
                }
            }
        }
        if (srcMimeTypeAtomList.isEmpty())
        {
            for (int i = 2; i < 5; ++i)
                if (clientMsg.data.l[i] != None)
                    srcMimeTypeAtomList.add ((unsigned long) clientMsg.data.l[i]);
            if (srcMimeTypeAtomList.isEmpty())
            {
                dragAndDropSourceWindow = 0;
                return;
            }
        }
        for (int i = 0; i < srcMimeTypeAtomList.size() && dragAndDropCurrentMimeType == 0; ++i)
            for (int j = 0; j < numElementsInArray (atoms.allowedMimeTypes); ++j)
                if (srcMimeTypeAtomList[i] == atoms.allowedMimeTypes[j])
                    dragAndDropCurrentMimeType = atoms.allowedMimeTypes[j];
        handleDragAndDropPosition (clientMsg, peer);
    }
    void handleDragAndDropExit()
    {
        if (auto* peer = getPeerFor (windowH))
            peer->handleDragExit (dragInfo);
        resetDragAndDrop();
    }
    void handleDragAndDropSelection (const XEvent& evt)
    {
        dragInfo.clear();
        if (evt.xselection.property != None)
        {
            StringArray lines;
            {
                MemoryBlock dropData;
                for (;;)
                {
                    XWindowSystemUtilities::GetXProperty prop (getDisplay(),
                                                               evt.xany.window,
                                                               evt.xselection.property,
                                                               (long) (dropData.getSize() / 4),
                                                               65536,
                                                               false,
                                                               AnyPropertyType);
                    if (! prop.success)
                        break;
                    dropData.append (prop.data, (size_t) (prop.actualFormat / 8) * prop.numItems);
                    if (prop.bytesLeft <= 0)
                        break;
                }
                lines.addLines (dropData.toString());
            }
            if (XWindowSystemUtilities::Atoms::isMimeTypeFile (getDisplay(), dragAndDropCurrentMimeType))
            {
                for (const auto& line : lines)
                {
                    const auto escaped = line.replace ("+", "%2B").replace ("file://", String(), true);
                    dragInfo.files.add (URL::removeEscapeChars (escaped));
                }
                dragInfo.files.trim();
                dragInfo.files.removeEmptyStrings();
            }
            else
            {
                dragInfo.text = lines.joinIntoString ("\n");
            }
            if (finishAfterDropDataReceived)
                handleDragAndDropDataReceived();
        }
    }
    void externalResetDragAndDrop()
    {
        if (dragging)
        {
            XWindowSystemUtilities::ScopedXLock xLock;
            X11Symbols::getInstance()->xUngrabPointer (getDisplay(), CurrentTime);
        }
        if (completionCallback != nullptr)
            completionCallback();
        dragging = false;
    }
    bool externalDragInit (::Window window, bool text, const String& str, std::function<void()>&& cb)
    {
        windowH            = window;
        isText             = text;
        textOrFiles        = str;
        targetWindow       = windowH;
        completionCallback = std::move (cb);
        auto* display = getDisplay();
        allowedTypes.add (XWindowSystemUtilities::Atoms::getCreating (display, isText ? "text/plain" : "text/uri-list"));
        auto pointerGrabMask = (unsigned int) (Button1MotionMask | ButtonReleaseMask);
        XWindowSystemUtilities::ScopedXLock xLock;
        if (X11Symbols::getInstance()->xGrabPointer (display, windowH, True, pointerGrabMask,
                                                     GrabModeAsync, GrabModeAsync, None, None, CurrentTime) == GrabSuccess)
        {
            const auto& atoms = getAtoms();
            // No other method of changing the pointer seems to work, this call is needed from this very context
            X11Symbols::getInstance()->xChangeActivePointerGrab (display, pointerGrabMask, (Cursor) createDraggingHandCursor(), CurrentTime);
            X11Symbols::getInstance()->xSetSelectionOwner (display, atoms.XdndSelection, windowH, CurrentTime);
            // save the available types to XdndTypeList
            X11Symbols::getInstance()->xChangeProperty (display, windowH, atoms.XdndTypeList, XA_ATOM, 32, PropModeReplace,
                                                        reinterpret_cast<const unsigned char*> (allowedTypes.getRawDataPointer()), allowedTypes.size());
            dragging = true;
            xdndVersion = getDnDVersionForWindow (targetWindow);
            sendExternalDragAndDropEnter();
            handleExternalDragMotionNotify();
            return true;
        }
        return false;
    }
private:
    //==============================================================================
    const XWindowSystemUtilities::Atoms& getAtoms() const noexcept  { return XWindowSystem::getInstance()->getAtoms(); }
    ::Display* getDisplay() const noexcept                          { return XWindowSystem::getInstance()->getDisplay(); }
    //==============================================================================
    void sendDragAndDropMessage (XClientMessageEvent& msg)
    {
        auto* display = getDisplay();
        msg.type      = ClientMessage;
        msg.display   = display;
        msg.window    = dragAndDropSourceWindow;
        msg.format    = 32;
        msg.data.l[0] = (long) windowH;
        XWindowSystemUtilities::ScopedXLock xLock;
        X11Symbols::getInstance()->xSendEvent (display, dragAndDropSourceWindow, False, 0, (XEvent*) &msg);
    }
    bool sendExternalDragAndDropMessage (XClientMessageEvent& msg)
    {
        auto* display = getDisplay();
        msg.type      = ClientMessage;
        msg.display   = display;
        msg.window    = targetWindow;
        msg.format    = 32;
        msg.data.l[0] = (long) windowH;
        XWindowSystemUtilities::ScopedXLock xLock;
        return X11Symbols::getInstance()->xSendEvent (display, targetWindow, False, 0, (XEvent*) &msg) != 0;
    }
    void sendExternalDragAndDropDrop()
    {
        XClientMessageEvent msg;
        zerostruct (msg);
        msg.message_type = getAtoms().XdndDrop;
        msg.data.l[2] = CurrentTime;
        sendExternalDragAndDropMessage (msg);
    }
    void sendExternalDragAndDropEnter()
    {
        XClientMessageEvent msg;
        zerostruct (msg);
        msg.message_type = getAtoms().XdndEnter;
        msg.data.l[1] = (xdndVersion << 24);
        for (int i = 0; i < 3; ++i)
            msg.data.l[i + 2] = (long) allowedTypes[i];
        sendExternalDragAndDropMessage (msg);
    }
    void sendExternalDragAndDropPosition()
    {
        XClientMessageEvent msg;
        zerostruct (msg);
        const auto& atoms = getAtoms();
        msg.message_type = atoms.XdndPosition;
        auto mousePos = Desktop::getInstance().getMousePosition();
        if (silentRect.contains (mousePos)) // we've been asked to keep silent
            return;
        mousePos = Desktop::getInstance().getDisplays().logicalToPhysical (mousePos);
        msg.data.l[1] = 0;
        msg.data.l[2] = (mousePos.x << 16) | mousePos.y;
        msg.data.l[3] = CurrentTime;
        msg.data.l[4] = (long) atoms.XdndActionCopy; // this is all JUCE currently supports
        expectingStatus = sendExternalDragAndDropMessage (msg);
    }
    void sendDragAndDropStatus (bool acceptDrop, Atom dropAction)
    {
        XClientMessageEvent msg;
        zerostruct (msg);
        msg.message_type = getAtoms().XdndStatus;
        msg.data.l[1]    = (acceptDrop ? 1 : 0) | 2; // 2 indicates that we want to receive position messages
        msg.data.l[4]    = (long) dropAction;
        sendDragAndDropMessage (msg);
    }
    void sendExternalDragAndDropLeave()
    {
        XClientMessageEvent msg;
        zerostruct (msg);
        msg.message_type = getAtoms().XdndLeave;
        sendExternalDragAndDropMessage (msg);
    }
    void sendDragAndDropFinish()
    {
        XClientMessageEvent msg;
        zerostruct (msg);
        msg.message_type = getAtoms().XdndFinished;
        sendDragAndDropMessage (msg);
    }
    void updateDraggedFileList (const XClientMessageEvent& clientMsg, ::Window requestor)
    {
        jassert (dragInfo.isEmpty());
        if (dragAndDropSourceWindow != None && dragAndDropCurrentMimeType != None)
        {
            auto* display = getDisplay();
            XWindowSystemUtilities::ScopedXLock xLock;
            X11Symbols::getInstance()->xConvertSelection (display, getAtoms().XdndSelection, dragAndDropCurrentMimeType,
                                                          XWindowSystemUtilities::Atoms::getCreating (display, "JXSelectionWindowProperty"),
                                                          requestor, (::Time) clientMsg.data.l[2]);
        }
    }
    bool isWindowDnDAware (::Window w) const
    {
        int numProperties = 0;
        auto* properties = X11Symbols::getInstance()->xListProperties (getDisplay(), w, &numProperties);
        bool dndAwarePropFound = false;
        for (int i = 0; i < numProperties; ++i)
            if (properties[i] == getAtoms().XdndAware)
                dndAwarePropFound = true;
        if (properties != nullptr)
            X11Symbols::getInstance()->xFree (properties);
        return dndAwarePropFound;
    }
    int getDnDVersionForWindow (::Window target)
    {
        XWindowSystemUtilities::GetXProperty prop (getDisplay(),
                                                   target,
                                                   getAtoms().XdndAware,
                                                   0,
                                                   2,
                                                   false,
                                                   AnyPropertyType);
        if (prop.success && prop.data != nullptr && prop.actualFormat == 32 && prop.numItems == 1)
            return jmin ((int) prop.data[0], (int) XWindowSystemUtilities::Atoms::DndVersion);
        return -1;
    }
    ::Window externalFindDragTargetWindow (::Window target)
    {
        if (target == None)
            return None;
        if (isWindowDnDAware (target))
            return target;
        ::Window child, phonyWin;
        int phony;
        unsigned int uphony;
        X11Symbols::getInstance()->xQueryPointer (getDisplay(), target, &phonyWin, &child, &phony, &phony, &phony, &phony, &uphony);
        return externalFindDragTargetWindow (child);
    }
    void handleDragAndDropDataReceived()
    {
        ComponentPeer::DragInfo dragInfoCopy (dragInfo);
        sendDragAndDropFinish();
        resetDragAndDrop();
        if (! dragInfoCopy.isEmpty())
            if (auto* peer = getPeerFor (windowH))
                peer->handleDragDrop (dragInfoCopy);
    }
    void resetDragAndDrop()
    {
        dragInfo.clear();
        dragInfo.position = Point<int> (-1, -1);
        dragAndDropCurrentMimeType = 0;
        dragAndDropSourceWindow = 0;
        srcMimeTypeAtomList.clear();
        finishAfterDropDataReceived = false;
    }
    //==============================================================================
    ::Window windowH = 0, targetWindow = 0, dragAndDropSourceWindow = 0;
    int xdndVersion = -1;
    bool isText = false, dragging = false, expectingStatus = false, canDrop = false, finishAfterDropDataReceived = false;
    Atom dragAndDropCurrentMimeType;
    Array<Atom> allowedTypes, srcMimeTypeAtomList;
    ComponentPeer::DragInfo dragInfo;
    Rectangle<int> silentRect;
    String textOrFiles;
    std::function<void()> completionCallback = nullptr;
    //==============================================================================
    JUCE_LEAK_DETECTOR (X11DragState)
};
} // namespace juce
 |