ltk

Socket-based GUI for X11 (WIP)
git clone git://lumidify.org/ltk.git (fast, but not encrypted)
git clone https://lumidify.org/git/ltk.git (encrypted, but very slow)
Log | Files | Refs | README | LICENSE

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 }