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