2 * Copyright Neil Brown ©2020-2023 <neil@brown.name>
3 * May be distributed under terms of GPLv2 - see file:COPYING
5 * xcbselection - integrate X11 clipboards with copybuf and selection.
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.
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.
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
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
24 * - If CLIPBOARD is requested while we own it, the result of "copy:get"
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.
33 * We also claim the edlib selection at startup on behalf of whichever X11
34 * application owns it.
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.
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
54 * - Notify:selection:commit - check if xcb-common can get selection
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
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.
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
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.
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.
85 * When we lose ownership of PRIMARY or CLIPBOARD, we discard any cached
86 * content from other client.
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".
92 * Some wisdom for understanding how to do this cam from xsel.c
93 * Thanks Conrad Parker <conrad@vergenet.net> !!
96 #define _GNU_SOURCE for ppoll
102 #include <xcb/xcbext.h>
104 #define PANE_DATA_TYPE struct xcbd_info
105 #define PANE_DATA_TYPE_2 struct xcbc_info
110 a_TIMESTAMP, a_TARGETS, a_MULTIPLE, a_INCR,
117 a_CLIPBOARD, a_PRIMARY,
122 static const char *atom_names[NR_ATOMS] = {
123 [a_TIMESTAMP] = "TIMESTAMP",
124 [a_TARGETS] = "TARGETS",
125 [a_MULTIPLE] = "MULTIPLE",
128 [a_STRING] = "STRING",
129 [a_UTF8_STRING] = "UTF8_STRING",
130 [a_COMPOUND_TEXT] = "COMPOUND_TEXT",
132 [a_textplain] = "text/plain",
133 [a_utf8] = "text/plain;charset=utf-8",
134 [a_UTF8] = "text/plain;charset=UTF-8",
136 [a_CLIPBOARD] = "CLIPBOARD",
137 [a_PRIMARY] = "PRIMARY",
138 [a_XSEL_DATA] = "XSEL_DATA",
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
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);
155 xcb_generic_event_t *ev;
160 struct command *c 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;
173 xcb_atom_t atoms[NR_ATOMS];
175 xcb_timestamp_t last_save;
176 xcb_timestamp_t timestamp; // "now"
177 xcb_timestamp_t have_primary, have_clipboard;
178 char *pending_content;
181 #include "core-pane.h"
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,
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);
192 /* Commit the selection - make it available for copy:get */
193 struct xcbc_info *xci = ci->home->data2;
195 pane_notify("Notify:xcb-commit", xci->p);
201 /* claim the selection - so other X11 clients and other edlib
202 * displays can ask for it
204 struct xcbc_info *xci = ci->home->data2;
206 home_pane_notify(xci->p, "Notify:xcb-claim", ci->focus);
207 claim_sel(xci, a_PRIMARY);
213 /* Claim the clipboard, because we just copied something to it */
214 struct xcbc_info *xci = ci->home->data2;
216 claim_sel(xci, a_CLIPBOARD); // and primary
222 /* If either PRIMARY or CLIPBOARD is newer than 'last_save',
223 * save it with "copy:save"
225 struct xcbc_info *xci = ci->home->data2;
226 enum my_atoms best = a_NULL;
230 if (!xci->have_primary) {
231 ts = collect_sel_stamp(xci, xci->atoms[a_PRIMARY]);
232 if (ts > xci->last_save) {
237 if (!xci->have_clipboard) {
238 ts = collect_sel_stamp(xci, xci->atoms[a_CLIPBOARD]);
239 if (ts > xci->last_save) {
245 collect_sel(xci, best);
249 DEF_CMD(xcbc_register_display)
251 pane_add_notify(ci->focus, ci->home, "Notify:xcb-claim");
252 pane_add_notify(ci->focus, ci->home, "Notify:xcb-commit");
254 pane_add_notify(ci->focus, ci->home, "Notify:xcb-check");
255 pane_add_notify(ci->home, ci->focus, "Notify:Close");
259 DEF_CMD(xcbc_handle_close)
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
264 if (pane_notify("Notify:xcb-check", ci->home) <= 0)
265 pane_close(ci->home);
269 DEF_CMD_CLOSED(xcbc_close)
271 struct xcbc_info *xci = ci->home->data2;
272 char *cn = strconcat(ci->home, "xcb-selection-", xci->display);
274 call_comm("global-set-command", ci->home, &edlib_noop,
276 xcb_disconnect(xci->conn);
278 free(xci->pending_content);
280 struct evlist *evl = xci->head;
281 xci->head = evl->next;
289 DEF_CMD(xcbd_copy_save)
291 struct xcbd_info *xdi = ci->home->data;
293 comm_call(xdi->c, "clip-set", ci->home);
297 DEF_CMD(xcbd_copy_get)
299 struct xcbd_info *xdi = ci->home->data;
302 /* If there is a selection, copy it now */
303 comm_call(xdi->c, "clip-get", ci->home);
307 DEF_CMD(xcbd_sel_claimed)
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.
314 struct xcbd_info *xdi = ci->home->data;
315 comm_call(xdi->c, "selection-claim", ci->home);
319 DEF_CMD(xcbd_sel_commit)
321 struct xcbd_info *xdi = ci->home->data;
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
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
337 DEF_CMD(xcbd_do_claim)
339 if (ci->focus == ci->home)
340 /* I'm asking for this, because my UI is claiming,
344 call("selection:claim", ci->home);
348 DEF_CMD(xcbd_do_commit)
350 struct xcbd_info *xdi = ci->home->data;
352 xdi->committing = True;
353 call("selection:commit", ci->home);
354 xdi->committing = False;
358 DEF_CMD(xcbc_do_check)
360 if (!(ci->home->damaged & DAMAGED_CLOSED))
361 /* Yes, I'm still here */
368 struct xcbc_info *xci = container_of(ci->comm, struct xcbc_info, c);
370 if (ci->home != ci->focus)
371 /* Not called via comm_call() retreived with global-get-command */
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);
379 static void xcbc_free_cmd(struct command *c safe)
381 struct xcbc_info *xci = container_of(c, struct xcbc_info, c);
388 struct xcbd_info *xdi;
394 d = pane_attr_get(ci->focus, "DISPLAY");
398 cn = strconcat(ci->focus, "xcb-selection-", d);
401 c = call_ret(comm, "global-get-command", ci->focus, 0, NULL, cn);
403 c = xcb_register(ci->focus, d);
406 call_comm("global-set-command", ci->focus, c,
409 p = pane_register(ci->focus, 0, &xcb_display_handle.c);
414 comm_call(c, "register", p);
415 call("selection:claim", p, 1);
416 comm_call(ci->comm2, "cb", p);
420 static void handle_property_notify(struct xcbc_info *xci,
421 xcb_property_notify_event_t *pne)
423 // FIXME Later - for INCR replies
428 static void handle_selection_clear(struct xcbc_info *xci safe,
429 xcb_selection_clear_event_t *sce safe)
431 if (sce->selection == xci->atoms[a_PRIMARY]) {
432 xci->have_primary = XCB_CURRENT_TIME;
433 pane_notify("Notify:xcb-claim", xci->p);
435 if (sce->selection == xci->atoms[a_CLIPBOARD])
436 xci->have_clipboard = XCB_CURRENT_TIME;
440 static void store_content(struct xcbc_info *xci safe, xcb_window_t requestor,
441 xcb_atom_t prop, xcb_atom_t target,
444 int len = strlen(content);
447 int max = (xci->maxlen+1)/2;
449 xcb_generic_error_t *ret = NULL;
455 c = xcb_change_property_checked(xci->conn,
456 XCB_PROP_MODE_REPLACE, requestor,
460 ret = xcb_request_check(xci->conn, c);
461 while ((!ret || ret->error_code == 0) && pos+send < len) {
462 /* Need to send some more */
467 c = xcb_change_property_checked(
469 XCB_PROP_MODE_APPEND, requestor,
474 ret = xcb_request_check(xci->conn, c);
476 if (!ret || ret->error_code != XCB_ALLOC) {
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?
486 LOG("Need to do INCR send after %d", pos);
490 static void handle_selection_request(struct xcbc_info *xci safe,
491 xcb_selection_request_event_t *sre safe)
494 xcb_selection_notify_event_t sne = {};
495 xcb_timestamp_t when = XCB_CURRENT_TIME;
497 // convert to iso-8859-15 for COMPOUND_TEXT and may for
499 //LOG("sel request %d", sre->target);
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;
508 for (a = 0; a < NR_TARGETS; a++)
509 if (xci->atoms[a] == sre->target)
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;
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);
535 LOG("Returning content for %s: %.20s",
536 sre->selection == xci->atoms[a_PRIMARY] ? "PRIMARY" : "CLIPBOARD",
539 store_content(xci, sre->requestor, sre->property,
540 sre->target, content);
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
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,
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");
562 sne.property = XCB_ATOM_NONE;
563 } else if (a == a_INCR) {
566 sne.property = XCB_ATOM_NONE;
568 xcb_send_event(xci->conn, 0, sre->requestor, 0, (void*)&sne);
569 xcb_flush(xci->conn);
573 #define Sec (1000 * 1000 * 1000)
574 #define Msec (1000 * 1000)
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)
580 struct timespec now, delay, deadline;
582 xcb_generic_event_t *ev;
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;
596 if (xcb_poll_for_reply(conn, request, (void**)&ev, e))
599 ev = xcb_poll_for_event(conn);
603 pfd.fd = xcb_get_file_descriptor(conn);
606 clock_gettime(CLOCK_MONOTONIC_COARSE, &now);
607 if (now.tv_sec > deadline.tv_sec)
609 if (now.tv_sec == deadline.tv_sec &&
610 now.tv_nsec >= deadline.tv_nsec)
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;
617 delay.tv_nsec = Sec + deadline.tv_nsec - now.tv_nsec;
619 if (delay.tv_sec == 0 && delay.tv_nsec < Msec)
620 /* Assume coarse granularity is at least 1ms */
622 if (ppoll(&pfd, 1, &delay, NULL) < 0)
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)
632 #define REPLY_TIMEO 50
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) \
640 return (xcb_##req##_reply_t *) \
641 xcb_wait_for_reply_timeo(c, cookie.sequence, \
645 DECL_REPLY_TIMEO(get_property)
646 DECL_REPLY_TIMEO(get_selection_owner)
647 DECL_REPLY_TIMEO(intern_atom)
649 static xcb_generic_event_t *wait_for(struct xcbc_info *xci safe,
653 xcb_generic_event_t *ev;
655 xcb_flush(xci->conn);
656 while ((ev = xcb_wait_for_event_timeo(xci->conn, 500)) != NULL) {
657 if ((ev->response_type & 0x7f) == type)
659 // LOG("Got %x wanted %x", ev->response_type & 0x7f, type);
661 xci->tail = &xci->head;
666 xci->tail = &evl->next;
671 static xcb_generic_event_t *next_event(struct xcbc_info *xci safe)
674 struct evlist *evl = xci->head;
675 xcb_generic_event_t *ev;
676 xci->head = evl->next;
678 xci->tail = &xci->head;
683 return xcb_poll_for_event(xci->conn);
688 struct xcbc_info *xci = ci->home->data2;
689 xcb_generic_event_t *ev;
693 /* This is a poll - only return 1 on something happening */
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:
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);
708 case XCB_SELECTION_CLEAR:
709 /* Someone claimed my select */
711 handle_selection_clear(xci, sce);
713 case XCB_SELECTION_REQUEST:
714 /* Someone wants my content */
716 handle_selection_request(xci, sre);
721 xcb_flush(xci->conn);
722 if (xcb_connection_has_error(xci->conn))
723 pane_close(ci->home);
727 static void get_timestamp(struct xcbc_info *xci safe)
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.
733 xcb_generic_event_t *ev;
734 xcb_property_notify_event_t *pev;
736 xcb_change_property(xci->conn, XCB_PROP_MODE_APPEND, xci->win,
737 XCB_ATOM_WM_NAME, XCB_ATOM_STRING,
739 ev = wait_for(xci, XCB_PROPERTY_NOTIFY);
743 xci->timestamp = pev->time;
747 static void claim_sel(struct xcbc_info *xci safe, enum my_atoms sel)
749 /* Always get primary, maybe get clipboard */
750 xcb_get_selection_owner_cookie_t pck, cck;
751 xcb_get_selection_owner_reply_t *rep;
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,
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;
767 LOG("failed to claim primary - have = %u",
768 (unsigned int)xci->have_primary);
769 xci->have_primary = XCB_CURRENT_TIME;
772 if (sel != a_PRIMARY) {
773 rep = xcb_get_selection_owner_reply_timeo(xci->conn, cck,
775 if (rep && rep->owner == xci->win)
776 xci->have_clipboard = xci->timestamp;
778 LOG("failed to claim clipboard - have = %u",
779 (unsigned int)xci->have_clipboard);
780 xci->have_clipboard = XCB_CURRENT_TIME;
786 static char *collect_incr(struct xcbc_info *xci safe,
787 xcb_atom_t self, int size_est)
790 xcb_delete_property(xci->conn, xci->win, xci->atoms[a_XSEL_DATA]);
794 static xcb_timestamp_t collect_sel_stamp(struct xcbc_info *xci safe,
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;
803 xcb_timestamp_t ret = XCB_CURRENT_TIME;
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);
812 if (nev->requestor != xci->win || nev->selection != sel ||
813 nev->property == XCB_ATOM_NONE) {
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);
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]);
834 static char *collect_sel_type(struct xcbc_info *xci safe,
835 xcb_atom_t sel, xcb_atom_t target)
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;
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);
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);
860 gpc = xcb_get_property(xci->conn, 0, xci->win, nev->property,
861 XCB_GET_PROPERTY_TYPE_ANY, start/4,
863 gpr = xcb_get_property_reply_timeo(xci->conn, gpc, NULL);
865 LOG("get property reply failed");
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);
876 if (gpr->format != 8) {
877 LOG("get_propery_value reported unsupported type: %d",
882 total = len + gpr->bytes_after + 1;
884 memcpy(ret+start, val, len);
885 while (len && gpr->bytes_after > 0) {
887 gpc = xcb_get_property(xci->conn, 0, xci->win, nev->property,
888 gpr->type, start/4, xci->maxlen/4/2);
890 gpr = xcb_get_property_reply_timeo(xci->conn, gpc, NULL);
892 LOG("get property reply failed");
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);
901 ret[start + len] = '\0';
903 xcb_delete_property(xci->conn, xci->win, xci->atoms[a_XSEL_DATA]);
913 static void strip_cr(char *from safe)
917 while ((*to = *from)) {
924 static void collect_sel(struct xcbc_info *xci safe, enum my_atoms sel)
926 /* If 'sel' can be fetched, copy:save it. */
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]};
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,
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;
955 ntargets = ARRAY_SIZE(dflt);
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 */
964 ret = collect_sel_type(xci, xci->atoms[sel],
969 LOG("copy:save from %s selection: %.20s",
970 sel == a_CLIPBOARD ? "CLIPBOARD" : "PRIMARY",
972 call("copy:save", xci->p, 0, NULL, ret);
979 static struct command *xcb_register(struct pane *p safe, char *display safe)
981 struct xcbc_info *xci;
983 xcb_connection_t *conn;
984 xcb_intern_atom_cookie_t cookies[NR_ATOMS];
985 xcb_screen_iterator_t iter;
991 disp_auth = pane_attr_get(p, "XAUTHORITY");
993 disp_auth = getenv("XAUTHORITY");
995 p2 = pane_register_2(pane_root(p), 0, &xcb_common_handle.c);
998 conn = xcb_connect_auth(display, disp_auth, &screen);
999 if (!conn || xcb_connection_has_error(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;
1012 for (i = 0; i < NR_ATOMS; i++) {
1013 const char *n = atom_names[i];
1016 cookies[i] = xcb_intern_atom(conn, 0, strlen(n), n);
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,
1023 0, 0, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_ONLY,
1024 xci->screen->root_visual,
1025 XCB_CW_EVENT_MASK, valwin);
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);
1034 xci->atoms[i] = r->atom;
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);
1049 void edlib_init(struct pane *ed safe)
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);
1056 key_add(xcb_common_map, "clip-set", &xcbc_set);
1057 key_add(xcb_common_map, "clip-get", &xcbc_get);
1059 key_add(xcb_common_map, "register", &xcbc_register_display);
1060 key_add(xcb_common_map, "Notify:Close", &xcbc_handle_close);
1062 key_add(xcb_common_map, "Close", &xcbc_close);
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",
1069 key_add(xcb_display_map, "Notify:selection:commit",
1071 key_add(xcb_display_map, "Notify:xcb-claim",
1073 key_add(xcb_display_map, "Notify:xcb-commit",
1075 key_add(xcb_display_map, "Notify:xcb-check",
1080 call_comm("global-set-command", ed, &xcbd_attach,
1081 0, NULL, "attach-x11selection");