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
_is_atom_match(DndClass * xdnd,Atom ** atom)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 */
_dnd_send_finished(DndClass * xdnd,Window window,Window from)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
unescape_string(char * src,char * dest)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 */
_dnd_paste_prop_internal(DndClass * xdnd,Window from,Window insert,Atom prop,Bool delete_prop)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 */
_dnd_get_selection(DndClass * xdnd,Window from,Atom prop,Window insert)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 */
_dnd_get_type_list(DndClass * xdnd,Window window,Atom ** typelist)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 */
_dnd_get_three_types(XEvent * xevent,Atom ** typelist)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 */
init_dnd(Display * display,DndClass * xdnd)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 */
make_window_dnd_aware(DndClass * xdnd,Window window,dnd_callback_t cb)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 */
process_client_dnd_message(DndClass * xdnd,XEvent * event)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