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.

611 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. if (completionCallback != nullptr)
  269. completionCallback();
  270. dragging = false;
  271. }
  272. bool externalDragInit (::Window window, bool text, const String& str, std::function<void()>&& cb)
  273. {
  274. windowH = window;
  275. isText = text;
  276. textOrFiles = str;
  277. targetWindow = windowH;
  278. completionCallback = std::move (cb);
  279. auto* display = getDisplay();
  280. allowedTypes.add (XWindowSystemUtilities::Atoms::getCreating (display, isText ? "text/plain" : "text/uri-list"));
  281. auto pointerGrabMask = (unsigned int) (Button1MotionMask | ButtonReleaseMask);
  282. XWindowSystemUtilities::ScopedXLock xLock;
  283. if (X11Symbols::getInstance()->xGrabPointer (display, windowH, True, pointerGrabMask,
  284. GrabModeAsync, GrabModeAsync, None, None, CurrentTime) == GrabSuccess)
  285. {
  286. const auto& atoms = getAtoms();
  287. // No other method of changing the pointer seems to work, this call is needed from this very context
  288. X11Symbols::getInstance()->xChangeActivePointerGrab (display, pointerGrabMask, (Cursor) createDraggingHandCursor(), CurrentTime);
  289. X11Symbols::getInstance()->xSetSelectionOwner (display, atoms.XdndSelection, windowH, CurrentTime);
  290. // save the available types to XdndTypeList
  291. X11Symbols::getInstance()->xChangeProperty (display, windowH, atoms.XdndTypeList, XA_ATOM, 32, PropModeReplace,
  292. reinterpret_cast<const unsigned char*> (allowedTypes.getRawDataPointer()), allowedTypes.size());
  293. dragging = true;
  294. xdndVersion = getDnDVersionForWindow (targetWindow);
  295. sendExternalDragAndDropEnter();
  296. handleExternalDragMotionNotify();
  297. return true;
  298. }
  299. return false;
  300. }
  301. private:
  302. //==============================================================================
  303. const XWindowSystemUtilities::Atoms& getAtoms() const noexcept { return XWindowSystem::getInstance()->getAtoms(); }
  304. ::Display* getDisplay() const noexcept { return XWindowSystem::getInstance()->getDisplay(); }
  305. //==============================================================================
  306. void sendDragAndDropMessage (XClientMessageEvent& msg)
  307. {
  308. auto* display = getDisplay();
  309. msg.type = ClientMessage;
  310. msg.display = display;
  311. msg.window = dragAndDropSourceWindow;
  312. msg.format = 32;
  313. msg.data.l[0] = (long) windowH;
  314. XWindowSystemUtilities::ScopedXLock xLock;
  315. X11Symbols::getInstance()->xSendEvent (display, dragAndDropSourceWindow, False, 0, (XEvent*) &msg);
  316. }
  317. bool sendExternalDragAndDropMessage (XClientMessageEvent& msg)
  318. {
  319. auto* display = getDisplay();
  320. msg.type = ClientMessage;
  321. msg.display = display;
  322. msg.window = targetWindow;
  323. msg.format = 32;
  324. msg.data.l[0] = (long) windowH;
  325. XWindowSystemUtilities::ScopedXLock xLock;
  326. return X11Symbols::getInstance()->xSendEvent (display, targetWindow, False, 0, (XEvent*) &msg) != 0;
  327. }
  328. void sendExternalDragAndDropDrop()
  329. {
  330. XClientMessageEvent msg;
  331. zerostruct (msg);
  332. msg.message_type = getAtoms().XdndDrop;
  333. msg.data.l[2] = CurrentTime;
  334. sendExternalDragAndDropMessage (msg);
  335. }
  336. void sendExternalDragAndDropEnter()
  337. {
  338. XClientMessageEvent msg;
  339. zerostruct (msg);
  340. msg.message_type = getAtoms().XdndEnter;
  341. msg.data.l[1] = (xdndVersion << 24);
  342. for (int i = 0; i < 3; ++i)
  343. msg.data.l[i + 2] = (long) allowedTypes[i];
  344. sendExternalDragAndDropMessage (msg);
  345. }
  346. void sendExternalDragAndDropPosition()
  347. {
  348. XClientMessageEvent msg;
  349. zerostruct (msg);
  350. const auto& atoms = getAtoms();
  351. msg.message_type = atoms.XdndPosition;
  352. auto mousePos = Desktop::getInstance().getMousePosition();
  353. if (silentRect.contains (mousePos)) // we've been asked to keep silent
  354. return;
  355. mousePos = Desktop::getInstance().getDisplays().logicalToPhysical (mousePos);
  356. msg.data.l[1] = 0;
  357. msg.data.l[2] = (mousePos.x << 16) | mousePos.y;
  358. msg.data.l[3] = CurrentTime;
  359. msg.data.l[4] = (long) atoms.XdndActionCopy; // this is all JUCE currently supports
  360. expectingStatus = sendExternalDragAndDropMessage (msg);
  361. }
  362. void sendDragAndDropStatus (bool acceptDrop, Atom dropAction)
  363. {
  364. XClientMessageEvent msg;
  365. zerostruct (msg);
  366. msg.message_type = getAtoms().XdndStatus;
  367. msg.data.l[1] = (acceptDrop ? 1 : 0) | 2; // 2 indicates that we want to receive position messages
  368. msg.data.l[4] = (long) dropAction;
  369. sendDragAndDropMessage (msg);
  370. }
  371. void sendExternalDragAndDropLeave()
  372. {
  373. XClientMessageEvent msg;
  374. zerostruct (msg);
  375. msg.message_type = getAtoms().XdndLeave;
  376. sendExternalDragAndDropMessage (msg);
  377. }
  378. void sendDragAndDropFinish()
  379. {
  380. XClientMessageEvent msg;
  381. zerostruct (msg);
  382. msg.message_type = getAtoms().XdndFinished;
  383. sendDragAndDropMessage (msg);
  384. }
  385. void updateDraggedFileList (const XClientMessageEvent& clientMsg, ::Window requestor)
  386. {
  387. jassert (dragInfo.isEmpty());
  388. if (dragAndDropSourceWindow != None && dragAndDropCurrentMimeType != None)
  389. {
  390. auto* display = getDisplay();
  391. XWindowSystemUtilities::ScopedXLock xLock;
  392. X11Symbols::getInstance()->xConvertSelection (display, getAtoms().XdndSelection, dragAndDropCurrentMimeType,
  393. XWindowSystemUtilities::Atoms::getCreating (display, "JXSelectionWindowProperty"),
  394. requestor, (::Time) clientMsg.data.l[2]);
  395. }
  396. }
  397. bool isWindowDnDAware (::Window w) const
  398. {
  399. int numProperties = 0;
  400. auto* properties = X11Symbols::getInstance()->xListProperties (getDisplay(), w, &numProperties);
  401. bool dndAwarePropFound = false;
  402. for (int i = 0; i < numProperties; ++i)
  403. if (properties[i] == getAtoms().XdndAware)
  404. dndAwarePropFound = true;
  405. if (properties != nullptr)
  406. X11Symbols::getInstance()->xFree (properties);
  407. return dndAwarePropFound;
  408. }
  409. int getDnDVersionForWindow (::Window target)
  410. {
  411. XWindowSystemUtilities::GetXProperty prop (getDisplay(),
  412. target,
  413. getAtoms().XdndAware,
  414. 0,
  415. 2,
  416. false,
  417. AnyPropertyType);
  418. if (prop.success && prop.data != nullptr && prop.actualFormat == 32 && prop.numItems == 1)
  419. return jmin ((int) prop.data[0], (int) XWindowSystemUtilities::Atoms::DndVersion);
  420. return -1;
  421. }
  422. ::Window externalFindDragTargetWindow (::Window target)
  423. {
  424. if (target == None)
  425. return None;
  426. if (isWindowDnDAware (target))
  427. return target;
  428. ::Window child, phonyWin;
  429. int phony;
  430. unsigned int uphony;
  431. X11Symbols::getInstance()->xQueryPointer (getDisplay(), target, &phonyWin, &child, &phony, &phony, &phony, &phony, &uphony);
  432. return externalFindDragTargetWindow (child);
  433. }
  434. void handleDragAndDropDataReceived()
  435. {
  436. ComponentPeer::DragInfo dragInfoCopy (dragInfo);
  437. sendDragAndDropFinish();
  438. resetDragAndDrop();
  439. if (! dragInfoCopy.isEmpty())
  440. if (auto* peer = getPeerFor (windowH))
  441. peer->handleDragDrop (dragInfoCopy);
  442. }
  443. void resetDragAndDrop()
  444. {
  445. dragInfo.clear();
  446. dragInfo.position = Point<int> (-1, -1);
  447. dragAndDropCurrentMimeType = 0;
  448. dragAndDropSourceWindow = 0;
  449. srcMimeTypeAtomList.clear();
  450. finishAfterDropDataReceived = false;
  451. }
  452. //==============================================================================
  453. ::Window windowH = 0, targetWindow = 0, dragAndDropSourceWindow = 0;
  454. int xdndVersion = -1;
  455. bool isText = false, dragging = false, expectingStatus = false, canDrop = false, finishAfterDropDataReceived = false;
  456. Atom dragAndDropCurrentMimeType;
  457. Array<Atom> allowedTypes, srcMimeTypeAtomList;
  458. ComponentPeer::DragInfo dragInfo;
  459. Rectangle<int> silentRect;
  460. String textOrFiles;
  461. std::function<void()> completionCallback = nullptr;
  462. //==============================================================================
  463. JUCE_LEAK_DETECTOR (X11DragState)
  464. };
  465. } // namespace juce