Fix CursorMove command to correctly honour EdgeScroll settings.
[fvwm.git] / modules / FvwmDragWell / xdndDragSource.c
blob4a991188d1951812e943804a70298f2dcf4a14a5
1 /* -*-c-*- */
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
17 #include "config.h"
19 #include <stdio.h>
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[];
32 extern XGlobals xg;
34 DragSource dsg;
35 XdndAtoms xdndAtoms;
36 void xdndSrcSendDrop(DragSource *, Window, Window, unsigned long);
37 int xdndSrcQueryDndAware (DragSource *ds, Window window, int *version,
38 Atom * typelist);
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))
48 return 1;
50 return 0;
55 /* Initializes a XEvent client message
56 * xev - the XEvent to initialize
57 * dpy - the display
58 * win - the window
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)
78 XEvent xevent;
79 int i;
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*/
96 tmpUL = 0x1UL;
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
112 * time - time
113 * action - the requested action */
114 void xdndSrcSendPosition(DragSource *ds, Window dstWin, Window srcWin, short x,
115 short y, unsigned long time, Atom action) {
116 XEvent xevent;
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
138 * is cached
139 * time - the time */
140 void xdndSrcReceiveStatus(DragSource *ds, XEvent *xev,unsigned short *cx,
141 unsigned short *cy, unsigned long time)
143 Atom action;
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);
168 else
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);
174 else
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*/
187 if (action != None)
188 xdndSrcSendDrop(ds, ds->dropTargWin, ds->dragSrcWin, time);
189 } else {
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)) {
196 /* position cached*/
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);
202 } else {
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) {
217 XEvent xevent;
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) {
234 XEvent xevent;
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,
245 srcWin,CurrentTime);
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) {
257 return;
259 XDeleteProperty(ds->display,ds->dragSrcWin,ds->dropTargProperty);/*ds->dropTargProperty*/
260 ds->state = 0;
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)
275 XEvent xev;
276 float x_mouse, y_mouse, radiusSqr;
277 int cacheTime = 0;
278 XdndCursor *cursorPtr;
279 Window trackWindow, root_return, child_return;
280 unsigned int mask_return;
281 Bool retBool;
282 int version = 0;
283 Atom retAction = None;
284 Atom target;
285 short doneDrag = 0;
288 /* User releases the mouse button without any motion->do nothing*/
289 do {
290 FNextEvent(ds->display,&xev);
291 if (xev.type == ButtonRelease) {
292 FSendEvent(ds->display, xev.xany.window, 0, ButtonReleaseMask, &xev);
293 return None;
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;
300 if (!ds->dragHalo)
301 ds->dragHalo = 4.0;
302 for (;;) {
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))
308 break;
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. */
329 if (FQueryPointer(
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;
338 while (!doneDrag) {
339 XAllowEvents(ds->display, SyncPointer, CurrentTime);
340 FNextEvent(ds->display, &xev);
341 switch (xev.type) {
342 case MotionNotify:
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;
353 xev.xmotion.x = 0;
354 xev.xmotion.y = 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);
376 } else {
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);
394 } else {
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
399 * position */
400 xdndSrcSendPosition(
401 ds, ds->dropTargWin, srcWin,xev.xmotion.x_root,
402 xev.xmotion.y_root,xev.xmotion.time,action);
403 ds->state =
404 XDND_SETBIT(ds->state,XDND_WAIT_4_STATUS,XDND_WAITING);
409 break;
410 case ButtonRelease:
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);
416 } else {
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;
423 ds->state =
424 XDND_SETBIT(ds->state,XDND_BUTRELEASE_CACHE,XDND_BUTCACHED);
425 } else {
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);
429 } else {
430 xdndSrcSendLeave(ds, ds->dropTargWin, srcWin);
431 retAction = None;
435 } else {
436 doneDrag = 1;
438 XUngrabPointer(ds->display,CurrentTime);
439 break;
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;
450 XChangeProperty(
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);
464 break;
465 case ClientMessage:
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);
470 doneDrag = 1;
471 } else {
472 fprintf(stderr,"FvwmQFS:xdndDrag:Unknown client message in dragSource\n");
474 break;
476 default:
477 break;
481 XUngrabPointer(ds->display,CurrentTime);
483 return retAction;
487 /* error handler for BadWindow errors. Does nothing at the moment...
488 * dpy - the display
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 */
499 exit(1);
500 /*goto gotoLabelDone;*/
502 return 0;
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
537 * dpy - the display
538 * root - the root window
539 * client - the drag source client window */
540 void dragSrcInit(DragSource *ds,Display *dpy,Window root,Window client) {
541 ds->display = dpy;
542 ds->rootWindow = root;
543 ds->dragSrcWin = client;
544 ds->cursors = &(xdndCursors[0]);
545 ds->atomSel = &xdndAtoms;
546 ds->state = 0;
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,
560 Atom * typelist)
562 Atom actual;
563 int format;
564 unsigned long count, remaining;
565 unsigned char *data = 0;
566 Atom *types;
567 int result = 1;
568 *version = 0;
570 XGetWindowProperty(
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) {
574 /* not supported */
575 if (data)
576 XFree (data);
577 return 0;
579 types = (Atom *) data;
580 *version = ds->version < types[0] ? ds->version : types[0]; /* minimum */
581 XFree (data);
582 return result;