2 /* This program is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or
5 * (at your option) any later version.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 #include <X11/Xatom.h>
22 #include "libs/fvwmlib.h"
23 #include "libs/Target.h"
24 #include "dragSource.h"
25 #include "cursorStuff.h"
26 #include "fvwmDragWell.h"
27 #define SQR(x) ((x)*(x))
29 extern int xdndCursorInit(Display
*, Window
);
30 extern XdndCursor xdndCursors
[];
31 extern char dropData
[];
36 void xdndSrcSendDrop(DragSource
*, Window
, Window
, unsigned long);
37 int xdndSrcQueryDndAware (DragSource
*ds
, Window window
, int *version
,
41 /* Determines if the cursor is in the rectangle .
42 * Returns true if the cursor is in the rectangle.
43 * ds - the drag source, contains the rectangle info.
44 * rx,ry - the cursor in root coordinates */
45 int cursorInRect(DragSource
*ds
, unsigned short rx
, unsigned short ry
) {
46 if ((rx
> ds
->rx
) && (rx
<ds
->rx
+ds
->w
)) {
47 if ((ry
> ds
->ry
) && (ry
<ds
->ry
+ds
->h
))
55 /* Initializes a XEvent client message
56 * xev - the XEvent to initialize
59 * messageType - the message type */
60 void xevClientInit(XEvent
*xev
, Display
*dpy
, Window win
, Atom messageType
) {
61 memset (xev
, 0, sizeof (XEvent
));
62 xev
->xany
.type
= ClientMessage
;
63 xev
->xany
.display
= dpy
;
64 xev
->xclient
.window
= win
;
65 xev
->xclient
.message_type
= messageType
;
66 xev
->xclient
.format
= 32;
70 /* Sends an "enter" event.
71 * ds - the drag source.
72 * dstWin - the recipient of the event.
73 * srcWin - the drag source win
74 * typelist - the types the drag source supports, assumes that
75 * the list is NULL terminated. */
76 void xdndSrcSendEnter(DragSource
*ds
, Window dstWin
, Window srcWin
, Atom
* typelist
)
80 unsigned long nTypes
=0,tmpUL
=0;
82 if (typelist
!= NULL
) {
83 while (typelist
[nTypes
++]!=0); /*get typelist length*/
86 xevClientInit(&xevent
,ds
->display
,dstWin
,ds
->atomSel
->xdndEnter
);
88 /* data.l[0] : XID of source window
89 data.l[1] : Bit 0 is set if source supports more than three types.
90 High byte is protocol version(min of source,target). Version
91 is set in window property of target.
92 data.l[2,3,4] : First three types. Unused set to none. */
94 xevent
.xclient
.data
.l
[0] = srcWin
;
95 if (nTypes
>3) /*this should never happen for fvwmQFS*/
97 xevent
.xclient
.data
.l
[1] = (tmpUL
| ((ds
->version
& 0xFFUL
) << 24));
98 for (i
= 0; i
< XDND_MIN(nTypes
,3) ; i
++)
99 xevent
.xclient
.data
.l
[i
+2] = typelist
[i
];
100 for (i
= nTypes
; i
< 3 ; i
++)
101 xevent
.xclient
.data
.l
[i
+2] = None
;
102 FSendEvent(ds
->display
, dstWin
, 0, 0, &xevent
);
107 /* Sends a position event
108 * ds - the drag source struct
109 * dstWin - the recipient of the event
110 * srcWin - the drag source window
111 * x,y - cursor position int root coords
113 * action - the requested action */
114 void xdndSrcSendPosition(DragSource
*ds
, Window dstWin
, Window srcWin
, short x
,
115 short y
, unsigned long time
, Atom action
) {
118 xevClientInit(&xevent
,ds
->display
,dstWin
,ds
->atomSel
->xdndPosition
);
120 /* data.l[0] : XID of source window
121 data.l[1] : Reserved for future use.
122 data.l[2] : Coordinates of mouse relative to root window(x<<16|y)
123 data.l[3] : time stamp for retrieving data
124 data.l[4] : action requested by user */
125 xevent
.xclient
.data
.l
[0] = srcWin
;
126 xevent
.xclient
.data
.l
[2] = (x
<<16) | y
;
127 xevent
.xclient
.data
.l
[3] = time
;
128 xevent
.xclient
.data
.l
[4] = action
;
129 FSendEvent(ds
->display
, dstWin
, 0, 0, &xevent
);
134 /* Handles the receipt of a status event
135 * ds - the drag source struct
136 * xev - the status event
137 * cx,cy - the mouse x,y coords, in root frame, used if the event
140 void xdndSrcReceiveStatus(DragSource
*ds
, XEvent
*xev
,unsigned short *cx
,
141 unsigned short *cy
, unsigned long time
)
145 if (ds
->dropTargWin
!= xev
->xclient
.data
.l
[0])
147 return; /*Got ClientMessage:XdndStatus from wrong client window*/
151 *data.l[0] : XID of target window
152 *data.l[1] : Bit 0 set if target accepts drop
153 * Bit 1 set if target wants coordinates while in rectangle
154 *data.l[2,3] : Coordinates of box. (x,y) relative to root. Null box ok.
155 *data.l[2] : (x<<16)|y
156 *data.l[3] : (w<<16)|h
157 *data.l[4] : action accepted by target. Either should be one of sent
158 * actions, XdndActionCopy, XdndActionPrivate, or None if drop
159 * will not be accepted
162 /*Need to remember that we have received at least one status event*/
163 ds
->state
= XDND_SETBIT(ds
->state
,XDND_STATUS_RECVD_CNT
,XDND_GOTONE_OR_MORE
);
165 /* Can we drop on the target?*/
166 if (xev
->xclient
.data
.l
[1] & 0x1)
167 ds
->state
= XDND_SETBIT(ds
->state
,XDND_DROPPABLE
,XDND_CAN_DROP
);
169 ds
->state
= XDND_SETBIT(ds
->state
,XDND_DROPPABLE
,XDND_CANT_DROP
);
171 /* How to send position information*/
172 if (xev
->xclient
.data
.l
[1] & 0x2)
173 ds
->state
= XDND_SETBIT(ds
->state
,XDND_SEND_POS
,XDND_ALWAYS_SEND
);
175 ds
->state
= XDND_SETBIT(ds
->state
,XDND_SEND_POS
,XDND_RECTSEND
);
177 action
= xev
->xclient
.data
.l
[4];
179 ds
->rx
= (xev
->xclient
.data
.l
[2]&0xffff0000)>>16;
180 ds
->ry
= xev
->xclient
.data
.l
[2]&0x0000ffff;
181 ds
->w
= (xev
->xclient
.data
.l
[3]&0xffff0000)>>16;
182 ds
->h
= xev
->xclient
.data
.l
[3]&0x0000ffff;
184 if (XDND_GETBIT(ds
->state
,XDND_DROPPABLE
) &&
185 XDND_GETBIT(ds
->state
,XDND_BUTRELEASE_CACHE
)) {
186 /* button released, but waiting for status*/
188 xdndSrcSendDrop(ds
, ds
->dropTargWin
, ds
->dragSrcWin
, time
);
190 if ((XDND_GETBIT(ds
->state
,XDND_POS_CACHE
))&&
191 (XDND_GETBIT(ds
->state
,XDND_WAIT_4_STATUS
))) {
192 /*only send position if ((always send motion)||(left box))*/
193 ds
->state
= XDND_SETBIT(ds
->state
,XDND_WAIT_4_STATUS
,XDND_NOTWAITING
);
194 if (XDND_GETBIT(ds
->state
,XDND_SEND_POS
) ||
195 !cursorInRect(ds
,ds
->cachexRoot
,ds
->cacheyRoot
)) {
197 ds
->state
= XDND_SETBIT(ds
->state
,XDND_WAIT_4_STATUS
,XDND_WAITING
);
198 xdndSrcSendPosition(ds
, ds
->dropTargWin
, ds
->dragSrcWin
, ds
->cachexRoot
,
199 ds
->cacheyRoot
, time
, action
);
201 ds
->state
= XDND_SETBIT(ds
->state
,XDND_POS_CACHE
,XDND_NOT_CACHED
);
203 /*Not cacheing position, do nothing*/
204 ds
->state
= XDND_SETBIT(ds
->state
,XDND_WAIT_4_STATUS
,XDND_NOTWAITING
);
212 /* Sends a "leave" event
213 * ds - the drag source struct
214 * dstWin - destination(drop) window
215 * srcWin - the source window(drag src) */
216 void xdndSrcSendLeave(DragSource
*ds
, Window dstWin
, Window srcWin
) {
218 xevClientInit(&xevent
,ds
->display
,dstWin
,ds
->atomSel
->xdndLeave
);
220 /* data.l[0] : XID of source window
221 data.l[1] : Reserved for future use. */
222 xevent
.xclient
.data
.l
[0] = srcWin
;
223 FSendEvent(ds
->display
, dstWin
, 0, 0, &xevent
);
228 /* Sends a "drop" event
229 * ds - the drag source struct
230 * dstWin - destination(drop) window
231 * srcWin - the source window(drag src)
232 * time - the time of last position */
233 void xdndSrcSendDrop(DragSource
*ds
, Window dstWin
, Window srcWin
, unsigned long time
) {
236 xevClientInit(&xevent
,ds
->display
,dstWin
,ds
->atomSel
->xdndDrop
);
238 /* data.l[0] : XID of source window
239 data.l[1] : Reserved for future use.
240 data.l[2] : Time stamp for retrieving data */
241 xevent
.xclient
.data
.l
[0] = srcWin
;
242 xevent
.xclient
.data
.l
[2] = time
;
244 XSetSelectionOwner(xg
.dpy
,ds
->atomSel
->xdndSelection
,
246 FSendEvent(ds
->display
, dstWin
, 0, 0, &xevent
);
250 /* Receive a finished event from the drop target
251 * ds - the drag source struct
252 * xev - the finish event */
253 void xdndSrcReceiveFinished(DragSource
*ds
, XEvent
*xev
) {
254 /* data.l[0] : XID of target window
255 data.l[1] : Reserved for future use. */
256 if (xev
->xclient
.data
.l
[0]!=ds
->dropTargWin
) {
259 XDeleteProperty(ds
->display
,ds
->dragSrcWin
,ds
->dropTargProperty
);/*ds->dropTargProperty*/
266 /* Main drag loop, should be called after a button press event in the drag source.
267 * Returns the atom of the action exported.
268 * ds - the drag source struct
269 * srcWin - the drag source window
270 * action - the action we are exporting
271 * typelist - the types we support */
273 Atom
xdndSrcDoDrag(DragSource
*ds
, Window srcWin
, Atom action
, Atom
* typelist
)
276 float x_mouse
, y_mouse
, radiusSqr
;
278 XdndCursor
*cursorPtr
;
279 Window trackWindow
, root_return
, child_return
;
280 unsigned int mask_return
;
283 Atom retAction
= None
;
288 /* User releases the mouse button without any motion->do nothing*/
290 FNextEvent(ds
->display
,&xev
);
291 if (xev
.type
== ButtonRelease
) {
292 FSendEvent(ds
->display
, xev
.xany
.window
, 0, ButtonReleaseMask
, &xev
);
295 } while (xev
.type
!= MotionNotify
);
297 /*We moved, wait until motion radius is larger than ds->halo*/
298 x_mouse
= (float) xev
.xmotion
.x_root
;
299 y_mouse
= (float) xev
.xmotion
.y_root
;
303 FNextEvent(ds
->display
, &xev
);
304 if (xev
.type
== MotionNotify
) {
305 radiusSqr
= SQR(x_mouse
- xev
.xmotion
.x_root
) + \
306 SQR(y_mouse
- xev
.xmotion
.y_root
);
307 if (radiusSqr
> SQR(ds
->dragHalo
))
309 if (xev
.type
== ButtonRelease
) {
310 /*Button release: radius less than halo->do nothing*/
311 FSendEvent(ds
->display
, xev
.xany
.window
, 0, ButtonReleaseMask
, &xev
);
312 return 0;/*What does this mean?*/
316 cursorPtr
= &(ds
->cursors
[0]);
317 /*We moved beyond the halo, now change the cursor. Handles the
318 typelist greater than three n = array_length (typelist); */
319 if (XGrabPointer(ds
->display
, ds
->rootWindow
, False
,
320 ButtonMotionMask
| PointerMotionMask
| ButtonPressMask
321 | ButtonReleaseMask
, GrabModeAsync
, GrabModeAsync
, None
,
322 cursorPtr
->cursor
, CurrentTime
) != GrabSuccess
) {
326 /*Main Drag loop. trackWindow is the current window the pointer is over. Note
327 that the actual client window of the top level widget of the application is
328 *not* trackWindow, but is actually one of the children of trackWindow. */
330 ds
->display
, XDefaultRootWindow(ds
->display
), &root_return
,
331 &child_return
, &xev
.xmotion
.x_root
, &xev
.xmotion
.y_root
,
332 &xev
.xmotion
.x
, &xev
.xmotion
.y
, &mask_return
) == False
)
334 /* pointer is on a different screen - that's okay here */
336 trackWindow
= child_return
;
339 XAllowEvents(ds
->display
, SyncPointer
, CurrentTime
);
340 FNextEvent(ds
->display
, &xev
);
343 retBool
= FQueryPointer(
344 ds
->display
, XDefaultRootWindow(ds
->display
),
345 &root_return
, &child_return
, &xev
.xmotion
.x_root
,
346 &xev
.xmotion
.y_root
, &xev
.xmotion
.x
,
347 &xev
.xmotion
.y
, &mask_return
);
348 if (retBool
== False
)
350 /* pointer is on a different screen */
351 xev
.xmotion
.x_root
= 0;
352 xev
.xmotion
.y_root
= 0;
357 if (trackWindow
!= child_return
) {
358 /*Cancel old drag if initiated.*/
359 if (XDND_GETBIT(ds
->state
,XDND_IN_AWAREWIN
)) {
360 xdndSrcSendLeave(ds
, ds
->dropTargWin
, srcWin
);
361 ds
->state
= XDND_SETBIT(ds
->state
,XDND_IN_AWAREWIN
,XDND_NOTAWARE
);
363 trackWindow
= child_return
;
365 if (child_return
!= None
) {
366 /* get new client window if not on root window.*/
367 ds
->dropTargWin
= fvwmlib_client_window(ds
->display
, child_return
);
369 /*Setup new drag if XDndAware*/
370 if (xdndSrcQueryDndAware (ds
, ds
->dropTargWin
, &version
, NULL
)) {
371 ds
->state
= XDND_SETBIT(ds
->state
,XDND_IN_AWAREWIN
,XDND_AWARE
);
372 xdndSrcSendEnter (ds
, ds
->dropTargWin
, srcWin
,typelist
);
373 xdndSrcSendPosition (ds
, ds
->dropTargWin
, srcWin
,xev
.xmotion
.x_root
,
374 xev
.xmotion
.y_root
,xev
.xmotion
.time
,action
);
375 ds
->state
= XDND_SETBIT(ds
->state
,XDND_WAIT_4_STATUS
,XDND_WAITING
);
377 ds
->state
= XDND_SETBIT(ds
->state
,XDND_IN_AWAREWIN
,XDND_NOTAWARE
);
380 } else { /*haven't changed windows*/
382 if (XDND_GETBIT(ds
->state
,XDND_IN_AWAREWIN
))
384 /*in an XdndAware window*/
385 /*Cache the position, and send when status is received*/
386 ds
->cachexRoot
= xev
.xmotion
.x_root
;
387 ds
->cacheyRoot
= xev
.xmotion
.y_root
;
388 /*Timeout after no status*/
389 /*if ()&&(XDND_GETBIT(ds->state,XDND_WAIT_4_STATUS)) */
390 if (XDND_GETBIT(ds
->state
,XDND_WAIT_4_STATUS
)) {
391 /*Waiting for a status event.*/
392 cacheTime
= xev
.xmotion
.time
;
393 ds
->state
= XDND_SETBIT(ds
->state
,XDND_POS_CACHE
,XDND_CACHED
);
395 /*Not waiting for a status event*/
396 if (XDND_GETBIT(ds
->state
,XDND_SEND_POS
) ||
397 !cursorInRect(ds
,xev
.xmotion
.x_root
,xev
.xmotion
.y_root
)) {
398 /* if left the box or always send position, then send the
401 ds
, ds
->dropTargWin
, srcWin
,xev
.xmotion
.x_root
,
402 xev
.xmotion
.y_root
,xev
.xmotion
.time
,action
);
404 XDND_SETBIT(ds
->state
,XDND_WAIT_4_STATUS
,XDND_WAITING
);
411 if (XDND_GETBIT(ds
->state
,XDND_IN_AWAREWIN
)) {
412 /*We're in an XdndAware win...*/
413 if (! XDND_GETBIT(ds
->state
,XDND_STATUS_RECVD_CNT
)) {
414 /* if never got XdndStatus, send a leave.*/
415 xdndSrcSendLeave(ds
, ds
->dropTargWin
, srcWin
);
417 /*I've received at least one status event...*/
418 if (XDND_GETBIT(ds
->state
,XDND_WAIT_4_STATUS
)) {
419 /*Waiting for status, cache the button release*/
420 ds
->cachexRoot
= xev
.xmotion
.x_root
;
421 ds
->cacheyRoot
= xev
.xmotion
.y_root
;
422 cacheTime
= xev
.xmotion
.time
;
424 XDND_SETBIT(ds
->state
,XDND_BUTRELEASE_CACHE
,XDND_BUTCACHED
);
426 /*Not waiting for a status, send the drop event*/
427 if (XDND_GETBIT(ds
->state
,XDND_DROPPABLE
)) {
428 xdndSrcSendDrop(ds
, ds
->dropTargWin
, srcWin
, CurrentTime
);
430 xdndSrcSendLeave(ds
, ds
->dropTargWin
, srcWin
);
438 XUngrabPointer(ds
->display
,CurrentTime
);
440 case SelectionRequest
:
441 /* to review how this works, we send the drop target a "drop"
442 event, the target sends a selection request event for the
443 data via XConvertSelection, we receive the request event,
444 prepare the data and send a notify, and the target then gets the data.*/
446 if (xev
.xselectionrequest
.selection
==ds
->atomSel
->xdndSelection
) {
447 if (xev
.xselectionrequest
.requestor
== ds
->dropTargWin
) {
448 target
= xev
.xselectionrequest
.target
;
449 ds
->dropTargProperty
= xev
.xselectionrequest
.property
;
451 ds
->display
, ds
->dragSrcWin
, ds
->dropTargProperty
, target
, 8,
452 PropModeReplace
, (unsigned char*)dropData
, strlen(dropData
)+1);
453 memset(&xev
, 0, sizeof (XEvent
));
455 xev
.xany
.type
= SelectionNotify
;
456 xev
.xany
.display
= ds
->display
;
457 xev
.xselection
.requestor
= ds
->dropTargWin
;
458 xev
.xselection
.selection
= ds
->atomSel
->xdndSelection
;
459 xev
.xselection
.property
= ds
->dropTargProperty
;
460 xev
.xselection
.target
= target
;
461 FSendEvent(ds
->display
, ds
->dropTargWin
, 0, 0, &xev
);
466 if (xev
.xclient
.message_type
==ds
->atomSel
->xdndStatus
) {
467 xdndSrcReceiveStatus(ds
,&xev
,0,0,cacheTime
);
468 } else if (xev
.xclient
.message_type
==ds
->atomSel
->xdndFinished
) {
469 xdndSrcReceiveFinished(ds
,&xev
);
472 fprintf(stderr
,"FvwmQFS:xdndDrag:Unknown client message in dragSource\n");
481 XUngrabPointer(ds
->display
,CurrentTime
);
487 /* error handler for BadWindow errors. Does nothing at the moment...
489 * errEv - the error Event */
490 int xdndErrorHandler(Display
*dpy
, XErrorEvent
*errEv
)
492 if (errEv
->error_code
== BadWindow
)
494 /* Well, the specification for Xdnd states that we should handle
495 * "BadWindow" errors with an error handler. I'm not real sure how to go
496 * beyond this point. One option is to use "goto". Another is to make
497 * doneDrag in the xdndSrcDoDrag a global, and set it to 1 here, but
498 * that is more dangerous. For now, we will just exit */
500 /*goto gotoLabelDone;*/
506 /* xdndInit - initializes the drag environment. Should only be called once.
507 * display - the display
508 * rw - the root window */
509 void xdndInit(Display
*display
, Window rw
) {
510 xdndCursorInit(display
, rw
); /*initializes the cursors*/
511 /*Initializes the atoms. Each DropTarget,DragSource points to this
512 * structure. Saves space.*/
513 xdndAtoms
.xdndAware
= XInternAtom(display
, "XdndAware", False
);;
514 xdndAtoms
.xdndEnter
= XInternAtom(display
, "XdndEnter", False
);
515 xdndAtoms
.xdndLeave
= XInternAtom(display
, "XdndLeave", False
);
516 xdndAtoms
.xdndStatus
= XInternAtom(display
, "XdndStatus", False
);
517 xdndAtoms
.xdndSelection
= XInternAtom(display
, "XdndSelection", False
);
518 xdndAtoms
.xdndPosition
= XInternAtom(display
, "XdndPosition", False
);
519 xdndAtoms
.xdndDrop
= XInternAtom(display
, "XdndDrop", False
);
520 xdndAtoms
.xdndFinished
= XInternAtom(display
, "XdndFinished", False
);
521 xdndAtoms
.xdndActionCopy
= XInternAtom(display
, "XdndActionCopy", False
);
522 xdndAtoms
.xdndActionMove
= XInternAtom(display
, "XdndActionMove", False
);
523 xdndAtoms
.xdndActionLink
= XInternAtom(display
, "XdndActionLink", False
);
524 xdndAtoms
.xdndActionAsk
= XInternAtom(display
, "XdndActionAsk", False
);
525 xdndAtoms
.xdndActionPrivate
= XInternAtom(display
, "XdndActionPrivate", False
);
526 xdndAtoms
.xdndTypeList
= XInternAtom(display
, "XdndTypeList", False
);
527 xdndAtoms
.xdndActionList
= XInternAtom(display
, "XdndActionList", False
);
528 xdndAtoms
.xdndActionDescription
=
529 XInternAtom(display
, "XdndActionDescription", False
);
530 XSetErrorHandler(xdndErrorHandler
);
535 /* dragSrcInit - initializes a drag source
536 * ds - the dragSource
538 * root - the root window
539 * client - the drag source client window */
540 void dragSrcInit(DragSource
*ds
,Display
*dpy
,Window root
,Window client
) {
542 ds
->rootWindow
= root
;
543 ds
->dragSrcWin
= client
;
544 ds
->cursors
= &(xdndCursors
[0]);
545 ds
->atomSel
= &xdndAtoms
;
551 /* xdndSrcQueryDndAware - queries whether the drop window is XdndAware. Returns 0
552 * if not aware, and returns one if aware
553 * ds - the drag source struct
554 * window - the drop window we want to query
555 * version - what version of Xdnd the drop window supports
556 * typelist - not used at this point, could be used to compare the source type list
557 * with the drop target list if the target puts the supported types in it property list
559 int xdndSrcQueryDndAware (DragSource
*ds
, Window window
, int *version
,
564 unsigned long count
, remaining
;
565 unsigned char *data
= 0;
571 ds
->display
, window
, ds
->atomSel
->xdndAware
, 0L, 0x8000000L
, False
,
572 XA_ATOM
, &actual
, &format
, &count
, &remaining
, &data
);
573 if (actual
!= XA_ATOM
|| format
!= 32 || count
== 0 || !data
) {
579 types
= (Atom
*) data
;
580 *version
= ds
->version
< types
[0] ? ds
->version
: types
[0]; /* minimum */