tctrlsel.c - ltk - Socket-based GUI for X11 (WIP)
HTML git clone git://lumidify.org/ltk.git (fast, but not encrypted)
HTML git clone https://lumidify.org/git/ltk.git (encrypted, but very slow)
DIR Log
DIR Files
DIR Refs
DIR README
DIR LICENSE
---
tctrlsel.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 }