-
Notifications
You must be signed in to change notification settings - Fork 0
/
libxclip.c
1223 lines (1039 loc) · 43.1 KB
/
libxclip.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
// libxclip -- If xclip / xsel was a C library
// Copyright (C) 2024 Emma Bastås <emma.bastas@protonmail.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
#include "./libxclip.h"
#include <stdlib.h>
#include <assert.h> // for assert
#include <unistd.h> // for fork, read, write and pipe
#include <stdio_ext.h> // for __fpurge
#include <time.h>
#include <string.h>
#include <X11/Xlib.h>
// #define DEBUG
#ifdef DEBUG
// for fprintf -- even though stdio_ext.h include stdio.h for us
#include <stdio.h>
#endif
struct libxclip_putopts {
char phantom; // Just here to remove -pedantic warning
};
/*
* Timeout related utilies
*
* In our library functions we want to give the caller the abillity to specify
* a timeout after which the library function should abourt an ongoing transfer
* and return empty handed —— to avoid scenarios where some ill'behaved owner /
* requestor causes our library function to block indefinetly.
*
* Now handling anything timeout related in C seams to be slightly messy
* compared to if you're used to JavaScript's `setTimeout` or the like. However,
* luckilly we only need two small utillity functions `x_millisecs_from_now`
* which returns a `struct timespec` representing —— as you'd imagine —— X mill-
* iseconds from the current time, and a second utillity `XNextEvent_timeout`
* which is like XNextEvent except with a timeout. Of note though is that
* XNextEvent_timeout asks for an "absolute" point in time ("You should time-
* out if the clocks strikes X") unlike a JS like setTimeout ("You should time-
* out if X time passes")
*/
// With a long as the datatype we can specify a point in time almost 600 hours
// from now, should suffice (famous last words).
static void x_millisecs_from_now(long millisecs, struct timespec *ts_return) {
// Are we compiling with POSIX real-time extensions? We need it for this
// functions implementation.
#if _POSIX_C_SOURCE < 199309L
#error "Need POSIX real-time exension! (for instance -std=gnu99)"
#endif
clock_gettime(CLOCK_MONOTONIC, ts_return);
// Break up millisecs into seconds + nanoseconds
ts_return->tv_sec += millisecs / 1000;
ts_return->tv_nsec += (millisecs % 1000) * 1000000;
}
// Expects the `timeout` variable to have been generated by
// `x_millisecs_from_now`
//
// Returns -1 if it timed out, 0 otherwise.
static int XNextEvent_timeout(Display *display,
XEvent *event_ret,
struct timespec timeout) {
// Are we compiling with POSIX real-time extensions? We need it for this
// functions implementation.
#if _POSIX_C_SOURCE < 199309L
#error "Need POSIX real-time exension! (for instance -std=gnu99)"
#endif
struct timespec ts_current;
while (True) {
// Is there an event in the queue now?
if (XPending(display) > 0) {
XNextEvent(display, event_ret);
return 0;
}
// No event in queue, should we timeout?
clock_gettime(CLOCK_MONOTONIC, &ts_current);
if (ts_current.tv_sec > timeout.tv_sec) {
return -1;
} else if (ts_current.tv_sec == timeout.tv_sec
&& ts_current.tv_nsec > timeout.tv_nsec) {
return -1;
}
// No we should not timeout, sleep for approx 1 microsecond.
usleep(1);
}
}
/*
* Dynamic buffer
*
* In libxclip_get, in the scenario where selection contents is being transferd
* incrementally we need some sort of dynamic buffer. We implement such a
* dynamic buffer now.
*
* AS AN ASIDE, libxclip_get dynamically allocates one big buffer that can hold
* the entire selections contents, this is not very efficent considering most
* use-cases I imagine wouldn't need to hold on to any data. So some more
* appropriate methods that I can think of instead would be to have the caller
* supply a callback or a pipe where they can recive selection contents
* incrementally. If there was a very simple builtin way of saying "take this
* pipe and read all of it's content and give me one big buffer with the
* contents" then I think I would have choosen pipes over dynamically allocated
* buffer, because I imagine that's more efficent but still really convinient
* for the caller to collect all of it in case they have a more niche use-case.
*/
// TODO: It would probably be wise to add error handling in case the buffer
// grows too large.
// The user should not meddle with the contents of the struct, only read `start`
// and `size`.
struct DynamicBuffer {
char *ptr; // Where the buffer starts.
size_t size; // How much data is stored already.
size_t capacity; // Total number of bytes allocated.
// remaining = capacity - size
};
// TODO: Have some more inteligent strategy than increasing the size by some
// fixed amount.
static const size_t DYNAMIC_BUFFER_BLOCK_SIZE = 4096 * 4;
static void dynamic_buffer_new(struct DynamicBuffer *buffer_ret) {
char *ptr = calloc(DYNAMIC_BUFFER_BLOCK_SIZE, sizeof(char));
if (ptr == NULL) {
// TODO: Do something with this.
assert(False);
}
buffer_ret->ptr = ptr;
buffer_ret->size = 0;
buffer_ret->capacity = DYNAMIC_BUFFER_BLOCK_SIZE;
}
// Append some data to our dynamic buffer, reallocating if we need more space
static void dynamic_buffer_append(struct DynamicBuffer *buffer,
char *data,
size_t len) {
// Do we need to reallocate more space?
if (buffer->size + len > buffer->capacity) {
// our new buffer will have enough space for the new data
// + DYNAMIC_BUFFER_BLOCK_SIZE.
const size_t NEW_CAPACITY =
buffer->size + len + DYNAMIC_BUFFER_BLOCK_SIZE;
char *new_ptr = realloc(buffer->ptr, NEW_CAPACITY);
if (new_ptr == NULL) {
// TODO: Do something with this.
free(buffer->ptr);
assert(False);
}
buffer->ptr = new_ptr;
buffer->capacity = NEW_CAPACITY;
}
assert(buffer->size + len <= buffer->capacity);
// Append new data.
memcpy(buffer->ptr + buffer->size, data, len);
buffer->size += len;
}
// We do not have a free function because it on the caller to free the
// underlying when the time commes
// static void dynamic_buffer_free(struct DynamicBuffer *buffer) {
// free(buffer->ptr);
// }
// The selection we hold may be so large we have to transfer it in chunks, in
// which case we have to keep track of our ongoing transfers. We do this with
// this struct, which forms a linked list.
struct transfer {
// The window associated with the requestor, this should be all that's
// needed to uniquely identify a requestor.
Window requestor_window;
Atom property; // The property where we're supposed "put" the chunk
size_t bytes_transfered;
struct transfer *next;
};
// Returns a transfer whose requestor_window is the one specified, or NULL of no
// such transfer was found.
static struct transfer *
get_transfer(struct transfer **head, Window requestor_window) {
struct transfer *current = *head;
while (current != NULL) {
if (current->requestor_window == requestor_window) {
return current;
}
current = current->next;
}
return NULL;
}
// Make a new transfer.
// An invariant is that no existing transfer with that requestor_window property
// exists already.
static void new_transfer(struct transfer **head, Window window, Atom property) {
struct transfer *new_transfer = calloc(1, sizeof(struct transfer));
if (new_transfer == NULL) { // couldn't allocate memory. Pretty fatal
#ifdef DEBUG
printf("COULDN'T ALLOCATE MEMORY");
assert(False);
#endif
// TODO: Is this the right way to do it?
exit(1);
}
new_transfer->requestor_window = window;
new_transfer->property = property;
new_transfer->bytes_transfered = 0;
new_transfer->next = *head;
*head = new_transfer;
}
static void delete_transfer(struct transfer **head, struct transfer *transfer) {
if ((*head)->requestor_window == transfer->requestor_window) {
*head = transfer->next;
free(transfer);
return;
}
struct transfer *past = NULL;
struct transfer *current = *head;
while (current != NULL) {
if (current->requestor_window == transfer->requestor_window) {
past->next = current->next;
free(current);
return;
}
past = current;
current = current->next;
}
assert(False);
}
/*
* Initializer for libxclip_getotpts
*/
void libxclip_getopts_initialize(struct libxclip_getopts *options) {
options->selection = None; // None = CLIPBOARD
options->target = None; // None = UTF8_STRING
options->timeout = -1; // -1 = no timeout
}
static void xclipboard_respond(XEvent request,
Atom property,
Atom selection,
Atom target) {
XEvent response;
// Perhaps FIXME: According to ICCCM section 2.5, we should
// confirm that XChangeProperty succeeded without any Alloc
// errors before replying with SelectionNotify. However, doing
// so would require an error handler which modifies a global
// variable, plus doing XSync after each XChangeProperty.
if (request.type == SelectionRequest) {
response.xselection.property = property;
response.xselection.type = SelectionNotify;
response.xselection.display = request.xselectionrequest.display;
response.xselection.requestor = request.xselectionrequest.requestor;
response.xselection.selection = selection;
response.xselection.target = target;
response.xselection.time = request.xselectionrequest.time;
XSendEvent(request.xselectionrequest.display,
request.xselectionrequest.requestor,
True,
0,
&response);
} else if (request.type == PropertyNotify) {
response.xselection.property = property;
response.xselection.type = SelectionNotify;
response.xselection.display = request.xproperty.display;
response.xselection.requestor = request.xproperty.window;
response.xselection.selection = selection;
response.xselection.target = target;
response.xselection.time = request.xproperty.time;
XSendEvent(request.xproperty.display,
request.xproperty.window,
True,
0,
&response);
} else {
assert(False);
}
XFlush(request.xselectionrequest.display);
// TODO what errors can this generate?
}
int libxclip_put(Display *display,
char *data,
size_t len,
libxclip_putopts *options) {
// The first thing we do, in an attempt to avoid race conditions,
// missed events, and so on, is to create the child process and then have
// the parent process freeze until the child process has performed all it's
// setup.
// NB. The selections contents are stored in `data` and the child process
// will of course read from this. What happens though in the case that the
// parent process exits before the child process is finished? One might
// think that all data allocated by the parent process is freed, including
// what `data` points to. This is true in some sense, but `fork` performs a
// copy-on-write duplication on all of the heap contents from the parent
// process to the child process. This means:
// 1) If the parent process never writes to `data` after having called
// `xlipboard_persit` then no copies are made of that data, yet the child
// process still has acess to it after the parent process exited. COOL!
// 2) If the parent process does write then the `data` is copied, and the
// child process will continue to acess the original contents.
// See:
// https://unix.stackexchange.com/questions/155017/does-fork-immediately-copy-the-entire-process-heap-in-linux
// THAT'S SO COOL
// We'll use these pipes for the child process to thell the parent that it
// can resume.
int pipefd[2];
int ret = pipe(pipefd);
if (ret == -1) {
assert(False);
}
pid_t pid = fork();
if (pid != 0) {
#ifdef DEBUG
printf("Waiting for child process to setup before returning to "
"caller\n");
#endif
char buf;
int ret = read(pipefd[0], &buf, 1);
if (ret == -1) { // indactes an error occured and errno has been set
#ifdef DEBUG
printf("Error occured reading from pipe :-(\n");
assert(False);
#endif
// TODO: do something to indicate an error occured?
}
#ifdef DEBUG
printf("Child process is done with setup\n");
#endif
close(pipefd[0]);
close(pipefd[1]);
return 0;
}
// Now that we're in the child process we re-open the connection to the
// display I'm not sure how this all works, if this is the correct thing to
// do. All I know is if I don't have this I run into problems and
// StackOverflow comments suggest that you "need one XOpenDisplay per
// thread", and that almost what we're doing here.
Display *parent_display = display;
display = XOpenDisplay(XDisplayString(parent_display));
// Intern some atoms
const Atom A_CLIPBOARD = XInternAtom(display, "CLIPBOARD", False);
const Atom A_TARGETS = XInternAtom(display, "TARGETS", False);
const Atom A_UTF8_STRING = XInternAtom(display, "UTF8_STRING", False);
const Atom A_INCR = XInternAtom(display, "INCR", False);
// A dummy window that exists only for us to intercept `SelectionRequest`
// events.
Window window = XCreateSimpleWindow(display,
DefaultRootWindow(display),
0, 0, 1, 1, 0, 0, 0);
// TODO: XCreateSimpleWindow can generate BadAlloc, BadMatch, BadValue, and
// BadWindow errors.
// https://tronche.com/gui/x/xlib/window/XCreateWindow.html
// take control of the selection so that we receive
// `SelectionRequest` events from other windows
// FIXME: Should not use CurrentTime, according to ICCCM section 2.1
XSetSelectionOwner(display, A_CLIPBOARD, window, CurrentTime);
// TODO: What errorrs can this generate?
// Double-check SetSelectionOwner did not "merely appear to succeed"
if (XGetSelectionOwner(display, A_CLIPBOARD) != window) {
assert(False);
// TODO handle error
}
// TODO: Can XGetSelectionOwner generate an error.
// As long as we are the selection owner the child process waits for more
// SelectionRequest's. However, as soon as we know we're no longer the owner
// we stop accepting new SelectionRequest, only sticking around so that we
// can complete transfers already in progress.
Bool selection_owner = True;
XSelectInput(display, window, PropertyChangeMask);
// TODO: XSelectInput() can generate a BadWindow error.
// https://tronche.com/gui/x/xlib/event-handling/XSelectInput.html
// when fork() creates the child process it copies the stack and the heap
// from the parent process, including the stdout buffer. This means that
// the (copied) stdout buffer is flushed when the child process terminates,
// in some cases resulting in mysterious "double printing". So the child
// process starts by clearing the outout buffer to avoid this.
__fpurge(stdout);
// Move into root, so that we don't cause any problems in case the
// directory we're currently in needs to be unmounted
int sucess = chdir("/");
if (sucess == -1) {
#ifdef DEBUG
printf("Failed to move child process into root directory!?\n");
#endif
}
// Determine chunk_size
// In the case that the selections contents is very large we may
// have to send the clipboard selection in multiple chunks,
// and the maximum chink size is defined by X11
//
// We consider selections larger than a quarter of the maximum
// request size to be "large". See ICCCM section 2.5
// FIXME: I think the chunk_size should be ~16x the size of what we
// currently do.
//
// First see if X supports extended-length encoding, it returns 0 if not
size_t chunk_size = XExtendedMaxRequestSize(display) / 4;
// Otherwise, try the normal encoding
if (!chunk_size) {
chunk_size = XMaxRequestSize(display) / 4;
}
// If this fails for some reason, we fallback to this
if (!chunk_size) {
chunk_size = 4096;
}
// The head of the linked list that keeps track of all ongoing INCR
// transfers.
struct transfer *transfers = NULL;
// Now we're ready for the parent process to return to the caller
// TODO: We can probably let the parent resume earlier than this, but let's
// stay safe for now
XSync(display, False);
ret = write(pipefd[1], "1", 1); // Notify parent
close(pipefd[0]);
close(pipefd[1]);
if (ret == -1) { // inducates an error occured an errno has been set
#ifdef DEBUG
printf("Error occured writing to pipe :-(\n");
#endif
// TODO: If this happens it means we cannot communicate to the parent
// process?? What do we do then?
}
XEvent event;
while (True) {
// We are no longer the selection owner and we have no ongoing
// transfers, time to exit this child process.
if (selection_owner == False && transfers == NULL) {
#ifdef DEBUG
printf("Exiting child process.\n");
#endif
_Exit(3);
}
XNextEvent(display, &event);
#ifdef DEBUG
printf("Got an event\n");
#endif
// Someone is making a SelectionRequest but we're no longer the
// selection's owner. Refuse the request.
if (selection_owner == False && event.type == SelectionRequest) {
#ifdef DEBUG
printf("Got a SelectionRequest when we're no longer the owner, "
"refusing.\n");
#endif
xclipboard_respond(event,
None,
A_CLIPBOARD,
event.xselection.target);
continue;
}
// FIXME: ICCCM 2.2: check evt.time and refuse requests from
// outside the period of time we have owned the selection.
// We have lost ownership of the selection (for instance the user did a
// CTRL-C in some other application). There is nothing more for us to
// do, except complete any ongoing transfers.
if (event.type == SelectionClear) {
#ifdef DEBUG
printf("Got a SelectionClear\n");
#endif
selection_owner = False;
continue;
}
int target = 0; // Initialized only to avoid -Wmaybe-uninitialized
#ifdef DEBUG
char *target_name = "";
#endif
if (event.type == SelectionRequest) {
target = event.xselectionrequest.target;
#ifdef DEBUG
target_name = XGetAtomName(display,
event.xselectionrequest.target);
// this is a memory leak but whatever we're in debug
#endif
}
// Some program asked us what kinds of formats (i.e. targets) we can
// send the selection contents in (like utf8, html, png, etc.). This can
// happen for instance when a user does CTRL-V in an application,
// usually the application wants to know what format the content is in,
// for instance if we support a png target maybe the application would
// like to insert an image instead of text for the user.
if (event.type == SelectionRequest
&& target == A_TARGETS) {
#ifdef DEBUG
printf("Got a selection request with target = TARGETS\n");
#endif
// This is the contents of our resonse.
// We supports two targets
// 1) The TARGETS target (duh)
// 2) UTF8_STRING
// TODO: Should we support more targets by default?
// Some reasonable targets could be:
// - STRING
// - TEXT
// - text/plain
// - text/plain;charset=utf-8
Atom types[2] = {
A_TARGETS,
A_UTF8_STRING,
};
// put the response contents into the request's property
XChangeProperty(display,
event.xselectionrequest.requestor,
event.xselectionrequest.property,
XInternAtom(display, "ATOM", False),
32,
PropModeReplace,
(unsigned char *) types,
(int) (sizeof(types) / sizeof(Atom)));
// TODO: XChangeProperty() can generate BadAlloc, BadAtom, BadMatch,
// BadValue, and BadWindow errors.
// Now we send the response
xclipboard_respond(event,
event.xselectionrequest.property,
A_CLIPBOARD,
XInternAtom(display, "TARGETS", False));
continue;
}
// The requestor asked us the send the contents of the selection as a
// UTF8 string, and we can send the contents in one chunk
if (event.type == SelectionRequest
&& target == XInternAtom(display, "UTF8_STRING", False)
&& len <= chunk_size) {
#ifdef DEBUG
printf("Got a selection request with target = %s and we can send"
"the response in one chunk\n",
target_name);
#endif
XChangeProperty(display,
event.xselectionrequest.requestor,
event.xselectionrequest.property,
XInternAtom(display, "UTF8_STRING", False),
8,
PropModeReplace,
(unsigned char *) data,
(int) len);
// TODO: XChangeProperty() can generate BadAlloc, BadAtom, BadMatch,
// BadValue, and BadWindow errors.
xclipboard_respond(event,
event.xselectionrequest.property,
A_CLIPBOARD,
XInternAtom(display, "UTF8_STRING", False));
continue;
}
// The requestor asked us the send the contents of the selection as a
// UTF8 string, and we have to send it in multiple chunks.
if (event.type == SelectionRequest
&& target == XInternAtom(display, "UTF8_STRING", False)
&& len > chunk_size) {
#ifdef DEBUG
printf("Got a selection request with target = %s but we can't send"
"the response in one chunk\n",
target_name);
#endif
// FIXME: instead of sending zero items we should send an integer
// representing the lower bound on the number of bytes to
// send ICCCM 2.7.2 INCR Properties.
XChangeProperty(display,
event.xselectionrequest.requestor,
event.xselectionrequest.property,
A_INCR,
32,
PropModeReplace,
0,
0);
// With the INCR mechanism, we need to know
// when the requestor window changes (deletes)
// its properties.
XSelectInput(display,
event.xselectionrequest.requestor,
PropertyChangeMask);
xclipboard_respond(event,
event.xselectionrequest.property,
A_CLIPBOARD,
XInternAtom(display, "UTF8_STRING", False));
// Do we have an ongoing transfer already?
struct transfer *t =
get_transfer(&transfers, event.xselectionrequest.requestor);
if (t != NULL) {
// TODO: handle somehow
assert(False);
}
// We don't have an ongoig transfer already, so register this one
new_transfer(&transfers,
event.xselectionrequest.requestor,
event.xselectionrequest.property);
continue;
}
// It _may_ be the case that some requestor is asking us to send another
// chunk
if (event.type == PropertyNotify
&& event.xproperty.state == PropertyDelete) {
#ifdef DEBUG
printf("Got a PropertyNotify and it's state field is"
"PropertyDelete\n");
#endif
struct transfer *t = get_transfer(&transfers,
event.xproperty.window);
if (t == NULL) {
#ifdef DEBUG
printf("PropertyNotify is not concearning an ongoing transfer"
"of ours, not interested.\n");
#endif
continue;
}
// This should never happen
if (len < t->bytes_transfered) {
assert(False);
}
size_t left_to_transfer = len - t->bytes_transfered;
size_t this_chunk_size = chunk_size;
unsigned char *this_data =
(unsigned char*) data + t->bytes_transfered;
// We have no data left to transfer, and we should send one last
// empty chunk to signal to the requestor that the transfer is
// complete.
if (left_to_transfer == 0) {
this_chunk_size = 0;
this_data = 0;
// At the end of this block we also do `delete_transfer`
} else if (left_to_transfer < chunk_size) {
this_chunk_size = left_to_transfer;
}
XChangeProperty(display,
event.xproperty.window,
t->property,
XInternAtom(display, "UTF8_STRING", False),
8,
PropModeReplace,
this_data,
(int) this_chunk_size);
t->bytes_transfered = t->bytes_transfered + this_chunk_size;
xclipboard_respond(event,
t->property,
A_CLIPBOARD,
XInternAtom(display, "UTF8_STRING", False));
if (left_to_transfer == 0) {
delete_transfer(&transfers, t);
}
continue;
}
// The target is not something that we support
if (event.type == SelectionRequest
&& event.xselectionrequest.target != A_TARGETS
&& event.xselectionrequest.target != A_UTF8_STRING) {
#ifdef DEBUG
printf("Got a selection request with target = %s. We do not support"
"this target\n", target_name);
#endif
xclipboard_respond(event,
None,
A_CLIPBOARD,
event.xselection.target);
continue;
}
#ifdef DEBUG
const char *evtstr[36] = {
"ProtocolError", "ProtocolReply", "KeyPress", "KeyRelease",
"ButtonPress", "ButtonRelease", "MotionNotify", "EnterNotify",
"LeaveNotify", "FocusIn", "FocusOut", "KeymapNotify", "Expose",
"GraphicsExpose", "NoExpose", "VisibilityNotify", "CreateNotify",
"DestroyNotify", "UnmapNotify", "MapNotify", "MapRequest",
"ReparentNotify", "ConfigureNotify", "ConfigureRequest",
"GravityNotify", "ResizeRequest", "CirculateNotify",
"CirculateRequest", "PropertyNotify", "SelectionClear",
"SelectionRequest", "SelectionNotify", "ColormapNotify",
"ClientMessage", "MappingNotify", "GenericEvent", };
printf("We got an unexpected %s event\n", evtstr[event.type]);
#endif
}
// Let everyone know that we're no longer taking care of the selection
XSetSelectionOwner(display, A_CLIPBOARD, None, CurrentTime);
return 0;
}
int libxclip_targets(Display *display,
Atom **targets_ret,
unsigned long *nitems_ret,
struct libxclip_getopts *options) {
// re-open the connextion to X. I'm not sure we need this, we're not doing
// multithreading or anything, by I _think_ getting a new connection is wise
// because we only want xevents related to us.
display = XOpenDisplay(XDisplayString(display));
// A dummy window to which we can attach a property where the selection
// owner can place their response.
Window window = XCreateSimpleWindow(display,
DefaultRootWindow(display),
0, 0, 1, 1, 0, 0, 0);
// The property where the selection owner can place their response.
Atom property = XInternAtom(display, "LIBXCLIP_OUT", False);
Atom selection;
if (options == NULL || options->selection == 0) {
selection = XInternAtom(display, "CLIPBOARD", False);
} else {
selection = options->selection;
}
// Make the request
XConvertSelection(display,
selection,
XInternAtom(display, "TARGETS", False),
property,
window,
CurrentTime);
#ifdef DEBUG
printf("Called XConvertSelection, waiting for an XEvent.\n");
#endif
// Wait for a response
XEvent event;
if (options == NULL || options->timeout == 0) {
XNextEvent(display, &event);
} else {
struct timespec timeout;
x_millisecs_from_now(options->timeout, &timeout);
int ret = XNextEvent_timeout(display, &event, timeout);
// Did we timeout?
if (ret == -1) {
return -1;
}
}
#ifdef DEBUG
printf("We got an XEvent.\n");
#endif
if (event.type != SelectionNotify) {
#ifdef DEBUG
printf("We got an event with type %d which is not a SelectionNotify, "
"returning error.\n", event.type);
#endif
return -1;
}
if (event.xselection.property == None) {
#ifdef DEBUG
printf("The SelectionNotify response we got gave None as a property, "
"maybe there is no selection owner, or the selection owner is "
"unhappy with our request. Return with error.\n");
#endif
return -1;
}
if (event.xselection.property != property) {
#ifdef DEBUG
printf("The SelectionNotify response we got does not pertain to our "
"prpoerty. Returning with error.\n");
#endif
return -1;
}
Atom property_type;
int format;
unsigned long nitems;
unsigned long bytes_after;
unsigned char *out_buffer;
// find the size and format of the data in property
XGetWindowProperty(display,
window,
property,
0,
0,
False,
AnyPropertyType,
&property_type,
&format,
&nitems,
&bytes_after,
&out_buffer);
if (property_type != XInternAtom(display, "ATOM", False)) {
#ifdef DEBUG
printf("Unexpected property_type atom \"%s\".\n",
XGetAtomName(display, property_type));
#endif
return -1;
}
if (format != 32) {
#ifdef DEBUG
printf("Unexpected format %d.\n", format);
#endif
return -1;
}
// Actually retrive the data
XGetWindowProperty(display,
window,
property,
0,
bytes_after / 4,
False,
AnyPropertyType,
&property_type,
&format,
&nitems,
&bytes_after,
&out_buffer);
assert(bytes_after == 0);
// Copy the retrived data to a memory block for the caller to access.
unsigned char *copied_buffer = calloc(nitems, sizeof(long));
memcpy(copied_buffer, out_buffer, nitems * sizeof(long));
XFree(out_buffer);
*targets_ret = (Atom *) copied_buffer;
*nitems_ret = nitems;
return 0;
}
int libxclip_get(Display *display,
char **data_ret,
size_t *size_ret,
struct libxclip_getopts *options) {
// re-open the connextion to X. I'm not sure we need this, we're not doing
// multithreading or anything, by I _think_ getting a new connection is wise
// because we only want xevents related to us.
display = XOpenDisplay(XDisplayString(display));
// A dummy window to which we can attach a property where the selection
// owner can place their response.
Window window = XCreateSimpleWindow(display,
DefaultRootWindow(display),
0, 0, 1, 1, 0, 0, 0);
// The property where the selection owner can place their response.
Atom property = XInternAtom(display, "LIBXCLIP_OUT", False);
Atom selection;
if (options == NULL || options->selection == None) {
selection = XInternAtom(display, "CLIPBOARD", False);
} else {
selection = options->selection;
}
Atom target;
if (options == NULL || options->target == None) {
target = XInternAtom(display, "UTF8_STRING", False);
} else {
target = options->target;
}
// Make the request
XConvertSelection(display,
selection,
target,
property,
window,
CurrentTime);
#ifdef DEBUG
printf("Called XConvertSelection, waiting for an XEvent.\n");
#endif
// In the case that the caller specified a timeout we will use this
// struct to define the point in time where if we pass it we should
// timeout.
// TODO: Should we set the timeout at the start of the function?
struct timespec timeout;
// Wait for a response
XEvent event;
if (options == NULL || options->timeout == -1) {
XNextEvent(display, &event);
} else {
x_millisecs_from_now(options->timeout, &timeout);
int ret = XNextEvent_timeout(display, &event, timeout);
// Did we timeout?
if (ret == -1) {
return -1;
}
}
#ifdef DEBUG
printf("We got an XEvent.\n");
#endif
if (event.type != SelectionNotify) {
#ifdef DEBUG