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