The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

610 lines
21KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. namespace juce
  19. {
  20. static Cursor createDraggingHandCursor();
  21. ComponentPeer* getPeerFor (::Window);
  22. //==============================================================================
  23. class X11DragState
  24. {
  25. public:
  26. X11DragState() = default;
  27. //==============================================================================
  28. bool isDragging() const noexcept
  29. {
  30. return dragging;
  31. }
  32. //==============================================================================
  33. void handleExternalSelectionClear()
  34. {
  35. if (dragging)
  36. externalResetDragAndDrop();
  37. }
  38. void handleExternalSelectionRequest (const XEvent& evt)
  39. {
  40. auto targetType = evt.xselectionrequest.target;
  41. XEvent s;
  42. s.xselection.type = SelectionNotify;
  43. s.xselection.requestor = evt.xselectionrequest.requestor;
  44. s.xselection.selection = evt.xselectionrequest.selection;
  45. s.xselection.target = targetType;
  46. s.xselection.property = None;
  47. s.xselection.time = evt.xselectionrequest.time;
  48. auto* display = getDisplay();
  49. if (allowedTypes.contains (targetType))
  50. {
  51. s.xselection.property = evt.xselectionrequest.property;
  52. X11Symbols::getInstance()->xChangeProperty (display, evt.xselectionrequest.requestor, evt.xselectionrequest.property,
  53. targetType, 8, PropModeReplace,
  54. reinterpret_cast<const unsigned char*> (textOrFiles.toRawUTF8()),
  55. (int) textOrFiles.getNumBytesAsUTF8());
  56. }
  57. X11Symbols::getInstance()->xSendEvent (display, evt.xselectionrequest.requestor, True, 0, &s);
  58. }
  59. void handleExternalDragAndDropStatus (const XClientMessageEvent& clientMsg)
  60. {
  61. if (expectingStatus)
  62. {
  63. expectingStatus = false;
  64. canDrop = false;
  65. silentRect = {};
  66. const auto& atoms = getAtoms();
  67. if ((clientMsg.data.l[1] & 1) != 0
  68. && ((Atom) clientMsg.data.l[4] == atoms.XdndActionCopy
  69. || (Atom) clientMsg.data.l[4] == atoms.XdndActionPrivate))
  70. {
  71. if ((clientMsg.data.l[1] & 2) == 0) // target requests silent rectangle
  72. silentRect.setBounds ((int) clientMsg.data.l[2] >> 16, (int) clientMsg.data.l[2] & 0xffff,
  73. (int) clientMsg.data.l[3] >> 16, (int) clientMsg.data.l[3] & 0xffff);
  74. canDrop = true;
  75. }
  76. }
  77. }
  78. void handleExternalDragButtonReleaseEvent()
  79. {
  80. if (dragging)
  81. X11Symbols::getInstance()->xUngrabPointer (getDisplay(), CurrentTime);
  82. if (canDrop)
  83. {
  84. sendExternalDragAndDropDrop();
  85. }
  86. else
  87. {
  88. sendExternalDragAndDropLeave();
  89. externalResetDragAndDrop();
  90. }
  91. }
  92. void handleExternalDragMotionNotify()
  93. {
  94. auto* display = getDisplay();
  95. auto newTargetWindow = externalFindDragTargetWindow (X11Symbols::getInstance()
  96. ->xRootWindow (display,
  97. X11Symbols::getInstance()->xDefaultScreen (display)));
  98. if (targetWindow != newTargetWindow)
  99. {
  100. if (targetWindow != None)
  101. sendExternalDragAndDropLeave();
  102. canDrop = false;
  103. silentRect = {};
  104. if (newTargetWindow == None)
  105. return;
  106. xdndVersion = getDnDVersionForWindow (newTargetWindow);
  107. if (xdndVersion == -1)
  108. return;
  109. targetWindow = newTargetWindow;
  110. sendExternalDragAndDropEnter();
  111. }
  112. if (! expectingStatus)
  113. sendExternalDragAndDropPosition();
  114. }
  115. void handleDragAndDropPosition (const XClientMessageEvent& clientMsg, ComponentPeer* peer)
  116. {
  117. if (dragAndDropSourceWindow == 0)
  118. return;
  119. dragAndDropSourceWindow = (::Window) clientMsg.data.l[0];
  120. if (windowH == 0)
  121. windowH = (::Window) peer->getNativeHandle();
  122. const auto displays = Desktop::getInstance().getDisplays();
  123. const auto logicalPos = displays.physicalToLogical (Point<int> ((int) clientMsg.data.l[2] >> 16,
  124. (int) clientMsg.data.l[2] & 0xffff));
  125. const auto dropPos = detail::ScalingHelpers::screenPosToLocalPos (peer->getComponent(), logicalPos.toFloat()).roundToInt();
  126. const auto& atoms = getAtoms();
  127. auto targetAction = atoms.XdndActionCopy;
  128. for (int i = numElementsInArray (atoms.allowedActions); --i >= 0;)
  129. {
  130. if ((Atom) clientMsg.data.l[4] == atoms.allowedActions[i])
  131. {
  132. targetAction = atoms.allowedActions[i];
  133. break;
  134. }
  135. }
  136. sendDragAndDropStatus (true, targetAction);
  137. if (dragInfo.position != dropPos)
  138. {
  139. dragInfo.position = dropPos;
  140. if (dragInfo.isEmpty())
  141. updateDraggedFileList (clientMsg, (::Window) peer->getNativeHandle());
  142. if (! dragInfo.isEmpty())
  143. peer->handleDragMove (dragInfo);
  144. }
  145. }
  146. void handleDragAndDropDrop (const XClientMessageEvent& clientMsg, ComponentPeer* peer)
  147. {
  148. if (dragInfo.isEmpty())
  149. {
  150. // no data, transaction finished in handleDragAndDropSelection()
  151. finishAfterDropDataReceived = true;
  152. updateDraggedFileList (clientMsg, (::Window) peer->getNativeHandle());
  153. }
  154. else
  155. {
  156. handleDragAndDropDataReceived(); // data was already received
  157. }
  158. }
  159. void handleDragAndDropEnter (const XClientMessageEvent& clientMsg, ComponentPeer* peer)
  160. {
  161. dragInfo.clear();
  162. srcMimeTypeAtomList.clear();
  163. dragAndDropCurrentMimeType = 0;
  164. auto dndCurrentVersion = (static_cast<unsigned long> (clientMsg.data.l[1]) & 0xff000000) >> 24;
  165. if (dndCurrentVersion < 3 || dndCurrentVersion > XWindowSystemUtilities::Atoms::DndVersion)
  166. {
  167. dragAndDropSourceWindow = 0;
  168. return;
  169. }
  170. const auto& atoms = getAtoms();
  171. dragAndDropSourceWindow = (::Window) clientMsg.data.l[0];
  172. if ((clientMsg.data.l[1] & 1) != 0)
  173. {
  174. XWindowSystemUtilities::ScopedXLock xLock;
  175. XWindowSystemUtilities::GetXProperty prop (getDisplay(),
  176. dragAndDropSourceWindow,
  177. atoms.XdndTypeList,
  178. 0,
  179. 0x8000000L,
  180. false,
  181. XA_ATOM);
  182. if (prop.success && prop.actualType == XA_ATOM && prop.actualFormat == 32 && prop.numItems != 0)
  183. {
  184. auto* types = prop.data;
  185. for (unsigned long i = 0; i < prop.numItems; ++i)
  186. {
  187. unsigned long type;
  188. memcpy (&type, types, sizeof (unsigned long));
  189. if (type != None)
  190. srcMimeTypeAtomList.add (type);
  191. types += sizeof (unsigned long);
  192. }
  193. }
  194. }
  195. if (srcMimeTypeAtomList.isEmpty())
  196. {
  197. for (int i = 2; i < 5; ++i)
  198. if (clientMsg.data.l[i] != None)
  199. srcMimeTypeAtomList.add ((unsigned long) clientMsg.data.l[i]);
  200. if (srcMimeTypeAtomList.isEmpty())
  201. {
  202. dragAndDropSourceWindow = 0;
  203. return;
  204. }
  205. }
  206. for (int i = 0; i < srcMimeTypeAtomList.size() && dragAndDropCurrentMimeType == 0; ++i)
  207. for (int j = 0; j < numElementsInArray (atoms.allowedMimeTypes); ++j)
  208. if (srcMimeTypeAtomList[i] == atoms.allowedMimeTypes[j])
  209. dragAndDropCurrentMimeType = atoms.allowedMimeTypes[j];
  210. handleDragAndDropPosition (clientMsg, peer);
  211. }
  212. void handleDragAndDropExit()
  213. {
  214. if (auto* peer = getPeerFor (windowH))
  215. peer->handleDragExit (dragInfo);
  216. resetDragAndDrop();
  217. }
  218. void handleDragAndDropSelection (const XEvent& evt)
  219. {
  220. dragInfo.clear();
  221. if (evt.xselection.property != None)
  222. {
  223. StringArray lines;
  224. {
  225. MemoryBlock dropData;
  226. for (;;)
  227. {
  228. XWindowSystemUtilities::GetXProperty prop (getDisplay(),
  229. evt.xany.window,
  230. evt.xselection.property,
  231. (long) (dropData.getSize() / 4),
  232. 65536,
  233. false,
  234. AnyPropertyType);
  235. if (! prop.success)
  236. break;
  237. dropData.append (prop.data, (size_t) (prop.actualFormat / 8) * prop.numItems);
  238. if (prop.bytesLeft <= 0)
  239. break;
  240. }
  241. lines.addLines (dropData.toString());
  242. }
  243. if (XWindowSystemUtilities::Atoms::isMimeTypeFile (getDisplay(), dragAndDropCurrentMimeType))
  244. {
  245. for (const auto& line : lines)
  246. {
  247. const auto escaped = line.replace ("+", "%2B").replace ("file://", String(), true);
  248. dragInfo.files.add (URL::removeEscapeChars (escaped));
  249. }
  250. dragInfo.files.trim();
  251. dragInfo.files.removeEmptyStrings();
  252. }
  253. else
  254. {
  255. dragInfo.text = lines.joinIntoString ("\n");
  256. }
  257. if (finishAfterDropDataReceived)
  258. handleDragAndDropDataReceived();
  259. }
  260. }
  261. void externalResetDragAndDrop()
  262. {
  263. if (dragging)
  264. {
  265. XWindowSystemUtilities::ScopedXLock xLock;
  266. X11Symbols::getInstance()->xUngrabPointer (getDisplay(), CurrentTime);
  267. }
  268. NullCheckedInvocation::invoke (completionCallback);
  269. dragging = false;
  270. }
  271. bool externalDragInit (::Window window, bool text, const String& str, std::function<void()>&& cb)
  272. {
  273. windowH = window;
  274. isText = text;
  275. textOrFiles = str;
  276. targetWindow = windowH;
  277. completionCallback = std::move (cb);
  278. auto* display = getDisplay();
  279. allowedTypes.add (XWindowSystemUtilities::Atoms::getCreating (display, isText ? "text/plain" : "text/uri-list"));
  280. auto pointerGrabMask = (unsigned int) (Button1MotionMask | ButtonReleaseMask);
  281. XWindowSystemUtilities::ScopedXLock xLock;
  282. if (X11Symbols::getInstance()->xGrabPointer (display, windowH, True, pointerGrabMask,
  283. GrabModeAsync, GrabModeAsync, None, None, CurrentTime) == GrabSuccess)
  284. {
  285. const auto& atoms = getAtoms();
  286. // No other method of changing the pointer seems to work, this call is needed from this very context
  287. X11Symbols::getInstance()->xChangeActivePointerGrab (display, pointerGrabMask, (Cursor) createDraggingHandCursor(), CurrentTime);
  288. X11Symbols::getInstance()->xSetSelectionOwner (display, atoms.XdndSelection, windowH, CurrentTime);
  289. // save the available types to XdndTypeList
  290. X11Symbols::getInstance()->xChangeProperty (display, windowH, atoms.XdndTypeList, XA_ATOM, 32, PropModeReplace,
  291. reinterpret_cast<const unsigned char*> (allowedTypes.getRawDataPointer()), allowedTypes.size());
  292. dragging = true;
  293. xdndVersion = getDnDVersionForWindow (targetWindow);
  294. sendExternalDragAndDropEnter();
  295. handleExternalDragMotionNotify();
  296. return true;
  297. }
  298. return false;
  299. }
  300. private:
  301. //==============================================================================
  302. const XWindowSystemUtilities::Atoms& getAtoms() const noexcept { return XWindowSystem::getInstance()->getAtoms(); }
  303. ::Display* getDisplay() const noexcept { return XWindowSystem::getInstance()->getDisplay(); }
  304. //==============================================================================
  305. void sendDragAndDropMessage (XClientMessageEvent& msg)
  306. {
  307. auto* display = getDisplay();
  308. msg.type = ClientMessage;
  309. msg.display = display;
  310. msg.window = dragAndDropSourceWindow;
  311. msg.format = 32;
  312. msg.data.l[0] = (long) windowH;
  313. XWindowSystemUtilities::ScopedXLock xLock;
  314. X11Symbols::getInstance()->xSendEvent (display, dragAndDropSourceWindow, False, 0, (XEvent*) &msg);
  315. }
  316. bool sendExternalDragAndDropMessage (XClientMessageEvent& msg)
  317. {
  318. auto* display = getDisplay();
  319. msg.type = ClientMessage;
  320. msg.display = display;
  321. msg.window = targetWindow;
  322. msg.format = 32;
  323. msg.data.l[0] = (long) windowH;
  324. XWindowSystemUtilities::ScopedXLock xLock;
  325. return X11Symbols::getInstance()->xSendEvent (display, targetWindow, False, 0, (XEvent*) &msg) != 0;
  326. }
  327. void sendExternalDragAndDropDrop()
  328. {
  329. XClientMessageEvent msg;
  330. zerostruct (msg);
  331. msg.message_type = getAtoms().XdndDrop;
  332. msg.data.l[2] = CurrentTime;
  333. sendExternalDragAndDropMessage (msg);
  334. }
  335. void sendExternalDragAndDropEnter()
  336. {
  337. XClientMessageEvent msg;
  338. zerostruct (msg);
  339. msg.message_type = getAtoms().XdndEnter;
  340. msg.data.l[1] = (xdndVersion << 24);
  341. for (int i = 0; i < 3; ++i)
  342. msg.data.l[i + 2] = (long) allowedTypes[i];
  343. sendExternalDragAndDropMessage (msg);
  344. }
  345. void sendExternalDragAndDropPosition()
  346. {
  347. XClientMessageEvent msg;
  348. zerostruct (msg);
  349. const auto& atoms = getAtoms();
  350. msg.message_type = atoms.XdndPosition;
  351. auto mousePos = Desktop::getInstance().getMousePosition();
  352. if (silentRect.contains (mousePos)) // we've been asked to keep silent
  353. return;
  354. mousePos = Desktop::getInstance().getDisplays().logicalToPhysical (mousePos);
  355. msg.data.l[1] = 0;
  356. msg.data.l[2] = (mousePos.x << 16) | mousePos.y;
  357. msg.data.l[3] = CurrentTime;
  358. msg.data.l[4] = (long) atoms.XdndActionCopy; // this is all JUCE currently supports
  359. expectingStatus = sendExternalDragAndDropMessage (msg);
  360. }
  361. void sendDragAndDropStatus (bool acceptDrop, Atom dropAction)
  362. {
  363. XClientMessageEvent msg;
  364. zerostruct (msg);
  365. msg.message_type = getAtoms().XdndStatus;
  366. msg.data.l[1] = (acceptDrop ? 1 : 0) | 2; // 2 indicates that we want to receive position messages
  367. msg.data.l[4] = (long) dropAction;
  368. sendDragAndDropMessage (msg);
  369. }
  370. void sendExternalDragAndDropLeave()
  371. {
  372. XClientMessageEvent msg;
  373. zerostruct (msg);
  374. msg.message_type = getAtoms().XdndLeave;
  375. sendExternalDragAndDropMessage (msg);
  376. }
  377. void sendDragAndDropFinish()
  378. {
  379. XClientMessageEvent msg;
  380. zerostruct (msg);
  381. msg.message_type = getAtoms().XdndFinished;
  382. sendDragAndDropMessage (msg);
  383. }
  384. void updateDraggedFileList (const XClientMessageEvent& clientMsg, ::Window requestor)
  385. {
  386. jassert (dragInfo.isEmpty());
  387. if (dragAndDropSourceWindow != None && dragAndDropCurrentMimeType != None)
  388. {
  389. auto* display = getDisplay();
  390. XWindowSystemUtilities::ScopedXLock xLock;
  391. X11Symbols::getInstance()->xConvertSelection (display, getAtoms().XdndSelection, dragAndDropCurrentMimeType,
  392. XWindowSystemUtilities::Atoms::getCreating (display, "JXSelectionWindowProperty"),
  393. requestor, (::Time) clientMsg.data.l[2]);
  394. }
  395. }
  396. bool isWindowDnDAware (::Window w) const
  397. {
  398. int numProperties = 0;
  399. auto* properties = X11Symbols::getInstance()->xListProperties (getDisplay(), w, &numProperties);
  400. bool dndAwarePropFound = false;
  401. for (int i = 0; i < numProperties; ++i)
  402. if (properties[i] == getAtoms().XdndAware)
  403. dndAwarePropFound = true;
  404. if (properties != nullptr)
  405. X11Symbols::getInstance()->xFree (properties);
  406. return dndAwarePropFound;
  407. }
  408. int getDnDVersionForWindow (::Window target)
  409. {
  410. XWindowSystemUtilities::GetXProperty prop (getDisplay(),
  411. target,
  412. getAtoms().XdndAware,
  413. 0,
  414. 2,
  415. false,
  416. AnyPropertyType);
  417. if (prop.success && prop.data != nullptr && prop.actualFormat == 32 && prop.numItems == 1)
  418. return jmin ((int) prop.data[0], (int) XWindowSystemUtilities::Atoms::DndVersion);
  419. return -1;
  420. }
  421. ::Window externalFindDragTargetWindow (::Window target)
  422. {
  423. if (target == None)
  424. return None;
  425. if (isWindowDnDAware (target))
  426. return target;
  427. ::Window child, phonyWin;
  428. int phony;
  429. unsigned int uphony;
  430. X11Symbols::getInstance()->xQueryPointer (getDisplay(), target, &phonyWin, &child, &phony, &phony, &phony, &phony, &uphony);
  431. return externalFindDragTargetWindow (child);
  432. }
  433. void handleDragAndDropDataReceived()
  434. {
  435. ComponentPeer::DragInfo dragInfoCopy (dragInfo);
  436. sendDragAndDropFinish();
  437. resetDragAndDrop();
  438. if (! dragInfoCopy.isEmpty())
  439. if (auto* peer = getPeerFor (windowH))
  440. peer->handleDragDrop (dragInfoCopy);
  441. }
  442. void resetDragAndDrop()
  443. {
  444. dragInfo.clear();
  445. dragInfo.position = Point<int> (-1, -1);
  446. dragAndDropCurrentMimeType = 0;
  447. dragAndDropSourceWindow = 0;
  448. srcMimeTypeAtomList.clear();
  449. finishAfterDropDataReceived = false;
  450. }
  451. //==============================================================================
  452. ::Window windowH = 0, targetWindow = 0, dragAndDropSourceWindow = 0;
  453. int xdndVersion = -1;
  454. bool isText = false, dragging = false, expectingStatus = false, canDrop = false, finishAfterDropDataReceived = false;
  455. Atom dragAndDropCurrentMimeType;
  456. Array<Atom> allowedTypes, srcMimeTypeAtomList;
  457. ComponentPeer::DragInfo dragInfo;
  458. Rectangle<int> silentRect;
  459. String textOrFiles;
  460. std::function<void()> completionCallback = nullptr;
  461. //==============================================================================
  462. JUCE_LEAK_DETECTOR (X11DragState)
  463. };
  464. } // namespace juce