1 /*
2  * Copyright (C) 2000-2019 the xine project
3  *
4  * This file is part of xine, a unix video player.
5  *
6  * xine is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * xine is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA
19  *
20  *
21  * Thanks to Paul Sheer for his nice xdnd implementation in cooledit.
22  */
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
26 
27 #include <stdio.h>
28 #include <sys/types.h>
29 #include <X11/Xatom.h>
30 #include <string.h>
31 #include <signal.h>
32 
33 #include "_xitk.h"
34 
35 #define XDND_VERSION 3
36 
37 #undef DEBUG_DND
38 
39 /*
40  * PRIVATES
41  */
42 
_is_atom_match(xitk_dnd_t * xdnd,Atom ** atom)43 static int _is_atom_match(xitk_dnd_t *xdnd, Atom **atom) {
44   int i, j;
45 
46   for(i = 0; (*atom)[i] != 0; i++) {
47     for(j = 0; j < MAX_SUPPORTED_TYPE; j++) {
48       if((*atom)[i] == xdnd->supported[j])
49 	return i;
50     }
51   }
52 
53   return -1;
54 }
55 
56 /*
57  * Send XdndFinished to 'window' from target 'from'
58  */
_dnd_send_finished(xitk_dnd_t * xdnd,Window window,Window from)59 static void _dnd_send_finished (xitk_dnd_t *xdnd, Window window, Window from) {
60   XEvent xevent;
61 
62   if((xdnd == NULL) || (window == None) || (from == None))
63     return;
64 
65   memset(&xevent, 0, sizeof(xevent));
66   xevent.xany.type                  = ClientMessage;
67   xevent.xany.display               = xdnd->xitk->display;
68   xevent.xclient.window             = window;
69   xevent.xclient.message_type       = xdnd->_XA_XdndFinished;
70   xevent.xclient.format             = 32;
71   XDND_FINISHED_TARGET_WIN(&xevent) = from;
72 
73   XLOCK (xdnd->xitk->x_lock_display, xdnd->xitk->display);
74   XSendEvent (xdnd->xitk->display, window, 0, 0, &xevent);
75   XUNLOCK (xdnd->xitk->x_unlock_display, xdnd->xitk->display);
76 }
77 
78 /*
79  * WARNING: X unlocked function
80  */
_dnd_paste_prop_internal(xitk_dnd_t * xdnd,Window from,Window insert,Atom prop,int delete_prop)81 static int _dnd_paste_prop_internal(xitk_dnd_t *xdnd, Window from,
82                     Window insert, Atom prop, int delete_prop) {
83   long           nread;
84   unsigned long  nitems;
85   unsigned long  bytes_after;
86 
87   nread = 0;
88 
89   do {
90     Atom      actual_type;
91     int       actual_fmt;
92     char *buf = 0;
93 
94     if(XGetWindowProperty (xdnd->xitk->display, insert, prop,
95 			  nread / (sizeof(unsigned char *)), 65536, delete_prop, AnyPropertyType,
96               &actual_type, &actual_fmt, &nitems, &bytes_after, (unsigned char **)&buf) != Success) {
97       /* Note that per XGetWindowProperty man page, buf will always be NULL-terminated */
98       if(buf)
99 	XFree(buf);
100       return 1;
101     }
102 
103     nread += nitems;
104 
105     /* Okay, got something, handle */
106       if(strlen(buf)) {
107 	char *p, *pbuf;
108 	int   plen;
109 
110 	pbuf = buf;
111 	/* Extract all data, '\n' separated */
112 	while((p = strsep(&pbuf, "\n")) != NULL) {
113 
114 	  plen = strlen(p) - 1;
115 
116 	  /* Cleanup end of string */
117 	  while((plen >= 0) && ((p[plen] == 10) || (p[plen] == 12) || (p[plen] == 13)))
118 	    p[plen--] = '\0';
119 
120 	  if(strlen(p)) {
121 #ifdef DEBUG_DND
122 	    printf("GOT '%s'\n", p);
123 #endif
124 	    if(xdnd->callback) {
125 	      xdnd->callback(p);
126 	    }
127 	  }
128 	}
129       }
130 
131     XFree(buf);
132   } while(bytes_after);
133 
134   if(!nread)
135     return 1;
136 
137   return 0;
138 }
139 
140 /*
141  * Getting selections, using INCR if possible.
142  */
_dnd_get_selection(xitk_dnd_t * xdnd,Window from,Atom prop,Window insert)143 static void _dnd_get_selection (xitk_dnd_t *xdnd, Window from, Atom prop, Window insert) {
144   struct timeval  tv, tv_start;
145   unsigned long   bytes_after;
146   Atom            actual_type;
147   int             actual_fmt;
148   unsigned long   nitems;
149   unsigned char  *s = NULL;
150 
151   if((xdnd == NULL) || (prop == None))
152     return;
153 
154   XLOCK (xdnd->xitk->x_lock_display, xdnd->xitk->display);
155   if(XGetWindowProperty (xdnd->xitk->display, insert, prop, 0, 8, False, AnyPropertyType,
156 			&actual_type, &actual_fmt, &nitems, &bytes_after, &s) != Success) {
157     XFree(s);
158     XUNLOCK (xdnd->xitk->x_unlock_display, xdnd->xitk->display);
159     return;
160   }
161 
162   XFree(s);
163 
164   if(actual_type != XInternAtom (xdnd->xitk->display, "INCR", False)) {
165 
166     (void) _dnd_paste_prop_internal(xdnd, from, insert, prop, True);
167     XUNLOCK (xdnd->xitk->x_unlock_display, xdnd->xitk->display);
168     return;
169   }
170 
171   XDeleteProperty (xdnd->xitk->display, insert, prop);
172   gettimeofday(&tv_start, 0);
173 
174   for(;;) {
175     long    t;
176     fd_set  r;
177     XEvent  xe;
178 
179     if(XCheckMaskEvent (xdnd->xitk->display, PropertyChangeMask, &xe)) {
180       if((xe.type == PropertyNotify) && (xe.xproperty.state == PropertyNewValue)) {
181 
182 	/* time between arrivals of data */
183 	gettimeofday (&tv_start, 0);
184 
185 	if(_dnd_paste_prop_internal(xdnd, from, insert, prop, True))
186 	  break;
187       }
188     }
189     else {
190       tv.tv_sec  = 0;
191       tv.tv_usec = 10000;
192       FD_ZERO(&r);
193       FD_SET (ConnectionNumber (xdnd->xitk->display), &r);
194       select (ConnectionNumber (xdnd->xitk->display) + 1, &r, 0, 0, &tv);
195 
196       if (FD_ISSET (ConnectionNumber (xdnd->xitk->display), &r))
197 	continue;
198     }
199     gettimeofday(&tv, 0);
200     t = (tv.tv_sec - tv_start.tv_sec) * 1000000L + (tv.tv_usec - tv_start.tv_usec);
201 
202     /* No data for five seconds, so quit */
203     if(t > 5000000L) {
204       XUNLOCK (xdnd->xitk->x_unlock_display, xdnd->xitk->display);
205       return;
206     }
207   }
208 
209   XUNLOCK (xdnd->xitk->x_unlock_display, xdnd->xitk->display);
210 }
211 
212 /*
213  * Get list of type from window (more than 3).
214  */
_dnd_get_type_list(xitk_dnd_t * xdnd,Window window,Atom ** typelist)215 static void _dnd_get_type_list (xitk_dnd_t *xdnd, Window window, Atom **typelist) {
216   Atom            type, *a;
217   int             format;
218   unsigned long   count, remaining, i;
219   unsigned char  *data = NULL;
220 
221   *typelist = 0;
222 
223   if((xdnd == NULL) || (window == None))
224     return;
225 
226   XLOCK (xdnd->xitk->x_lock_display, xdnd->xitk->display);
227   XGetWindowProperty (xdnd->xitk->display, window, xdnd->_XA_XdndTypeList, 0, 0x8000000L,
228 		     False, XA_ATOM, &type, &format, &count, &remaining, &data);
229   XUNLOCK (xdnd->xitk->x_unlock_display, xdnd->xitk->display);
230 
231   if((type != XA_ATOM) || (format != 32) || (count == 0) || (!data)) {
232 
233     if(data) {
234       XLOCK (xdnd->xitk->x_lock_display, xdnd->xitk->display);
235       XFree(data);
236       XUNLOCK (xdnd->xitk->x_unlock_display, xdnd->xitk->display);
237     }
238 
239     XITK_WARNING("%s@%d: XGetWindowProperty failed in xdnd_get_type_list - "
240 		 "dnd->_XA_XdndTypeList = %ld\n", __FILE__, __LINE__, xdnd->_XA_XdndTypeList);
241     return;
242   }
243 
244   *typelist = (Atom *) calloc((count + 1), sizeof(Atom));
245   a = (Atom *) data;
246 
247   for(i = 0; i < count; i++)
248     (*typelist)[i] = a[i];
249 
250   (*typelist)[count] = 0;
251 
252   XLOCK (xdnd->xitk->x_lock_display, xdnd->xitk->display);
253   XFree(data);
254   XUNLOCK (xdnd->xitk->x_unlock_display, xdnd->xitk->display);
255 }
256 
257 /*
258  * Get list of type from window (3).
259  */
_dnd_get_three_types(XEvent * xevent,Atom ** typelist)260 static void _dnd_get_three_types (XEvent * xevent, Atom **typelist) {
261   int i;
262 
263   *typelist = (Atom *) xitk_xmalloc((XDND_THREE + 1) * sizeof(Atom));
264 
265   for(i = 0; i < XDND_THREE; i++)
266     (*typelist)[i] = XDND_ENTER_TYPE(xevent, i);
267   /* although (*typelist)[1] or (*typelist)[2] may also be set to nill */
268   (*typelist)[XDND_THREE] = 0;
269 }
270 
271 /*
272  * END OF PRIVATES
273  */
274 
275 /*
276  * Initialize Atoms, ...
277  */
xitk_init_dnd(xitk_t * xitk,xitk_dnd_t * xdnd)278 void xitk_init_dnd (xitk_t *xitk, xitk_dnd_t *xdnd) {
279 
280   enum {
281     XdndAware,
282     XdndEnter,
283     XdndLeave,
284     XdndDrop,
285     XdndPosition,
286     XdndStatus,
287     XdndSelection,
288     XdndFinished,
289     XdndTypeList,
290     WM_DELETE_WINDOW,
291     XiTkXselWindowProperty,
292     _XA_ATOMS_COUNT
293   };
294 
295   static const char *const prop_names[_XA_ATOMS_COUNT] = {
296     "XdndAware", /* _XA_XdndAware */
297     "XdndEnter", /* _XA_XdndEnter */
298     "XdndLeave", /* _XA_XdndLeave */
299     "XdndDrop", /* _XA_XdndDrop */
300     "XdndPosition", /* _XA_XdndPosition */
301     "XdndStatus", /* _XA_XdndStatus */
302     "XdndSelection", /* _XA_XdndSelection */
303     "XdndFinished", /* _XA_XdndFinished */
304     "XdndTypeList", /* _XA_XdndTypeList */
305     "WM_DELETE_WINDOW", /* _XA_WM_DELETE_WINDOW */
306     "XiTKXSelWindowProperty" /* _XA_XITK_PROTOCOL_ATOM */
307   };
308 
309   static const char *const mime_names[MAX_SUPPORTED_TYPE] = {
310     "text/uri-list", /* supported[0] */
311     "text/plain"  /* supported[1] */
312   };
313 
314   Atom props[_XA_ATOMS_COUNT];
315 
316   xdnd->xitk = xitk;
317 
318   XLOCK (xdnd->xitk->x_lock_display, xdnd->xitk->display);
319 
320   XInternAtoms (xdnd->xitk->display, (char**)prop_names, _XA_ATOMS_COUNT, False, props);
321   XInternAtoms (xdnd->xitk->display, (char**)mime_names, MAX_SUPPORTED_TYPE, False, xdnd->supported);
322 
323   xdnd->_XA_XdndAware             = props[XdndAware];
324   xdnd->_XA_XdndEnter             = props[XdndEnter];
325   xdnd->_XA_XdndLeave             = props[XdndLeave];
326   xdnd->_XA_XdndDrop              = props[XdndDrop];
327   xdnd->_XA_XdndPosition          = props[XdndPosition];
328   xdnd->_XA_XdndStatus            = props[XdndStatus];
329   xdnd->_XA_XdndSelection         = props[XdndSelection];
330   xdnd->_XA_XdndFinished          = props[XdndFinished];
331   xdnd->_XA_XdndTypeList          = props[XdndTypeList];
332   xdnd->_XA_WM_DELETE_WINDOW      = props[WM_DELETE_WINDOW];
333   xdnd->_XA_XITK_PROTOCOL_ATOM    = props[XiTkXselWindowProperty];
334 
335   XUNLOCK (xdnd->xitk->x_unlock_display, xdnd->xitk->display);
336 
337   xdnd->version                   = XDND_VERSION;
338   xdnd->callback                  = NULL;
339   xdnd->dragger_typelist          = NULL;
340   xdnd->desired                   = 0;
341 
342 }
343 
344 /*
345  * Add/Replace the XdndAware property of given window.
346  */
xitk_make_window_dnd_aware(xitk_dnd_t * xdnd,Window window)347 int xitk_make_window_dnd_aware(xitk_dnd_t *xdnd, Window window) {
348   Status        status;
349 
350   if (!xdnd->xitk->display)
351     return 0;
352 
353   XLOCK (xdnd->xitk->x_lock_display, xdnd->xitk->display);
354   status = XChangeProperty (xdnd->xitk->display, window, xdnd->_XA_XdndAware, XA_ATOM,
355 			   32, PropModeReplace, (unsigned char *)&xdnd->version, 1);
356   XUNLOCK (xdnd->xitk->x_unlock_display, xdnd->xitk->display);
357 
358   if((status == BadAlloc) || (status == BadAtom) ||
359      (status == BadMatch) || (status == BadValue) || (status == BadWindow)) {
360     XITK_WARNING("XChangeProperty() failed.\n");
361     return 0;
362   }
363 
364   XLOCK (xdnd->xitk->x_lock_display, xdnd->xitk->display);
365   XChangeProperty (xdnd->xitk->display, window, xdnd->_XA_XdndTypeList, XA_ATOM, 32,
366 		  PropModeAppend, (unsigned char *)&xdnd->supported, 1);
367   XUNLOCK (xdnd->xitk->x_unlock_display, xdnd->xitk->display);
368 
369   if((status == BadAlloc) || (status == BadAtom) ||
370      (status == BadMatch) || (status == BadValue) || (status == BadWindow)) {
371     XITK_WARNING("XChangeProperty() failed.\n");
372     return 0;
373   }
374 
375   xdnd->win = window;
376 
377   return 1;
378 }
379 
380 /*
381  * Set/Unset callback
382  */
xitk_set_dnd_callback(xitk_dnd_t * xdnd,xitk_dnd_callback_t cb)383 void xitk_set_dnd_callback(xitk_dnd_t *xdnd, xitk_dnd_callback_t cb) {
384 
385   if((xdnd == NULL) || (cb == NULL))
386     return;
387 
388   xdnd->callback = cb;
389 }
xitk_unset_dnd_callback(xitk_dnd_t * xdnd)390 void xitk_unset_dnd_callback(xitk_dnd_t *xdnd) {
391 
392   if(xdnd == NULL)
393     return;
394 
395   xdnd->callback = NULL;
396 }
397 
398 /*
399  * Handle ClientMessage/SelectionNotify events.
400  */
xitk_process_client_dnd_message(xitk_dnd_t * xdnd,XEvent * event)401 int xitk_process_client_dnd_message(xitk_dnd_t *xdnd, XEvent *event) {
402   int retval = 0;
403 
404   if((xdnd == NULL) || (event == NULL))
405     return 0;
406 
407   if(event->type == ClientMessage) {
408 
409     if((event->xclient.format == 32) &&
410        (XDND_ENTER_SOURCE_WIN(event) == xdnd->_XA_WM_DELETE_WINDOW)) {
411       XEvent xevent;
412 
413 #ifdef DEBUG_DND
414       printf("ClientMessage KILL\n");
415 #endif
416 
417       memset(&xevent, 0, sizeof(xevent));
418       xevent.xany.type                 = DestroyNotify;
419       xevent.xany.display              = xdnd->xitk->display;
420       xevent.xdestroywindow.type       = DestroyNotify;
421       xevent.xdestroywindow.send_event = True;
422       xevent.xdestroywindow.display    = xdnd->xitk->display;
423       xevent.xdestroywindow.event      = xdnd->win;
424       xevent.xdestroywindow.window     = xdnd->win;
425 
426       XLOCK (xdnd->xitk->x_lock_display, xdnd->xitk->display);
427       XSendEvent (xdnd->xitk->display, xdnd->win, True, 0L, &xevent);
428       XUNLOCK (xdnd->xitk->x_unlock_display, xdnd->xitk->display);
429 
430       retval = 1;
431     }
432     else if(event->xclient.message_type == xdnd->_XA_XdndEnter) {
433 
434 #ifdef DEBUG_DND
435       printf("XdndEnter\n");
436 #endif
437 
438       if(XDND_ENTER_VERSION(event) < 3) {
439 	return 0;
440       }
441 
442       xdnd->dragger_window   = XDND_ENTER_SOURCE_WIN(event);
443       xdnd->dropper_toplevel = event->xany.window;
444       xdnd->dropper_window   = None;
445 
446       XITK_FREE(xdnd->dragger_typelist);
447 
448       if(XDND_ENTER_THREE_TYPES(event)) {
449 #ifdef DEBUG_DND
450 	printf("Three types only\n");
451 #endif
452 	_dnd_get_three_types(event, &xdnd->dragger_typelist);
453       }
454       else {
455 #ifdef DEBUG_DND
456 	printf("More than three types - getting list\n");
457 #endif
458 	_dnd_get_type_list(xdnd, xdnd->dragger_window, &xdnd->dragger_typelist);
459       }
460 
461       if(xdnd->dragger_typelist) {
462 	int atom_match;
463 #ifdef DEBUG_DND
464 	{
465 	  int   i;
466 	  for(i = 0; xdnd->dragger_typelist[i] != 0; i++) {
467             XLOCK (xdnd->xitk->x_lock_display, xdnd->xitk->display);
468 	    printf("%d: '%s' ", i, XGetAtomName (xdnd->xitk->display, xdnd->dragger_typelist[i]));
469             XUNLOCK (xdnd->xitk->x_unlock_display, xdnd->xitk->display);
470 	    printf("\n");
471 	  }
472 	}
473 #endif
474 
475 	if((atom_match = _is_atom_match(xdnd, &xdnd->dragger_typelist)) >= 0) {
476 	  xdnd->desired = xdnd->dragger_typelist[atom_match];
477 	}
478 
479       }
480       else {
481 	XITK_WARNING("%s@%d: xdnd->dragger_typelist is zero length!\n", __FILE__, __LINE__);
482 	/* Probably doesn't work */
483 	if ((event->xclient.data.l[1] & 1) == 0) {
484 	  xdnd->desired = (Atom) event->xclient.data.l[1];
485 	}
486       }
487       retval = 1;
488     }
489     else if(event->xclient.message_type == xdnd->_XA_XdndLeave) {
490 #ifdef DEBUG_DND
491       printf("XdndLeave\n");
492 #endif
493 
494       if((event->xany.window == xdnd->dropper_toplevel) && (xdnd->dropper_window != None))
495 	event->xany.window = xdnd->dropper_window;
496 
497       if(xdnd->dragger_window == XDND_LEAVE_SOURCE_WIN(event)) {
498 	XITK_FREE(xdnd->dragger_typelist);
499 	xdnd->dropper_toplevel = xdnd->dropper_window = None;
500 	xdnd->desired = 0;
501       }
502 
503       retval = 1;
504     }
505     else if(event->xclient.message_type == xdnd->_XA_XdndDrop) {
506       Window  win;
507 
508 #ifdef DEBUG_DND
509       printf("XdndDrop\n");
510 #endif
511 
512       if(xdnd->desired != 0) {
513 
514 	if((event->xany.window == xdnd->dropper_toplevel) && (xdnd->dropper_window != None))
515 	  event->xany.window = xdnd->dropper_window;
516 
517 	if(xdnd->dragger_window == XDND_DROP_SOURCE_WIN(event)) {
518 
519 	  xdnd->time = XDND_DROP_TIME (event);
520 
521           XLOCK (xdnd->xitk->x_lock_display, xdnd->xitk->display);
522 	  if(!(win = XGetSelectionOwner (xdnd->xitk->display, xdnd->_XA_XdndSelection))) {
523 	    XITK_WARNING("%s@%d: XGetSelectionOwner() failed.\n", __FILE__, __LINE__);
524             XUNLOCK (xdnd->xitk->x_unlock_display, xdnd->xitk->display);
525 	    return 0;
526 	  }
527 
528 	  XConvertSelection (xdnd->xitk->display, xdnd->_XA_XdndSelection, xdnd->desired,
529 			    xdnd->_XA_XITK_PROTOCOL_ATOM, xdnd->dropper_window, xdnd->time);
530           XUNLOCK (xdnd->xitk->x_unlock_display, xdnd->xitk->display);
531 	}
532       }
533 
534       _dnd_send_finished(xdnd, xdnd->dragger_window, xdnd->dropper_toplevel);
535 
536       retval = 1;
537     }
538     else if(event->xclient.message_type == xdnd->_XA_XdndPosition) {
539       XEvent  xevent;
540       Window  parent, child, new_child;
541 
542 #ifdef DEBUG_DND
543       printf("XdndPosition\n");
544 #endif
545 
546       XLOCK (xdnd->xitk->x_lock_display, xdnd->xitk->display);
547 
548       parent   = DefaultRootWindow (xdnd->xitk->display);
549       child    = xdnd->dropper_toplevel;
550 
551       for(;;) {
552 	int xd, yd;
553 
554 	new_child = None;
555 	if(!XTranslateCoordinates  (xdnd->xitk->display, parent, child,
556 				   XDND_POSITION_ROOT_X(event), XDND_POSITION_ROOT_Y(event),
557 				   &xd, &yd, &new_child))
558 	  break;
559 
560 	if(new_child == None)
561 	  break;
562 
563 	child = new_child;
564       }
565 
566       XUNLOCK (xdnd->xitk->x_unlock_display, xdnd->xitk->display);
567 
568       xdnd->dropper_window = event->xany.window = child;
569 
570       xdnd->x    = XDND_POSITION_ROOT_X(event);
571       xdnd->y    = XDND_POSITION_ROOT_Y(event);
572       xdnd->time = XDND_POSITION_TIME(event);
573 
574       memset (&xevent, 0, sizeof(xevent));
575       xevent.xany.type            = ClientMessage;
576       xevent.xany.display         = xdnd->xitk->display;
577       xevent.xclient.window       = xdnd->dragger_window;
578       xevent.xclient.message_type = xdnd->_XA_XdndStatus;
579       xevent.xclient.format       = 32;
580 
581       XDND_STATUS_TARGET_WIN(&xevent) = event->xclient.window;
582       XDND_STATUS_WILL_ACCEPT_SET(&xevent, True);
583       XDND_STATUS_WANT_POSITION_SET(&xevent, True);
584       XDND_STATUS_RECT_SET(&xevent, xdnd->x, xdnd->y, 1, 1);
585       XDND_STATUS_ACTION(&xevent) = XDND_POSITION_ACTION(event);
586 
587       XLOCK (xdnd->xitk->x_lock_display, xdnd->xitk->display);
588       XSendEvent (xdnd->xitk->display, xdnd->dragger_window, 0, 0, &xevent);
589       XUNLOCK (xdnd->xitk->x_unlock_display, xdnd->xitk->display);
590     }
591 
592     retval = 1;
593   }
594   else if(event->type == SelectionNotify) {
595 
596 #ifdef DEBUG_DND
597       printf("SelectionNotify\n");
598 #endif
599 
600     if(event->xselection.property == xdnd->_XA_XITK_PROTOCOL_ATOM) {
601       _dnd_get_selection(xdnd, xdnd->dragger_window,
602 			 event->xselection.property, event->xany.window);
603       _dnd_send_finished(xdnd, xdnd->dragger_window, xdnd->dropper_toplevel);
604     }
605 
606     XITK_FREE(xdnd->dragger_typelist);
607 
608     retval = 1;
609   }
610 
611   return retval;
612 }
613