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