ledit

Text editor (WIP)
git clone git://lumidify.org/ledit.git (fast, but not encrypted)
git clone https://lumidify.org/ledit.git (encrypted, but very slow)
git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ledit.git (over tor)
Log | Files | Refs | README | 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 }