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