]> git.neil.brown.name Git - edlib.git/blob - lib-x11selection-xcb.c
TODO: clean out done items.
[edlib.git] / lib-x11selection-xcb.c
1 /*
2  * Copyright Neil Brown ©2020-2023 <neil@brown.name>
3  * May be distributed under terms of GPLv2 - see file:COPYING
4  *
5  * xcbselection - integrate X11 clipboards with copybuf and selection.
6  *
7  * Use XCB directly to access PRIMARY and CLIPBOARD content in the X11
8  * server and use it to access the selection and recent copied
9  * content to other applications, and to use what is provided by those
10  * applications to satisfy internal requests.
11  *
12  * "PRIMARY" represents a selection which only gets copied when a paste
13  * request is made in another window.  "CLIPBOARD" represents content
14  * that has already been copied.
15  *
16  * - If we don't own either PRIMARY or CLIPBOARD (as is initially the case),
17  *   copy:get checks the timestamp on both (or whichever aren't owned) and
18  *   compares with anything we previously collected.  If one is newer than
19  *   newest, we collect it and copy:save it before falling through to let
20  *   it be returned.
21  * - If a local pane claims the selection, we claim PRIMARY.
22  * - If a local pane creates a copy with copy:save, we claim PRIMARY and
23  *   CLIPBOARD.
24  * - If CLIPBOARD is requested while we own it, the result of "copy:get"
25  *   is provided.
26  * - If PRIMARY or CLIPBOARD is requested while we own it, we ask for the
27  *   selection to be committed, then return the result of "copy:get".
28  *   The commit happens for TIMESTAMP or content, but not for TARGETS.
29  * - If copy:save is called locally (on this $DISPLAY) we claim both
30  *   PRIMARY and CLIPBOARD.
31  *
32  *
33  * We also claim the edlib selection at startup on behalf of whichever X11
34  * application owns it.
35  *
36  * As multiple display panes could use the same X11 display, and it only
37  * really makes sense to have a single selection manager per display,
38  * the code that talks to X11 does not live in the pane stack.  We
39  * create a global command "xcb-selection-$DISPLAY" which handles the
40  * display, and any display-stack on that DISPLAY gets an xcb_display
41  * pane which communicates with it.  The per-display selection becomes
42  * shared among all displays with the same DISPLAY.  The xcb-common
43  * command (which has a private pane for sending notifications) sits
44  * between all the different displays and the X11 display and when any
45  * claims the selection, it will claim from all the others.  When any
46  * requests the selection be committed, it will notify the current owner
47  * which will copy:save it.
48  *
49  * per-display pane handles:
50  * - copy:save - ask xcb-common to claim both
51  * - copy:get  - check if xcb-common can get clipboard content
52  * - Notify:selection:claimed  - tell xcb-common that this display has
53  *                               claimed selection
54  * - Notify:selection:commit   - check if xcb-common can get selection
55  *                               content
56  *
57  * When a mouse selection happens, the UI pane calls "selection:claim"
58  * which will typically result in the xcb:display pane getting notified
59  * that it losts the selection.  It tells the DISPLAY command
60  * "selection-claim" which then notifies all related per-display panes to
61  * claim their selections "Notify:xcb-claim", and claims PRIMARY on the
62  * X11 server.
63  *
64  * When text is explicitly copied the UI pane calls "copy:save".  Our
65  * per-display pane tells the DISPLAY command "clip-set" and it claims
66  * both PRIMARY and CLIPBOARD from X11.
67  *
68  * When text is pasted via mouse, the UI pane calls "selection:commit"
69  * and then "copy:get".  If some other display owns the selection, our
70  * xcb-display pane is notified and sends "selection-commit" to
71  * xcb-common.  If it doesn't own PRIMARY, it calls for the content of
72  * PRIMARY copy:saves the result and claims the clipboard.  The
73  * "copy:get" is then intercepted and xcb-common is asked for
74  * "clip-get".  As it owns the clipboard it allows the request to fall
75  * through.
76  *
77  * When text is yanked (pasted via keyboard), the "selection-commit"
78  * isn't performed, so xcb-common won't have taken ownership of
79  * CLIPBOARD.  So when copy:get calls "clip-get" xcb-common will
80  * fetch content for CLIPBOARD if it exists and return that.
81  *
82  * When we lose ownership of PRIMARY, we tell all per-display panes
83  * to claim the selection ("Notify:xcb-claim") on behalf of remote client.
84  *
85  * When we lose ownership of PRIMARY or CLIPBOARD, we discard any cached
86  * content from other client.
87  *
88  * When content of CLIPBOARD is requested, we use copy:get to collect it.
89  * When content of PRIMARY is requestd, we ask our per-display panes
90  * to call selection:commit first ("Notify:xcb-commit", then do "copy:get".
91  *
92  * Some wisdom for understanding how to do this cam from xsel.c
93  * Thanks Conrad Parker <conrad@vergenet.net> !!
94  *
95  */
96 #define _GNU_SOURCE for ppoll
97 #include <unistd.h>
98 #include <stdlib.h>
99 #include <poll.h>
100 #include <string.h>
101 #include <xcb/xcb.h>
102 #include <xcb/xcbext.h>
103
104 #define PANE_DATA_TYPE struct xcbd_info
105 #define PANE_DATA_TYPE_2 struct xcbc_info
106 #include "xcb.h"
107 #include "core.h"
108
109 enum my_atoms {
110         a_TIMESTAMP, a_TARGETS, a_MULTIPLE, a_INCR,
111         a_TEXT, a_STRING,
112         a_text, a_textplain,
113         a_COMPOUND_TEXT,
114         a_UTF8_STRING,
115         a_utf8, a_UTF8,
116         a_NULL,
117         a_CLIPBOARD, a_PRIMARY,
118         a_XSEL_DATA,
119         NR_ATOMS,
120         NR_TARGETS = a_NULL,
121 };
122 static const char *atom_names[NR_ATOMS] = {
123         [a_TIMESTAMP]           = "TIMESTAMP",
124         [a_TARGETS]             = "TARGETS",
125         [a_MULTIPLE]            = "MULTIPLE",
126         [a_INCR]                = "INCR",
127         [a_TEXT]                = "TEXT",
128         [a_STRING]              = "STRING",
129         [a_UTF8_STRING]         = "UTF8_STRING",
130         [a_COMPOUND_TEXT]       = "COMPOUND_TEXT",
131         [a_text]                = "text",
132         [a_textplain]           = "text/plain",
133         [a_utf8]                = "text/plain;charset=utf-8",
134         [a_UTF8]                = "text/plain;charset=UTF-8",
135         [a_NULL]                = "NULL",
136         [a_CLIPBOARD]           = "CLIPBOARD",
137         [a_PRIMARY]             = "PRIMARY",
138         [a_XSEL_DATA]           = "XSEL_DATA",
139 };
140
141 /* There are two different command maps.
142  * xcb_common_map is attached to the common pane and handles
143  *  requests sent to the DISPLAY command, and callbacks such as
144  *  per-display panes closing.
145  * xcb_display_map is attached to each per-display map and handles
146  *  selection notifications and copy:* requests.
147  * The DISPLAY command directs all valid request to the common
148  * pane.
149  */
150 static struct map *xcb_common_map, *xcb_display_map;
151 DEF_LOOKUP_CMD(xcb_common_handle, xcb_common_map);
152 DEF_LOOKUP_CMD(xcb_display_handle, xcb_display_map);
153
154 struct evlist {
155         xcb_generic_event_t     *ev;
156         struct evlist           *next;
157 };
158
159 struct xcbd_info {
160         struct command          *c safe;
161         bool                    committing;
162 };
163
164 struct xcbc_info {
165         struct command          c;
166         struct pane             *p safe;
167         char                    *display safe;
168         xcb_connection_t        *conn safe;
169         const xcb_setup_t       *setup safe;
170         const xcb_screen_t      *screen safe;
171         struct evlist           *head, **tail safe;
172         int                     maxlen;
173         xcb_atom_t              atoms[NR_ATOMS];
174         xcb_window_t            win;
175         xcb_timestamp_t         last_save;
176         xcb_timestamp_t         timestamp; // "now"
177         xcb_timestamp_t         have_primary, have_clipboard;
178         char                    *pending_content;
179         // targets??
180 };
181 #include "core-pane.h"
182
183 static void collect_sel(struct xcbc_info *xci safe, enum my_atoms sel);
184 static xcb_timestamp_t collect_sel_stamp(struct xcbc_info *xci safe,
185                                          xcb_atom_t sel);
186 static void claim_sel(struct xcbc_info *xci safe, enum my_atoms sel);
187 static struct command *xcb_register(struct pane *p safe, char *display safe);
188 static void get_timestamp(struct xcbc_info *xci safe);
189
190 DEF_CMD(xcbc_commit)
191 {
192         /* Commit the selection - make it available for copy:get */
193         struct xcbc_info *xci = ci->home->data2;
194
195         pane_notify("Notify:xcb-commit", xci->p);
196         return 1;
197 }
198
199 DEF_CMD(xcbc_claim)
200 {
201         /* claim the selection - so other X11 clients and other edlib
202          * displays can ask for it
203          */
204         struct xcbc_info *xci = ci->home->data2;
205
206         home_pane_notify(xci->p, "Notify:xcb-claim", ci->focus);
207         claim_sel(xci, a_PRIMARY);
208         return 1;
209 }
210
211 DEF_CMD(xcbc_set)
212 {
213         /* Claim the clipboard, because we just copied something to it */
214         struct xcbc_info *xci = ci->home->data2;
215
216         claim_sel(xci, a_CLIPBOARD); // and primary
217         return 1;
218 }
219
220 DEF_CMD(xcbc_get)
221 {
222         /* If either PRIMARY or CLIPBOARD is newer than 'last_save',
223          * save it with "copy:save"
224          */
225         struct xcbc_info *xci = ci->home->data2;
226         enum my_atoms best = a_NULL;
227         xcb_timestamp_t ts;
228
229         get_timestamp(xci);
230         if (!xci->have_primary) {
231                 ts = collect_sel_stamp(xci, xci->atoms[a_PRIMARY]);
232                 if (ts > xci->last_save) {
233                         xci->last_save = ts;
234                         best = a_PRIMARY;
235                 }
236         }
237         if (!xci->have_clipboard) {
238                 ts = collect_sel_stamp(xci, xci->atoms[a_CLIPBOARD]);
239                 if (ts > xci->last_save) {
240                         xci->last_save = ts;
241                         best = a_CLIPBOARD;
242                 }
243         }
244         if (best != a_NULL)
245                 collect_sel(xci, best);
246         return 1;
247 }
248
249 DEF_CMD(xcbc_register_display)
250 {
251         pane_add_notify(ci->focus, ci->home, "Notify:xcb-claim");
252         pane_add_notify(ci->focus, ci->home, "Notify:xcb-commit");
253
254         pane_add_notify(ci->focus, ci->home, "Notify:xcb-check");
255         pane_add_notify(ci->home, ci->focus, "Notify:Close");
256         return 1;
257 }
258
259 DEF_CMD(xcbc_handle_close)
260 {
261         /* A display window has closed.  If it was the last one we must
262          * close too, else we keep the x11 connection open unnecessarily
263          */
264         if (pane_notify("Notify:xcb-check", ci->home) <= 0)
265                 pane_close(ci->home);
266         return 1;
267 }
268
269 DEF_CMD_CLOSED(xcbc_close)
270 {
271         struct xcbc_info *xci = ci->home->data2;
272         char *cn = strconcat(ci->home, "xcb-selection-", xci->display);
273
274         call_comm("global-set-command", ci->home, &edlib_noop,
275                   0, NULL, cn);
276         xcb_disconnect(xci->conn);
277         free(xci->display);
278         free(xci->pending_content);
279         while (xci->head) {
280                 struct evlist *evl = xci->head;
281                 xci->head = evl->next;
282                 free(evl->ev);
283                 free(evl);
284         }
285
286         return 1;
287 }
288
289 DEF_CMD(xcbd_copy_save)
290 {
291         struct xcbd_info *xdi = ci->home->data;
292
293         comm_call(xdi->c, "clip-set", ci->home);
294         return Efallthrough;
295 }
296
297 DEF_CMD(xcbd_copy_get)
298 {
299         struct xcbd_info *xdi = ci->home->data;
300
301         if (ci->num == 0)
302                 /* If there is a selection, copy it now */
303                 comm_call(xdi->c, "clip-get", ci->home);
304         return Efallthrough;
305 }
306
307 DEF_CMD(xcbd_sel_claimed)
308 {
309         /* Something else on this display is claiming the selection.
310          * We need to tell the common pane to claim from elsewhere
311          * as a proxy.  When it trys to claim back from us,
312          * we will know to ignore because ->focus will be us.
313          */
314         struct xcbd_info *xdi = ci->home->data;
315         comm_call(xdi->c, "selection-claim", ci->home);
316         return Efallthrough;
317 }
318
319 DEF_CMD(xcbd_sel_commit)
320 {
321         struct xcbd_info *xdi = ci->home->data;
322
323         if (ci->focus != ci->home)
324                 /* Wasn't explicitly addressed to me, so must be for
325                  * some other pane (probably mode-emacs will handle it)
326                  * so just fall through
327                  */
328                 return Efallthrough;
329         if (!xdi->committing)
330                 comm_call(xdi->c, "selection-commit", ci->home);
331         /* '2' means 'call me again if someone else commits, I won't refresh
332          * my ownership.
333          */
334         return 2;
335 }
336
337 DEF_CMD(xcbd_do_claim)
338 {
339         if (ci->focus == ci->home)
340                 /* I'm asking for this, because my UI is claiming,
341                  * so I must ignore.
342                  */
343                 return Efallthrough;
344         call("selection:claim", ci->home);
345         return 1;
346 }
347
348 DEF_CMD(xcbd_do_commit)
349 {
350         struct xcbd_info *xdi = ci->home->data;
351
352         xdi->committing = True;
353         call("selection:commit", ci->home);
354         xdi->committing = False;
355         return 1;
356 }
357
358 DEF_CMD(xcbc_do_check)
359 {
360         if (!(ci->home->damaged & DAMAGED_CLOSED))
361                 /* Yes, I'm still here */
362                 return 1;
363         return Efallthrough;
364 }
365
366 DEF_CMD(xcb_common)
367 {
368         struct xcbc_info *xci = container_of(ci->comm, struct xcbc_info, c);
369
370         if (ci->home != ci->focus)
371                 /* Not called via comm_call() retreived with global-get-command */
372                 return Efallthrough;
373         return pane_call(xci->p, ci->key, ci->focus,
374                          ci->num, ci->mark, ci->str,
375                          ci->num2, ci->mark2, ci->str2,
376                          ci->x, ci->y, ci->comm2);
377 }
378
379 static void xcbc_free_cmd(struct command *c safe)
380 {
381         struct xcbc_info *xci = container_of(c, struct xcbc_info, c);
382
383         pane_close(xci->p);
384 }
385
386 DEF_CMD(xcbd_attach)
387 {
388         struct xcbd_info *xdi;
389         char *d;
390         char *cn;
391         struct command *c;
392         struct pane *p;
393
394         d = pane_attr_get(ci->focus, "DISPLAY");
395         if (!d || !*d)
396                 return Efalse;
397
398         cn = strconcat(ci->focus, "xcb-selection-", d);
399         if (!cn)
400                 return Efail;
401         c = call_ret(comm, "global-get-command", ci->focus, 0, NULL, cn);
402         if (!c) {
403                 c = xcb_register(ci->focus, d);
404                 if (!c)
405                         return Efail;
406                 call_comm("global-set-command", ci->focus, c,
407                           0, NULL, cn);
408         }
409         p = pane_register(ci->focus, 0, &xcb_display_handle.c);
410         if (!p)
411                 return Efail;
412         xdi = p->data;
413         xdi->c = c;
414         comm_call(c, "register", p);
415         call("selection:claim", p, 1);
416         comm_call(ci->comm2, "cb", p);
417         return 1;
418 }
419
420 static void handle_property_notify(struct xcbc_info *xci,
421                                    xcb_property_notify_event_t *pne)
422 {
423         // FIXME Later - for INCR replies
424
425         return;
426 }
427
428 static void handle_selection_clear(struct xcbc_info *xci safe,
429                                    xcb_selection_clear_event_t *sce safe)
430 {
431         if (sce->selection == xci->atoms[a_PRIMARY]) {
432                 xci->have_primary = XCB_CURRENT_TIME;
433                 pane_notify("Notify:xcb-claim", xci->p);
434         }
435         if (sce->selection == xci->atoms[a_CLIPBOARD])
436                 xci->have_clipboard = XCB_CURRENT_TIME;
437         return;
438 }
439
440 static void store_content(struct xcbc_info *xci safe, xcb_window_t requestor,
441                           xcb_atom_t prop, xcb_atom_t target,
442                           char *content safe)
443 {
444         int len = strlen(content);
445         int pos = 0;
446         int send;
447         int max = (xci->maxlen+1)/2;
448         xcb_void_cookie_t c;
449         xcb_generic_error_t *ret = NULL;
450
451         send = len;
452         if (send > max)
453                 send = max;
454
455         c = xcb_change_property_checked(xci->conn,
456                                         XCB_PROP_MODE_REPLACE, requestor,
457                                         prop, target,
458                                         8, send,
459                                         content + pos);
460         ret = xcb_request_check(xci->conn, c);
461         while ((!ret || ret->error_code == 0) && pos+send < len) {
462                 /* Need to send some more */
463                 pos += send;
464                 send = len - pos;
465                 if (send > max)
466                         send = max;
467                 c = xcb_change_property_checked(
468                         xci->conn,
469                         XCB_PROP_MODE_APPEND, requestor,
470                         prop, target,
471                         8, send,
472                         content + pos);
473                 free(ret);
474                 ret = xcb_request_check(xci->conn, c);
475         }
476         if (!ret || ret->error_code != XCB_ALLOC) {
477                 free(ret);
478                 free(content);
479                 return;
480         }
481         /* Got an ALLOC error, need to do INCR sent */
482         /* Possibly shoul do INCR much earlier.  Don't want too much
483          * wastage.  But then... I can send 2M without INCR,
484          * maybe there isn't a limit?
485          */
486         LOG("Need to do INCR send after %d", pos);
487         free(content);
488 }
489
490 static void handle_selection_request(struct xcbc_info *xci safe,
491                                      xcb_selection_request_event_t *sre safe)
492 {
493         int a;
494         xcb_selection_notify_event_t sne = {};
495         xcb_timestamp_t when = XCB_CURRENT_TIME;
496         // FIXME
497         // convert to iso-8859-15 for COMPOUND_TEXT and may for
498         // TEXT
499         //LOG("sel request %d", sre->target);
500
501         sne.response_type = XCB_SELECTION_NOTIFY;
502         sne.time = sre->time;
503         sne.requestor = sre->requestor;
504         sne.selection = sre->selection;
505         sne.target = sre->target;
506         sne.property = sre->property;
507
508         for (a = 0; a < NR_TARGETS; a++)
509                 if (xci->atoms[a] == sre->target)
510                         break;
511
512         if (sre->selection == xci->atoms[a_PRIMARY])
513                 when = xci->have_primary;
514         if (sre->selection == xci->atoms[a_CLIPBOARD])
515                 when = xci->have_clipboard;
516
517         if (when == XCB_CURRENT_TIME) {
518                 /* I want to require sre->time >= when, but sometimes it isn't. */
519                 LOG("x11selection-xcb request for selection not held %d %d %d",
520                     sre->selection, when, sre->time);
521                 sne.property = XCB_ATOM_NONE;
522         } else if (a >= NR_TARGETS) {
523                 LOG("unknown target %d", sre->target);
524                 sne.property = XCB_ATOM_NONE;
525         } else if (a >= a_TEXT) {
526                 char *content = NULL;
527                 // LOG("Request for text");
528                 if ((sre->selection == xci->atoms[a_PRIMARY] &&
529                      xci->have_primary) ||
530                     (sre->selection == xci->atoms[a_CLIPBOARD] &&
531                      xci->have_clipboard)) {
532                         pane_notify("Notify:xcb-commit", xci->p);
533                         content = call_ret(str, "copy:get", xci->p);
534                 }
535                 LOG("Returning content for %s: %.20s",
536                     sre->selection == xci->atoms[a_PRIMARY] ? "PRIMARY" : "CLIPBOARD",
537                     content);
538                 if (content)
539                         store_content(xci, sre->requestor, sre->property,
540                                       sre->target, content);
541                 else
542                         sne.property = XCB_ATOM_NONE;
543         } else if (a == a_TIMESTAMP) {
544                 // LOG("Sending timestamp");
545                 /* If there is a new selection, this will reclaim and update
546                  * timestamp.
547                  */
548                 pane_notify("Notify:xcb-commit", xci->p);
549                 xcb_change_property(xci->conn,
550                                     XCB_PROP_MODE_REPLACE, sre->requestor,
551                                     sre->property, XCB_ATOM_INTEGER, 32,
552                                     1, &when);
553         } else if (a == a_TARGETS) {
554                 // LOG("Sending targets to %d", sre->requestor);
555                 xcb_change_property(xci->conn,
556                                     XCB_PROP_MODE_REPLACE, sre->requestor,
557                                     sre->property, XCB_ATOM_ATOM, 32,
558                                     NR_TARGETS, xci->atoms);
559         } else if (a == a_MULTIPLE) {
560                 LOG("Failing MULTIPLE");
561                 // FIXME
562                 sne.property = XCB_ATOM_NONE;
563         } else if (a == a_INCR) {
564                 LOG("Failing INCR");
565                 // FIXME
566                 sne.property = XCB_ATOM_NONE;
567         }
568         xcb_send_event(xci->conn, 0, sre->requestor, 0, (void*)&sne);
569         xcb_flush(xci->conn);
570         return;
571 }
572
573 #define Sec (1000 * 1000 * 1000)
574 #define Msec (1000 * 1000)
575
576 static xcb_generic_event_t *_xcb_wait_for_event_timeo(
577         xcb_connection_t *conn, unsigned int request,
578         xcb_generic_error_t **e, int msecs)
579 {
580         struct timespec now, delay, deadline;
581         struct pollfd pfd;
582         xcb_generic_event_t *ev;
583
584         if (!conn)
585                 return NULL;
586         clock_gettime(CLOCK_MONOTONIC_COARSE, &deadline);
587         deadline.tv_nsec += msecs * Msec;
588         if (deadline.tv_nsec >= Sec) {
589                 deadline.tv_sec += 1;
590                 deadline.tv_nsec -= Sec;
591         }
592         if (request)
593                 xcb_flush(conn);
594         while (1) {
595                 if (request) {
596                         if (xcb_poll_for_reply(conn, request, (void**)&ev, e))
597                                 return ev;
598                 } else {
599                         ev = xcb_poll_for_event(conn);
600                         if (ev)
601                                 return ev;
602                 }
603                 pfd.fd = xcb_get_file_descriptor(conn);
604                 pfd.events = POLLIN;
605                 pfd.revents = 0;
606                 clock_gettime(CLOCK_MONOTONIC_COARSE, &now);
607                 if (now.tv_sec > deadline.tv_sec)
608                         return NULL;
609                 if (now.tv_sec == deadline.tv_sec &&
610                     now.tv_nsec >= deadline.tv_nsec)
611                         return NULL;
612                 delay.tv_sec = deadline.tv_sec - now.tv_sec;
613                 if (deadline.tv_nsec >= now.tv_nsec)
614                         delay.tv_nsec = deadline.tv_nsec - now.tv_nsec;
615                 else {
616                         delay.tv_sec -= 1;
617                         delay.tv_nsec = Sec + deadline.tv_nsec - now.tv_nsec;
618                 }
619                 if (delay.tv_sec == 0 && delay.tv_nsec < Msec)
620                         /* Assume coarse granularity is at least 1ms */
621                         return NULL;
622                 if (ppoll(&pfd, 1, &delay, NULL) < 0)
623                         return NULL;
624         }
625 }
626
627 #define xcb_wait_for_event_timeo(c, ms)         \
628         _xcb_wait_for_event_timeo(c, 0, NULL, ms)
629 #define xcb_wait_for_reply_timeo(c, rq, e, ms)          \
630         _xcb_wait_for_event_timeo(c, rq, e, ms)
631
632 #define REPLY_TIMEO 50
633
634 #define DECL_REPLY_TIMEO(req)                                           \
635         static inline xcb_##req##_reply_t *                             \
636         xcb_##req##_reply_timeo(xcb_connection_t *c,                    \
637                                 xcb_##req##_cookie_t cookie,            \
638                                 xcb_generic_error_t **e)                \
639         {                                                               \
640                 return (xcb_##req##_reply_t *)                          \
641                         xcb_wait_for_reply_timeo(c, cookie.sequence,    \
642                                                  e, REPLY_TIMEO);       \
643         }
644
645 DECL_REPLY_TIMEO(get_property)
646 DECL_REPLY_TIMEO(get_selection_owner)
647 DECL_REPLY_TIMEO(intern_atom)
648
649 static xcb_generic_event_t *wait_for(struct xcbc_info *xci safe,
650                                      uint8_t type)
651 {
652         struct evlist *evl;
653         xcb_generic_event_t *ev;
654
655         xcb_flush(xci->conn);
656         while ((ev = xcb_wait_for_event_timeo(xci->conn, 500)) != NULL) {
657                 if ((ev->response_type & 0x7f) == type)
658                         return ev;
659                 // LOG("Got %x wanted %x", ev->response_type & 0x7f, type);
660                 if (!xci->head)
661                         xci->tail = &xci->head;
662                 alloc(evl, pane);
663                 evl->ev = ev;
664                 evl->next = NULL;
665                 *xci->tail = evl;
666                 xci->tail = &evl->next;
667         }
668         return NULL;
669 }
670
671 static xcb_generic_event_t *next_event(struct xcbc_info *xci safe)
672 {
673         if (xci->head) {
674                 struct evlist *evl = xci->head;
675                 xcb_generic_event_t *ev;
676                 xci->head = evl->next;
677                 if (!xci->head)
678                         xci->tail = &xci->head;
679                 ev = evl->ev;
680                 unalloc(evl, pane);
681                 return ev;
682         }
683         return xcb_poll_for_event(xci->conn);
684 }
685
686 DEF_CMD(xcbc_input)
687 {
688         struct xcbc_info *xci = ci->home->data2;
689         xcb_generic_event_t *ev;
690         int ret = 1;
691
692         if (ci->num < 0)
693                 /* This is a poll - only return 1 on something happening */
694                 ret = Efalse;
695
696         while((ev = next_event(xci)) != NULL) {
697                 switch (ev->response_type & 0x7f) {
698                         xcb_property_notify_event_t *pne;
699                         xcb_selection_clear_event_t *sce;
700                         xcb_selection_request_event_t *sre;
701                 case XCB_PROPERTY_NOTIFY:
702                         pne = (void*)ev;
703                         xci->timestamp = pne->time;
704                         if (pne->state == XCB_PROPERTY_DELETE)
705                                 // might need to provide more content
706                                 handle_property_notify(xci, pne);
707                         break;
708                 case XCB_SELECTION_CLEAR:
709                         /* Someone claimed my select */
710                         sce = (void*)ev;
711                         handle_selection_clear(xci, sce);
712                         break;
713                 case XCB_SELECTION_REQUEST:
714                         /* Someone wants my content */
715                         sre = (void*)ev;
716                         handle_selection_request(xci, sre);
717                         break;
718                 }
719                 free(ev);
720         }
721         xcb_flush(xci->conn);
722         if (xcb_connection_has_error(xci->conn))
723                 pane_close(ci->home);
724         return ret;
725 }
726
727 static void get_timestamp(struct xcbc_info *xci safe)
728 {
729         /* We need a timestamp - use property change which appends
730          * nothing to WM_NAME to get it.  This will cause a
731          * XCB_PROPERT_NOTIFY event.
732          */
733         xcb_generic_event_t *ev;
734         xcb_property_notify_event_t *pev;
735
736         xcb_change_property(xci->conn, XCB_PROP_MODE_APPEND, xci->win,
737                             XCB_ATOM_WM_NAME, XCB_ATOM_STRING,
738                             8, 0, NULL);
739         ev = wait_for(xci, XCB_PROPERTY_NOTIFY);
740         if (!ev)
741                 return;
742         pev = (void*)ev;
743         xci->timestamp = pev->time;
744         free(ev);
745 }
746
747 static void claim_sel(struct xcbc_info *xci safe, enum my_atoms sel)
748 {
749         /* Always get primary, maybe get clipboard */
750         xcb_get_selection_owner_cookie_t pck, cck;
751         xcb_get_selection_owner_reply_t *rep;
752
753         get_timestamp(xci);
754         xcb_set_selection_owner(xci->conn, xci->win,
755                                 xci->atoms[a_PRIMARY], xci->timestamp);
756         if (sel != a_PRIMARY)
757                 xcb_set_selection_owner(xci->conn, xci->win,
758                                         xci->atoms[sel], xci->timestamp);
759         pck = xcb_get_selection_owner(xci->conn, xci->atoms[a_PRIMARY]);
760         if (sel != a_PRIMARY)
761                 cck = xcb_get_selection_owner(xci->conn,
762                                               xci->atoms[sel]);
763         rep = xcb_get_selection_owner_reply_timeo(xci->conn, pck, NULL);
764         if (rep && rep->owner == xci->win)
765                 xci->have_primary = xci->timestamp;
766         else {
767                 LOG("failed to claim primary - have = %u",
768                     (unsigned int)xci->have_primary);
769                 xci->have_primary = XCB_CURRENT_TIME;
770         }
771         free(rep);
772         if (sel != a_PRIMARY) {
773                 rep = xcb_get_selection_owner_reply_timeo(xci->conn, cck,
774                                                           NULL);
775                 if (rep && rep->owner == xci->win)
776                         xci->have_clipboard = xci->timestamp;
777                 else {
778                         LOG("failed to claim clipboard - have = %u",
779                             (unsigned int)xci->have_clipboard);
780                         xci->have_clipboard = XCB_CURRENT_TIME;
781                 }
782                 free(rep);
783         }
784 }
785
786 static char *collect_incr(struct xcbc_info *xci safe,
787                           xcb_atom_t self, int size_est)
788 {
789         // FIXME;
790         xcb_delete_property(xci->conn, xci->win, xci->atoms[a_XSEL_DATA]);
791         return NULL;
792 }
793
794 static xcb_timestamp_t collect_sel_stamp(struct xcbc_info *xci safe,
795                                          xcb_atom_t sel)
796 {
797         xcb_generic_event_t *ev;
798         xcb_selection_notify_event_t *nev;
799         xcb_get_property_cookie_t gpc;
800         xcb_get_property_reply_t *gpr;
801         void *val;
802         unsigned int len;
803         xcb_timestamp_t ret = XCB_CURRENT_TIME;
804
805         xcb_convert_selection(xci->conn, xci->win, sel,
806                               xci->atoms[a_TIMESTAMP],
807                               xci->atoms[a_XSEL_DATA], xci->timestamp);
808         ev = wait_for(xci, XCB_SELECTION_NOTIFY);
809         if (!ev)
810                 return ret;
811         nev = (void*)ev;
812         if (nev->requestor != xci->win || nev->selection != sel ||
813             nev->property == XCB_ATOM_NONE) {
814                 free(ev);
815                 return ret;
816         }
817         gpc = xcb_get_property(xci->conn, 0, xci->win, nev->property,
818                                XCB_ATOM_INTEGER, 0, 4);
819         gpr = xcb_get_property_reply_timeo(xci->conn, gpc, NULL);
820         if (!gpr) {
821                 free(ev);
822                 return ret;
823         }
824         val = xcb_get_property_value(gpr);
825         len = xcb_get_property_value_length(gpr);
826         if (gpr->type == XCB_ATOM_INTEGER && len == 4 && val)
827                 ret = *(uint32_t*)val;
828         xcb_delete_property(xci->conn, xci->win, xci->atoms[a_XSEL_DATA]);
829         free(gpr);
830         free(ev);
831         return ret;
832 }
833
834 static char *collect_sel_type(struct xcbc_info *xci safe,
835                               xcb_atom_t sel, xcb_atom_t target)
836 {
837         xcb_generic_event_t *ev;
838         xcb_selection_notify_event_t *nev;
839         xcb_get_property_cookie_t gpc;
840         xcb_get_property_reply_t *gpr = NULL;
841         void *val;
842         unsigned int len;
843         char *ret = NULL;
844         int start = 0;
845         unsigned int total;
846
847         xcb_convert_selection(xci->conn, xci->win, sel, target,
848                               xci->atoms[a_XSEL_DATA], xci->timestamp);
849         ev = wait_for(xci, XCB_SELECTION_NOTIFY);
850         if (!ev)
851                 return NULL;
852         nev = (void*)ev;
853         if (nev->requestor != xci->win || nev->selection != sel ||
854             nev->property == XCB_ATOM_NONE) {
855                 LOG("not for me  %d/%d %d/%d %d/%d", nev->requestor, xci->win,
856                     nev->selection, sel, nev->property, XCB_ATOM_NONE);
857                 free(ev);
858                 return NULL;
859         }
860         gpc = xcb_get_property(xci->conn, 0, xci->win, nev->property,
861                                XCB_GET_PROPERTY_TYPE_ANY, start/4,
862                                xci->maxlen/4/2);
863         gpr = xcb_get_property_reply_timeo(xci->conn, gpc, NULL);
864         if (!gpr) {
865                 LOG("get property reply failed");
866                 goto abort;
867         }
868         val = xcb_get_property_value(gpr);
869         len = xcb_get_property_value_length(gpr);
870         if (gpr->type == xci->atoms[a_INCR] && len >= sizeof(uint32_t) && val) {
871                 ret = collect_incr(xci, sel, *(uint32_t*)val);
872                 free(gpr);
873                 free(ev);
874                 return ret;
875         }
876         if (gpr->format != 8) {
877                 LOG("get_propery_value reported unsupported type: %d",
878                     gpr->format);
879                 free(gpr);
880                 goto abort;
881         }
882         total = len + gpr->bytes_after + 1;
883         ret = malloc(total);
884         memcpy(ret+start, val, len);
885         while (len && gpr->bytes_after > 0) {
886                 start += len;
887                 gpc = xcb_get_property(xci->conn, 0, xci->win, nev->property,
888                                        gpr->type, start/4, xci->maxlen/4/2);
889                 free(gpr);
890                 gpr = xcb_get_property_reply_timeo(xci->conn, gpc, NULL);
891                 if (!gpr) {
892                         LOG("get property reply failed");
893                         goto abort;
894                 }
895                 val = xcb_get_property_value(gpr);
896                 len = xcb_get_property_value_length(gpr);
897                 if (start + len >= total)
898                         len = total - 1 - start;
899                 memcpy(ret+start, val, len);
900         }
901         ret[start + len] = '\0';
902
903         xcb_delete_property(xci->conn, xci->win, xci->atoms[a_XSEL_DATA]);
904         free(gpr);
905         free(ev);
906         return ret;
907 abort:
908         free(ev);
909         free(ret);
910         return NULL;
911 }
912
913 static void strip_cr(char *from safe)
914 {
915         char *to = from;
916
917         while ((*to = *from)) {
918                 from++;
919                 if (*to != '\r')
920                         to++;
921         }
922 }
923
924 static void collect_sel(struct xcbc_info *xci safe, enum my_atoms sel)
925 {
926         /* If 'sel' can be fetched, copy:save it. */
927         char *ret = NULL;
928         enum my_atoms a;
929         xcb_selection_notify_event_t *nev;
930         xcb_get_property_reply_t *gpr = NULL;
931         xcb_atom_t *targets = NULL,
932                 dflt[] = {XCB_ATOM_STRING, xci->atoms[a_TEXT]};
933         int ntargets;
934         int i;
935
936         xcb_convert_selection(xci->conn, xci->win, xci->atoms[sel],
937                               xci->atoms[a_TARGETS],
938                               xci->atoms[a_XSEL_DATA], xci->timestamp);
939         nev = (void*)wait_for(xci, XCB_SELECTION_NOTIFY);
940         if (nev && nev->requestor == xci->win &&
941             nev->selection == xci->atoms[sel] &&
942             nev->property != XCB_ATOM_NONE) {
943                 xcb_get_property_cookie_t gpc;
944                 gpc = xcb_get_property(xci->conn, 0, xci->win, nev->property,
945                                        XCB_ATOM_ATOM, 0,
946                                        xci->maxlen/4/2);
947                 gpr = xcb_get_property_reply_timeo(xci->conn, gpc, NULL);
948                 if (gpr && gpr->type == XCB_ATOM_ATOM && gpr->format == 32) {
949                         targets = (void*) xcb_get_property_value(gpr);
950                         ntargets = gpr->value_len;
951                 }
952         }
953         if (!targets) {
954                 targets = dflt;
955                 ntargets = ARRAY_SIZE(dflt);
956         }
957
958         for (a = NR_TARGETS - 1; !ret && a >= a_TEXT; a -= 1) {
959                 for (i = 0; i < ntargets; i++)
960                         if (targets[i] == xci->atoms[a])
961                                 /* This one please */
962                                 break;
963                 if (i < ntargets)
964                         ret = collect_sel_type(xci, xci->atoms[sel],
965                                                targets[i]);
966         }
967         if (ret) {
968                 strip_cr(ret);
969                 LOG("copy:save from %s selection: %.20s",
970                     sel == a_CLIPBOARD ? "CLIPBOARD" : "PRIMARY",
971                     ret);
972                 call("copy:save", xci->p, 0, NULL, ret);
973                 free(ret);
974         }
975         free(gpr);
976         free(nev);
977 }
978
979 static struct command *xcb_register(struct pane *p safe, char *display safe)
980 {
981         struct xcbc_info *xci;
982         struct pane *p2;
983         xcb_connection_t *conn;
984         xcb_intern_atom_cookie_t cookies[NR_ATOMS];
985         xcb_screen_iterator_t iter;
986         uint32_t valwin[1];
987         int screen;
988         int i;
989         char *disp_auth;
990
991         disp_auth = pane_attr_get(p, "XAUTHORITY");
992         if (!disp_auth)
993                 disp_auth = getenv("XAUTHORITY");
994
995         p2 = pane_register_2(pane_root(p), 0, &xcb_common_handle.c);
996         if (!p2)
997                 return NULL;
998         conn = xcb_connect_auth(display, disp_auth, &screen);
999         if (!conn || xcb_connection_has_error(conn))
1000                 goto abort;
1001         xci = p2->data2;
1002         xci->p = p2;
1003         xci->conn = conn;
1004         xci->display = strdup(display);
1005         xci->setup = safe_cast xcb_get_setup(conn);
1006         xci->maxlen = xci->setup->maximum_request_length;
1007         iter = xcb_setup_roots_iterator(xci->setup);
1008         for (i = 0; i < screen; i++)
1009                 xcb_screen_next(&iter);
1010         xci->screen = safe_cast iter.data;
1011
1012         for (i = 0; i < NR_ATOMS; i++) {
1013                 const char *n = atom_names[i];
1014                 if (!n)
1015                         continue;
1016                 cookies[i] = xcb_intern_atom(conn, 0, strlen(n), n);
1017         }
1018         /* Need a dedicated (invisible) window to getting events */
1019         xci->win = xcb_generate_id(conn);
1020         valwin[0] = XCB_EVENT_MASK_PROPERTY_CHANGE;
1021         xcb_create_window(conn, XCB_COPY_FROM_PARENT, xci->win,
1022                                     xci->screen->root,
1023                                     0, 0, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_ONLY,
1024                                     xci->screen->root_visual,
1025                                     XCB_CW_EVENT_MASK, valwin);
1026         get_timestamp(xci);
1027         xcb_flush(conn);
1028         /* Now resolve all those cookies */
1029         for (i = 0; i < NR_ATOMS; i++) {
1030                 xcb_intern_atom_reply_t *r;
1031                 r = xcb_intern_atom_reply_timeo(conn, cookies[i], NULL);
1032                 if (!r)
1033                         goto abort;
1034                 xci->atoms[i] = r->atom;
1035                 free(r);
1036         }
1037
1038         xci->c = xcb_common;
1039         xci->c.free = xcbc_free_cmd;
1040         call_comm("event:read", p2, &xcbc_input, xcb_get_file_descriptor(conn));
1041         call_comm("event:poll", p2, &xcbc_input);
1042         return &xci->c;
1043
1044 abort:
1045         pane_close(p2);
1046         return NULL;
1047 }
1048
1049 void edlib_init(struct pane *ed safe)
1050 {
1051         if (!xcb_common_map) {
1052                 xcb_common_map = key_alloc();
1053                 key_add(xcb_common_map, "selection-commit", &xcbc_commit);
1054                 key_add(xcb_common_map, "selection-claim", &xcbc_claim);
1055
1056                 key_add(xcb_common_map, "clip-set", &xcbc_set);
1057                 key_add(xcb_common_map, "clip-get", &xcbc_get);
1058
1059                 key_add(xcb_common_map, "register", &xcbc_register_display);
1060                 key_add(xcb_common_map, "Notify:Close", &xcbc_handle_close);
1061
1062                 key_add(xcb_common_map, "Close", &xcbc_close);
1063
1064                 xcb_display_map = key_alloc();
1065                 key_add(xcb_display_map, "copy:save", &xcbd_copy_save);
1066                 key_add(xcb_display_map, "copy:get", &xcbd_copy_get);
1067                 key_add(xcb_display_map, "Notify:selection:claimed",
1068                         &xcbd_sel_claimed);
1069                 key_add(xcb_display_map, "Notify:selection:commit",
1070                         &xcbd_sel_commit);
1071                 key_add(xcb_display_map, "Notify:xcb-claim",
1072                         &xcbd_do_claim);
1073                 key_add(xcb_display_map, "Notify:xcb-commit",
1074                         &xcbd_do_commit);
1075                 key_add(xcb_display_map, "Notify:xcb-check",
1076                         &xcbc_do_check);
1077
1078         }
1079
1080         call_comm("global-set-command", ed, &xcbd_attach,
1081                   0, NULL, "attach-x11selection");
1082 }