JUCE-7.0.12-0-g4f43011b96 JUCE-7.0.12-0-g4f43011b96
JUCE — C++ application framework with suport for VST, VST3, LV2 audio plug-ins

« « « Anklang Documentation
Loading...
Searching...
No Matches
juce_DragAndDrop_linux.cpp
Go to the documentation of this file.
1 /*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - Raw Material Software Limited
6
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
9
10 By using JUCE, you agree to the terms of both the JUCE 7 End-User License
11 Agreement and JUCE Privacy Policy.
12
13 End User License Agreement: www.juce.com/juce-7-licence
14 Privacy Policy: www.juce.com/juce-privacy-policy
15
16 Or: You may also use this code under the terms of the GPL v3 (see
17 www.gnu.org/licenses).
18
19 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21 DISCLAIMED.
22
23 ==============================================================================
24*/
25
26namespace juce
27{
28
29static Cursor createDraggingHandCursor();
30ComponentPeer* getPeerFor (::Window);
31
32//==============================================================================
34{
35public:
36 X11DragState() = default;
37
38 //==============================================================================
39 bool isDragging() const noexcept
40 {
41 return dragging;
42 }
43
44 //==============================================================================
45 void handleExternalSelectionClear()
46 {
47 if (dragging)
48 externalResetDragAndDrop();
49 }
50
51 void handleExternalSelectionRequest (const XEvent& evt)
52 {
53 auto targetType = evt.xselectionrequest.target;
54
55 XEvent s;
56 s.xselection.type = SelectionNotify;
57 s.xselection.requestor = evt.xselectionrequest.requestor;
58 s.xselection.selection = evt.xselectionrequest.selection;
59 s.xselection.target = targetType;
60 s.xselection.property = None;
61 s.xselection.time = evt.xselectionrequest.time;
62
63 auto* display = getDisplay();
64
65 if (allowedTypes.contains (targetType))
66 {
67 s.xselection.property = evt.xselectionrequest.property;
68
69 X11Symbols::getInstance()->xChangeProperty (display, evt.xselectionrequest.requestor, evt.xselectionrequest.property,
71 reinterpret_cast<const unsigned char*> (textOrFiles.toRawUTF8()),
72 (int) textOrFiles.getNumBytesAsUTF8());
73 }
74
75 X11Symbols::getInstance()->xSendEvent (display, evt.xselectionrequest.requestor, True, 0, &s);
76 }
77
78 void handleExternalDragAndDropStatus (const XClientMessageEvent& clientMsg)
79 {
80 if (expectingStatus)
81 {
82 expectingStatus = false;
83 canDrop = false;
84 silentRect = {};
85
86 const auto& atoms = getAtoms();
87
88 if ((clientMsg.data.l[1] & 1) != 0
89 && ((Atom) clientMsg.data.l[4] == atoms.XdndActionCopy
90 || (Atom) clientMsg.data.l[4] == atoms.XdndActionPrivate))
91 {
92 if ((clientMsg.data.l[1] & 2) == 0) // target requests silent rectangle
93 silentRect.setBounds ((int) clientMsg.data.l[2] >> 16, (int) clientMsg.data.l[2] & 0xffff,
94 (int) clientMsg.data.l[3] >> 16, (int) clientMsg.data.l[3] & 0xffff);
95
96 canDrop = true;
97 }
98 }
99 }
100
101 void handleExternalDragButtonReleaseEvent()
102 {
103 if (dragging)
104 X11Symbols::getInstance()->xUngrabPointer (getDisplay(), CurrentTime);
105
106 if (canDrop)
107 {
108 sendExternalDragAndDropDrop();
109 }
110 else
111 {
112 sendExternalDragAndDropLeave();
113 externalResetDragAndDrop();
114 }
115 }
116
117 void handleExternalDragMotionNotify()
118 {
119 auto* display = getDisplay();
120
121 auto newTargetWindow = externalFindDragTargetWindow (X11Symbols::getInstance()
122 ->xRootWindow (display,
123 X11Symbols::getInstance()->xDefaultScreen (display)));
124
125 if (targetWindow != newTargetWindow)
126 {
127 if (targetWindow != None)
128 sendExternalDragAndDropLeave();
129
130 canDrop = false;
131 silentRect = {};
132
133 if (newTargetWindow == None)
134 return;
135
136 xdndVersion = getDnDVersionForWindow (newTargetWindow);
137
138 if (xdndVersion == -1)
139 return;
140
141 targetWindow = newTargetWindow;
142 sendExternalDragAndDropEnter();
143 }
144
145 if (! expectingStatus)
146 sendExternalDragAndDropPosition();
147 }
148
149 void handleDragAndDropPosition (const XClientMessageEvent& clientMsg, ComponentPeer* peer)
150 {
151 if (dragAndDropSourceWindow == 0)
152 return;
153
154 dragAndDropSourceWindow = (::Window) clientMsg.data.l[0];
155
156 if (windowH == 0)
157 windowH = (::Window) peer->getNativeHandle();
158
159 const auto displays = Desktop::getInstance().getDisplays();
160 const auto logicalPos = displays.physicalToLogical (Point<int> ((int) clientMsg.data.l[2] >> 16,
161 (int) clientMsg.data.l[2] & 0xffff));
162 const auto dropPos = detail::ScalingHelpers::screenPosToLocalPos (peer->getComponent(), logicalPos.toFloat()).roundToInt();
163
164 const auto& atoms = getAtoms();
165
166 auto targetAction = atoms.XdndActionCopy;
167
168 for (int i = numElementsInArray (atoms.allowedActions); --i >= 0;)
169 {
170 if ((Atom) clientMsg.data.l[4] == atoms.allowedActions[i])
171 {
172 targetAction = atoms.allowedActions[i];
173 break;
174 }
175 }
176
177 sendDragAndDropStatus (true, targetAction);
178
179 if (dragInfo.position != dropPos)
180 {
181 dragInfo.position = dropPos;
182
183 if (dragInfo.isEmpty())
184 updateDraggedFileList (clientMsg, (::Window) peer->getNativeHandle());
185
186 if (! dragInfo.isEmpty())
187 peer->handleDragMove (dragInfo);
188 }
189 }
190
191 void handleDragAndDropDrop (const XClientMessageEvent& clientMsg, ComponentPeer* peer)
192 {
193 if (dragInfo.isEmpty())
194 {
195 // no data, transaction finished in handleDragAndDropSelection()
196 finishAfterDropDataReceived = true;
197 updateDraggedFileList (clientMsg, (::Window) peer->getNativeHandle());
198 }
199 else
200 {
201 handleDragAndDropDataReceived(); // data was already received
202 }
203 }
204
205 void handleDragAndDropEnter (const XClientMessageEvent& clientMsg, ComponentPeer* peer)
206 {
207 dragInfo.clear();
208 srcMimeTypeAtomList.clear();
209
210 dragAndDropCurrentMimeType = 0;
211 auto dndCurrentVersion = (static_cast<unsigned long> (clientMsg.data.l[1]) & 0xff000000) >> 24;
212
213 if (dndCurrentVersion < 3 || dndCurrentVersion > XWindowSystemUtilities::Atoms::DndVersion)
214 {
215 dragAndDropSourceWindow = 0;
216 return;
217 }
218
219 const auto& atoms = getAtoms();
220
221 dragAndDropSourceWindow = (::Window) clientMsg.data.l[0];
222
223 if ((clientMsg.data.l[1] & 1) != 0)
224 {
226
228 dragAndDropSourceWindow,
229 atoms.XdndTypeList,
230 0,
231 0x8000000L,
232 false,
233 XA_ATOM);
234
235 if (prop.success && prop.actualType == XA_ATOM && prop.actualFormat == 32 && prop.numItems != 0)
236 {
237 auto* types = prop.data;
238
239 for (unsigned long i = 0; i < prop.numItems; ++i)
240 {
241 unsigned long type;
242 memcpy (&type, types, sizeof (unsigned long));
243
244 if (type != None)
245 srcMimeTypeAtomList.add (type);
246
247 types += sizeof (unsigned long);
248 }
249 }
250 }
251
252 if (srcMimeTypeAtomList.isEmpty())
253 {
254 for (int i = 2; i < 5; ++i)
255 if (clientMsg.data.l[i] != None)
256 srcMimeTypeAtomList.add ((unsigned long) clientMsg.data.l[i]);
257
258 if (srcMimeTypeAtomList.isEmpty())
259 {
260 dragAndDropSourceWindow = 0;
261 return;
262 }
263 }
264
265 for (int i = 0; i < srcMimeTypeAtomList.size() && dragAndDropCurrentMimeType == 0; ++i)
266 for (int j = 0; j < numElementsInArray (atoms.allowedMimeTypes); ++j)
267 if (srcMimeTypeAtomList[i] == atoms.allowedMimeTypes[j])
268 dragAndDropCurrentMimeType = atoms.allowedMimeTypes[j];
269
270 handleDragAndDropPosition (clientMsg, peer);
271 }
272
273 void handleDragAndDropExit()
274 {
275 if (auto* peer = getPeerFor (windowH))
276 peer->handleDragExit (dragInfo);
277
278 resetDragAndDrop();
279 }
280
281 void handleDragAndDropSelection (const XEvent& evt)
282 {
283 dragInfo.clear();
284
285 if (evt.xselection.property != None)
286 {
287 StringArray lines;
288
289 {
291
292 for (;;)
293 {
295 evt.xany.window,
296 evt.xselection.property,
297 (long) (dropData.getSize() / 4),
298 65536,
299 false,
301
302 if (! prop.success)
303 break;
304
305 dropData.append (prop.data, (size_t) (prop.actualFormat / 8) * prop.numItems);
306
307 if (prop.bytesLeft <= 0)
308 break;
309 }
310
311 lines.addLines (dropData.toString());
312 }
313
314 if (XWindowSystemUtilities::Atoms::isMimeTypeFile (getDisplay(), dragAndDropCurrentMimeType))
315 {
316 for (const auto& line : lines)
317 {
318 const auto escaped = line.replace ("+", "%2B").replace ("file://", String(), true);
319 dragInfo.files.add (URL::removeEscapeChars (escaped));
320 }
321
322 dragInfo.files.trim();
323 dragInfo.files.removeEmptyStrings();
324 }
325 else
326 {
327 dragInfo.text = lines.joinIntoString ("\n");
328 }
329
330 if (finishAfterDropDataReceived)
331 handleDragAndDropDataReceived();
332 }
333 }
334
335 void externalResetDragAndDrop()
336 {
337 if (dragging)
338 {
340 X11Symbols::getInstance()->xUngrabPointer (getDisplay(), CurrentTime);
341 }
342
343 NullCheckedInvocation::invoke (completionCallback);
344
345 dragging = false;
346 }
347
348 bool externalDragInit (::Window window, bool text, const String& str, std::function<void()>&& cb)
349 {
350 windowH = window;
351 isText = text;
352 textOrFiles = str;
353 targetWindow = windowH;
354 completionCallback = std::move (cb);
355
356 auto* display = getDisplay();
357
358 allowedTypes.add (XWindowSystemUtilities::Atoms::getCreating (display, isText ? "text/plain" : "text/uri-list"));
359
361
363
364 if (X11Symbols::getInstance()->xGrabPointer (display, windowH, True, pointerGrabMask,
366 {
367 const auto& atoms = getAtoms();
368
369 // No other method of changing the pointer seems to work, this call is needed from this very context
370 X11Symbols::getInstance()->xChangeActivePointerGrab (display, pointerGrabMask, (Cursor) createDraggingHandCursor(), CurrentTime);
371
372 X11Symbols::getInstance()->xSetSelectionOwner (display, atoms.XdndSelection, windowH, CurrentTime);
373
374 // save the available types to XdndTypeList
375 X11Symbols::getInstance()->xChangeProperty (display, windowH, atoms.XdndTypeList, XA_ATOM, 32, PropModeReplace,
376 reinterpret_cast<const unsigned char*> (allowedTypes.getRawDataPointer()), allowedTypes.size());
377
378 dragging = true;
379 xdndVersion = getDnDVersionForWindow (targetWindow);
380
381 sendExternalDragAndDropEnter();
382 handleExternalDragMotionNotify();
383
384 return true;
385 }
386
387 return false;
388 }
389
390private:
391 //==============================================================================
392 const XWindowSystemUtilities::Atoms& getAtoms() const noexcept { return XWindowSystem::getInstance()->getAtoms(); }
393 ::Display* getDisplay() const noexcept { return XWindowSystem::getInstance()->getDisplay(); }
394
395 //==============================================================================
396 void sendDragAndDropMessage (XClientMessageEvent& msg)
397 {
398 auto* display = getDisplay();
399
400 msg.type = ClientMessage;
401 msg.display = display;
402 msg.window = dragAndDropSourceWindow;
403 msg.format = 32;
404 msg.data.l[0] = (long) windowH;
405
407 X11Symbols::getInstance()->xSendEvent (display, dragAndDropSourceWindow, False, 0, (XEvent*) &msg);
408 }
409
410 bool sendExternalDragAndDropMessage (XClientMessageEvent& msg)
411 {
412 auto* display = getDisplay();
413
414 msg.type = ClientMessage;
415 msg.display = display;
416 msg.window = targetWindow;
417 msg.format = 32;
418 msg.data.l[0] = (long) windowH;
419
421 return X11Symbols::getInstance()->xSendEvent (display, targetWindow, False, 0, (XEvent*) &msg) != 0;
422 }
423
424 void sendExternalDragAndDropDrop()
425 {
427 zerostruct (msg);
428
429 msg.message_type = getAtoms().XdndDrop;
430 msg.data.l[2] = CurrentTime;
431
432 sendExternalDragAndDropMessage (msg);
433 }
434
435 void sendExternalDragAndDropEnter()
436 {
438 zerostruct (msg);
439
440 msg.message_type = getAtoms().XdndEnter;
441 msg.data.l[1] = (xdndVersion << 24);
442
443 for (int i = 0; i < 3; ++i)
444 msg.data.l[i + 2] = (long) allowedTypes[i];
445
446 sendExternalDragAndDropMessage (msg);
447 }
448
449 void sendExternalDragAndDropPosition()
450 {
452 zerostruct (msg);
453
454 const auto& atoms = getAtoms();
455
456 msg.message_type = atoms.XdndPosition;
457
459
460 if (silentRect.contains (mousePos)) // we've been asked to keep silent
461 return;
462
464
465 msg.data.l[1] = 0;
466 msg.data.l[2] = (mousePos.x << 16) | mousePos.y;
467 msg.data.l[3] = CurrentTime;
468 msg.data.l[4] = (long) atoms.XdndActionCopy; // this is all JUCE currently supports
469
470 expectingStatus = sendExternalDragAndDropMessage (msg);
471 }
472
473 void sendDragAndDropStatus (bool acceptDrop, Atom dropAction)
474 {
476 zerostruct (msg);
477
478 msg.message_type = getAtoms().XdndStatus;
479 msg.data.l[1] = (acceptDrop ? 1 : 0) | 2; // 2 indicates that we want to receive position messages
480 msg.data.l[4] = (long) dropAction;
481
482 sendDragAndDropMessage (msg);
483 }
484
485 void sendExternalDragAndDropLeave()
486 {
488 zerostruct (msg);
489
490 msg.message_type = getAtoms().XdndLeave;
491 sendExternalDragAndDropMessage (msg);
492 }
493
494 void sendDragAndDropFinish()
495 {
497 zerostruct (msg);
498
499 msg.message_type = getAtoms().XdndFinished;
500 sendDragAndDropMessage (msg);
501 }
502
503 void updateDraggedFileList (const XClientMessageEvent& clientMsg, ::Window requestor)
504 {
505 jassert (dragInfo.isEmpty());
506
507 if (dragAndDropSourceWindow != None && dragAndDropCurrentMimeType != None)
508 {
509 auto* display = getDisplay();
510
512 X11Symbols::getInstance()->xConvertSelection (display, getAtoms().XdndSelection, dragAndDropCurrentMimeType,
513 XWindowSystemUtilities::Atoms::getCreating (display, "JXSelectionWindowProperty"),
514 requestor, (::Time) clientMsg.data.l[2]);
515 }
516 }
517
518 bool isWindowDnDAware (::Window w) const
519 {
520 int numProperties = 0;
521 auto* properties = X11Symbols::getInstance()->xListProperties (getDisplay(), w, &numProperties);
522
523 bool dndAwarePropFound = false;
524
525 for (int i = 0; i < numProperties; ++i)
526 if (properties[i] == getAtoms().XdndAware)
527 dndAwarePropFound = true;
528
529 if (properties != nullptr)
530 X11Symbols::getInstance()->xFree (properties);
531
532 return dndAwarePropFound;
533 }
534
535 int getDnDVersionForWindow (::Window target)
536 {
538 target,
539 getAtoms().XdndAware,
540 0,
541 2,
542 false,
544
545 if (prop.success && prop.data != nullptr && prop.actualFormat == 32 && prop.numItems == 1)
546 return jmin ((int) prop.data[0], (int) XWindowSystemUtilities::Atoms::DndVersion);
547
548 return -1;
549 }
550
551 ::Window externalFindDragTargetWindow (::Window target)
552 {
553 if (target == None)
554 return None;
555
556 if (isWindowDnDAware (target))
557 return target;
558
559 ::Window child, phonyWin;
560 int phony;
561 unsigned int uphony;
562
563 X11Symbols::getInstance()->xQueryPointer (getDisplay(), target, &phonyWin, &child, &phony, &phony, &phony, &phony, &uphony);
564
565 return externalFindDragTargetWindow (child);
566 }
567
568 void handleDragAndDropDataReceived()
569 {
571
572 sendDragAndDropFinish();
573 resetDragAndDrop();
574
575 if (! dragInfoCopy.isEmpty())
576 if (auto* peer = getPeerFor (windowH))
577 peer->handleDragDrop (dragInfoCopy);
578 }
579
580 void resetDragAndDrop()
581 {
582 dragInfo.clear();
583 dragInfo.position = Point<int> (-1, -1);
584 dragAndDropCurrentMimeType = 0;
585 dragAndDropSourceWindow = 0;
586 srcMimeTypeAtomList.clear();
587 finishAfterDropDataReceived = false;
588 }
589
590 //==============================================================================
591 ::Window windowH = 0, targetWindow = 0, dragAndDropSourceWindow = 0;
592
593 int xdndVersion = -1;
594 bool isText = false, dragging = false, expectingStatus = false, canDrop = false, finishAfterDropDataReceived = false;
595
596 Atom dragAndDropCurrentMimeType;
597 Array<Atom> allowedTypes, srcMimeTypeAtomList;
598
600 Rectangle<int> silentRect;
601 String textOrFiles;
602
603 std::function<void()> completionCallback = nullptr;
604
605 //==============================================================================
607};
608
609} // namespace juce
Holds a resizable array of primitive or copy-by-value objects.
Definition juce_Array.h:56
bool isEmpty() const noexcept
Returns true if the array is empty, false otherwise.
Definition juce_Array.h:222
int size() const noexcept
Returns the current number of elements in the array.
Definition juce_Array.h:215
ElementType * getRawDataPointer() noexcept
Returns a pointer to the actual array data.
Definition juce_Array.h:310
void add(const ElementType &newElement)
Appends a new element at the end of the array.
Definition juce_Array.h:418
bool contains(ParameterType elementToLookFor) const
Returns true if the array contains at least one occurrence of an object.
Definition juce_Array.h:400
void clear()
Removes all elements from the array.
Definition juce_Array.h:188
The Component class uses a ComponentPeer internally to create and manage a real operating-system wind...
virtual void * getNativeHandle() const =0
Returns the raw handle to whatever kind of window is being used.
Component & getComponent() noexcept
Returns the component being represented by this peer.
const Displays & getDisplays() const noexcept
Returns the Displays object representing the connected displays.
static Point< int > getMousePosition()
Returns the mouse position.
static Desktop &JUCE_CALLTYPE getInstance()
There's only one desktop object, and this method will return it.
Rectangle< int > logicalToPhysical(Rectangle< int > logicalRect, const Display *useScaleFactorOfDisplay=nullptr) const noexcept
Converts an integer Rectangle from logical to physical pixels.
Rectangle< int > physicalToLogical(Rectangle< int > physicalRect, const Display *useScaleFactorOfDisplay=nullptr) const noexcept
Converts an integer Rectangle from physical to logical pixels.
A class to hold a resizable block of raw data.
A pair of (x, y) coordinates.
Definition juce_Point.h:42
Manages a rectangle and allows geometric operations to be performed on it.
bool contains(ValueType xCoord, ValueType yCoord) const noexcept
Returns true if this coordinate is inside the rectangle.
void setBounds(ValueType newX, ValueType newY, ValueType newWidth, ValueType newHeight) noexcept
Changes all the rectangle's coordinates.
A special array for holding a list of strings.
String joinIntoString(StringRef separatorString, int startIndex=0, int numberOfElements=-1) const
Joins the strings in the array together into one string.
void removeEmptyStrings(bool removeWhitespaceStrings=true)
Removes empty strings from the array.
void trim()
Deletes any whitespace characters from the starts and ends of all the strings.
void add(String stringToAdd)
Appends a string at the end of the array.
int addLines(StringRef stringToBreakUp)
Breaks up a string into lines and adds them to this array.
The JUCE String class!
Definition juce_String.h:53
const char * toRawUTF8() const
Returns a pointer to a UTF-8 version of this string.
size_t getNumBytesAsUTF8() const noexcept
Returns the number of bytes required to represent this string as UTF8.
static String removeEscapeChars(const String &stringToRemoveEscapeCharsFrom)
Replaces any escape character sequences in a string with their original character codes.
Definition juce_URL.cpp:937
#define JUCE_LEAK_DETECTOR(OwnerClass)
This macro lets you embed a leak-detecting object inside a class.
#define jassert(expression)
Platform-independent assertion macro.
typedef int
memcpy
JUCE Namespace.
void zerostruct(Type &structure) noexcept
Overwrites a structure or object with zeros.
Definition juce_Memory.h:32
constexpr Type jmin(Type a, Type b)
Returns the smaller of two values.
Type unalignedPointerCast(void *ptr) noexcept
Casts a pointer to another type via void*, which suppresses the cast-align warning which sometimes ar...
Definition juce_Memory.h:88
constexpr int numElementsInArray(Type(&)[N]) noexcept
Handy function for getting the number of elements in a simple const C array.
Structure to describe drag and drop information.
Initialises and stores some atoms for the display.
Gets a specified window property and stores its associated data, freeing it on deletion.
A handy struct that uses XLockDisplay and XUnlockDisplay to lock the X server using RAII.
typedef long