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