ctrlsel.c - ledit - Text editor (WIP) | |
git clone git://lumidify.org/ledit.git (fast, but not encrypted) | |
git clone https://lumidify.org/ledit.git (encrypted, but very slow) | |
git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/… | |
Log | |
Files | |
Refs | |
README | |
LICENSE | |
--- | |
ctrlsel.c (39384B) | |
--- | |
1 #include <stdlib.h> | |
2 #include <string.h> | |
3 | |
4 #include <X11/Xlib.h> | |
5 #include <X11/Xatom.h> | |
6 #include <X11/keysym.h> | |
7 #include <X11/cursorfont.h> | |
8 #include <X11/Xcursor/Xcursor.h> | |
9 | |
10 #include "ctrlsel.h" | |
11 | |
12 #define _TIMESTAMP_PROP "_TIMESTAMP_PROP" | |
13 #define TIMESTAMP "TIMESTAMP" | |
14 #define ATOM_PAIR "ATOM_PAIR" | |
15 #define MULTIPLE "MULTIPLE" | |
16 #define MANAGER "MANAGER" | |
17 #define TARGETS "TARGETS" | |
18 #define INCR "INCR" | |
19 #define SELDEFSIZE 0x4000 | |
20 #define FLAG(f, b) (((f) & (b)) == (b)) | |
21 #define MOTION_TIME 32 | |
22 #define DND_DISTANCE 8 /* distance from pointer to dnd … | |
23 #define XDND_VERSION 5 /* XDND protocol version */ | |
24 #define NCLIENTMSG_DATA 5 /* number of members on a the .d… | |
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… | |
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->transf… | |
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, … | |
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, Windo… | |
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 pro… | |
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 MULTIP… | |
463 if ((supported = calloc(nsupported, sizeof(*supported)))… | |
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, pr… | |
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, Fal… | |
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, Fa… | |
698 .format = 32, | |
699 .data.l[0] = time, /* timestamp */ | |
700 .data.l[1] = selection, /* manager selec… | |
701 .data.l[2] = window, /* window owning… | |
702 .data.l[3] = 0, /* manager-speci… | |
703 .data.l[4] = 0, /* manager-speci… | |
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… | |
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->reque… | |
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 … | |
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 =… | |
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 =… | |
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.wind… | |
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]… | |
1130 case CURSOR_PIRATE: | |
1131 case CURSOR_NODROP: | |
1132 return SOME(cursors[CURSOR_NODROP], cursors[CURSOR_PIRAT… | |
1133 case CURSOR_COPY: | |
1134 return SOME(cursors[CURSOR_COPY], cursors[CURSOR_DRAG], … | |
1135 case CURSOR_MOVE: | |
1136 return SOME(cursors[CURSOR_MOVE], cursors[CURSOR_DRAG], … | |
1137 case CURSOR_LINK: | |
1138 return SOME(cursors[CURSOR_LINK], cursors[CURSOR_DRAG], … | |
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-no… | |
1149 cursors[CURSOR_COPY] = XcursorLibraryLoadCursor(display, "dnd-co… | |
1150 cursors[CURSOR_MOVE] = XcursorLibraryLoadCursor(display, "dnd-mo… | |
1151 cursors[CURSOR_LINK] = XcursorLibraryLoadCursor(display, "dnd-li… | |
1152 cursors[CURSOR_NODROP] = XcursorLibraryLoadCursor(display, "forb… | |
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, Wind… | |
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 *versi… | |
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… | |
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], … | |
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… | |
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, F… | |
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->dndwind… | |
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->dndwind… | |
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->dndwind… | |
1324 return CTRLSEL_NONE; | |
1325 if (((Atom)event->xclient.data.l[4] == atoms[XDND_ACTION… | |
1326 context->dndactions & CTRLSEL_COPY) || | |
1327 ((Atom)event->xclient.data.l[4] == atoms[XDND_ACTION… | |
1328 context->dndactions & CTRLSEL_MOVE) || | |
1329 ((Atom)event->xclient.data.l[4] == atoms[XDND_ACTION… | |
1330 context->dndactions & CTRLSEL_LINK) || | |
1331 ((Atom)event->xclient.data.l[4] == atoms[XDND_ACTION… | |
1332 context->dndactions & CTRLSEL_ASK) || | |
1333 ((Atom)event->xclient.data.l[4] == atoms[XDND_ACTION… | |
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 s… | |
1342 d[3] = 0xFFFFFFFF; /* so we do not get lots of mess… | |
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… | |
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, ato… | |
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(displ… | |
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_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_DISTA… | |
1524 event.xmotion.y_root + DND_DISTA… | |
1525 ); | |
1526 } | |
1527 inside = between(event.xmotion.x, event.xmotion.… | |
1528 if ((lastaction != action || sendposition || !in… | |
1529 && lastwin != None) { | |
1530 if (lastaction != None) | |
1531 d[4] = lastaction; | |
1532 else if (FLAG(event.xmotion.state, Contr… | |
1533 d[4] = atoms[XDND_ACTION_LINK]; | |
1534 else if (FLAG(event.xmotion.state, Shift… | |
1535 d[4] = atoms[XDND_ACTION_MOVE]; | |
1536 else if (FLAG(event.xmotion.state, Contr… | |
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_P… | |
1545 sendposition = 1; | |
1546 } | |
1547 time = event.xmotion.time; | |
1548 lastaction = action; | |
1549 winbelow = getdndwindowbelow(display, wattr.root… | |
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_L… | |
1559 } | |
1560 if (winbelow != None && winbelow != window) { | |
1561 d[1] = version; | |
1562 d[1] <<= 24; | |
1563 d[2] = ntargets > 0 ? targets[0].target … | |
1564 d[3] = ntargets > 1 ? targets[1].target … | |
1565 d[4] = ntargets > 2 ? targets[2].target … | |
1566 clientmsg(display, winbelow, atoms[XDND_… | |
1567 } | |
1568 if (winbelow == None) | |
1569 cursor = getcursor(cursors, CURSOR_NODRO… | |
1570 else if (FLAG(event.xmotion.state, ControlMask|S… | |
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… | |
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 } |