#include #include #include "WINGsP.h" #define SPIT(a) puts(a) #define IS_DROPPABLE(view) (view!=NULL && view->droppableTypes!=NULL && \ view->dragDestinationProcs!=NULL) static Bool _XErrorOccured = False; void WMSetViewDragSourceProcs(WMView *view, WMDragSourceProcs *procs) { if (view->dragSourceProcs) free(view->dragSourceProcs); view->dragSourceProcs = wmalloc(sizeof(WMDragSourceProcs)); *view->dragSourceProcs = *procs; /* XXX fill in non-implemented stuffs */ } /***********************************************************************/ static int handleXError(Display *dpy, XErrorEvent *ev) { _XErrorOccured = True; return 1; } static void protectBlock(Bool begin) { static void *oldHandler = NULL; if (begin) { oldHandler = XSetErrorHandler(handleXError); } else { XSetErrorHandler(oldHandler); } } static Window makeDragIcon(WMScreen *scr, WMPixmap *pixmap) { Window window; WMSize size; unsigned long flags; XSetWindowAttributes attribs; Pixmap pix, mask; if (!pixmap) { pixmap = scr->defaultObjectIcon; } size = WMGetPixmapSize(pixmap); pix = pixmap->pixmap; mask = pixmap->mask; flags = CWSaveUnder|CWBackPixmap|CWOverrideRedirect|CWColormap; attribs.save_under = True; attribs.background_pixmap = pix; attribs.override_redirect = True; attribs.colormap = scr->colormap; window = XCreateWindow(scr->display, scr->rootWin, 0, 0, size.width, size.height, 0, scr->depth, InputOutput, scr->visual, flags, &attribs); #ifdef SHAPE if (mask) { XShapeCombineMask(dpy, scr->balloon->window, ShapeBounding, 0, 0, mask, ShapeSet); } #endif return window; } static void slideWindow(Display *dpy, Window win, int srcX, int srcY, int dstX, int dstY) { double x, y, dx, dy; int i; int iterations; iterations = WMIN(25, WMAX(abs(dstX-srcX), abs(dstY-srcY))); x = srcX; y = srcY; dx = (double)(dstX-srcX)/iterations; dy = (double)(dstY-srcY)/iterations; for (i = 0; i <= iterations; i++) { XMoveWindow(dpy, win, x, y); XFlush(dpy); wusleep(800); x += dx; y += dy; } } static Window findChildInWindow(Display *dpy, Window toplevel, int x, int y) { Window foo, bar; Window *children; unsigned nchildren; int i; if (!XQueryTree(dpy, toplevel, &foo, &bar, &children, &nchildren) || children == NULL) { return None; } /* first window that contains the point is the one */ for (i = nchildren-1; i >= 0; i--) { XWindowAttributes attr; if (XGetWindowAttributes(dpy, children[i], &attr) && attr.map_state == IsViewable && x >= attr.x && y >= attr.y && x < attr.x + attr.width && y < attr.y + attr.height) { Window child; child = findChildInWindow(dpy, children[i], x - attr.x, y - attr.y); XFree(children); if (child == None) return toplevel; else return child; } } XFree(children); return None; } static WMView* findViewInToplevel(Display *dpy, Window toplevel, int x, int y) { Window child; child = findChildInWindow(dpy, toplevel, x, y); if (child != None) return W_GetViewForXWindow(dpy, child); else return NULL; } static Window lookForToplevel(WMScreen *scr, Window window, Bool *isAware) { Window toplevel = None; Atom *atoms; int j, count; *isAware = False; atoms = XListProperties(scr->display, window, &count); for (j = 0; j < count; j++) { if (atoms[j] == scr->wmStateAtom) { toplevel = window; } else if (atoms[j] == scr->xdndAwareAtom) { *isAware = True; } } if (atoms) XFree(atoms); if (toplevel == None) { Window *children; Window foo, bar; unsigned nchildren; if (!XQueryTree(scr->display, window, &foo, &bar, &children, &nchildren) || children == NULL) { return None; } for (j = 0; j < nchildren; j++) { toplevel = lookForToplevel(scr, children[j], isAware); if (toplevel != None) break; } XFree(children); } return toplevel; } static Window findToplevelUnderDragPointer(WMScreen *scr, int x, int y, Window iconWindow) { Window foo, bar; Window *children; unsigned nchildren; Bool overSomething = False; int i; if (!XQueryTree(scr->display, scr->rootWin, &foo, &bar, &children, &nchildren) || children == NULL) { SPIT("couldnt query tree!"); return None; } /* try to find the window below the iconWindow by traversing * the whole window list */ /* first find the position of the iconWindow */ for (i = nchildren-1; i >= 0; i--) { if (children[i] == iconWindow) { i--; break; } } if (i <= 0) { XFree(children); return scr->rootWin; } /* first window that contains the point is the one */ for (; i >= 0; i--) { XWindowAttributes attr; if (XGetWindowAttributes(scr->display, children[i], &attr) && attr.map_state == IsViewable && x >= attr.x && y >= attr.y && x < attr.x + attr.width && y < attr.y + attr.height) { Window toplevel; Bool isaware; overSomething = True; toplevel = lookForToplevel(scr, children[i], &isaware); XFree(children); if (isaware) return toplevel; else return None; } } XFree(children); if (!overSomething) return scr->rootWin; else return None; } static void sendClientMessage(Display *dpy, Window win, Atom message, unsigned data1, unsigned data2, unsigned data3, unsigned data4, unsigned data5) { XEvent ev; ev.type = ClientMessage; ev.xclient.message_type = message; ev.xclient.format = 32; ev.xclient.window = win; ev.xclient.data.l[0] = data1; ev.xclient.data.l[1] = data2; ev.xclient.data.l[2] = data3; ev.xclient.data.l[3] = data4; ev.xclient.data.l[4] = data5; XSendEvent(dpy, win, False, 0, &ev); XFlush(dpy); } static unsigned notifyPosition(WMScreen *scr, WMDraggingInfo *info) { unsigned operation; switch (info->sourceOperation) { default: operation = None; break; } sendClientMessage(scr->display, info->destinationWindow, scr->xdndPositionAtom, info->sourceWindow, 0, /* reserved */ info->location.x<<16|info->location.y, info->timestamp, operation/* operation */); return 0; } static void notifyDrop(WMScreen *scr, WMDraggingInfo *info) { sendClientMessage(scr->display, info->destinationWindow, scr->xdndDropAtom, info->sourceWindow, 0, /* reserved */ info->timestamp, 0, 0); } static void notifyDragLeave(WMScreen *scr, WMDraggingInfo *info) { sendClientMessage(scr->display, info->destinationWindow, scr->xdndLeaveAtom, info->sourceWindow, 0, 0, 0, 0); } static unsigned notifyDragEnter(WMScreen *scr, WMDraggingInfo *info) { unsigned d; d = XDND_VERSION << 24; sendClientMessage(scr->display, info->destinationWindow, scr->xdndEnterAtom, info->sourceWindow, d, 0, 0, 0); return 0; } static void translateCoordinates(WMScreen *scr, Window target, int fromX, int fromY, int *x, int *y) { Window child; XTranslateCoordinates(scr->display, scr->rootWin, target, fromX, fromY, x, y, &child); } static void updateDraggingInfo(WMScreen *scr, WMDraggingInfo *info, XEvent *event, Window iconWindow) { Window toplevel; WMSize size; size = WMGetPixmapSize(info->image); if (event->type == MotionNotify) { info->imageLocation.x = event->xmotion.x_root-(int)size.width/2; info->imageLocation.y = event->xmotion.y_root-(int)size.height/2; info->location.x = event->xmotion.x_root; info->location.y = event->xmotion.y_root; info->timestamp = event->xmotion.time; } else if (event->type == ButtonRelease) { info->imageLocation.x = event->xbutton.x_root-(int)size.width/2; info->imageLocation.y = event->xbutton.y_root-(int)size.height/2; info->location.x = event->xbutton.x_root; info->location.y = event->xbutton.y_root; info->timestamp = event->xbutton.time; } toplevel = findToplevelUnderDragPointer(scr, info->location.x, info->location.y, iconWindow); info->destinationWindow = toplevel; /* if (toplevel == None) { info->destinationWindow = None; } else if (toplevel == scr->rootWin) { info->destinationWindow = scr->rootWin; } else { Window child; int x, y; XTranslateCoordinates(scr->display, scr->rootWin, toplevel, info->location.x, info->location.y, &x, &y, &child); child = findChildInWindow(scr->display, toplevel, x, y); if (child != None) { info->destination = W_GetViewForXWindow(scr->display, child); if (info->destination->droppableTypes == NULL) { info->destination = NULL; } else if (info->destination->dragDestinationProcs == NULL) { info->destination = NULL; } } else { info->destination = NULL; } info->destinationWindow = toplevel; } */ } static void processMotion(WMScreen *scr, WMDraggingInfo *info, WMDraggingInfo *oldInfo, WMRect *rect, unsigned currentAction) { unsigned action; if (info->destinationWindow == None) { /* entered an unsupporeted window */ if (oldInfo->destinationWindow != None && oldInfo->destinationWindow != scr->rootWin) { SPIT("left window"); notifyDragLeave(scr, oldInfo); } } else if (info->destinationWindow == scr->rootWin) { if (oldInfo->destinationWindow != None && oldInfo->destinationWindow != scr->rootWin) { SPIT("left window to root"); notifyDragLeave(scr, oldInfo); } else { /* nothing */ } } else if (oldInfo->destinationWindow != info->destinationWindow) { if (oldInfo->destinationWindow != None && oldInfo->destinationWindow != scr->rootWin) { notifyDragLeave(scr, oldInfo); SPIT("crossed"); } else { SPIT("entered window"); } action = notifyDragEnter(scr, info); } else { #define LEFT_RECT(r, X, Y) (X < r->pos.x || Y < r->pos.y \ || X >= r->pos.x + r->size.width \ || Y >= r->pos.y + r->size.height) if (rect->size.width == 0 || (LEFT_RECT(rect, info->location.x, info->location.y))) { action = notifyPosition(scr, info); rect->size.width = 0; } } /* little trick to simulate XdndStatus for local dnd */ /* if (bla && action != currentAction) { XEvent ev; ev.type = ClientMessage; ev.xclient.display = scr->display; ev.xclient.message_type = scr->xdndStatusAtom; ev.xclient.format = 32; ev.xclient.window = info->destinationWindow; ev.xclient.data.l[0] = info->sourceWindow; ev.xclient.data.l[1] = (action ? 1 : 0); ev.xclient.data.l[2] = 0; ev.xclient.data.l[3] = 0; ev.xclient.data.l[4] = action; XPutBackEvent(scr->display, &ev); }*/ } static void timeoutCallback(void *data) { wwarning("drag & drop timed out while waiting for response from 0x%x\n", (unsigned)data); _XErrorOccured = 1; } /* * State Machine For Drag Source: * ------------------------------ * Events * State Call Mtn Ent Lea Crs BUp StA StR Fin TO * 0) idle 1bu - - - - - - - - - * 1) drag over target - 1au - 2cu 1cbu 5fu 3 4 1w - * 2) drag over nothing - 2 1bu - - 0 - - 2w - * 3) drag targ+accept - 3u - 2cu 1cbu 6f 3 4w 0z - * 4) drag targ+reject - 4u - 2cu 1cbu 0 3w 4 0z - * 5) waiting status - 5X 5X 5X 5X - 6f 0 0z 0w * 6) dropped - - - - - - - - 0 0w * * Events: * Call - called WMDragImageFromView() * Mtn - Motion * Ent - Enter droppable window * Lea - Leave droppable window (or rectangle) * Crs - Leave droppable window (or rectangle) and enter another * BUp - Button Released * StA - XdndStatus client msg with Accept drop * StR - XdndStatus client msg with Reject drop * Fin - XdndFinish client msg * TO - timeout * * Actions: * a - send update message * b - send enter message * c - send leave message * d - release drag section info * e - send drop message * f - setup timeout * u - update dragInfo * * X - ignore * w - warn about unexpected reply * z - abort operation.. unexpected reply * - shouldnt happen */ void WMDragImageFromView(WMView *view, WMPixmap *image, char *dataTypes[], WMPoint atLocation, WMSize mouseOffset, XEvent *event, Bool slideBack) { WMScreen *scr = view->screen; Display *dpy = scr->display; Window icon; XEvent ev; WMSize size; WMRect rect = {{0,0},{0,0}}; int ostate = -1; int state; int action = -1; XColor black = {0, 0,0,0, DoRed|DoGreen|DoBlue}; XColor green = {0x0045b045, 0x4500,0xb000,0x4500, DoRed|DoGreen|DoBlue}; XColor back = {0, 0xffff,0xffff,0xffff, DoRed|DoGreen|DoBlue}; WMDraggingInfo dragInfo; WMDraggingInfo oldDragInfo; WMHandlerID timer = NULL; WMData *draggedData = NULL; wassertr(view->dragSourceProcs != NULL); /* prepare icon to be dragged */ if (image == NULL) image = scr->defaultObjectIcon; size = WMGetPixmapSize(image); icon = makeDragIcon(scr, image); XMoveWindow(dpy, icon, event->xmotion.x_root-(int)size.width/2, event->xmotion.y_root-(int)size.height/2); XMapRaised(dpy, icon); /* init dragging info */ memset(&dragInfo, 0, sizeof(WMDraggingInfo)); memset(&oldDragInfo, 0, sizeof(WMDraggingInfo)); dragInfo.image = image; dragInfo.sourceWindow = W_VIEW_DRAWABLE(W_TopLevelOfView(view)); dragInfo.destinationWindow = dragInfo.sourceWindow; dragInfo.location.x = event->xmotion.x_root; dragInfo.location.y = event->xmotion.y_root; dragInfo.imageLocation = atLocation; /* start pointer grab */ XGrabPointer(dpy, scr->rootWin, False, ButtonPressMask|ButtonReleaseMask|ButtonMotionMask, GrabModeSync, GrabModeAsync, None, scr->defaultCursor, CurrentTime); XFlush(dpy); _XErrorOccured = False; /* XXX: take ownership of XdndSelection */ if (view->dragSourceProcs->beganDragImage != NULL) { view->dragSourceProcs->beganDragImage(view, image, atLocation); } processMotion(scr, &dragInfo, &oldDragInfo, &rect, action); state = 1; while (state != 6 && state != 0 && !_XErrorOccured) { XAllowEvents(dpy, SyncPointer, CurrentTime); WMNextEvent(dpy, &ev); switch (ev.type) { case MotionNotify: if (state >= 1 && state <= 4) { while (XCheckTypedEvent(dpy, MotionNotify, &ev)) ; protectBlock(True); oldDragInfo = dragInfo; updateDraggingInfo(scr, &dragInfo, &ev, icon); XMoveWindow(dpy, icon, dragInfo.imageLocation.x, dragInfo.imageLocation.y); if (state != 2) { processMotion(scr, &dragInfo, &oldDragInfo, &rect, action); } protectBlock(False); /* XXXif entered a different destination, check the operation */ switch (state) { case 1: if (oldDragInfo.destinationWindow != None && (dragInfo.destinationWindow == None || dragInfo.destinationWindow == scr->rootWin)) { /* left the droppable window */ state = 2; action = -1; } break; case 2: if (dragInfo.destinationWindow != None) { state = 1; action = -1; } break; case 3: case 4: if (oldDragInfo.destinationWindow != None && (dragInfo.destinationWindow == None || dragInfo.destinationWindow == scr->rootWin)) { /* left the droppable window */ state = 2; action = -1; } break; } } break; case ButtonRelease: /* if (state >= 1 && state <= 4) */ { protectBlock(True); oldDragInfo = dragInfo; updateDraggingInfo(scr, &dragInfo, &ev, icon); XMoveWindow(dpy, icon, dragInfo.imageLocation.x, dragInfo.imageLocation.y); processMotion(scr, &dragInfo, &oldDragInfo, &rect, action); protectBlock(False); switch (state) { case 1: state = 5; timer = WMAddTimerHandler(3000, timeoutCallback, (void*)dragInfo.destinationWindow); break; case 2: state = 0; break; case 3: state = 6; break; case 4: state = 0; break; } } break; case SelectionRequest: draggedData = NULL; break; case ClientMessage: if ((state == 1 || state == 3 || state == 4 || state == 5) && ev.xclient.message_type == scr->xdndStatusAtom && ev.xclient.window == dragInfo.destinationWindow) { if (ev.xclient.data.l[1] & 1) { puts("got accept msg"); /* will accept drop */ switch (state) { case 1: case 3: case 4: state = 3; break; case 5: if (timer) { WMDeleteTimerHandler(timer); timer = NULL; } state = 6; break; } if (ev.xclient.data.l[4] == None) { action = 0; } else { action = ev.xclient.data.l[4];/*XXX*/ } } else { puts("got reject msg"); switch (state) { case 1: case 3: case 4: state = 4; break; case 5: state = 0; if (timer) { WMDeleteTimerHandler(timer); timer = NULL; } break; } action = 0; } if (ev.xclient.data.l[1] & (1<<1)) { rect.pos.x = ev.xclient.data.l[2] >> 16; rect.pos.y = ev.xclient.data.l[2] & 0xffff; rect.size.width = ev.xclient.data.l[3] >> 16; rect.size.height = ev.xclient.data.l[3] & 0xffff; } else { rect.size.width = 0; } } else if ((state >= 1 && state <= 5) && ev.xclient.message_type == scr->xdndFinishedAtom && ev.xclient.window == dragInfo.destinationWindow) { wwarning("drag source received unexpected XdndFinished message from %x", (unsigned)dragInfo.destinationWindow); if (state == 3 || state == 4 || state == 5) { state = 0; if (timer) { WMDeleteTimerHandler(timer); timer = NULL; } } } default: WMHandleEvent(&ev); break; } if (ostate != state) { printf("state changed to %i\n", state); if (state == 3) { XRecolorCursor(dpy, scr->defaultCursor, &green, &back); } else if (ostate == 3) { XRecolorCursor(dpy, scr->defaultCursor, &black, &back); } ostate = state; } } if (timer) { WMDeleteTimerHandler(timer); timer = NULL; } XUngrabPointer(dpy, CurrentTime); SPIT("exited main loop"); if (_XErrorOccured || state != 6) { goto cancelled; } assert(dragInfo.destinationWindow != None); protectBlock(True); notifyDrop(scr, &dragInfo); protectBlock(False); if (_XErrorOccured) goto cancelled; SPIT("dropped"); /* wait for Finished message and SelectionRequest if not a local drop */ XDestroyWindow(dpy, icon); if (view->dragSourceProcs->endedDragImage != NULL) { view->dragSourceProcs->endedDragImage(view, image, dragInfo.imageLocation, True); } return; cancelled: if (draggedData) { WMReleaseData(draggedData); } if (slideBack) { slideWindow(dpy, icon, dragInfo.imageLocation.x, dragInfo.imageLocation.y, atLocation.x, atLocation.y); } XDestroyWindow(dpy, icon); if (view->dragSourceProcs->endedDragImage != NULL) { view->dragSourceProcs->endedDragImage(view, image, dragInfo.imageLocation, False); } } static Atom* getTypeList(Window window, XClientMessageEvent *event) { int i = 0; Atom *types = NULL; if (event->data.l[1] & 1) { /* > 3 types */ } else { types = wmalloc(4 * sizeof(Atom)); if (event->data.l[2] != None) types[i++] = event->data.l[2]; if (event->data.l[3] != None) types[i++] = event->data.l[3]; if (event->data.l[4] != None) types[i++] = event->data.l[4]; types[i] = 0; } if (types[0] == 0) { wwarning("received invalid drag & drop type list"); /*XXX return;*/ } return types; } void W_HandleDNDClientMessage(WMView *toplevel, XClientMessageEvent *event) { #if 0 WMScreen *scr = W_VIEW_SCREEN(toplevel); WMView *oldView = NULL, *newView = NULL; unsigned operation = 0; int x, y; if (event->message_type == scr->xdndEnterAtom) { Window foo, bar; int bla; unsigned ble; puts("entered"); if (scr->dragInfo.sourceWindow != None) { puts("received Enter event in bad order"); } memset(&scr->dragInfo, 0, sizeof(WMDraggingInfo)); if ((event->data.l[0] >> 24) > XDND_VERSION) { wwarning("received drag & drop request with unsupported version %i", (event->data.l[0] >> 24)); return; } scr->dragInfo.protocolVersion = event->data.l[1] >> 24; scr->dragInfo.sourceWindow = event->data.l[0]; scr->dragInfo.destinationWindow = event->window; /* XXX */ scr->dragInfo.image = NULL; XQueryPointer(scr->display, scr->rootWin, &foo, &bar, &scr->dragInfo.location.x, &scr->dragInfo.location.y, &bla, &bla, &ble); translateCoordinates(scr, scr->dragInfo.destinationWindow, scr->dragInfo.location.x, scr->dragInfo.location.y, &x, &y); newView = findViewInToplevel(scr->display, scr->dragInfo.destinationWindow, x, y); if (IS_DROPPABLE(view)) { scr->dragInfo.destinationView = view; operation = view->dragDestinationProcs->draggingEntered(view, &scr->dragInfo); } if (operation > 0) { Atom action; switch (operation) { default: action = 0; break; } sendClientMessage(scr->display, scr->dragInfo.sourceWindow, scr->xdndStatusAtom, scr->dragInfo.destinationWindow, 1, 0, 0, action); } } else if (event->message_type == scr->xdndPositionAtom && scr->dragInfo.sourceWindow == event->data.l[0]) { scr->dragInfo.location.x = event->data.l[2] >> 16; scr->dragInfo.location.y = event->data.l[2] & 0xffff; if (scr->dragInfo.protocolVersion >= 1) { scr->dragInfo.timestamp = event->data.l[3]; scr->dragInfo.sourceOperation = event->data.l[4]; } else { scr->dragInfo.timestamp = CurrentTime; scr->dragInfo.sourceOperation = 0; /*XXX*/ } translateCoordinates(scr, scr->dragInfo.destinationWindow, scr->dragInfo.location.x, scr->dragInfo.location.y, &x, &y); view = findViewInToplevel(scr->display, scr->dragInfo.destinationWindow, x, y); if (scr->dragInfo.destinationView != view) { WMView *oldVIew = scr->dragInfo.destinationView; oldView->dragDestinationProcs->draggingExited(oldView, &scr->dragInfo); scr->dragInfo.destinationView = NULL; } if (IS_DROPPABLE(view)) { operation = view->dragDestinationProcs->draggingUpdated(view, &scr->dragInfo); } if (operation == 0) { sendClientMessage(scr->display, scr->dragInfo.sourceWindow, scr->xdndStatusAtom, scr->dragInfo.destinationWindow, 0, 0, 0, None); } else { Atom action; switch (operation) { default: action = 0; break; } sendClientMessage(scr->display, scr->dragInfo.sourceWindow, scr->xdndStatusAtom, scr->dragInfo.destinationWindow, 1, 0, 0, action); } } else if (event->message_type == scr->xdndLeaveAtom && scr->dragInfo.sourceWindow == event->data.l[0]) { void (*draggingExited)(WMView *self, WMDraggingInfo *info); puts("leave"); } else if (event->message_type == scr->xdndDropAtom && scr->dragInfo.sourceWindow == event->data.l[0]) { puts("drop"); } #endif }