|
- /*
- ==============================================================================
-
- 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);
- }
-
- NullCheckedInvocation::invoke (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
|