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