ctrlsel.c (40556B)
1 /* 2 * MIT/X Consortium License 3 * 4 * © 2022-2023 Lucas de Sena <lucas at seninha dot org> 5 * 6 * Permission is hereby granted, free of charge, to any person obtaining a 7 * copy of this software and associated documentation files (the "Software"), 8 * to deal in the Software without restriction, including without limitation 9 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 * and/or sell copies of the Software, and to permit persons to whom the 11 * Software is furnished to do so, subject to the following conditions: 12 * 13 * The above copyright notice and this permission notice shall be included in 14 * all copies or substantial portions of the Software. 15 * 16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 22 * DEALINGS IN THE SOFTWARE. 23 */ 24 25 #include <stdlib.h> 26 #include <string.h> 27 28 #include <X11/Xlib.h> 29 #include <X11/Xatom.h> 30 #include <X11/keysym.h> 31 #include <X11/cursorfont.h> 32 #include <X11/Xcursor/Xcursor.h> 33 34 #include "ctrlsel.h" 35 36 #define _TIMESTAMP_PROP "_TIMESTAMP_PROP" 37 #define TIMESTAMP "TIMESTAMP" 38 #define ATOM_PAIR "ATOM_PAIR" 39 #define MULTIPLE "MULTIPLE" 40 #define MANAGER "MANAGER" 41 #define TARGETS "TARGETS" 42 #define INCR "INCR" 43 #define SELDEFSIZE 0x4000 44 #define FLAG(f, b) (((f) & (b)) == (b)) 45 #define MOTION_TIME 32 46 #define DND_DISTANCE 8 /* distance from pointer to dnd miniwindow */ 47 #define XDND_VERSION 5 /* XDND protocol version */ 48 #define NCLIENTMSG_DATA 5 /* number of members on a the .data.l[] array of a XClientMessageEvent */ 49 50 enum { 51 CONTENT_INCR, 52 CONTENT_ZERO, 53 CONTENT_ERROR, 54 CONTENT_SUCCESS, 55 }; 56 57 enum { 58 PAIR_TARGET, 59 PAIR_PROPERTY, 60 PAIR_LAST 61 }; 62 63 enum { 64 /* xdnd window properties */ 65 XDND_AWARE, 66 67 /* xdnd selections */ 68 XDND_SELECTION, 69 70 /* xdnd client messages */ 71 XDND_ENTER, 72 XDND_POSITION, 73 XDND_STATUS, 74 XDND_LEAVE, 75 XDND_DROP, 76 XDND_FINISHED, 77 78 /* xdnd actions */ 79 XDND_ACTION_COPY, 80 XDND_ACTION_MOVE, 81 XDND_ACTION_LINK, 82 XDND_ACTION_ASK, 83 XDND_ACTION_PRIVATE, 84 85 XDND_ATOM_LAST, 86 }; 87 88 enum { 89 CURSOR_TARGET, 90 CURSOR_PIRATE, 91 CURSOR_DRAG, 92 CURSOR_COPY, 93 CURSOR_MOVE, 94 CURSOR_LINK, 95 CURSOR_NODROP, 96 CURSOR_LAST, 97 }; 98 99 struct Transfer { 100 /* 101 * When a client request the clipboard but its content is too 102 * large, we perform incremental transfer. We keep track of 103 * each incremental transfer in a list of transfers. 104 */ 105 struct Transfer *prev, *next; 106 struct CtrlSelTarget *target; 107 Window requestor; 108 Atom property; 109 unsigned long size; /* how much have we transferred */ 110 }; 111 112 struct PredArg { 113 CtrlSelContext *context; 114 Window window; 115 Atom message_type; 116 }; 117 118 struct CtrlSelContext { 119 Display *display; 120 Window window; 121 Atom selection; 122 Time time; 123 unsigned long ntargets; 124 struct CtrlSelTarget *targets; 125 126 /* 127 * Items below are used internally to keep track of any 128 * incremental transference in progress. 129 */ 130 unsigned long selmaxsize; 131 unsigned long ndone; 132 void *transfers; 133 134 /* 135 * Items below are used internally for drag-and-dropping. 136 */ 137 Window dndwindow; 138 unsigned int dndactions, dndresult; 139 }; 140 141 static char *atomnames[XDND_ATOM_LAST] = { 142 [XDND_AWARE] = "XdndAware", 143 [XDND_SELECTION] = "XdndSelection", 144 [XDND_ENTER] = "XdndEnter", 145 [XDND_POSITION] = "XdndPosition", 146 [XDND_STATUS] = "XdndStatus", 147 [XDND_LEAVE] = "XdndLeave", 148 [XDND_DROP] = "XdndDrop", 149 [XDND_FINISHED] = "XdndFinished", 150 [XDND_ACTION_COPY] = "XdndActionCopy", 151 [XDND_ACTION_MOVE] = "XdndActionMove", 152 [XDND_ACTION_LINK] = "XdndActionLink", 153 [XDND_ACTION_ASK] = "XdndActionAsk", 154 [XDND_ACTION_PRIVATE] = "XdndActionPrivate", 155 }; 156 157 static int 158 between(int x, int y, int x0, int y0, int w0, int h0) 159 { 160 return x >= x0 && x < x0 + w0 && y >= y0 && y < y0 + h0; 161 } 162 163 static void 164 clientmsg(Display *dpy, Window win, Atom atom, long d[5]) 165 { 166 XEvent ev; 167 168 ev.xclient.type = ClientMessage; 169 ev.xclient.display = dpy; 170 ev.xclient.serial = 0; 171 ev.xclient.send_event = True; 172 ev.xclient.message_type = atom; 173 ev.xclient.window = win; 174 ev.xclient.format = 32; 175 ev.xclient.data.l[0] = d[0]; 176 ev.xclient.data.l[1] = d[1]; 177 ev.xclient.data.l[2] = d[2]; 178 ev.xclient.data.l[3] = d[3]; 179 ev.xclient.data.l[4] = d[4]; 180 (void)XSendEvent(dpy, win, False, 0x0, &ev); 181 } 182 183 static unsigned long 184 getselmaxsize(Display *display) 185 { 186 unsigned long n; 187 188 if ((n = XExtendedMaxRequestSize(display)) > 0) 189 return n; 190 if ((n = XMaxRequestSize(display)) > 0) 191 return n; 192 return SELDEFSIZE; 193 } 194 195 static int 196 getservertime(Display *display, Time *time) 197 { 198 XEvent xev; 199 Window window; 200 Atom timeprop; 201 202 /* 203 * According to ICCCM, a client wishing to acquire ownership of 204 * a selection should set the specfied time to some time between 205 * the current last-change time of the selection concerned and 206 * the current server time. 207 * 208 * Those clients should not set the time value to `CurrentTime`, 209 * because if they do so, they have no way of finding when they 210 * gained ownership of the selection. 211 * 212 * In the case that an event triggers the acquisition of the 213 * selection, this time value can be obtained from the event 214 * itself. 215 * 216 * In the case that the client must unconditionally acquire the 217 * ownership of a selection (which is our case), a zero-length 218 * append to a property is a way to obtain a timestamp for this 219 * purpose. The timestamp is in the corresponding 220 * `PropertyNotify` event. 221 */ 222 223 if (time != CurrentTime) 224 return 1; 225 timeprop = XInternAtom(display, _TIMESTAMP_PROP, False); 226 if (timeprop == None) 227 goto error; 228 window = XCreateWindow( 229 display, 230 DefaultRootWindow(display), 231 0, 0, 1, 1, 0, 232 CopyFromParent, CopyFromParent, CopyFromParent, 233 CWEventMask, 234 &(XSetWindowAttributes){ 235 .event_mask = PropertyChangeMask, 236 } 237 ); 238 if (window == None) 239 goto error; 240 XChangeProperty( 241 display, window, 242 timeprop, timeprop, 243 8L, PropModeAppend, NULL, 0 244 ); 245 while (!XWindowEvent(display, window, PropertyChangeMask, &xev)) { 246 if (xev.type == PropertyNotify && 247 xev.xproperty.window == window && 248 xev.xproperty.atom == timeprop) { 249 *time = xev.xproperty.time; 250 break; 251 } 252 } 253 (void)XDestroyWindow(display, window); 254 return 1; 255 error: 256 return 0; 257 } 258 259 static int 260 nbytes(int format) 261 { 262 switch (format) { 263 default: return sizeof(char); 264 case 16: return sizeof(short); 265 case 32: return sizeof(long); 266 } 267 } 268 269 static int 270 getcontent(struct CtrlSelTarget *target, Display *display, Window window, Atom property) 271 { 272 unsigned char *p, *q; 273 unsigned long len, addsize, size; 274 unsigned long dl; /* dummy variable */ 275 int status; 276 Atom incr; 277 278 incr = XInternAtom(display, INCR, False), 279 status = XGetWindowProperty( 280 display, 281 window, 282 property, 283 0L, 0x1FFFFFFF, 284 True, 285 AnyPropertyType, 286 &target->type, 287 &target->format, 288 &len, &dl, &p 289 ); 290 if (target->format != 32 && target->format != 16) 291 target->format = 8; 292 if (target->type == incr) { 293 XFree(p); 294 return CONTENT_INCR; 295 } 296 if (len == 0) { 297 XFree(p); 298 return CONTENT_ZERO; 299 } 300 if (status != Success) { 301 XFree(p); 302 return CONTENT_ERROR; 303 } 304 if (p == NULL) { 305 XFree(p); 306 return CONTENT_ERROR; 307 } 308 addsize = len * nbytes(target->format); 309 size = addsize; 310 if (target->buffer != NULL) { 311 /* append buffer */ 312 size += target->bufsize; 313 if ((q = realloc(target->buffer, size + 1)) == NULL) { 314 XFree(p); 315 return CONTENT_ERROR; 316 } 317 memcpy(q + target->bufsize, p, addsize); 318 target->buffer = q; 319 target->bufsize = size; 320 target->nitems += len; 321 } else { 322 /* new buffer */ 323 if ((q = malloc(size + 1)) == NULL) { 324 XFree(p); 325 return CONTENT_ERROR; 326 } 327 memcpy(q, p, addsize); 328 target->buffer = q; 329 target->bufsize = size; 330 target->nitems = len; 331 } 332 target->buffer[size] = '\0'; 333 XFree(p); 334 return CONTENT_SUCCESS; 335 } 336 337 static void 338 deltransfer(CtrlSelContext *context, struct Transfer *transfer) 339 { 340 if (transfer->prev != NULL) { 341 transfer->prev->next = transfer->next; 342 } else { 343 context->transfers = transfer->next; 344 } 345 if (transfer->next != NULL) { 346 transfer->next->prev = transfer->prev; 347 } 348 } 349 350 static void 351 freetransferences(CtrlSelContext *context) 352 { 353 struct Transfer *transfer; 354 355 while (context->transfers != NULL) { 356 transfer = (struct Transfer *)context->transfers; 357 context->transfers = ((struct Transfer *)context->transfers)->next; 358 XDeleteProperty( 359 context->display, 360 transfer->requestor, 361 transfer->property 362 ); 363 free(transfer); 364 } 365 context->transfers = NULL; 366 } 367 368 static void 369 freebuffers(CtrlSelContext *context) 370 { 371 unsigned long i; 372 373 for (i = 0; i < context->ntargets; i++) { 374 free(context->targets[i].buffer); 375 context->targets[i].buffer = NULL; 376 context->targets[i].nitems = 0; 377 context->targets[i].bufsize = 0; 378 } 379 } 380 381 static unsigned long 382 getatomsprop(Display *display, Window window, Atom property, Atom type, Atom **atoms) 383 { 384 unsigned char *p; 385 unsigned long len; 386 unsigned long dl; /* dummy variable */ 387 int format; 388 Atom gottype; 389 unsigned long size; 390 int success; 391 392 success = XGetWindowProperty( 393 display, 394 window, 395 property, 396 0L, 0x1FFFFFFF, 397 False, 398 type, &gottype, 399 &format, &len, 400 &dl, &p 401 ); 402 if (success != Success || len == 0 || p == NULL || format != 32) 403 goto error; 404 if (type != AnyPropertyType && type != gottype) 405 goto error; 406 size = len * sizeof(**atoms); 407 if ((*atoms = malloc(size)) == NULL) 408 goto error; 409 memcpy(*atoms, p, size); 410 XFree(p); 411 return len; 412 error: 413 XFree(p); 414 *atoms = NULL; 415 return 0; 416 } 417 418 static int 419 newtransfer(CtrlSelContext *context, struct CtrlSelTarget *target, Window requestor, Atom property) 420 { 421 struct Transfer *transfer; 422 423 transfer = malloc(sizeof(*transfer)); 424 if (transfer == NULL) 425 return 0; 426 *transfer = (struct Transfer){ 427 .prev = NULL, 428 .next = (struct Transfer *)context->transfers, 429 .requestor = requestor, 430 .property = property, 431 .target = target, 432 .size = 0, 433 }; 434 if (context->transfers != NULL) 435 ((struct Transfer *)context->transfers)->prev = transfer; 436 context->transfers = transfer; 437 return 1; 438 } 439 440 static Bool 441 convert(CtrlSelContext *context, Window requestor, Atom target, Atom property) 442 { 443 Atom multiple, timestamp, targets, incr; 444 Atom *supported; 445 unsigned long i; 446 int nsupported; 447 448 incr = XInternAtom(context->display, INCR, False); 449 targets = XInternAtom(context->display, TARGETS, False); 450 multiple = XInternAtom(context->display, MULTIPLE, False); 451 timestamp = XInternAtom(context->display, TIMESTAMP, False); 452 if (target == multiple) { 453 /* A MULTIPLE should be handled when processing a 454 * SelectionRequest event. We do not support nested 455 * MULTIPLE targets. 456 */ 457 return False; 458 } 459 if (target == timestamp) { 460 /* 461 * According to ICCCM, to avoid some race conditions, it 462 * is important that requestors be able to discover the 463 * timestamp the owner used to acquire ownership. 464 * Requestors do that by requesting selection owners to 465 * convert the `TIMESTAMP` target. Selection owners 466 * must return the timestamp as an `XA_INTEGER`. 467 */ 468 XChangeProperty( 469 context->display, 470 requestor, 471 property, 472 XA_INTEGER, 32, 473 PropModeReplace, 474 (unsigned char *)&context->time, 475 1 476 ); 477 return True; 478 } 479 if (target == targets) { 480 /* 481 * According to ICCCM, when requested for the `TARGETS` 482 * target, the selection owner should return a list of 483 * atoms representing the targets for which an attempt 484 * to convert the selection will (hopefully) succeed. 485 */ 486 nsupported = context->ntargets + 2; /* +2 for MULTIPLE + TIMESTAMP */ 487 if ((supported = calloc(nsupported, sizeof(*supported))) == NULL) 488 return False; 489 for (i = 0; i < context->ntargets; i++) { 490 supported[i] = context->targets[i].target; 491 } 492 supported[i++] = multiple; 493 supported[i++] = timestamp; 494 XChangeProperty( 495 context->display, 496 requestor, 497 property, 498 XA_ATOM, 32, 499 PropModeReplace, 500 (unsigned char *)supported, 501 nsupported 502 ); 503 free(supported); 504 return True; 505 } 506 for (i = 0; i < context->ntargets; i++) { 507 if (target == context->targets[i].target) 508 goto found; 509 } 510 return False; 511 found: 512 if (context->targets[i].bufsize > context->selmaxsize) { 513 XSelectInput( 514 context->display, 515 requestor, 516 StructureNotifyMask | PropertyChangeMask 517 ); 518 XChangeProperty( 519 context->display, 520 requestor, 521 property, 522 incr, 523 32L, 524 PropModeReplace, 525 (unsigned char *)context->targets[i].buffer, 526 1 527 ); 528 newtransfer(context, &context->targets[i], requestor, property); 529 } else { 530 XChangeProperty( 531 context->display, 532 requestor, 533 property, 534 target, 535 context->targets[i].format, 536 PropModeReplace, 537 context->targets[i].buffer, 538 context->targets[i].nitems 539 ); 540 } 541 return True; 542 } 543 544 static int 545 request(CtrlSelContext *context) 546 { 547 Atom multiple, atom_pair; 548 Atom *pairs; 549 unsigned long i, size; 550 551 for (i = 0; i < context->ntargets; i++) { 552 context->targets[i].nitems = 0; 553 context->targets[i].bufsize = 0; 554 context->targets[i].buffer = NULL; 555 } 556 if (context->ntargets == 1) { 557 (void)XConvertSelection( 558 context->display, 559 context->selection, 560 context->targets[0].target, 561 context->targets[0].target, 562 context->window, 563 context->time 564 ); 565 } else if (context->ntargets > 1) { 566 multiple = XInternAtom(context->display, MULTIPLE, False); 567 atom_pair = XInternAtom(context->display, ATOM_PAIR, False); 568 size = 2 * context->ntargets; 569 pairs = calloc(size, sizeof(*pairs)); 570 if (pairs == NULL) 571 return 0; 572 for (i = 0; i < context->ntargets; i++) { 573 pairs[i * 2 + 0] = context->targets[i].target; 574 pairs[i * 2 + 1] = context->targets[i].target; 575 } 576 (void)XChangeProperty( 577 context->display, 578 context->window, 579 multiple, 580 atom_pair, 581 32, 582 PropModeReplace, 583 (unsigned char *)pairs, 584 size 585 ); 586 (void)XConvertSelection( 587 context->display, 588 context->selection, 589 multiple, 590 multiple, 591 context->window, 592 context->time 593 ); 594 free(pairs); 595 } 596 return 1; 597 } 598 599 void 600 ctrlsel_filltarget( 601 Atom target, 602 Atom type, 603 int format, 604 unsigned char *buffer, 605 unsigned long size, 606 struct CtrlSelTarget *fill 607 ) { 608 if (fill == NULL) 609 return; 610 if (format != 32 && format != 16) 611 format = 8; 612 *fill = (struct CtrlSelTarget){ 613 .target = target, 614 .type = type, 615 .action = None, 616 .format = format, 617 .nitems = size / nbytes(format), 618 .buffer = buffer, 619 .bufsize = size, 620 }; 621 } 622 623 CtrlSelContext * 624 ctrlsel_request( 625 Display *display, 626 Window window, 627 Atom selection, 628 Time time, 629 struct CtrlSelTarget targets[], 630 unsigned long ntargets 631 ) { 632 CtrlSelContext *context; 633 634 if (!getservertime(display, &time)) 635 return NULL; 636 if ((context = malloc(sizeof(*context))) == NULL) 637 return NULL; 638 *context = (CtrlSelContext){ 639 .display = display, 640 .window = window, 641 .selection = selection, 642 .time = time, 643 .targets = targets, 644 .ntargets = ntargets, 645 .selmaxsize = getselmaxsize(display), 646 .ndone = 0, 647 .transfers = NULL, 648 .dndwindow = None, 649 .dndactions = 0x00, 650 .dndresult = 0x00, 651 }; 652 if (ntargets == 0) 653 return context; 654 if (request(context)) 655 return context; 656 free(context); 657 return NULL; 658 } 659 660 CtrlSelContext * 661 ctrlsel_setowner( 662 Display *display, 663 Window window, 664 Atom selection, 665 Time time, 666 int ismanager, 667 struct CtrlSelTarget targets[], 668 unsigned long ntargets 669 ) { 670 CtrlSelContext *context; 671 Window root; 672 673 root = DefaultRootWindow(display); 674 if (!getservertime(display, &time)) 675 return NULL; 676 if ((context = malloc(sizeof(*context))) == NULL) 677 return NULL; 678 *context = (CtrlSelContext){ 679 .display = display, 680 .window = window, 681 .selection = selection, 682 .time = time, 683 .targets = targets, 684 .ntargets = ntargets, 685 .selmaxsize = getselmaxsize(display), 686 .ndone = 0, 687 .transfers = NULL, 688 .dndwindow = None, 689 .dndactions = 0x00, 690 .dndresult = 0x00, 691 }; 692 (void)XSetSelectionOwner(display, selection, window, time); 693 if (XGetSelectionOwner(display, selection) != window) { 694 free(context); 695 return NULL; 696 } 697 if (!ismanager) 698 return context; 699 700 /* 701 * According to ICCCM, a manager client (that is, a client 702 * responsible for managing shared resources) should take 703 * ownership of an appropriate selection. 704 * 705 * Immediately after a manager successfully acquires ownership 706 * of a manager selection, it should announce its arrival by 707 * sending a `ClientMessage` event. (That is necessary for 708 * clients to be able to know when a specific manager has 709 * started: any client that wish to do so should select for 710 * `StructureNotify` on the root window and should watch for 711 * the appropriate `MANAGER` `ClientMessage`). 712 */ 713 (void)XSendEvent( 714 display, 715 root, 716 False, 717 StructureNotifyMask, 718 (XEvent *)&(XClientMessageEvent){ 719 .type = ClientMessage, 720 .window = root, 721 .message_type = XInternAtom(display, MANAGER, False), 722 .format = 32, 723 .data.l[0] = time, /* timestamp */ 724 .data.l[1] = selection, /* manager selection atom */ 725 .data.l[2] = window, /* window owning the selection */ 726 .data.l[3] = 0, /* manager-specific data */ 727 .data.l[4] = 0, /* manager-specific data */ 728 } 729 ); 730 return context; 731 } 732 733 static int 734 receiveinit(CtrlSelContext *context, XEvent *xev) 735 { 736 struct CtrlSelTarget *targetp; 737 XSelectionEvent *xselev; 738 Atom multiple, atom_pair; 739 Atom *pairs; 740 Atom pair[PAIR_LAST]; 741 unsigned long j, natoms; 742 unsigned long i; 743 int status, success; 744 745 multiple = XInternAtom(context->display, MULTIPLE, False); 746 atom_pair = XInternAtom(context->display, ATOM_PAIR, False); 747 xselev = &xev->xselection; 748 if (xselev->selection != context->selection) 749 return CTRLSEL_NONE; 750 if (xselev->requestor != context->window) 751 return CTRLSEL_NONE; 752 if (xselev->property == None) 753 return CTRLSEL_ERROR; 754 if (xselev->target == multiple) { 755 natoms = getatomsprop( 756 xselev->display, 757 xselev->requestor, 758 xselev->property, 759 atom_pair, 760 &pairs 761 ); 762 if (natoms == 0 || pairs == NULL) { 763 free(pairs); 764 return CTRLSEL_ERROR; 765 } 766 } else { 767 pair[PAIR_TARGET] = xselev->target; 768 pair[PAIR_PROPERTY] = xselev->property; 769 pairs = pair; 770 natoms = 2; 771 } 772 success = 1; 773 for (j = 0; j < natoms; j += 2) { 774 targetp = NULL; 775 for (i = 0; i < context->ntargets; i++) { 776 if (pairs[j + PAIR_TARGET] == context->targets[i].target) { 777 targetp = &context->targets[i]; 778 break; 779 } 780 } 781 if (pairs[j + PAIR_PROPERTY] == None) 782 pairs[j + PAIR_PROPERTY] = pairs[j + PAIR_TARGET]; 783 if (targetp == NULL) { 784 success = 0; 785 continue; 786 } 787 status = getcontent( 788 targetp, 789 xselev->display, 790 xselev->requestor, 791 pairs[j + PAIR_PROPERTY] 792 ); 793 switch (status) { 794 case CONTENT_ERROR: 795 success = 0; 796 break; 797 case CONTENT_SUCCESS: 798 /* fallthrough */ 799 case CONTENT_ZERO: 800 context->ndone++; 801 break; 802 case CONTENT_INCR: 803 if (!newtransfer(context, targetp, xselev->requestor, pairs[j + PAIR_PROPERTY])) 804 success = 0; 805 break; 806 } 807 } 808 if (xselev->target == multiple) 809 free(pairs); 810 return success ? CTRLSEL_INTERNAL : CTRLSEL_ERROR; 811 } 812 813 static int 814 receiveincr(CtrlSelContext *context, XEvent *xev) 815 { 816 struct Transfer *transfer; 817 XPropertyEvent *xpropev; 818 int status; 819 820 xpropev = &xev->xproperty; 821 if (xpropev->state != PropertyNewValue) 822 return CTRLSEL_NONE; 823 if (xpropev->window != context->window) 824 return CTRLSEL_NONE; 825 for (transfer = (struct Transfer *)context->transfers; transfer != NULL; transfer = transfer->next) 826 if (transfer->property == xpropev->atom) 827 goto found; 828 return CTRLSEL_NONE; 829 found: 830 status = getcontent( 831 transfer->target, 832 xpropev->display, 833 xpropev->window, 834 xpropev->atom 835 ); 836 switch (status) { 837 case CONTENT_ERROR: 838 case CONTENT_INCR: 839 return CTRLSEL_ERROR; 840 case CONTENT_SUCCESS: 841 return CTRLSEL_INTERNAL; 842 case CONTENT_ZERO: 843 context->ndone++; 844 deltransfer(context, transfer); 845 break; 846 } 847 return CTRLSEL_INTERNAL; 848 } 849 850 int 851 ctrlsel_receive(CtrlSelContext *context, XEvent *xev) 852 { 853 int status; 854 855 if (xev->type == SelectionNotify) 856 status = receiveinit(context, xev); 857 else if (xev->type == PropertyNotify) 858 status = receiveincr(context, xev); 859 else 860 return CTRLSEL_NONE; 861 if (status == CTRLSEL_INTERNAL) { 862 if (context->ndone >= context->ntargets) { 863 status = CTRLSEL_RECEIVED; 864 goto done; 865 } 866 } else if (status == CTRLSEL_ERROR) { 867 freebuffers(context); 868 freetransferences(context); 869 } 870 done: 871 if (status == CTRLSEL_RECEIVED) 872 freetransferences(context); 873 return status; 874 } 875 876 static int 877 sendinit(CtrlSelContext *context, XEvent *xev) 878 { 879 XSelectionRequestEvent *xreqev; 880 XSelectionEvent xselev; 881 unsigned long natoms, i; 882 Atom *pairs; 883 Atom pair[PAIR_LAST]; 884 Atom multiple, atom_pair; 885 Bool success; 886 887 xreqev = &xev->xselectionrequest; 888 if (xreqev->selection != context->selection) 889 return CTRLSEL_NONE; 890 multiple = XInternAtom(context->display, MULTIPLE, False); 891 atom_pair = XInternAtom(context->display, ATOM_PAIR, False); 892 xselev = (XSelectionEvent){ 893 .type = SelectionNotify, 894 .display = xreqev->display, 895 .requestor = xreqev->requestor, 896 .selection = xreqev->selection, 897 .time = xreqev->time, 898 .target = xreqev->target, 899 .property = None, 900 }; 901 if (xreqev->time != CurrentTime && xreqev->time < context->time) { 902 /* 903 * According to ICCCM, the selection owner 904 * should compare the timestamp with the period 905 * it has owned the selection and, if the time 906 * is outside, refuse the `SelectionRequest` by 907 * sending the requestor window a 908 * `SelectionNotify` event with the property set 909 * to `None` (by means of a `SendEvent` request 910 * with an empty event mask). 911 */ 912 goto done; 913 } 914 if (xreqev->target == multiple) { 915 if (xreqev->property == None) 916 goto done; 917 natoms = getatomsprop( 918 xreqev->display, 919 xreqev->requestor, 920 xreqev->property, 921 atom_pair, 922 &pairs 923 ); 924 } else { 925 pair[PAIR_TARGET] = xreqev->target; 926 pair[PAIR_PROPERTY] = xreqev->property; 927 pairs = pair; 928 natoms = 2; 929 } 930 success = True; 931 for (i = 0; i < natoms; i += 2) { 932 if (!convert(context, xreqev->requestor, 933 pairs[i + PAIR_TARGET], 934 pairs[i + PAIR_PROPERTY])) { 935 success = False; 936 pairs[i + PAIR_PROPERTY] = None; 937 } 938 } 939 if (xreqev->target == multiple) { 940 XChangeProperty( 941 xreqev->display, 942 xreqev->requestor, 943 xreqev->property, 944 atom_pair, 945 32, PropModeReplace, 946 (unsigned char *)pairs, 947 natoms 948 ); 949 free(pairs); 950 } 951 if (success) { 952 if (xreqev->property == None) { 953 xselev.property = xreqev->target; 954 } else { 955 xselev.property = xreqev->property; 956 } 957 } 958 done: 959 XSendEvent( 960 xreqev->display, 961 xreqev->requestor, 962 False, 963 NoEventMask, 964 (XEvent *)&xselev 965 ); 966 return CTRLSEL_INTERNAL; 967 } 968 969 static int 970 sendlost(CtrlSelContext *context, XEvent *xev) 971 { 972 XSelectionClearEvent *xclearev; 973 974 xclearev = &xev->xselectionclear; 975 if (xclearev->selection == context->selection && 976 xclearev->window == context->window) { 977 return CTRLSEL_LOST; 978 } 979 return CTRLSEL_NONE; 980 } 981 982 static int 983 senddestroy(CtrlSelContext *context, XEvent *xev) 984 { 985 struct Transfer *transfer; 986 XDestroyWindowEvent *xdestroyev; 987 988 xdestroyev = &xev->xdestroywindow; 989 for (transfer = context->transfers; transfer != NULL; transfer = transfer->next) 990 if (transfer->requestor == xdestroyev->window) 991 deltransfer(context, transfer); 992 return CTRLSEL_NONE; 993 } 994 995 static int 996 sendincr(CtrlSelContext *context, XEvent *xev) 997 { 998 struct Transfer *transfer; 999 XPropertyEvent *xpropev; 1000 unsigned long size; 1001 1002 xpropev = &xev->xproperty; 1003 if (xpropev->state != PropertyDelete) 1004 return CTRLSEL_NONE; 1005 for (transfer = context->transfers; transfer != NULL; transfer = transfer->next) 1006 if (transfer->property == xpropev->atom && 1007 transfer->requestor == xpropev->window) 1008 goto found; 1009 return CTRLSEL_NONE; 1010 found: 1011 if (transfer->size >= transfer->target->bufsize) 1012 transfer->size = transfer->target->bufsize; 1013 size = transfer->target->bufsize - transfer->size; 1014 if (size > context->selmaxsize) 1015 size = context->selmaxsize; 1016 XChangeProperty( 1017 xpropev->display, 1018 xpropev->window, 1019 xpropev->atom, 1020 transfer->target->target, 1021 transfer->target->format, 1022 PropModeReplace, 1023 transfer->target->buffer + transfer->size, 1024 size / nbytes(transfer->target->format) 1025 ); 1026 if (transfer->size >= transfer->target->bufsize) { 1027 deltransfer(context, transfer); 1028 } else { 1029 transfer->size += size; 1030 } 1031 return CTRLSEL_INTERNAL; 1032 } 1033 1034 int 1035 ctrlsel_send(CtrlSelContext *context, XEvent *xev) 1036 { 1037 int status; 1038 1039 if (xev->type == SelectionRequest) 1040 status = sendinit(context, xev); 1041 else if (xev->type == SelectionClear) 1042 status = sendlost(context, xev); 1043 else if (xev->type == DestroyNotify) 1044 status = senddestroy(context, xev); 1045 else if (xev->type == PropertyNotify) 1046 status = sendincr(context, xev); 1047 else 1048 return CTRLSEL_NONE; 1049 if (status == CTRLSEL_LOST || status == CTRLSEL_ERROR) { 1050 status = CTRLSEL_LOST; 1051 freetransferences(context); 1052 } 1053 return status; 1054 } 1055 1056 void 1057 ctrlsel_cancel(CtrlSelContext *context) 1058 { 1059 if (context == NULL) 1060 return; 1061 freebuffers(context); 1062 freetransferences(context); 1063 free(context); 1064 } 1065 1066 void 1067 ctrlsel_disown(CtrlSelContext *context) 1068 { 1069 if (context == NULL) 1070 return; 1071 freetransferences(context); 1072 free(context); 1073 } 1074 1075 static Bool 1076 dndpred(Display *display, XEvent *event, XPointer p) 1077 { 1078 struct PredArg *arg; 1079 struct Transfer *transfer; 1080 1081 arg = (struct PredArg *)p; 1082 switch (event->type) { 1083 case KeyPress: 1084 case KeyRelease: 1085 if (event->xkey.display == display && 1086 event->xkey.window == arg->window) 1087 return True; 1088 break; 1089 case ButtonPress: 1090 case ButtonRelease: 1091 if (event->xbutton.display == display && 1092 event->xbutton.window == arg->window) 1093 return True; 1094 break; 1095 case MotionNotify: 1096 if (event->xmotion.display == display && 1097 event->xmotion.window == arg->window) 1098 return True; 1099 break; 1100 case DestroyNotify: 1101 if (event->xdestroywindow.display == display && 1102 event->xdestroywindow.window == arg->window) 1103 return True; 1104 break; 1105 case UnmapNotify: 1106 if (event->xunmap.display == display && 1107 event->xunmap.window == arg->window) 1108 return True; 1109 break; 1110 case SelectionClear: 1111 if (event->xselectionclear.display == display && 1112 event->xselectionclear.window == arg->window) 1113 return True; 1114 break; 1115 case SelectionRequest: 1116 if (event->xselectionrequest.display == display && 1117 event->xselectionrequest.owner == arg->window) 1118 return True; 1119 break; 1120 case ClientMessage: 1121 if (event->xclient.display == display && 1122 event->xclient.window == arg->window && 1123 event->xclient.message_type == arg->message_type) 1124 return True; 1125 break; 1126 case PropertyNotify: 1127 if (event->xproperty.display != display || 1128 event->xproperty.state != PropertyDelete) 1129 return False; 1130 for (transfer = arg->context->transfers; 1131 transfer != NULL; 1132 transfer = transfer->next) { 1133 if (transfer->property == event->xproperty.atom && 1134 transfer->requestor == event->xproperty.window) { 1135 return True; 1136 } 1137 } 1138 break; 1139 default: 1140 break; 1141 } 1142 return False; 1143 } 1144 1145 #define SOME(a, b, c) ((a) != None ? (a) : ((b) != None ? (b) : (c))) 1146 1147 static Cursor 1148 getcursor(Cursor cursors[CURSOR_LAST], int type) 1149 { 1150 switch (type) { 1151 case CURSOR_TARGET: 1152 case CURSOR_DRAG: 1153 return SOME(cursors[CURSOR_DRAG], cursors[CURSOR_TARGET], None); 1154 case CURSOR_PIRATE: 1155 case CURSOR_NODROP: 1156 return SOME(cursors[CURSOR_NODROP], cursors[CURSOR_PIRATE], None); 1157 case CURSOR_COPY: 1158 return SOME(cursors[CURSOR_COPY], cursors[CURSOR_DRAG], cursors[CURSOR_TARGET]); 1159 case CURSOR_MOVE: 1160 return SOME(cursors[CURSOR_MOVE], cursors[CURSOR_DRAG], cursors[CURSOR_TARGET]); 1161 case CURSOR_LINK: 1162 return SOME(cursors[CURSOR_LINK], cursors[CURSOR_DRAG], cursors[CURSOR_TARGET]); 1163 }; 1164 return None; 1165 } 1166 1167 static void 1168 initcursors(Display *display, Cursor cursors[CURSOR_LAST]) 1169 { 1170 cursors[CURSOR_TARGET] = XCreateFontCursor(display, XC_target); 1171 cursors[CURSOR_PIRATE] = XCreateFontCursor(display, XC_pirate); 1172 cursors[CURSOR_DRAG] = XcursorLibraryLoadCursor(display, "dnd-none"); 1173 cursors[CURSOR_COPY] = XcursorLibraryLoadCursor(display, "dnd-copy"); 1174 cursors[CURSOR_MOVE] = XcursorLibraryLoadCursor(display, "dnd-move"); 1175 cursors[CURSOR_LINK] = XcursorLibraryLoadCursor(display, "dnd-link"); 1176 cursors[CURSOR_NODROP] = XcursorLibraryLoadCursor(display, "forbidden"); 1177 } 1178 1179 static void 1180 freecursors(Display *display, Cursor cursors[CURSOR_LAST]) 1181 { 1182 int i; 1183 1184 for (i = 0; i < CURSOR_LAST; i++) { 1185 if (cursors[i] != None) { 1186 XFreeCursor(display, cursors[i]); 1187 } 1188 } 1189 } 1190 1191 static int 1192 querypointer(Display *display, Window window, int *retx, int *rety, Window *retwin) 1193 { 1194 Window root, child; 1195 unsigned int mask; 1196 int rootx, rooty; 1197 int x, y; 1198 int retval; 1199 1200 retval = XQueryPointer( 1201 display, 1202 window, 1203 &root, &child, 1204 &rootx, &rooty, 1205 &x, &y, 1206 &mask 1207 ); 1208 if (retwin != NULL) 1209 *retwin = child; 1210 if (retx != NULL) 1211 *retx = x; 1212 if (rety != NULL) 1213 *rety = y; 1214 return retval; 1215 } 1216 1217 static Window 1218 getdndwindowbelow(Display *display, Window root, Atom aware, Atom *version) 1219 { 1220 Atom *p; 1221 Window window; 1222 1223 /* 1224 * Query pointer location and return the window below it, 1225 * and the version of the XDND protocol it uses. 1226 */ 1227 *version = None; 1228 window = root; 1229 p = NULL; 1230 while (querypointer(display, window, NULL, NULL, &window)) { 1231 if (window == None) 1232 break; 1233 p = NULL; 1234 if (getatomsprop(display, window, aware, AnyPropertyType, &p) > 0) { 1235 *version = *p; 1236 XFree(p); 1237 return window; 1238 } 1239 } 1240 XFree(p); 1241 return None; 1242 } 1243 1244 CtrlSelContext * 1245 ctrlsel_dndwatch( 1246 Display *display, 1247 Window window, 1248 unsigned int actions, 1249 struct CtrlSelTarget targets[], 1250 unsigned long ntargets 1251 ) { 1252 CtrlSelContext *context; 1253 Atom version = XDND_VERSION; /* yes, version is an Atom */ 1254 Atom xdndaware, xdndselection; 1255 1256 xdndaware = XInternAtom(display, atomnames[XDND_AWARE], False); 1257 if (xdndaware == None) 1258 return NULL; 1259 xdndselection = XInternAtom(display, atomnames[XDND_SELECTION], False); 1260 if (xdndselection == None) 1261 return NULL; 1262 if ((context = malloc(sizeof(*context))) == NULL) 1263 return NULL; 1264 *context = (CtrlSelContext){ 1265 .display = display, 1266 .window = window, 1267 .selection = xdndselection, 1268 .time = CurrentTime, 1269 .targets = targets, 1270 .ntargets = ntargets, 1271 .selmaxsize = getselmaxsize(display), 1272 .ndone = 0, 1273 .transfers = NULL, 1274 .dndwindow = None, 1275 .dndactions = actions, 1276 .dndresult = 0x00, 1277 }; 1278 (void)XChangeProperty( 1279 display, 1280 window, 1281 xdndaware, 1282 XA_ATOM, 32, 1283 PropModeReplace, 1284 (unsigned char *)&version, 1285 1 1286 ); 1287 return context; 1288 } 1289 1290 static void 1291 finishdrop(CtrlSelContext *context) 1292 { 1293 long d[NCLIENTMSG_DATA]; 1294 unsigned long i; 1295 Atom finished; 1296 1297 if (context->dndwindow == None) 1298 return; 1299 finished = XInternAtom(context->display, atomnames[XDND_FINISHED], False); 1300 if (finished == None) 1301 return; 1302 for (i = 0; i < context->ntargets; i++) 1303 context->targets[i].action = context->dndresult; 1304 d[0] = context->window; 1305 d[1] = d[2] = d[3] = d[4] = 0; 1306 clientmsg(context->display, context->dndwindow, finished, d); 1307 context->dndwindow = None; 1308 } 1309 1310 int 1311 ctrlsel_dndreceive(CtrlSelContext *context, XEvent *event) 1312 { 1313 Atom atoms[XDND_ATOM_LAST]; 1314 Atom action; 1315 long d[NCLIENTMSG_DATA]; 1316 1317 if (!XInternAtoms(context->display, atomnames, XDND_ATOM_LAST, False, atoms)) 1318 return CTRLSEL_NONE; 1319 switch (ctrlsel_receive(context, event)) { 1320 case CTRLSEL_RECEIVED: 1321 finishdrop(context); 1322 return CTRLSEL_RECEIVED; 1323 case CTRLSEL_INTERNAL: 1324 case CTRLSEL_ERROR: 1325 return CTRLSEL_INTERNAL; 1326 default: 1327 break; 1328 } 1329 if (event->type != ClientMessage) 1330 return CTRLSEL_NONE; 1331 if (event->xclient.message_type == atoms[XDND_ENTER]) { 1332 context->dndwindow = (Window)event->xclient.data.l[0]; 1333 context->dndresult = 0x00; 1334 } else if (event->xclient.message_type == atoms[XDND_LEAVE]) { 1335 if ((Window)event->xclient.data.l[0] == None || 1336 (Window)event->xclient.data.l[0] != context->dndwindow) 1337 return CTRLSEL_NONE; 1338 context->dndwindow = None; 1339 } else if (event->xclient.message_type == atoms[XDND_DROP]) { 1340 if ((Window)event->xclient.data.l[0] == None || 1341 (Window)event->xclient.data.l[0] != context->dndwindow) 1342 return CTRLSEL_NONE; 1343 context->time = (Time)event->xclient.data.l[2]; 1344 (void)request(context); 1345 } else if (event->xclient.message_type == atoms[XDND_POSITION]) { 1346 if ((Window)event->xclient.data.l[0] == None || 1347 (Window)event->xclient.data.l[0] != context->dndwindow) 1348 return CTRLSEL_NONE; 1349 if (((Atom)event->xclient.data.l[4] == atoms[XDND_ACTION_COPY] && 1350 context->dndactions & CTRLSEL_COPY) || 1351 ((Atom)event->xclient.data.l[4] == atoms[XDND_ACTION_MOVE] && 1352 context->dndactions & CTRLSEL_MOVE) || 1353 ((Atom)event->xclient.data.l[4] == atoms[XDND_ACTION_LINK] && 1354 context->dndactions & CTRLSEL_LINK) || 1355 ((Atom)event->xclient.data.l[4] == atoms[XDND_ACTION_ASK] && 1356 context->dndactions & CTRLSEL_ASK) || 1357 ((Atom)event->xclient.data.l[4] == atoms[XDND_ACTION_PRIVATE] && 1358 context->dndactions & CTRLSEL_PRIVATE)) { 1359 action = (Atom)event->xclient.data.l[4]; 1360 } else { 1361 action = atoms[XDND_ACTION_COPY]; 1362 } 1363 d[0] = context->window; 1364 d[1] = 0x1; 1365 d[2] = 0; /* our rectangle is the entire screen */ 1366 d[3] = 0xFFFFFFFF; /* so we do not get lots of messages */ 1367 d[4] = action; 1368 if (action == atoms[XDND_ACTION_PRIVATE]) 1369 context->dndresult = CTRLSEL_PRIVATE; 1370 else if (action == atoms[XDND_ACTION_ASK]) 1371 context->dndresult = CTRLSEL_ASK; 1372 else if (action == atoms[XDND_ACTION_LINK]) 1373 context->dndresult = CTRLSEL_LINK; 1374 else if (action == atoms[XDND_ACTION_MOVE]) 1375 context->dndresult = CTRLSEL_MOVE; 1376 else 1377 context->dndresult = CTRLSEL_COPY; 1378 clientmsg( 1379 context->display, 1380 (Window)event->xclient.data.l[0], 1381 atoms[XDND_STATUS], 1382 d 1383 ); 1384 } else { 1385 return CTRLSEL_NONE; 1386 } 1387 return CTRLSEL_INTERNAL; 1388 } 1389 1390 void 1391 ctrlsel_dndclose(CtrlSelContext *context) 1392 { 1393 if (context == NULL) 1394 return; 1395 finishdrop(context); 1396 freebuffers(context); 1397 freetransferences(context); 1398 free(context); 1399 } 1400 1401 void 1402 ctrlsel_dnddisown(CtrlSelContext *context) 1403 { 1404 ctrlsel_disown(context); 1405 } 1406 1407 int 1408 ctrlsel_dndsend(CtrlSelContext *context, XEvent *event) 1409 { 1410 Atom finished; 1411 1412 finished = XInternAtom(context->display, atomnames[XDND_FINISHED], False); 1413 if (event->type == ClientMessage && 1414 event->xclient.message_type == finished && 1415 (Window)event->xclient.data.l[0] == context->dndwindow) { 1416 ctrlsel_dnddisown(context); 1417 return CTRLSEL_SENT; 1418 } 1419 return ctrlsel_send(context, event); 1420 } 1421 1422 int 1423 ctrlsel_dndown( 1424 Display *display, 1425 Window window, 1426 Window miniature, 1427 Time time, 1428 struct CtrlSelTarget targets[], 1429 unsigned long ntargets, 1430 CtrlSelContext **context_ret 1431 ) { 1432 CtrlSelContext *context; 1433 struct PredArg arg; 1434 XWindowAttributes wattr; 1435 XEvent event; 1436 Atom atoms[XDND_ATOM_LAST]; 1437 Cursor cursors[CURSOR_LAST] = { None, None }; 1438 Cursor cursor; 1439 Window lastwin, winbelow; 1440 Atom lastaction, action, version; 1441 long d[NCLIENTMSG_DATA]; 1442 int sendposition, retval, status, inside; 1443 int x, y, w, h; 1444 1445 *context_ret = NULL; 1446 if (display == NULL || window == None) 1447 return CTRLSEL_ERROR; 1448 if (!XGetWindowAttributes(display, window, &wattr)) 1449 return CTRLSEL_ERROR; 1450 if ((wattr.your_event_mask & StructureNotifyMask) == 0x00) 1451 return CTRLSEL_ERROR; 1452 if (wattr.map_state != IsViewable) 1453 return CTRLSEL_ERROR; 1454 if (!XInternAtoms(display, atomnames, XDND_ATOM_LAST, False, atoms)) 1455 return CTRLSEL_ERROR; 1456 context = ctrlsel_setowner( 1457 display, 1458 window, 1459 atoms[XDND_SELECTION], 1460 time, 1461 0, 1462 targets, 1463 ntargets 1464 ); 1465 if (context == NULL) 1466 return CTRLSEL_ERROR; 1467 d[0] = window; 1468 sendposition = 1; 1469 x = y = w = h = 0; 1470 retval = CTRLSEL_ERROR; 1471 lastaction = action = None; 1472 lastwin = None; 1473 arg = (struct PredArg){ 1474 .context = context, 1475 .window = window, 1476 .message_type = atoms[XDND_STATUS], 1477 }; 1478 initcursors(display, cursors); 1479 status = XGrabPointer( 1480 display, 1481 window, 1482 True, 1483 ButtonPressMask | ButtonMotionMask | 1484 ButtonReleaseMask | PointerMotionMask, 1485 GrabModeAsync, 1486 GrabModeAsync, 1487 None, 1488 None, 1489 time 1490 ); 1491 if (status != GrabSuccess) 1492 goto done; 1493 status = XGrabKeyboard( 1494 display, 1495 window, 1496 True, 1497 GrabModeAsync, 1498 GrabModeAsync, 1499 time 1500 ); 1501 if (status != GrabSuccess) 1502 goto done; 1503 if (miniature != None) 1504 XMapRaised(display, miniature); 1505 cursor = getcursor(cursors, CURSOR_DRAG); 1506 for (;;) { 1507 (void)XIfEvent(display, &event, &dndpred, (XPointer)&arg); 1508 switch (ctrlsel_send(context, &event)) { 1509 case CTRLSEL_LOST: 1510 retval = CTRLSEL_NONE; 1511 goto done; 1512 case CTRLSEL_INTERNAL: 1513 continue; 1514 default: 1515 break; 1516 } 1517 switch (event.type) { 1518 case KeyPress: 1519 case KeyRelease: 1520 if (event.xkey.keycode != 0 && 1521 event.xkey.keycode == XKeysymToKeycode(display, XK_Escape)) { 1522 retval = CTRLSEL_NONE; 1523 goto done; 1524 } 1525 break; 1526 case ButtonPress: 1527 case ButtonRelease: 1528 if (lastwin == None) { 1529 retval = CTRLSEL_NONE; 1530 } else if (lastwin == window) { 1531 retval = CTRLSEL_DROPSELF; 1532 } else { 1533 retval = CTRLSEL_DROPOTHER; 1534 d[1] = d[3] = d[4] = 0; 1535 d[2] = event.xbutton.time; 1536 clientmsg(display, lastwin, atoms[XDND_DROP], d); 1537 context->dndwindow = lastwin; 1538 } 1539 goto done; 1540 case MotionNotify: 1541 if (event.xmotion.time - time < MOTION_TIME) 1542 break; 1543 if (miniature != None) { 1544 XMoveWindow( 1545 display, 1546 miniature, 1547 event.xmotion.x_root + DND_DISTANCE, 1548 event.xmotion.y_root + DND_DISTANCE 1549 ); 1550 } 1551 inside = between(event.xmotion.x, event.xmotion.y, x, y, w, h); 1552 if ((lastaction != action || sendposition || !inside) 1553 && lastwin != None) { 1554 if (lastaction != None) 1555 d[4] = lastaction; 1556 else if (FLAG(event.xmotion.state, ControlMask|ShiftMask)) 1557 d[4] = atoms[XDND_ACTION_LINK]; 1558 else if (FLAG(event.xmotion.state, ShiftMask)) 1559 d[4] = atoms[XDND_ACTION_MOVE]; 1560 else if (FLAG(event.xmotion.state, ControlMask)) 1561 d[4] = atoms[XDND_ACTION_COPY]; 1562 else 1563 d[4] = atoms[XDND_ACTION_ASK]; 1564 d[1] = 0; 1565 d[2] = event.xmotion.x_root << 16; 1566 d[2] |= event.xmotion.y_root & 0xFFFF; 1567 d[3] = event.xmotion.time; 1568 clientmsg(display, lastwin, atoms[XDND_POSITION], d); 1569 sendposition = 1; 1570 } 1571 time = event.xmotion.time; 1572 lastaction = action; 1573 winbelow = getdndwindowbelow(display, wattr.root, atoms[XDND_AWARE], &version); 1574 if (winbelow == lastwin) 1575 break; 1576 sendposition = 1; 1577 x = y = w = h = 0; 1578 if (version > XDND_VERSION) 1579 version = XDND_VERSION; 1580 if (lastwin != None && lastwin != window) { 1581 d[1] = d[2] = d[3] = d[4] = 0; 1582 clientmsg(display, lastwin, atoms[XDND_LEAVE], d); 1583 } 1584 if (winbelow != None && winbelow != window) { 1585 d[1] = version; 1586 d[1] <<= 24; 1587 d[2] = ntargets > 0 ? targets[0].target : None; 1588 d[3] = ntargets > 1 ? targets[1].target : None; 1589 d[4] = ntargets > 2 ? targets[2].target : None; 1590 clientmsg(display, winbelow, atoms[XDND_ENTER], d); 1591 } 1592 if (winbelow == None) 1593 cursor = getcursor(cursors, CURSOR_NODROP); 1594 else if (FLAG(event.xmotion.state, ControlMask|ShiftMask)) 1595 cursor = getcursor(cursors, CURSOR_LINK); 1596 else if (FLAG(event.xmotion.state, ShiftMask)) 1597 cursor = getcursor(cursors, CURSOR_MOVE); 1598 else if (FLAG(event.xmotion.state, ControlMask)) 1599 cursor = getcursor(cursors, CURSOR_COPY); 1600 else 1601 cursor = getcursor(cursors, CURSOR_DRAG); 1602 XDefineCursor(display, window, cursor); 1603 lastwin = winbelow; 1604 lastaction = action = None; 1605 break; 1606 case ClientMessage: 1607 if ((Window)event.xclient.data.l[0] != lastwin) 1608 break; 1609 sendposition = (event.xclient.data.l[1] & 0x02); 1610 if (event.xclient.data.l[1] & 0x01) 1611 XDefineCursor(display, window, cursor); 1612 else 1613 XDefineCursor(display, window, getcursor(cursors, CURSOR_NODROP)); 1614 x = event.xclient.data.l[2] >> 16; 1615 y = event.xclient.data.l[2] & 0xFFF; 1616 w = event.xclient.data.l[3] >> 16; 1617 h = event.xclient.data.l[3] & 0xFFF; 1618 if ((Atom)event.xclient.data.l[4] != None) 1619 action = (Atom)event.xclient.data.l[4]; 1620 else 1621 action = atoms[XDND_ACTION_COPY]; 1622 break; 1623 case DestroyNotify: 1624 case UnmapNotify: 1625 XPutBackEvent(display, &event); 1626 retval = CTRLSEL_ERROR; 1627 goto done; 1628 default: 1629 break; 1630 } 1631 } 1632 done: 1633 XUndefineCursor(display, window); 1634 if (miniature != None) 1635 XUnmapWindow(display, miniature); 1636 XUngrabPointer(display, CurrentTime); 1637 XUngrabKeyboard(display, CurrentTime); 1638 freecursors(display, cursors); 1639 if (retval != CTRLSEL_DROPOTHER) { 1640 ctrlsel_dnddisown(context); 1641 context = NULL; 1642 } 1643 *context_ret = context; 1644 return retval; 1645 }