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