/* -*-c-*- */ /* This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "config.h" #include #include #include "libs/fvwmlib.h" #include "libs/Target.h" #include "dragSource.h" #include "cursorStuff.h" #include "fvwmDragWell.h" #define SQR(x) ((x)*(x)) extern int xdndCursorInit(Display *, Window); extern XdndCursor xdndCursors[]; extern char dropData[]; extern XGlobals xg; DragSource dsg; XdndAtoms xdndAtoms; void xdndSrcSendDrop(DragSource *, Window, Window, unsigned long); int xdndSrcQueryDndAware (DragSource *ds, Window window, int *version, Atom * typelist); /* Determines if the cursor is in the rectangle . * Returns true if the cursor is in the rectangle. * ds - the drag source, contains the rectangle info. * rx,ry - the cursor in root coordinates */ int cursorInRect(DragSource *ds, unsigned short rx, unsigned short ry) { if ((rx > ds->rx) && (rx rx+ds->w)) { if ((ry > ds->ry) && (ry ry+ds->h)) return 1; } return 0; } /* Initializes a XEvent client message * xev - the XEvent to initialize * dpy - the display * win - the window * messageType - the message type */ void xevClientInit(XEvent *xev, Display *dpy, Window win, Atom messageType) { memset (xev, 0, sizeof (XEvent)); xev->xany.type = ClientMessage; xev->xany.display = dpy; xev->xclient.window = win; xev->xclient.message_type = messageType; xev->xclient.format = 32; } /* Sends an "enter" event. * ds - the drag source. * dstWin - the recipient of the event. * srcWin - the drag source win * typelist - the types the drag source supports, assumes that * the list is NULL terminated. */ void xdndSrcSendEnter(DragSource *ds, Window dstWin, Window srcWin, Atom * typelist) { XEvent xevent; int i; unsigned long nTypes=0,tmpUL=0; if (typelist != NULL) { while (typelist[nTypes++]!=0); /*get typelist length*/ } xevClientInit(&xevent,ds->display,dstWin,ds->atomSel->xdndEnter); /* data.l[0] : XID of source window data.l[1] : Bit 0 is set if source supports more than three types. High byte is protocol version(min of source,target). Version is set in window property of target. data.l[2,3,4] : First three types. Unused set to none. */ xevent.xclient.data.l[0] = srcWin; if (nTypes>3) /*this should never happen for fvwmQFS*/ tmpUL = 0x1UL; xevent.xclient.data.l[1] = (tmpUL | ((ds->version & 0xFFUL) << 24)); for (i = 0; i < XDND_MIN(nTypes,3) ; i++) xevent.xclient.data.l[i+2] = typelist[i]; for (i = nTypes; i < 3 ; i++) xevent.xclient.data.l[i+2] = None; FSendEvent(ds->display, dstWin, 0, 0, &xevent); } /* Sends a position event * ds - the drag source struct * dstWin - the recipient of the event * srcWin - the drag source window * x,y - cursor position int root coords * time - time * action - the requested action */ void xdndSrcSendPosition(DragSource *ds, Window dstWin, Window srcWin, short x, short y, unsigned long time, Atom action) { XEvent xevent; xevClientInit(&xevent,ds->display,dstWin,ds->atomSel->xdndPosition); /* data.l[0] : XID of source window data.l[1] : Reserved for future use. data.l[2] : Coordinates of mouse relative to root window(x<<16|y) data.l[3] : time stamp for retrieving data data.l[4] : action requested by user */ xevent.xclient.data.l[0] = srcWin; xevent.xclient.data.l[2] = (x<<16) | y; xevent.xclient.data.l[3] = time; xevent.xclient.data.l[4] = action; FSendEvent(ds->display, dstWin, 0, 0, &xevent); } /* Handles the receipt of a status event * ds - the drag source struct * xev - the status event * cx,cy - the mouse x,y coords, in root frame, used if the event * is cached * time - the time */ void xdndSrcReceiveStatus(DragSource *ds, XEvent *xev,unsigned short *cx, unsigned short *cy, unsigned long time) { Atom action; if (ds->dropTargWin != xev->xclient.data.l[0]) { return; /*Got ClientMessage:XdndStatus from wrong client window*/ } /* *data.l[0] : XID of target window *data.l[1] : Bit 0 set if target accepts drop * Bit 1 set if target wants coordinates while in rectangle *data.l[2,3] : Coordinates of box. (x,y) relative to root. Null box ok. *data.l[2] : (x<<16)|y *data.l[3] : (w<<16)|h *data.l[4] : action accepted by target. Either should be one of sent * actions, XdndActionCopy, XdndActionPrivate, or None if drop * will not be accepted */ /*Need to remember that we have received at least one status event*/ ds->state = XDND_SETBIT(ds->state,XDND_STATUS_RECVD_CNT,XDND_GOTONE_OR_MORE); /* Can we drop on the target?*/ if (xev->xclient.data.l[1] & 0x1) ds->state = XDND_SETBIT(ds->state,XDND_DROPPABLE,XDND_CAN_DROP); else ds->state = XDND_SETBIT(ds->state,XDND_DROPPABLE,XDND_CANT_DROP); /* How to send position information*/ if (xev->xclient.data.l[1] & 0x2) ds->state = XDND_SETBIT(ds->state,XDND_SEND_POS,XDND_ALWAYS_SEND); else ds->state = XDND_SETBIT(ds->state,XDND_SEND_POS,XDND_RECTSEND); action = xev->xclient.data.l[4]; ds->rx = (xev->xclient.data.l[2]&0xffff0000)>>16; ds->ry = xev->xclient.data.l[2]&0x0000ffff; ds->w = (xev->xclient.data.l[3]&0xffff0000)>>16; ds->h = xev->xclient.data.l[3]&0x0000ffff; if (XDND_GETBIT(ds->state,XDND_DROPPABLE) && XDND_GETBIT(ds->state,XDND_BUTRELEASE_CACHE)) { /* button released, but waiting for status*/ if (action != None) xdndSrcSendDrop(ds, ds->dropTargWin, ds->dragSrcWin, time); } else { if ((XDND_GETBIT(ds->state,XDND_POS_CACHE))&& (XDND_GETBIT(ds->state,XDND_WAIT_4_STATUS))) { /*only send position if ((always send motion)||(left box))*/ ds->state = XDND_SETBIT(ds->state,XDND_WAIT_4_STATUS,XDND_NOTWAITING); if (XDND_GETBIT(ds->state,XDND_SEND_POS) || !cursorInRect(ds,ds->cachexRoot,ds->cacheyRoot)) { /* position cached*/ ds->state = XDND_SETBIT(ds->state,XDND_WAIT_4_STATUS,XDND_WAITING); xdndSrcSendPosition(ds, ds->dropTargWin, ds->dragSrcWin, ds->cachexRoot, ds->cacheyRoot, time, action); } ds->state = XDND_SETBIT(ds->state,XDND_POS_CACHE,XDND_NOT_CACHED); } else { /*Not cacheing position, do nothing*/ ds->state = XDND_SETBIT(ds->state,XDND_WAIT_4_STATUS,XDND_NOTWAITING); } } } /* Sends a "leave" event * ds - the drag source struct * dstWin - destination(drop) window * srcWin - the source window(drag src) */ void xdndSrcSendLeave(DragSource *ds, Window dstWin, Window srcWin) { XEvent xevent; xevClientInit(&xevent,ds->display,dstWin,ds->atomSel->xdndLeave); /* data.l[0] : XID of source window data.l[1] : Reserved for future use. */ xevent.xclient.data.l[0] = srcWin; FSendEvent(ds->display, dstWin, 0, 0, &xevent); } /* Sends a "drop" event * ds - the drag source struct * dstWin - destination(drop) window * srcWin - the source window(drag src) * time - the time of last position */ void xdndSrcSendDrop(DragSource *ds, Window dstWin, Window srcWin, unsigned long time) { XEvent xevent; xevClientInit(&xevent,ds->display,dstWin,ds->atomSel->xdndDrop); /* data.l[0] : XID of source window data.l[1] : Reserved for future use. data.l[2] : Time stamp for retrieving data */ xevent.xclient.data.l[0] = srcWin; xevent.xclient.data.l[2] = time; XSetSelectionOwner(xg.dpy,ds->atomSel->xdndSelection, srcWin,CurrentTime); FSendEvent(ds->display, dstWin, 0, 0, &xevent); } /* Receive a finished event from the drop target * ds - the drag source struct * xev - the finish event */ void xdndSrcReceiveFinished(DragSource *ds, XEvent *xev) { /* data.l[0] : XID of target window data.l[1] : Reserved for future use. */ if (xev->xclient.data.l[0]!=ds->dropTargWin) { return; } XDeleteProperty(ds->display,ds->dragSrcWin,ds->dropTargProperty);/*ds->dropTargProperty*/ ds->state = 0; } /* Main drag loop, should be called after a button press event in the drag source. * Returns the atom of the action exported. * ds - the drag source struct * srcWin - the drag source window * action - the action we are exporting * typelist - the types we support */ Atom xdndSrcDoDrag(DragSource *ds, Window srcWin, Atom action, Atom * typelist) { XEvent xev; float x_mouse, y_mouse, radiusSqr; int cacheTime = 0; XdndCursor *cursorPtr; Window trackWindow, root_return, child_return; unsigned int mask_return; Bool retBool; int version = 0; Atom retAction = None; Atom target; short doneDrag = 0; /* User releases the mouse button without any motion->do nothing*/ do { FNextEvent(ds->display,&xev); if (xev.type == ButtonRelease) { FSendEvent(ds->display, xev.xany.window, 0, ButtonReleaseMask, &xev); return None; } } while (xev.type != MotionNotify); /*We moved, wait until motion radius is larger than ds->halo*/ x_mouse = (float) xev.xmotion.x_root; y_mouse = (float) xev.xmotion.y_root; if (!ds->dragHalo) ds->dragHalo = 4.0; for (;;) { FNextEvent(ds->display, &xev); if (xev.type == MotionNotify) { radiusSqr = SQR(x_mouse - xev.xmotion.x_root) + \ SQR(y_mouse - xev.xmotion.y_root); if (radiusSqr > SQR(ds->dragHalo)) break; if (xev.type == ButtonRelease) { /*Button release: radius less than halo->do nothing*/ FSendEvent(ds->display, xev.xany.window, 0, ButtonReleaseMask, &xev); return 0;/*What does this mean?*/ } } } cursorPtr = &(ds->cursors[0]); /*We moved beyond the halo, now change the cursor. Handles the typelist greater than three n = array_length (typelist); */ if (XGrabPointer(ds->display, ds->rootWindow, False, ButtonMotionMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask, GrabModeAsync, GrabModeAsync, None, cursorPtr->cursor, CurrentTime) != GrabSuccess) { } /*Main Drag loop. trackWindow is the current window the pointer is over. Note that the actual client window of the top level widget of the application is *not* trackWindow, but is actually one of the children of trackWindow. */ if (FQueryPointer( ds->display, XDefaultRootWindow(ds->display), &root_return, &child_return, &xev.xmotion.x_root, &xev.xmotion.y_root, &xev.xmotion.x, &xev.xmotion.y, &mask_return) == False) { /* pointer is on a different screen - that's okay here */ } trackWindow = child_return; while (!doneDrag) { XAllowEvents(ds->display, SyncPointer, CurrentTime); FNextEvent(ds->display, &xev); switch (xev.type) { case MotionNotify: retBool = FQueryPointer( ds->display, XDefaultRootWindow(ds->display), &root_return, &child_return, &xev.xmotion.x_root, &xev.xmotion.y_root, &xev.xmotion.x, &xev.xmotion.y, &mask_return); if (retBool == False) { /* pointer is on a different screen */ xev.xmotion.x_root = 0; xev.xmotion.y_root = 0; xev.xmotion.x = 0; xev.xmotion.y = 0; } if (trackWindow != child_return) { /*Cancel old drag if initiated.*/ if (XDND_GETBIT(ds->state,XDND_IN_AWAREWIN)) { xdndSrcSendLeave(ds, ds->dropTargWin, srcWin); ds->state = XDND_SETBIT(ds->state,XDND_IN_AWAREWIN,XDND_NOTAWARE); } trackWindow = child_return; if (child_return != None) { /* get new client window if not on root window.*/ ds->dropTargWin = fvwmlib_client_window(ds->display, child_return); /*Setup new drag if XDndAware*/ if (xdndSrcQueryDndAware (ds, ds->dropTargWin, &version, NULL)) { ds->state = XDND_SETBIT(ds->state,XDND_IN_AWAREWIN,XDND_AWARE); xdndSrcSendEnter (ds, ds->dropTargWin, srcWin,typelist); xdndSrcSendPosition (ds, ds->dropTargWin, srcWin,xev.xmotion.x_root, xev.xmotion.y_root,xev.xmotion.time,action); ds->state = XDND_SETBIT(ds->state,XDND_WAIT_4_STATUS,XDND_WAITING); } else { ds->state = XDND_SETBIT(ds->state,XDND_IN_AWAREWIN,XDND_NOTAWARE); } } } else { /*haven't changed windows*/ if (XDND_GETBIT(ds->state,XDND_IN_AWAREWIN)) { /*in an XdndAware window*/ /*Cache the position, and send when status is received*/ ds->cachexRoot = xev.xmotion.x_root; ds->cacheyRoot = xev.xmotion.y_root; /*Timeout after no status*/ /*if ()&&(XDND_GETBIT(ds->state,XDND_WAIT_4_STATUS)) */ if (XDND_GETBIT(ds->state,XDND_WAIT_4_STATUS)) { /*Waiting for a status event.*/ cacheTime = xev.xmotion.time; ds->state = XDND_SETBIT(ds->state,XDND_POS_CACHE,XDND_CACHED); } else { /*Not waiting for a status event*/ if (XDND_GETBIT(ds->state,XDND_SEND_POS) || !cursorInRect(ds,xev.xmotion.x_root,xev.xmotion.y_root)) { /* if left the box or always send position, then send the * position */ xdndSrcSendPosition( ds, ds->dropTargWin, srcWin,xev.xmotion.x_root, xev.xmotion.y_root,xev.xmotion.time,action); ds->state = XDND_SETBIT(ds->state,XDND_WAIT_4_STATUS,XDND_WAITING); } } } } break; case ButtonRelease: if (XDND_GETBIT(ds->state,XDND_IN_AWAREWIN)) { /*We're in an XdndAware win...*/ if (! XDND_GETBIT(ds->state,XDND_STATUS_RECVD_CNT)) { /* if never got XdndStatus, send a leave.*/ xdndSrcSendLeave(ds, ds->dropTargWin, srcWin); } else { /*I've received at least one status event...*/ if (XDND_GETBIT(ds->state,XDND_WAIT_4_STATUS)) { /*Waiting for status, cache the button release*/ ds->cachexRoot = xev.xmotion.x_root; ds->cacheyRoot = xev.xmotion.y_root; cacheTime = xev.xmotion.time; ds->state = XDND_SETBIT(ds->state,XDND_BUTRELEASE_CACHE,XDND_BUTCACHED); } else { /*Not waiting for a status, send the drop event*/ if (XDND_GETBIT(ds->state,XDND_DROPPABLE)) { xdndSrcSendDrop(ds, ds->dropTargWin, srcWin, CurrentTime); } else { xdndSrcSendLeave(ds, ds->dropTargWin, srcWin); retAction = None; } } } } else { doneDrag = 1; } XUngrabPointer(ds->display,CurrentTime); break; case SelectionRequest: /* to review how this works, we send the drop target a "drop" event, the target sends a selection request event for the data via XConvertSelection, we receive the request event, prepare the data and send a notify, and the target then gets the data.*/ if (xev.xselectionrequest.selection==ds->atomSel->xdndSelection) { if (xev.xselectionrequest.requestor == ds->dropTargWin) { target = xev.xselectionrequest.target; ds->dropTargProperty = xev.xselectionrequest.property; XChangeProperty( ds->display, ds->dragSrcWin, ds->dropTargProperty, target, 8, PropModeReplace, (unsigned char*)dropData, strlen(dropData)+1); memset(&xev, 0, sizeof (XEvent)); xev.xany.type = SelectionNotify; xev.xany.display = ds->display; xev.xselection.requestor = ds->dropTargWin; xev.xselection.selection = ds->atomSel->xdndSelection; xev.xselection.property = ds->dropTargProperty; xev.xselection.target = target; FSendEvent(ds->display, ds->dropTargWin, 0, 0, &xev); } } break; case ClientMessage: if (xev.xclient.message_type==ds->atomSel->xdndStatus) { xdndSrcReceiveStatus(ds,&xev,0,0,cacheTime); } else if (xev.xclient.message_type==ds->atomSel->xdndFinished) { xdndSrcReceiveFinished(ds,&xev); doneDrag = 1; } else { fprintf(stderr,"FvwmQFS:xdndDrag:Unknown client message in dragSource\n"); } break; default: break; } } XUngrabPointer(ds->display,CurrentTime); return retAction; } /* error handler for BadWindow errors. Does nothing at the moment... * dpy - the display * errEv - the error Event */ int xdndErrorHandler(Display *dpy, XErrorEvent *errEv) { if (errEv->error_code == BadWindow) { /* Well, the specification for Xdnd states that we should handle * "BadWindow" errors with an error handler. I'm not real sure how to go * beyond this point. One option is to use "goto". Another is to make * doneDrag in the xdndSrcDoDrag a global, and set it to 1 here, but * that is more dangerous. For now, we will just exit */ exit(1); /*goto gotoLabelDone;*/ } return 0; } /* xdndInit - initializes the drag environment. Should only be called once. * display - the display * rw - the root window */ void xdndInit(Display *display, Window rw) { xdndCursorInit(display, rw); /*initializes the cursors*/ /*Initializes the atoms. Each DropTarget,DragSource points to this * structure. Saves space.*/ xdndAtoms.xdndAware = XInternAtom(display, "XdndAware", False);; xdndAtoms.xdndEnter = XInternAtom(display, "XdndEnter", False); xdndAtoms.xdndLeave = XInternAtom(display, "XdndLeave", False); xdndAtoms.xdndStatus = XInternAtom(display, "XdndStatus", False); xdndAtoms.xdndSelection = XInternAtom(display, "XdndSelection", False); xdndAtoms.xdndPosition = XInternAtom(display, "XdndPosition", False); xdndAtoms.xdndDrop = XInternAtom(display, "XdndDrop", False); xdndAtoms.xdndFinished = XInternAtom(display, "XdndFinished", False); xdndAtoms.xdndActionCopy = XInternAtom(display, "XdndActionCopy", False); xdndAtoms.xdndActionMove = XInternAtom(display, "XdndActionMove", False); xdndAtoms.xdndActionLink = XInternAtom(display, "XdndActionLink", False); xdndAtoms.xdndActionAsk = XInternAtom(display, "XdndActionAsk", False); xdndAtoms.xdndActionPrivate = XInternAtom(display, "XdndActionPrivate", False); xdndAtoms.xdndTypeList = XInternAtom(display, "XdndTypeList", False); xdndAtoms.xdndActionList = XInternAtom(display, "XdndActionList", False); xdndAtoms.xdndActionDescription = XInternAtom(display, "XdndActionDescription", False); XSetErrorHandler(xdndErrorHandler); } /* dragSrcInit - initializes a drag source * ds - the dragSource * dpy - the display * root - the root window * client - the drag source client window */ void dragSrcInit(DragSource *ds,Display *dpy,Window root,Window client) { ds->display = dpy; ds->rootWindow = root; ds->dragSrcWin = client; ds->cursors = &(xdndCursors[0]); ds->atomSel = &xdndAtoms; ds->state = 0; } /* xdndSrcQueryDndAware - queries whether the drop window is XdndAware. Returns 0 * if not aware, and returns one if aware * ds - the drag source struct * window - the drop window we want to query * version - what version of Xdnd the drop window supports * typelist - not used at this point, could be used to compare the source type list * with the drop target list if the target puts the supported types in it property list */ int xdndSrcQueryDndAware (DragSource *ds, Window window, int *version, Atom * typelist) { Atom actual; int format; unsigned long count, remaining; unsigned char *data = 0; Atom *types; int result = 1; *version = 0; XGetWindowProperty( ds->display, window, ds->atomSel->xdndAware, 0L, 0x8000000L, False, XA_ATOM, &actual, &format, &count, &remaining, &data); if (actual != XA_ATOM || format != 32 || count == 0 || !data) { /* not supported */ if (data) XFree (data); return 0; } types = (Atom *) data; *version = ds->version < types[0] ? ds->version : types[0]; /* minimum */ XFree (data); return result; }