1 
2 #include "wconfig.h"
3 #include "WINGsP.h"
4 
5 #include <X11/Xatom.h>
6 #include <X11/cursorfont.h>
7 #ifdef USE_XSHAPE
8 #include <X11/extensions/shape.h>
9 #endif
10 
11 #define XDND_DESTINATION_RESPONSE_MAX_DELAY 10000
12 #define MIN_X_MOVE_OFFSET 5
13 #define MIN_Y_MOVE_OFFSET 5
14 #define MAX_SLIDEBACK_ITER 15
15 
16 #define XDND_PROPERTY_FORMAT 32
17 #define XDND_ACTION_DESCRIPTION_FORMAT 8
18 
19 #define XDND_DEST_VERSION(dragInfo) dragInfo->protocolVersion
20 #define XDND_SOURCE_INFO(dragInfo) dragInfo->sourceInfo
21 #define XDND_DEST_WIN(dragInfo) dragInfo->sourceInfo->destinationWindow
22 #define XDND_SOURCE_ACTION(dragInfo) dragInfo->sourceAction
23 #define XDND_DEST_ACTION(dragInfo) dragInfo->destinationAction
24 #define XDND_SOURCE_VIEW(dragInfo) dragInfo->sourceInfo->sourceView
25 #define XDND_SOURCE_STATE(dragInfo) dragInfo->sourceInfo->state
26 #define XDND_SELECTION_PROCS(dragInfo) dragInfo->sourceInfo->selectionProcs
27 #define XDND_DRAG_ICON(dragInfo) dragInfo->sourceInfo->icon
28 #define XDND_MOUSE_OFFSET(dragInfo) dragInfo->sourceInfo->mouseOffset
29 #define XDND_DRAG_CURSOR(dragInfo) dragInfo->sourceInfo->dragCursor
30 #define XDND_DRAG_ICON_POS(dragInfo) dragInfo->sourceInfo->imageLocation
31 #define XDND_NO_POS_ZONE(dragInfo) dragInfo->sourceInfo->noPositionMessageZone
32 #define XDND_TIMESTAMP(dragInfo) dragInfo->timestamp
33 #define XDND_3_TYPES(dragInfo) dragInfo->sourceInfo->firstThreeTypes
34 #define XDND_SOURCE_VIEW_STORED(dragInfo) dragInfo->sourceInfo != NULL \
35     && dragInfo->sourceInfo->sourceView != NULL
36 
37 static WMHandlerID dndSourceTimer = NULL;
38 
39 static void *idleState(WMView * srcView, XClientMessageEvent * event, WMDraggingInfo * info);
40 static void *dropAllowedState(WMView * srcView, XClientMessageEvent * event, WMDraggingInfo * info);
41 static void *finishDropState(WMView * srcView, XClientMessageEvent * event, WMDraggingInfo * info);
42 
43 #ifdef XDND_DEBUG
stateName(W_DndState * state)44 static const char *stateName(W_DndState * state)
45 {
46 	if (state == NULL)
47 		return "no state defined";
48 
49 	if (state == idleState)
50 		return "idleState";
51 
52 	if (state == dropAllowedState)
53 		return "dropAllowedState";
54 
55 	if (state == finishDropState)
56 		return "finishDropState";
57 
58 	return "unknown state";
59 }
60 #endif
61 
sourceScreen(WMDraggingInfo * info)62 static WMScreen *sourceScreen(WMDraggingInfo * info)
63 {
64 	return W_VIEW_SCREEN(XDND_SOURCE_VIEW(info));
65 }
66 
endDragProcess(WMDraggingInfo * info,Bool deposited)67 static void endDragProcess(WMDraggingInfo * info, Bool deposited)
68 {
69 	WMView *view = XDND_SOURCE_VIEW(info);
70 	WMScreen *scr = W_VIEW_SCREEN(XDND_SOURCE_VIEW(info));
71 
72 	/* free selection handler while view exists */
73 	WMDeleteSelectionHandler(view, scr->xdndSelectionAtom, CurrentTime);
74 	wfree(XDND_SELECTION_PROCS(info));
75 
76 	if (XDND_DRAG_CURSOR(info) != None) {
77 		XFreeCursor(scr->display, XDND_DRAG_CURSOR(info));
78 		XDND_DRAG_CURSOR(info) = None;
79 	}
80 
81 	if (view->dragSourceProcs->endedDrag != NULL) {
82 		/* this can destroy source view (with a "move" action for example) */
83 		view->dragSourceProcs->endedDrag(view, &XDND_DRAG_ICON_POS(info), deposited);
84 	}
85 
86 	/* clear remaining draggging infos */
87 	wfree(XDND_SOURCE_INFO(info));
88 	XDND_SOURCE_INFO(info) = NULL;
89 }
90 
91 /* ----- drag cursor ----- */
initDragCursor(WMDraggingInfo * info)92 static void initDragCursor(WMDraggingInfo * info)
93 {
94 	WMScreen *scr = sourceScreen(info);
95 	XColor cursorFgColor, cursorBgColor;
96 
97 	/* green */
98 	cursorFgColor.red = 0x4500;
99 	cursorFgColor.green = 0xb000;
100 	cursorFgColor.blue = 0x4500;
101 
102 	/* white */
103 	cursorBgColor.red = 0xffff;
104 	cursorBgColor.green = 0xffff;
105 	cursorBgColor.blue = 0xffff;
106 
107 	XDND_DRAG_CURSOR(info) = XCreateFontCursor(scr->display, XC_left_ptr);
108 	XRecolorCursor(scr->display, XDND_DRAG_CURSOR(info), &cursorFgColor, &cursorBgColor);
109 
110 	XFlush(scr->display);
111 }
112 
recolorCursor(WMDraggingInfo * info,Bool dropIsAllowed)113 static void recolorCursor(WMDraggingInfo * info, Bool dropIsAllowed)
114 {
115 	WMScreen *scr = sourceScreen(info);
116 
117 	if (dropIsAllowed) {
118 		XDefineCursor(scr->display, scr->rootWin, XDND_DRAG_CURSOR(info));
119 	} else {
120 		XDefineCursor(scr->display, scr->rootWin, scr->defaultCursor);
121 	}
122 
123 	XFlush(scr->display);
124 }
125 
126 /* ----- end of drag cursor ----- */
127 
128 /* ----- selection procs ----- */
convertSelection(WMView * view,Atom selection,Atom target,void * cdata,Atom * type)129 static WMData *convertSelection(WMView * view, Atom selection, Atom target, void *cdata, Atom * type)
130 {
131 	WMScreen *scr;
132 	WMData *data;
133 	char *typeName;
134 
135 	/* Parameter not used, but tell the compiler that it is ok */
136 	(void) selection;
137 	(void) cdata;
138 
139 	scr = W_VIEW_SCREEN(view);
140 	typeName = XGetAtomName(scr->display, target);
141 
142 	*type = target;
143 
144 	if (view->dragSourceProcs->fetchDragData != NULL) {
145 		data = view->dragSourceProcs->fetchDragData(view, typeName);
146 	} else {
147 		data = NULL;
148 	}
149 
150 	if (typeName != NULL)
151 		XFree(typeName);
152 
153 	return data;
154 }
155 
selectionLost(WMView * view,Atom selection,void * cdata)156 static void selectionLost(WMView * view, Atom selection, void *cdata)
157 {
158 	/* Parameter not used, but tell the compiler that it is ok */
159 	(void) view;
160 	(void) selection;
161 	(void) cdata;
162 
163 	wwarning(_("XDND selection lost during drag operation..."));
164 }
165 
selectionDone(WMView * view,Atom selection,Atom target,void * cdata)166 static void selectionDone(WMView * view, Atom selection, Atom target, void *cdata)
167 {
168 	/* Parameter not used, but tell the compiler that it is ok */
169 	(void) view;
170 	(void) selection;
171 	(void) target;
172 	(void) cdata;
173 
174 #ifdef XDND_DEBUG
175 	printf("selection done\n");
176 #endif
177 }
178 
179 /* ----- end of selection procs ----- */
180 
181 /* ----- visual part ----- */
182 
makeDragIcon(WMScreen * scr,WMPixmap * pixmap)183 static Window makeDragIcon(WMScreen * scr, WMPixmap * pixmap)
184 {
185 	Window window;
186 	WMSize size;
187 	unsigned long flags;
188 	XSetWindowAttributes attribs;
189 
190 	if (!pixmap) {
191 		pixmap = scr->defaultObjectIcon;
192 	}
193 
194 	size = WMGetPixmapSize(pixmap);
195 
196 	flags = CWSaveUnder | CWBackPixmap | CWOverrideRedirect | CWColormap;
197 	attribs.save_under = True;
198 	attribs.background_pixmap = pixmap->pixmap;
199 	attribs.override_redirect = True;
200 	attribs.colormap = scr->colormap;
201 
202 	window = XCreateWindow(scr->display, scr->rootWin, 0, 0, size.width,
203 			       size.height, 0, scr->depth, InputOutput, scr->visual, flags, &attribs);
204 
205 #ifdef USE_XSHAPE
206 	if (pixmap->mask) {
207 		XShapeCombineMask(scr->display, window, ShapeBounding, 0, 0, pixmap->mask, ShapeSet);
208 	}
209 #endif
210 
211 	return window;
212 }
213 
slideWindow(Display * dpy,Window win,int srcX,int srcY,int dstX,int dstY)214 static void slideWindow(Display * dpy, Window win, int srcX, int srcY, int dstX, int dstY)
215 {
216 	double x, y, dx, dy;
217 	int i;
218 	int iterations;
219 
220 	iterations = WMIN(MAX_SLIDEBACK_ITER, WMAX(abs(dstX - srcX), abs(dstY - srcY)));
221 
222 	x = srcX;
223 	y = srcY;
224 
225 	dx = (double)(dstX - srcX) / iterations;
226 	dy = (double)(dstY - srcY) / iterations;
227 
228 	for (i = 0; i <= iterations; i++) {
229 		XMoveWindow(dpy, win, x, y);
230 		XFlush(dpy);
231 
232 		wusleep(800);
233 
234 		x += dx;
235 		y += dy;
236 	}
237 }
238 
getInitialDragImageCoord(int viewCoord,int mouseCoord,int viewSize,int iconSize)239 static int getInitialDragImageCoord(int viewCoord, int mouseCoord, int viewSize, int iconSize)
240 {
241 	if (iconSize >= viewSize) {
242 		/* center icon coord on view */
243 		return viewCoord - iconSize / 2;
244 	} else {
245 		/* try to center icon on mouse pos */
246 
247 		if (mouseCoord - iconSize / 2 <= viewCoord)
248 			/* if icon was centered on mouse, it would be off view
249 			   thus, put icon left (resp. top) side
250 			   at view (resp. top) side */
251 			return viewCoord;
252 
253 		else if (mouseCoord + iconSize / 2 >= viewCoord + viewSize)
254 			/* if icon was centered on mouse, it would be off view
255 			   thus, put icon right (resp. bottom) side
256 			   at view right (resp. bottom) side */
257 			return viewCoord + viewSize - iconSize;
258 
259 		else
260 			return mouseCoord - iconSize / 2;
261 	}
262 
263 }
264 
initDragImagePos(WMView * view,WMDraggingInfo * info,XEvent * event)265 static void initDragImagePos(WMView * view, WMDraggingInfo * info, XEvent * event)
266 {
267 	WMSize iconSize = WMGetPixmapSize(view->dragImage);
268 	WMSize viewSize = WMGetViewSize(view);
269 	WMPoint viewPos;
270 	Window foo;
271 
272 	XTranslateCoordinates(W_VIEW_SCREEN(view)->display,
273 			      WMViewXID(view), W_VIEW_SCREEN(view)->rootWin,
274 			      0, 0, &(viewPos.x), &(viewPos.y), &foo);
275 
276 	/* set icon pos */
277 	XDND_DRAG_ICON_POS(info).x =
278 	    getInitialDragImageCoord(viewPos.x, event->xmotion.x_root, viewSize.width, iconSize.width);
279 
280 	XDND_DRAG_ICON_POS(info).y =
281 	    getInitialDragImageCoord(viewPos.y, event->xmotion.y_root, viewSize.height, iconSize.height);
282 
283 	/* set mouse offset relative to icon */
284 	XDND_MOUSE_OFFSET(info).x = event->xmotion.x_root - XDND_DRAG_ICON_POS(info).x;
285 	XDND_MOUSE_OFFSET(info).y = event->xmotion.y_root - XDND_DRAG_ICON_POS(info).y;
286 }
287 
refreshDragImage(WMView * view,WMDraggingInfo * info)288 static void refreshDragImage(WMView * view, WMDraggingInfo * info)
289 {
290 	WMScreen *scr = W_VIEW_SCREEN(view);
291 
292 	XMoveWindow(scr->display, XDND_DRAG_ICON(info), XDND_DRAG_ICON_POS(info).x, XDND_DRAG_ICON_POS(info).y);
293 }
294 
startDragImage(WMView * view,WMDraggingInfo * info,XEvent * event)295 static void startDragImage(WMView * view, WMDraggingInfo * info, XEvent * event)
296 {
297 	WMScreen *scr = W_VIEW_SCREEN(view);
298 
299 	XDND_DRAG_ICON(info) = makeDragIcon(scr, view->dragImage);
300 	initDragImagePos(view, info, event);
301 	refreshDragImage(view, info);
302 	XMapRaised(scr->display, XDND_DRAG_ICON(info));
303 
304 	initDragCursor(info);
305 }
306 
endDragImage(WMDraggingInfo * info,Bool slideBack)307 static void endDragImage(WMDraggingInfo * info, Bool slideBack)
308 {
309 	WMView *view = XDND_SOURCE_VIEW(info);
310 	Display *dpy = W_VIEW_SCREEN(view)->display;
311 
312 	if (slideBack) {
313 		WMPoint toLocation;
314 		Window foo;
315 
316 		XTranslateCoordinates(W_VIEW_SCREEN(view)->display,
317 				      WMViewXID(view), W_VIEW_SCREEN(view)->rootWin,
318 				      0, 0, &(toLocation.x), &(toLocation.y), &foo);
319 
320 		slideWindow(dpy, XDND_DRAG_ICON(info),
321 			    XDND_DRAG_ICON_POS(info).x, XDND_DRAG_ICON_POS(info).y, toLocation.x, toLocation.y);
322 	}
323 
324 	XDestroyWindow(dpy, XDND_DRAG_ICON(info));
325 }
326 
327 /* ----- end of visual part ----- */
328 
329 /* ----- messages ----- */
330 
331 /* send a DnD message to the destination window */
332 static Bool
sendDnDClientMessage(WMDraggingInfo * info,Atom message,unsigned long data1,unsigned long data2,unsigned long data3,unsigned long data4)333 sendDnDClientMessage(WMDraggingInfo * info, Atom message,
334 		     unsigned long data1, unsigned long data2, unsigned long data3, unsigned long data4)
335 {
336 	Display *dpy = sourceScreen(info)->display;
337 	Window srcWin = WMViewXID(XDND_SOURCE_VIEW(info));
338 	Window destWin = XDND_DEST_WIN(info);
339 
340 	if (!W_SendDnDClientMessage(dpy, destWin, message, srcWin, data1, data2, data3, data4)) {
341 		/* drop failed */
342 		recolorCursor(info, False);
343 		endDragImage(info, True);
344 		endDragProcess(info, False);
345 		return False;
346 	}
347 
348 	return True;
349 }
350 
sendEnterMessage(WMDraggingInfo * info)351 static Bool sendEnterMessage(WMDraggingInfo * info)
352 {
353 	WMScreen *scr = sourceScreen(info);
354 	unsigned long version;
355 
356 	if (XDND_DEST_VERSION(info) > 2) {
357 		if (XDND_DEST_VERSION(info) < XDND_VERSION)
358 			version = XDND_DEST_VERSION(info);
359 		else
360 			version = XDND_VERSION;
361 	} else {
362 		version = 3;
363 	}
364 
365 	return sendDnDClientMessage(info, scr->xdndEnterAtom, (version << 24) | 1,	/* 1: support of type list */
366 				    XDND_3_TYPES(info)[0], XDND_3_TYPES(info)[1], XDND_3_TYPES(info)[2]);
367 }
368 
sendPositionMessage(WMDraggingInfo * info,WMPoint * mousePos)369 static Bool sendPositionMessage(WMDraggingInfo * info, WMPoint * mousePos)
370 {
371 	WMScreen *scr = sourceScreen(info);
372 	WMRect *noPosZone = &(XDND_NO_POS_ZONE(info));
373 
374 	if (noPosZone->size.width != 0 || noPosZone->size.height != 0) {
375 		if (mousePos->x < noPosZone->pos.x || mousePos->x > (noPosZone->pos.x + noPosZone->size.width)
376 		    || mousePos->y < noPosZone->pos.y || mousePos->y > (noPosZone->pos.y + noPosZone->size.height)) {
377 			/* send position if out of zone defined by destination */
378 			return sendDnDClientMessage(info, scr->xdndPositionAtom,
379 						    0,
380 						    mousePos->x << 16 | mousePos->y,
381 						    XDND_TIMESTAMP(info), XDND_SOURCE_ACTION(info));
382 		}
383 
384 		/* Nothing to send, always succeed */
385 		return True;
386 
387 	}
388 
389 	/* send position on each move */
390 	return sendDnDClientMessage(info, scr->xdndPositionAtom,
391 				    0,
392 				    mousePos->x << 16 | mousePos->y,
393 				    XDND_TIMESTAMP(info), XDND_SOURCE_ACTION(info));
394 }
395 
sendLeaveMessage(WMDraggingInfo * info)396 static Bool sendLeaveMessage(WMDraggingInfo * info)
397 {
398 	WMScreen *scr = sourceScreen(info);
399 
400 	return sendDnDClientMessage(info, scr->xdndLeaveAtom, 0, 0, 0, 0);
401 }
402 
sendDropMessage(WMDraggingInfo * info)403 static Bool sendDropMessage(WMDraggingInfo * info)
404 {
405 	WMScreen *scr = sourceScreen(info);
406 
407 	return sendDnDClientMessage(info, scr->xdndDropAtom, 0, XDND_TIMESTAMP(info), 0, 0);
408 }
409 
410 /* ----- end of messages ----- */
411 
getTypeAtomList(WMScreen * scr,WMView * view,int * count)412 static Atom *getTypeAtomList(WMScreen * scr, WMView * view, int *count)
413 {
414 	WMArray *types;
415 	Atom *typeAtoms;
416 	int i;
417 
418 	types = view->dragSourceProcs->dropDataTypes(view);
419 
420 	if (types != NULL) {
421 		*count = WMGetArrayItemCount(types);
422 		if (*count > 0) {
423 			typeAtoms = wmalloc((*count) * sizeof(Atom));
424 			for (i = 0; i < *count; i++) {
425 				typeAtoms[i] = XInternAtom(scr->display, WMGetFromArray(types, i), False);
426 			}
427 
428 			/* WMFreeArray(types); */
429 			return typeAtoms;
430 		}
431 
432 		/* WMFreeArray(types); */
433 	}
434 
435 	*count = 1;
436 	typeAtoms = wmalloc(sizeof(Atom));
437 	*typeAtoms = None;
438 
439 	return typeAtoms;
440 }
441 
registerDropTypes(WMScreen * scr,WMView * view,WMDraggingInfo * info)442 static void registerDropTypes(WMScreen * scr, WMView * view, WMDraggingInfo * info)
443 {
444 	Atom *typeList;
445 	int i, count;
446 
447 	typeList = getTypeAtomList(scr, view, &count);
448 
449 	/* store the first 3 types */
450 	for (i = 0; i < 3 && i < count; i++)
451 		XDND_3_TYPES(info)[i] = typeList[i];
452 
453 	for (; i < 3; i++)
454 		XDND_3_TYPES(info)[i] = None;
455 
456 	/* store the entire type list */
457 	XChangeProperty(scr->display,
458 			WMViewXID(view),
459 			scr->xdndTypeListAtom,
460 			XA_ATOM, XDND_PROPERTY_FORMAT, PropModeReplace, (unsigned char *)typeList, count);
461 }
462 
registerOperationList(WMScreen * scr,WMView * view,WMArray * operationArray)463 static void registerOperationList(WMScreen * scr, WMView * view, WMArray * operationArray)
464 {
465 	Atom *actionList;
466 	WMDragOperationType operation;
467 	int count = WMGetArrayItemCount(operationArray);
468 	int i;
469 
470 	actionList = wmalloc(sizeof(Atom) * count);
471 
472 	for (i = 0; i < count; i++) {
473 		operation = WMGetDragOperationItemType(WMGetFromArray(operationArray, i));
474 		actionList[i] = W_OperationToAction(scr, operation);
475 	}
476 
477 	XChangeProperty(scr->display,
478 			WMViewXID(view),
479 			scr->xdndActionListAtom,
480 			XA_ATOM, XDND_PROPERTY_FORMAT, PropModeReplace, (unsigned char *)actionList, count);
481 }
482 
registerDescriptionList(WMScreen * scr,WMView * view,WMArray * operationArray)483 static void registerDescriptionList(WMScreen * scr, WMView * view, WMArray * operationArray)
484 {
485 	char *text, *textListItem, *textList;
486 	int count = WMGetArrayItemCount(operationArray);
487 	int i;
488 	int size = 0;
489 
490 	/* size of XA_STRING info */
491 	for (i = 0; i < count; i++) {
492 		size += strlen(WMGetDragOperationItemText(WMGetFromArray(operationArray, i))) + 1 /* NULL */;
493 	}
494 
495 	/* create text list */
496 	textList = wmalloc(size);
497 	textListItem = textList;
498 
499 	for (i = 0; i < count; i++) {
500 		text = WMGetDragOperationItemText(WMGetFromArray(operationArray, i));
501 		wstrlcpy(textListItem, text, size);
502 
503 		/* to next text offset */
504 		textListItem = &(textListItem[strlen(textListItem) + 1]);
505 	}
506 
507 	XChangeProperty(scr->display,
508 			WMViewXID(view),
509 			scr->xdndActionDescriptionAtom,
510 			XA_STRING,
511 			XDND_ACTION_DESCRIPTION_FORMAT, PropModeReplace, (unsigned char *)textList, size);
512 }
513 
514 /* called if wanted operation is WDOperationAsk */
registerSupportedOperations(WMView * view)515 static void registerSupportedOperations(WMView * view)
516 {
517 	WMScreen *scr = W_VIEW_SCREEN(view);
518 	WMArray *operationArray;
519 
520 	operationArray = view->dragSourceProcs->askedOperations(view);
521 
522 	registerOperationList(scr, view, operationArray);
523 	registerDescriptionList(scr, view, operationArray);
524 
525 	/* WMFreeArray(operationArray); */
526 }
527 
initSourceDragInfo(WMView * sourceView,WMDraggingInfo * info)528 static void initSourceDragInfo(WMView * sourceView, WMDraggingInfo * info)
529 {
530 	WMRect emptyZone;
531 
532 	XDND_SOURCE_INFO(info) = (W_DragSourceInfo *) wmalloc(sizeof(W_DragSourceInfo));
533 
534 	XDND_SOURCE_VIEW(info) = sourceView;
535 	XDND_DEST_WIN(info) = None;
536 	XDND_DRAG_ICON(info) = None;
537 
538 	XDND_SOURCE_ACTION(info) = W_OperationToAction(W_VIEW_SCREEN(sourceView),
539 						       sourceView->dragSourceProcs->
540 						       wantedDropOperation(sourceView));
541 
542 	XDND_DEST_ACTION(info) = None;
543 
544 	XDND_SOURCE_STATE(info) = idleState;
545 
546 	emptyZone.pos = wmkpoint(0, 0);
547 	emptyZone.size = wmksize(0, 0);
548 	XDND_NO_POS_ZONE(info) = emptyZone;
549 }
550 
551 /*
552  Returned array is destroyed after dropDataTypes call
553  */
defDropDataTypes(WMView * self)554 static WMArray *defDropDataTypes(WMView * self)
555 {
556 	/* Parameter not used, but tell the compiler that it is ok */
557 	(void) self;
558 
559 	return NULL;
560 }
561 
defWantedDropOperation(WMView * self)562 static WMDragOperationType defWantedDropOperation(WMView * self)
563 {
564 	/* Parameter not used, but tell the compiler that it is ok */
565 	(void) self;
566 
567 	return WDOperationNone;
568 }
569 
570 /*
571  Must be defined if wantedDropOperation return WDOperationAsk
572  (useless otherwise).
573  Return a WMDragOperationItem array (destroyed after call).
574  A WMDragOperationItem links a label to an operation.
575  static WMArray*
576  defAskedOperations(WMView *self); */
577 
defAcceptDropOperation(WMView * self,WMDragOperationType allowedOperation)578 static Bool defAcceptDropOperation(WMView * self, WMDragOperationType allowedOperation)
579 {
580 	/* Parameter not used, but tell the compiler that it is ok */
581 	(void) self;
582 	(void) allowedOperation;
583 
584 	return False;
585 }
586 
defBeganDrag(WMView * self,WMPoint * point)587 static void defBeganDrag(WMView * self, WMPoint * point)
588 {
589 	/* Parameter not used, but tell the compiler that it is ok */
590 	(void) self;
591 	(void) point;
592 }
593 
defEndedDrag(WMView * self,WMPoint * point,Bool deposited)594 static void defEndedDrag(WMView * self, WMPoint * point, Bool deposited)
595 {
596 	/* Parameter not used, but tell the compiler that it is ok */
597 	(void) self;
598 	(void) point;
599 	(void) deposited;
600 }
601 
602 /*
603  Returned data is not destroyed
604  */
defFetchDragData(WMView * self,char * type)605 static WMData *defFetchDragData(WMView * self, char *type)
606 {
607 	/* Parameter not used, but tell the compiler that it is ok */
608 	(void) self;
609 	(void) type;
610 
611 	return NULL;
612 }
613 
WMSetViewDragSourceProcs(WMView * view,WMDragSourceProcs * procs)614 void WMSetViewDragSourceProcs(WMView * view, WMDragSourceProcs * procs)
615 {
616 	if (view->dragSourceProcs)
617 		wfree(view->dragSourceProcs);
618 	view->dragSourceProcs = wmalloc(sizeof(WMDragSourceProcs));
619 
620 	*view->dragSourceProcs = *procs;
621 
622 	if (procs->dropDataTypes == NULL)
623 		view->dragSourceProcs->dropDataTypes = defDropDataTypes;
624 
625 	if (procs->wantedDropOperation == NULL)
626 		view->dragSourceProcs->wantedDropOperation = defWantedDropOperation;
627 
628 	/*
629 	   Note: askedOperations can be NULL, if wantedDropOperation never returns
630 	   WDOperationAsk.
631 	 */
632 
633 	if (procs->acceptDropOperation == NULL)
634 		view->dragSourceProcs->acceptDropOperation = defAcceptDropOperation;
635 
636 	if (procs->beganDrag == NULL)
637 		view->dragSourceProcs->beganDrag = defBeganDrag;
638 
639 	if (procs->endedDrag == NULL)
640 		view->dragSourceProcs->endedDrag = defEndedDrag;
641 
642 	if (procs->fetchDragData == NULL)
643 		view->dragSourceProcs->fetchDragData = defFetchDragData;
644 }
645 
isXdndAware(WMScreen * scr,Window win)646 static Bool isXdndAware(WMScreen * scr, Window win)
647 {
648 	Atom type;
649 	int format;
650 	unsigned long count, remain;
651 	unsigned char *winXdndVersion;
652 
653 	if (win == None)
654 		return False;
655 
656 	XGetWindowProperty(scr->display, win, scr->xdndAwareAtom,
657 			   0, 1, False, XA_ATOM, &type, &format, &count, &remain, &winXdndVersion);
658 
659 	if (type != XA_ATOM || format != XDND_PROPERTY_FORMAT || count == 0 || !winXdndVersion) {
660 		if (winXdndVersion)
661 			XFree(winXdndVersion);
662 		return False;
663 	}
664 
665 	XFree(winXdndVersion);
666 	return (count == 1);	/* xdnd version is set */
667 }
668 
windowChildren(Display * dpy,Window win,unsigned * nchildren)669 static Window *windowChildren(Display * dpy, Window win, unsigned *nchildren)
670 {
671 	Window *children;
672 	Window foo, bar;
673 
674 	if (!XQueryTree(dpy, win, &foo, &bar, &children, nchildren)) {
675 		*nchildren = 0;
676 		return NULL;
677 	} else
678 		return children;
679 }
680 
lookForAwareWindow(WMScreen * scr,WMPoint * mousePos,Window win)681 static Window lookForAwareWindow(WMScreen * scr, WMPoint * mousePos, Window win)
682 {
683 	int tmpx, tmpy;
684 	Window child;
685 
686 	/* Since xdnd v3, only the toplevel window should be aware */
687 	if (isXdndAware(scr, win))
688 		return win;
689 
690 	/* inspect child under pointer */
691 	if (XTranslateCoordinates(scr->display, scr->rootWin, win, mousePos->x, mousePos->y, &tmpx, &tmpy, &child)) {
692 		if (child == None)
693 			return None;
694 		else
695 			return lookForAwareWindow(scr, mousePos, child);
696 	}
697 
698 	return None;
699 }
700 
findDestination(WMDraggingInfo * info,WMPoint * mousePos)701 static Window findDestination(WMDraggingInfo * info, WMPoint * mousePos)
702 {
703 	WMScreen *scr = sourceScreen(info);
704 	unsigned nchildren;
705 	Window *children = windowChildren(scr->display, scr->rootWin, &nchildren);
706 	int i;
707 	XWindowAttributes attr;
708 
709 	if (isXdndAware(scr, scr->rootWin))
710 		return scr->rootWin;
711 
712 	/* exclude drag icon (and upper) from search */
713 	for (i = nchildren - 1; i >= 0; i--) {
714 		if (children[i] == XDND_DRAG_ICON(info)) {
715 			i--;
716 			break;
717 		}
718 	}
719 
720 	if (i < 0) {
721 		/* root window has no child under drag icon, and is not xdnd aware. */
722 		return None;
723 	}
724 
725 	/* inspecting children, from upper to lower */
726 	for (; i >= 0; i--) {
727 		if (XGetWindowAttributes(scr->display, children[i], &attr)
728 		    && attr.map_state == IsViewable
729 		    && mousePos->x >= attr.x
730 		    && mousePos->y >= attr.y
731 		    && mousePos->x < attr.x + attr.width && mousePos->y < attr.y + attr.height) {
732 			return lookForAwareWindow(scr, mousePos, children[i]);
733 		}
734 	}
735 
736 	/* No child window under drag pointer */
737 	return None;
738 }
739 
storeDestinationProtocolVersion(WMDraggingInfo * info)740 static void storeDestinationProtocolVersion(WMDraggingInfo * info)
741 {
742 	Atom type;
743 	int format;
744 	unsigned long count, remain;
745 	unsigned char *winXdndVersion;
746 	WMScreen *scr = W_VIEW_SCREEN(XDND_SOURCE_VIEW(info));
747 
748 	wassertr(XDND_DEST_WIN(info) != None);
749 
750 	if (XGetWindowProperty(scr->display, XDND_DEST_WIN(info),
751 			       scr->xdndAwareAtom,
752 			       0, 1, False, XA_ATOM, &type, &format,
753 			       &count, &remain, &winXdndVersion) == Success) {
754 		XDND_DEST_VERSION(info) = *winXdndVersion;
755 		XFree(winXdndVersion);
756 	} else {
757 		XDND_DEST_VERSION(info) = 0;
758 		wwarning(_("could not get XDND version for target of drop"));
759 	}
760 }
761 
initMotionProcess(WMView * view,WMDraggingInfo * info,XEvent * event,WMPoint * startLocation)762 static void initMotionProcess(WMView * view, WMDraggingInfo * info, XEvent * event, WMPoint * startLocation)
763 {
764 	WMScreen *scr = W_VIEW_SCREEN(view);
765 
766 	/* take ownership of XdndSelection */
767 	XDND_SELECTION_PROCS(info) = (WMSelectionProcs *) wmalloc(sizeof(WMSelectionProcs));
768 	XDND_SELECTION_PROCS(info)->convertSelection = convertSelection;
769 	XDND_SELECTION_PROCS(info)->selectionLost = selectionLost;
770 	XDND_SELECTION_PROCS(info)->selectionDone = selectionDone;
771 	XDND_TIMESTAMP(info) = event->xmotion.time;
772 
773 	if (!WMCreateSelectionHandler(view, scr->xdndSelectionAtom, CurrentTime, XDND_SELECTION_PROCS(info), NULL)) {
774 		wwarning(_("could not get ownership of XDND selection"));
775 		return;
776 	}
777 
778 	registerDropTypes(scr, view, info);
779 
780 	if (XDND_SOURCE_ACTION(info) == W_VIEW_SCREEN(view)->xdndActionAsk)
781 		registerSupportedOperations(view);
782 
783 	if (view->dragSourceProcs->beganDrag != NULL) {
784 		view->dragSourceProcs->beganDrag(view, startLocation);
785 	}
786 }
787 
processMotion(WMDraggingInfo * info,WMPoint * mousePos)788 static void processMotion(WMDraggingInfo * info, WMPoint * mousePos)
789 {
790 	Window newDestination = findDestination(info, mousePos);
791 
792 	W_DragSourceStopTimer();
793 
794 	if (newDestination != XDND_DEST_WIN(info)) {
795 		recolorCursor(info, False);
796 
797 		if (XDND_DEST_WIN(info) != None) {
798 			/* leaving a xdnd window */
799 			sendLeaveMessage(info);
800 		}
801 
802 		XDND_DEST_WIN(info) = newDestination;
803 		XDND_DEST_ACTION(info) = None;
804 		XDND_NO_POS_ZONE(info).size.width = 0;
805 		XDND_NO_POS_ZONE(info).size.height = 0;
806 
807 		if (newDestination != None) {
808 			/* entering a xdnd window */
809 			XDND_SOURCE_STATE(info) = idleState;
810 			storeDestinationProtocolVersion(info);
811 
812 			if (!sendEnterMessage(info)) {
813 				XDND_DEST_WIN(info) = None;
814 				return;
815 			}
816 
817 			W_DragSourceStartTimer(info);
818 		} else {
819 			XDND_SOURCE_STATE(info) = NULL;
820 		}
821 	} else {
822 		if (XDND_DEST_WIN(info) != None) {
823 			if (!sendPositionMessage(info, mousePos)) {
824 				XDND_DEST_WIN(info) = None;
825 				return;
826 			}
827 
828 			W_DragSourceStartTimer(info);
829 		}
830 	}
831 }
832 
processButtonRelease(WMDraggingInfo * info)833 static Bool processButtonRelease(WMDraggingInfo * info)
834 {
835 	if (XDND_SOURCE_STATE(info) == dropAllowedState) {
836 		/* begin drop */
837 		W_DragSourceStopTimer();
838 
839 		if (!sendDropMessage(info))
840 			return False;
841 
842 		W_DragSourceStartTimer(info);
843 		return True;
844 	} else {
845 		if (XDND_DEST_WIN(info) != None)
846 			sendLeaveMessage(info);
847 
848 		W_DragSourceStopTimer();
849 		return False;
850 	}
851 }
852 
WMIsDraggingFromView(WMView * view)853 Bool WMIsDraggingFromView(WMView * view)
854 {
855 	WMDraggingInfo *info = &W_VIEW_SCREEN(view)->dragInfo;
856 
857 	return (XDND_SOURCE_INFO(info) != NULL && XDND_SOURCE_STATE(info) != finishDropState);
858 	/* return W_VIEW_SCREEN(view)->dragInfo.sourceInfo != NULL; */
859 }
860 
WMDragImageFromView(WMView * view,XEvent * event)861 void WMDragImageFromView(WMView * view, XEvent * event)
862 {
863 	WMDraggingInfo *info = &W_VIEW_SCREEN(view)->dragInfo;
864 	WMPoint mouseLocation;
865 
866 	switch (event->type) {
867 	case ButtonPress:
868 		if (event->xbutton.button == Button1) {
869 			XEvent nextEvent;
870 
871 			XPeekEvent(event->xbutton.display, &nextEvent);
872 
873 			/* Initialize only if a drag really begins (avoid clicks) */
874 			if (nextEvent.type == MotionNotify) {
875 				initSourceDragInfo(view, info);
876 			}
877 		}
878 
879 		break;
880 
881 	case ButtonRelease:
882 		if (WMIsDraggingFromView(view)) {
883 			Bool dropBegan = processButtonRelease(info);
884 
885 			recolorCursor(info, False);
886 			if (dropBegan) {
887 				endDragImage(info, False);
888 				XDND_SOURCE_STATE(info) = finishDropState;
889 			} else {
890 				/* drop failed */
891 				endDragImage(info, True);
892 				endDragProcess(info, False);
893 			}
894 		}
895 		break;
896 
897 	case MotionNotify:
898 		if (WMIsDraggingFromView(view)) {
899 			mouseLocation = wmkpoint(event->xmotion.x_root, event->xmotion.y_root);
900 
901 			if (abs(XDND_DRAG_ICON_POS(info).x - mouseLocation.x) >=
902 			    MIN_X_MOVE_OFFSET
903 			    || abs(XDND_DRAG_ICON_POS(info).y - mouseLocation.y) >= MIN_Y_MOVE_OFFSET) {
904 				if (XDND_DRAG_ICON(info) == None) {
905 					initMotionProcess(view, info, event, &mouseLocation);
906 					startDragImage(view, info, event);
907 				} else {
908 					XDND_DRAG_ICON_POS(info).x = mouseLocation.x - XDND_MOUSE_OFFSET(info).x;
909 					XDND_DRAG_ICON_POS(info).y = mouseLocation.y - XDND_MOUSE_OFFSET(info).y;
910 
911 					refreshDragImage(view, info);
912 					processMotion(info, &mouseLocation);
913 				}
914 			}
915 		}
916 		break;
917 	}
918 }
919 
920 /* Minimal mouse events handler: no right or double-click detection,
921  only drag is supported */
dragImageHandler(XEvent * event,void * cdata)922 static void dragImageHandler(XEvent * event, void *cdata)
923 {
924 	WMView *view = (WMView *) cdata;
925 
926 	WMDragImageFromView(view, event);
927 }
928 
929 /* ----- source states ----- */
930 
931 #ifdef XDND_DEBUG
traceStatusMsg(Display * dpy,XClientMessageEvent * statusEvent)932 static void traceStatusMsg(Display * dpy, XClientMessageEvent * statusEvent)
933 {
934 	printf("Xdnd status message:\n");
935 
936 	if (statusEvent->data.l[1] & 0x2UL)
937 		printf("\tsend position on every move\n");
938 	else {
939 		int x, y, w, h;
940 		x = statusEvent->data.l[2] >> 16;
941 		y = statusEvent->data.l[2] & 0xFFFFL;
942 		w = statusEvent->data.l[3] >> 16;
943 		h = statusEvent->data.l[3] & 0xFFFFL;
944 
945 		printf("\tsend position out of ((%d,%d) , (%d,%d))\n", x, y, x + w, y + h);
946 	}
947 
948 	if (statusEvent->data.l[1] & 0x1L)
949 		printf("\tallowed action: %s\n", XGetAtomName(dpy, statusEvent->data.l[4]));
950 	else
951 		printf("\tno action allowed\n");
952 }
953 #endif
954 
storeDropAction(WMDraggingInfo * info,Atom destAction)955 static void storeDropAction(WMDraggingInfo * info, Atom destAction)
956 {
957 	WMView *sourceView = XDND_SOURCE_VIEW(info);
958 	WMScreen *scr = W_VIEW_SCREEN(sourceView);
959 
960 	if (sourceView->dragSourceProcs->acceptDropOperation != NULL) {
961 		if (sourceView->dragSourceProcs->acceptDropOperation(sourceView,
962 								     W_ActionToOperation(scr, destAction)))
963 			XDND_DEST_ACTION(info) = destAction;
964 		else
965 			XDND_DEST_ACTION(info) = None;
966 	} else {
967 		XDND_DEST_ACTION(info) = destAction;
968 	}
969 }
970 
storeStatusMessageInfos(WMDraggingInfo * info,XClientMessageEvent * statusEvent)971 static void storeStatusMessageInfos(WMDraggingInfo * info, XClientMessageEvent * statusEvent)
972 {
973 	WMRect *noPosZone = &(XDND_NO_POS_ZONE(info));
974 
975 #ifdef XDND_DEBUG
976 
977 	traceStatusMsg(sourceScreen(info)->display, statusEvent);
978 #endif
979 
980 	if (statusEvent->data.l[1] & 0x2UL) {
981 		/* bit 1 set: destination wants position messages on every move */
982 		noPosZone->size.width = 0;
983 		noPosZone->size.height = 0;
984 	} else {
985 		/* don't send another position message while in given rectangle */
986 		noPosZone->pos.x = statusEvent->data.l[2] >> 16;
987 		noPosZone->pos.y = statusEvent->data.l[2] & 0xFFFFL;
988 		noPosZone->size.width = statusEvent->data.l[3] >> 16;
989 		noPosZone->size.height = statusEvent->data.l[3] & 0xFFFFL;
990 	}
991 
992 	if ((statusEvent->data.l[1] & 0x1L) || statusEvent->data.l[4] != None) {
993 		/* destination accept drop */
994 		storeDropAction(info, statusEvent->data.l[4]);
995 	} else {
996 		XDND_DEST_ACTION(info) = None;
997 	}
998 }
999 
idleState(WMView * view,XClientMessageEvent * event,WMDraggingInfo * info)1000 static void *idleState(WMView * view, XClientMessageEvent * event, WMDraggingInfo * info)
1001 {
1002 	WMScreen *scr;
1003 	Atom destMsg = event->message_type;
1004 
1005 	scr = W_VIEW_SCREEN(view);
1006 
1007 	if (destMsg == scr->xdndStatusAtom) {
1008 		storeStatusMessageInfos(info, event);
1009 
1010 		if (XDND_DEST_ACTION(info) != None) {
1011 			recolorCursor(info, True);
1012 			W_DragSourceStartTimer(info);
1013 			return dropAllowedState;
1014 		} else {
1015 			/* drop denied */
1016 			recolorCursor(info, False);
1017 			return idleState;
1018 		}
1019 	}
1020 
1021 	if (destMsg == scr->xdndFinishedAtom) {
1022 		wwarning("received xdndFinishedAtom before drop began");
1023 	}
1024 
1025 	W_DragSourceStartTimer(info);
1026 	return idleState;
1027 }
1028 
dropAllowedState(WMView * view,XClientMessageEvent * event,WMDraggingInfo * info)1029 static void *dropAllowedState(WMView * view, XClientMessageEvent * event, WMDraggingInfo * info)
1030 {
1031 	WMScreen *scr = W_VIEW_SCREEN(view);
1032 	Atom destMsg = event->message_type;
1033 
1034 	if (destMsg == scr->xdndStatusAtom) {
1035 		storeStatusMessageInfos(info, event);
1036 
1037 		if (XDND_DEST_ACTION(info) == None) {
1038 			/* drop denied */
1039 			recolorCursor(info, False);
1040 			return idleState;
1041 		}
1042 	}
1043 
1044 	W_DragSourceStartTimer(info);
1045 	return dropAllowedState;
1046 }
1047 
finishDropState(WMView * view,XClientMessageEvent * event,WMDraggingInfo * info)1048 static void *finishDropState(WMView * view, XClientMessageEvent * event, WMDraggingInfo * info)
1049 {
1050 	WMScreen *scr = W_VIEW_SCREEN(view);
1051 	Atom destMsg = event->message_type;
1052 
1053 	if (destMsg == scr->xdndFinishedAtom) {
1054 		endDragProcess(info, True);
1055 		return NULL;
1056 	}
1057 
1058 	W_DragSourceStartTimer(info);
1059 	return finishDropState;
1060 }
1061 
1062 /* ----- end of source states ----- */
1063 
1064 /* ----- source timer ----- */
dragSourceResponseTimeOut(void * source)1065 static void dragSourceResponseTimeOut(void *source)
1066 {
1067 	WMView *view = (WMView *) source;
1068 	WMDraggingInfo *info = &(W_VIEW_SCREEN(view)->dragInfo);
1069 
1070 	wwarning(_("delay for drag destination response expired"));
1071 	sendLeaveMessage(info);
1072 
1073 	recolorCursor(info, False);
1074 	if (XDND_SOURCE_STATE(info) == finishDropState) {
1075 		/* drop failed */
1076 		endDragImage(info, True);
1077 		endDragProcess(info, False);
1078 	} else {
1079 		XDND_SOURCE_STATE(info) = idleState;
1080 	}
1081 }
1082 
W_DragSourceStopTimer()1083 void W_DragSourceStopTimer()
1084 {
1085 	if (dndSourceTimer != NULL) {
1086 		WMDeleteTimerHandler(dndSourceTimer);
1087 		dndSourceTimer = NULL;
1088 	}
1089 }
1090 
W_DragSourceStartTimer(WMDraggingInfo * info)1091 void W_DragSourceStartTimer(WMDraggingInfo * info)
1092 {
1093 	W_DragSourceStopTimer();
1094 
1095 	dndSourceTimer = WMAddTimerHandler(XDND_DESTINATION_RESPONSE_MAX_DELAY,
1096 					   dragSourceResponseTimeOut, XDND_SOURCE_VIEW(info));
1097 }
1098 
1099 /* ----- End of Destination timer ----- */
1100 
W_DragSourceStateHandler(WMDraggingInfo * info,XClientMessageEvent * event)1101 void W_DragSourceStateHandler(WMDraggingInfo * info, XClientMessageEvent * event)
1102 {
1103 	WMView *view;
1104 	W_DndState *newState;
1105 
1106 	if (XDND_SOURCE_VIEW_STORED(info)) {
1107 		if (XDND_SOURCE_STATE(info) != NULL) {
1108 			view = XDND_SOURCE_VIEW(info);
1109 #ifdef XDND_DEBUG
1110 
1111 			printf("current source state: %s\n", stateName(XDND_SOURCE_STATE(info)));
1112 #endif
1113 
1114 			newState = (W_DndState *) XDND_SOURCE_STATE(info) (view, event, info);
1115 
1116 #ifdef XDND_DEBUG
1117 
1118 			printf("new source state: %s\n", stateName(newState));
1119 #endif
1120 
1121 			if (newState != NULL)
1122 				XDND_SOURCE_STATE(info) = newState;
1123 			/* else drop finished, and info has been flushed */
1124 		}
1125 
1126 	} else {
1127 		wwarning("received DnD message without having a target");
1128 	}
1129 }
1130 
WMSetViewDragImage(WMView * view,WMPixmap * dragImage)1131 void WMSetViewDragImage(WMView * view, WMPixmap * dragImage)
1132 {
1133 	if (view->dragImage != NULL)
1134 		WMReleasePixmap(view->dragImage);
1135 
1136 	view->dragImage = WMRetainPixmap(dragImage);
1137 }
1138 
WMReleaseViewDragImage(WMView * view)1139 void WMReleaseViewDragImage(WMView * view)
1140 {
1141 	if (view->dragImage != NULL)
1142 		WMReleasePixmap(view->dragImage);
1143 }
1144 
1145 /* Create a drag handler, associating drag event masks with dragEventProc */
WMCreateDragHandler(WMView * view,WMEventProc * dragEventProc,void * clientData)1146 void WMCreateDragHandler(WMView * view, WMEventProc * dragEventProc, void *clientData)
1147 {
1148 	WMCreateEventHandler(view,
1149 			     ButtonPressMask | ButtonReleaseMask | Button1MotionMask, dragEventProc, clientData);
1150 }
1151 
WMDeleteDragHandler(WMView * view,WMEventProc * dragEventProc,void * clientData)1152 void WMDeleteDragHandler(WMView * view, WMEventProc * dragEventProc, void *clientData)
1153 {
1154 	WMDeleteEventHandler(view,
1155 			     ButtonPressMask | ButtonReleaseMask | Button1MotionMask, dragEventProc, clientData);
1156 }
1157 
1158 /* set default drag handler for view */
WMSetViewDraggable(WMView * view,WMDragSourceProcs * dragSourceProcs,WMPixmap * dragImage)1159 void WMSetViewDraggable(WMView * view, WMDragSourceProcs * dragSourceProcs, WMPixmap * dragImage)
1160 {
1161 	wassertr(dragImage != NULL);
1162 	view->dragImage = WMRetainPixmap(dragImage);
1163 
1164 	WMSetViewDragSourceProcs(view, dragSourceProcs);
1165 
1166 	WMCreateDragHandler(view, dragImageHandler, view);
1167 }
1168 
WMUnsetViewDraggable(WMView * view)1169 void WMUnsetViewDraggable(WMView * view)
1170 {
1171 	if (view->dragSourceProcs) {
1172 		wfree(view->dragSourceProcs);
1173 		view->dragSourceProcs = NULL;
1174 	}
1175 
1176 	WMReleaseViewDragImage(view);
1177 
1178 	WMDeleteDragHandler(view, dragImageHandler, view);
1179 }
1180