Fix compiler warnings from OS X Lion: a missing #include and some
[sgt/utils] / xcopy / xcopy.c
1 /*
2 * xcopy: quickly pipe text data into, or out of, the primary X
3 * selection
4 */
5
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <string.h>
9 #include <stdarg.h>
10 #include <math.h>
11 #include <errno.h>
12 #include <assert.h>
13 #include <ctype.h>
14
15 #include <unistd.h>
16
17 #include <X11/X.h>
18 #include <X11/Intrinsic.h>
19 #include <X11/Xlib.h>
20 #include <X11/Xutil.h>
21 #include <X11/Xatom.h>
22
23 int init_X(void);
24 void run_X(void);
25 void done_X(void);
26 void full_redraw(void);
27 void do_paste(Window window, Atom property, int Delete);
28
29 char *pname; /* program name */
30
31 void error (char *fmt, ...);
32
33 /* set from command-line parameters */
34 char *display = NULL;
35 enum { STRING, CTEXT, UTF8, TARGETS, TIMESTAMP, CUSTOM } mode = STRING;
36 int use_clipboard = False;
37 char *custom_seltype = NULL;
38 int fork_when_writing = True;
39 int use_cutbuffers = True;
40 int use_outgoing_incr = True;
41 int sel_delta = 16384;
42
43 /* selection data */
44 char *seltext;
45 int sellen, selsize;
46
47 /* incremental transfers still pending when we return to the event loop */
48 struct incr {
49 Window window;
50 Atom property;
51 Atom type;
52 int format;
53 unsigned char *data;
54 size_t size;
55 } *incrs = NULL;
56 int nincrs = 0, incrsize = 0;
57
58 /* functional parameters */
59 int reading; /* read instead of writing? */
60 int convert_to_ctext = True; /* Xmb convert to compound text? */
61 int verbose;
62
63 const char usagemsg[] =
64 "usage: xcopy [ -r ] [ -u | -c ] [ -C ]\n"
65 "modes: -r read X selection and print on stdout\n"
66 " no -r write to X selection from stdin\n"
67 " read: -t get the list of targets available to retrieve\n"
68 " -T get the time stamp of the selection contents\n"
69 " -a atom get an arbitrary form of the selection data\n"
70 " -v verbosely report progress in reading selection\n"
71 "write: -F remain in foreground until selection reclaimed\n"
72 " -I do not attempt incremental transfers (INCR)\n"
73 " both: -u work with UTF8_STRING type selections\n"
74 " -c work with COMPOUND_TEXT type selections\n"
75 " -C suppress automatic conversion to COMPOUND_TEXT\n"
76 " -b use the CLIPBOARD rather than the PRIMARY selection\n"
77 " -d size transfer data in chunks of at most <size> (default 16384)\n"
78 " -B do not use root window cut buffers\n"
79 " also: xcopy --version report version number\n"
80 " xcopy --help display this help text\n"
81 " xcopy --licence display the (MIT) licence text\n"
82 ;
83
84 void usage(void) {
85 fputs(usagemsg, stdout);
86 }
87
88 const char licencemsg[] =
89 "xcopy is copyright 2001-2004,2008 Simon Tatham.\n"
90 "\n"
91 "Permission is hereby granted, free of charge, to any person\n"
92 "obtaining a copy of this software and associated documentation files\n"
93 "(the \"Software\"), to deal in the Software without restriction,\n"
94 "including without limitation the rights to use, copy, modify, merge,\n"
95 "publish, distribute, sublicense, and/or sell copies of the Software,\n"
96 "and to permit persons to whom the Software is furnished to do so,\n"
97 "subject to the following conditions:\n"
98 "\n"
99 "The above copyright notice and this permission notice shall be\n"
100 "included in all copies or substantial portions of the Software.\n"
101 "\n"
102 "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n"
103 "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n"
104 "MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n"
105 "NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n"
106 "BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n"
107 "ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n"
108 "CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n"
109 "SOFTWARE.\n"
110 ;
111
112 void licence(void) {
113 fputs(licencemsg, stdout);
114 }
115
116 void version(void) {
117 #define SVN_REV "$Revision$"
118 char rev[sizeof(SVN_REV)];
119 char *p, *q;
120
121 strcpy(rev, SVN_REV);
122
123 for (p = rev; *p && *p != ':'; p++);
124 if (*p) {
125 p++;
126 while (*p && isspace(*p)) p++;
127 for (q = p; *q && *q != '$'; q++);
128 if (*q) *q = '\0';
129 printf("xcopy revision %s\n", p);
130 } else {
131 printf("xcopy: unknown version\n");
132 }
133 }
134
135 int main(int ac, char **av) {
136 int n;
137 int eventloop;
138
139 pname = *av;
140
141 /* parse the command line arguments */
142 while (--ac > 0) {
143 char *p = *++av;
144
145 if (!strcmp(p, "-display") || !strcmp(p, "-disp")) {
146 if (!av[1])
147 error ("option `%s' expects a parameter", p);
148 display = *++av, --ac;
149 } else if (!strcmp(p, "-r")) {
150 reading = True;
151 } else if (!strcmp(p, "-u")) {
152 mode = UTF8;
153 } else if (!strcmp(p, "-c")) {
154 mode = CTEXT;
155 } else if (!strcmp(p, "-C")) {
156 convert_to_ctext = False;
157 } else if (!strcmp(p, "-b")) {
158 use_clipboard = True;
159 } else if (!strcmp(p, "-t")) {
160 mode = TARGETS;
161 } else if (!strcmp(p, "-T")) {
162 mode = TIMESTAMP;
163 } else if (!strcmp(p, "-I")) {
164 use_outgoing_incr = False;
165 } else if (!strcmp(p, "-B")) {
166 use_cutbuffers = False;
167 } else if (!strcmp(p, "-a")) {
168 if (--ac > 0)
169 custom_seltype = *++av;
170 else
171 error ("expected an argument to `-a'");
172 mode = CUSTOM;
173 } else if (!strcmp(p, "-d")) {
174 if (--ac > 0)
175 sel_delta = atoi(*++av);
176 else
177 error ("expected an argument to `-d'");
178 } else if (!strcmp(p, "-F")) {
179 fork_when_writing = False;
180 } else if (!strcmp(p, "-v")) {
181 verbose = True;
182 } else if (!strcmp(p, "--help")) {
183 usage();
184 return 0;
185 } else if (!strcmp(p, "--version")) {
186 version();
187 return 0;
188 } else if (!strcmp(p, "--licence") || !strcmp(p, "--license")) {
189 licence();
190 return 0;
191 } else if (*p=='-') {
192 error ("unrecognised option `%s'", p);
193 } else {
194 error ("no parameters required");
195 }
196 }
197
198 if (!reading) {
199 if (mode == TARGETS || mode == TIMESTAMP || mode == CUSTOM) {
200 error ("%s not supported in writing mode; use -r",
201 (mode == TARGETS ? "-t" :
202 mode == TIMESTAMP ? "-T" :
203 /* mode == CUSTOM ? */ "-a"));
204 }
205 }
206
207 if (!reading) {
208 seltext = malloc(sel_delta);
209 if (!seltext)
210 error ("out of memory");
211 selsize = sel_delta;
212 sellen = 0;
213 do {
214 n = fread(seltext+sellen, 1, selsize-sellen, stdin);
215 sellen += n;
216 if (sellen >= selsize) {
217 seltext = realloc(seltext, selsize += sel_delta);
218 if (!seltext)
219 error ("out of memory");
220 }
221 } while (n > 0);
222 if (sellen == selsize) {
223 seltext = realloc(seltext, selsize += sel_delta);
224 if (!seltext)
225 error ("out of memory");
226 }
227 seltext[sellen] = '\0';
228 }
229
230 eventloop = init_X();
231 if (!reading && fork_when_writing) {
232 /*
233 * If we are writing the selection, we must go into the
234 * background now.
235 */
236 int pid = fork();
237 if (pid < 0) {
238 error("unable to fork: %s", strerror(errno));
239 } else if (pid > 0) {
240 /*
241 * we are the parent; just exit
242 */
243 return 0;
244 }
245 /*
246 * we are the child
247 */
248 close(0);
249 close(1);
250 close(2);
251 chdir("/");
252 }
253 if (eventloop)
254 run_X();
255 done_X();
256 return 0;
257 }
258
259 /* handle errors */
260
261 void error (char *fmt, ...) {
262 va_list ap;
263 char errbuf[200];
264
265 done_X();
266 va_start (ap, fmt);
267 vsprintf (errbuf, fmt, ap);
268 va_end (ap);
269 fprintf (stderr, "%s: %s\n", pname, errbuf);
270 exit (1);
271 }
272
273 /* begin the X interface */
274
275 char *lcasename = "xcopy";
276 char *ucasename = "XCopy";
277
278 Display *disp = NULL;
279 Window ourwin = None;
280 Atom compound_text_atom, targets_atom, timestamp_atom, atom_atom, integer_atom;
281 Atom multiple_atom, atom_pair_atom, incr_atom;
282 int screen, wwidth, wheight;
283
284 Atom strtype = XA_STRING;
285 Atom sel_atom = XA_PRIMARY;
286 Atom expected_type = (Atom)None;
287 int expected_format = 8;
288
289 static const char *translate_atom(Display *disp, Atom atom)
290 {
291 if (atom == None)
292 return "None";
293 else
294 return XGetAtomName(disp, atom);
295 }
296
297 /*
298 * Returns TRUE if we need to enter an event loop, FALSE otherwise.
299 */
300 int init_X(void) {
301 Window root;
302 int x = 0, y = 0, width = 512, height = 128;
303 int i, got = 0;
304 XWMHints wm_hints;
305 XSizeHints size_hints;
306 XClassHint class_hints;
307 XTextProperty textprop;
308 XGCValues gcv;
309
310 /* open the X display */
311 disp = XOpenDisplay (display);
312 if (!disp)
313 error ("unable to open display");
314
315 targets_atom = XInternAtom(disp, "TARGETS", False);
316 timestamp_atom = XInternAtom(disp, "TIMESTAMP", False);
317 atom_atom = XInternAtom(disp, "ATOM", False);
318 atom_pair_atom = XInternAtom(disp, "ATOM_PAIR", False);
319 multiple_atom = XInternAtom(disp, "MULTIPLE", False);
320 integer_atom = XInternAtom(disp, "INTEGER", False);
321 incr_atom = XInternAtom(disp, "INCR", False);
322 if (mode == UTF8) {
323 strtype = XInternAtom(disp, "UTF8_STRING", False);
324 } else if (mode == CTEXT) {
325 strtype = XInternAtom(disp, "COMPOUND_TEXT", False);
326 } else if (mode == TARGETS) {
327 strtype = targets_atom;
328 expected_type = atom_atom;
329 expected_format = 32;
330 } else if (mode == TIMESTAMP) {
331 strtype = timestamp_atom;
332 expected_type = integer_atom;
333 expected_format = 32;
334 } else if (mode == CUSTOM) {
335 strtype = XInternAtom(disp, custom_seltype, True);
336 if (!strtype)
337 error ("atom '%s' does not exist on the server", custom_seltype);
338 expected_format = 0;
339 }
340 if (use_clipboard) {
341 sel_atom = XInternAtom(disp, "CLIPBOARD", False);
342 }
343
344 /* get the screen and root-window */
345 screen = DefaultScreen (disp);
346 root = RootWindow (disp, screen);
347
348 x = y = 0;
349 width = height = 10; /* doesn't really matter */
350
351 /* actually create the window */
352 ourwin = XCreateSimpleWindow (disp, root, x, y, width, height,0,
353 BlackPixel(disp, screen),
354 WhitePixel(disp, screen));
355
356 /* resource class name */
357 class_hints.res_name = lcasename;
358 class_hints.res_class = ucasename;
359 XSetClassHint (disp, ourwin, &class_hints);
360
361 /* do selection fiddling */
362 if (reading) {
363 /*
364 * We are reading the selection. Call XConvertSelection to
365 * request transmission of the selection data in the
366 * appropriate format; the X event loop will then wait to
367 * receive the data from the selection owner. Also we need to
368 * make sure we receive PropertyNotify events, for INCR
369 * transfers.
370 *
371 * If there is no selection owner, look in the cut buffer
372 * property on the root window.
373 */
374 XSelectInput(disp, ourwin, PropertyChangeMask);
375 if (XGetSelectionOwner(disp, sel_atom) != None) {
376 Atom sel_property = XInternAtom(disp, "VT_SELECTION", False);
377 if (verbose)
378 fprintf(stderr, "calling XConvertSelection: selection=%s"
379 " target=%s property=%s requestor=%08lx\n",
380 translate_atom(disp, sel_atom),
381 translate_atom(disp, strtype),
382 translate_atom(disp, sel_property),
383 ourwin);
384 XConvertSelection(disp, sel_atom, strtype,
385 sel_property, ourwin, CurrentTime);
386 return True;
387 } else if (use_cutbuffers) {
388 /* No primary selection, so use the cut buffer. */
389 if (verbose)
390 fprintf(stderr, "no selection owner: trying cut buffer\n");
391 if (strtype == XA_STRING)
392 do_paste(DefaultRootWindow(disp), XA_CUT_BUFFER0, True);
393 return False;
394 } else {
395 /* Last fallback: do nothing. */
396 return False;
397 }
398 } else {
399 /*
400 * We are writing to the selection, so we establish ourselves
401 * as selection owner.
402 *
403 * Also place the data in CUT_BUFFER0, if it isn't of an
404 * exotic type (cut buffers can only take ordinary string
405 * data, it turns out) or bigger than our maximum chunk size.
406 */
407 XSetSelectionOwner (disp, sel_atom, ourwin, CurrentTime);
408 if (XGetSelectionOwner (disp, sel_atom) != ourwin)
409 error ("unable to obtain primary X selection");
410 compound_text_atom = XInternAtom(disp, "COMPOUND_TEXT", False);
411 if (strtype == XA_STRING && sellen <= sel_delta && use_cutbuffers) {
412 /*
413 * ICCCM-required cut buffer initialisation.
414 */
415 static const unsigned char emptystring[] = {0};
416 XChangeProperty(disp, root, XA_CUT_BUFFER0,
417 XA_STRING, 8, PropModeAppend, emptystring, 0);
418 XChangeProperty(disp, root, XA_CUT_BUFFER1,
419 XA_STRING, 8, PropModeAppend, emptystring, 0);
420 XChangeProperty(disp, root, XA_CUT_BUFFER2,
421 XA_STRING, 8, PropModeAppend, emptystring, 0);
422 XChangeProperty(disp, root, XA_CUT_BUFFER3,
423 XA_STRING, 8, PropModeAppend, emptystring, 0);
424 XChangeProperty(disp, root, XA_CUT_BUFFER4,
425 XA_STRING, 8, PropModeAppend, emptystring, 0);
426 XChangeProperty(disp, root, XA_CUT_BUFFER5,
427 XA_STRING, 8, PropModeAppend, emptystring, 0);
428 XChangeProperty(disp, root, XA_CUT_BUFFER6,
429 XA_STRING, 8, PropModeAppend, emptystring, 0);
430 XChangeProperty(disp, root, XA_CUT_BUFFER7,
431 XA_STRING, 8, PropModeAppend, emptystring, 0);
432 /*
433 * Rotate the cut buffers and add our text in CUT_BUFFER0.
434 */
435 XRotateBuffers(disp, 1);
436 XStoreBytes(disp, seltext, sellen);
437 }
438 return True;
439 }
440 }
441
442 void write_data(Window requestor, Atom property, Atom type, int format,
443 void *vdata, size_t size)
444 {
445 int bformat = format / 8; /* bytes per element */
446 unsigned char *data = (unsigned char *)vdata;
447 XEvent ev;
448
449 if (!use_outgoing_incr || size * bformat <= sel_delta) {
450 XChangeProperty(disp, requestor, property, type, format,
451 PropModeReplace, data, size);
452 } else {
453 /*
454 * For large data, an incremental transfer as per ICCCM 2.7.2.
455 */
456 Cardinal totalsize = size * bformat;
457 Cardinal sent, thissize;
458
459 /*
460 * We're going to need PropertyNotify events on the target
461 * window to tell us when to send the next chunk.
462 */
463 XSelectInput(disp, requestor, PropertyChangeMask);
464
465 /*
466 * Start by sending a single 32-bit word with type INCR giving
467 * the total size in bytes.
468 */
469 XChangeProperty(disp, requestor, property, incr_atom, 32,
470 PropModeReplace, (unsigned char *)&totalsize, 1);
471
472 /*
473 * Now set up an entry in our list of ongoing incremental
474 * transfers, so that whenever that property is deleted, we'll
475 * send the next batch.
476 */
477 if (nincrs >= incrsize) {
478 incrsize = nincrs * 9 / 8 + 16;
479 incrs = realloc(incrs, incrsize * sizeof(*incrs));
480 if (!incrs)
481 error ("out of memory");
482 }
483 incrs[nincrs].window = requestor;
484 incrs[nincrs].property = property;
485 incrs[nincrs].type = type;
486 incrs[nincrs].format = format;
487 incrs[nincrs].size = totalsize;
488 incrs[nincrs].data = malloc(totalsize);
489 if (!incrs[nincrs].data)
490 error("out of memory");
491 memcpy(incrs[nincrs].data, data, size);
492 nincrs++;
493 }
494 }
495
496 Atom convert_sel_inner(Window requestor, Atom target, Atom property) {
497 if (target == strtype) {
498 write_data(requestor, property, strtype, 8, seltext, sellen);
499 return property;
500 } else if (target == compound_text_atom && convert_to_ctext) {
501 XTextProperty tp;
502 XmbTextListToTextProperty(disp, &seltext, 1, XCompoundTextStyle, &tp);
503 write_data(requestor, property, target, tp.format, tp.value,tp.nitems);
504 return property;
505 } else if (target == targets_atom) {
506 Atom targets[16];
507 int len = 0;
508 targets[len++] = timestamp_atom;
509 targets[len++] = targets_atom;
510 targets[len++] = multiple_atom;
511 targets[len++] = strtype;
512 if (strtype != compound_text_atom && convert_to_ctext)
513 targets[len++] = compound_text_atom;
514 write_data(requestor, property, atom_atom, 32, targets, len);
515 return property;
516 } else if (target == timestamp_atom) {
517 Time rettime = CurrentTime;
518 write_data(requestor, property, integer_atom, 32, &rettime, 1);
519 return property;
520 } else {
521 return None;
522 }
523 }
524
525 Atom convert_sel_outer(Window requestor, Atom target, Atom property) {
526 /*
527 * ICCCM 2.2 says that obsolete clients requesting the selection
528 * request may not specify a property name under which they want
529 * the data written to their window; selection owners are
530 * encouraged to support such clients by reusing the selection
531 * target name as the property.
532 */
533 if (property == None)
534 property = target;
535
536 if (target == multiple_atom) {
537 /*
538 * Support for the MULTIPLE selection type, since it's
539 * specified as required in the ICCCM. Completely
540 * untested, though, because I have no idea of what X
541 * application might implement it for me to test against.
542 */
543
544 int size = sel_delta;
545 Atom actual_type;
546 int actual_format, i;
547 unsigned long nitems, bytes_after, nread;
548 unsigned char *data;
549 Atom *adata;
550
551 if (property == (Atom)None)
552 return None; /* ICCCM says this isn't allowed */
553
554 /*
555 * Fetch the requestor's window property giving a list of
556 * selection requests.
557 */
558 while (XGetWindowProperty(disp, requestor, property, 0, size,
559 False, AnyPropertyType, &actual_type,
560 &actual_format, &nitems, &bytes_after,
561 (unsigned char **) &data) == Success &&
562 nitems * (actual_format / 8) == size) {
563 XFree(data);
564 size *= 3 / 2;
565 }
566
567 if (actual_type != atom_pair_atom || actual_format != 32) {
568 XFree(data);
569 return None;
570 }
571
572 adata = (Atom *)data;
573
574 for (i = 0; i+1 < (long)nitems; i += 2) {
575 if (adata[i+1] != (Atom)None) /* ICCCM says this isn't allowed */
576 adata[i+1] = convert_sel_inner(requestor, adata[i],
577 adata[i+1]);
578 }
579
580 XChangeProperty (disp, requestor, property,
581 atom_pair_atom, 32, PropModeReplace,
582 data, nitems);
583
584 XFree(data);
585
586 return property;
587 } else {
588 if (property == (Atom)None)
589 property = target; /* ICCCM says this is a sensible default */
590 return convert_sel_inner(requestor, target, property);
591 }
592 }
593
594 void run_X(void) {
595 XEvent ev, e2;
596 int i, j;
597
598 while (1) {
599 XNextEvent (disp, &ev);
600 if (reading) {
601 switch (ev.type) {
602 case SelectionNotify:
603 if (verbose)
604 fprintf(stderr, "got SelectionNotify: requestor=%08lx "
605 "selection=%s target=%s property=%s\n",
606 ev.xselection.requestor,
607 translate_atom(disp, ev.xselection.selection),
608 translate_atom(disp, ev.xselection.target),
609 translate_atom(disp, ev.xselection.property));
610
611 if (ev.xselection.property != None)
612 do_paste(ev.xselection.requestor,
613 ev.xselection.property, False);
614 return;
615 }
616 } else {
617 int have_ownership = True;
618
619 switch (ev.type) {
620 case SelectionClear:
621 /* Selection has been cleared by another app. */
622 have_ownership = False;
623 break;
624 case SelectionRequest:
625 if (have_ownership) {
626 e2.xselection.type = SelectionNotify;
627 e2.xselection.requestor = ev.xselectionrequest.requestor;
628 e2.xselection.selection = ev.xselectionrequest.selection;
629 e2.xselection.target = ev.xselectionrequest.target;
630 e2.xselection.time = ev.xselectionrequest.time;
631 e2.xselection.property =
632 convert_sel_outer(ev.xselectionrequest.requestor,
633 ev.xselectionrequest.target,
634 ev.xselectionrequest.property);
635 XSendEvent (disp, ev.xselectionrequest.requestor,
636 False, 0, &e2);
637 }
638 break;
639 case PropertyNotify:
640 for (i = j = 0; i < nincrs; i++) {
641 int keep = True;
642 if (incrs[i].window == ev.xproperty.window &&
643 incrs[i].property == ev.xproperty.atom &&
644 ev.xproperty.state == PropertyDelete) {
645 size_t thissize = incrs[i].size;
646 if (thissize > sel_delta)
647 thissize = sel_delta;
648
649 XChangeProperty(disp,
650 incrs[i].window, incrs[i].property,
651 incrs[i].type, incrs[i].format,
652 PropModeReplace, incrs[i].data,
653 thissize / (incrs[i].format/8));
654
655 if (thissize == 0) {
656 /*
657 * If we've just sent a zero-length block,
658 * the incremental transfer is over and we
659 * should delete this entry.
660 */
661 keep = False;
662 }
663
664 incrs[i].data += thissize;
665 incrs[i].size -= thissize;
666 }
667 if (keep) {
668 if (j != i)
669 incrs[j] = incrs[i];
670 j++;
671 }
672 }
673 nincrs = j;
674 break;
675 }
676 if (nincrs == 0 && !have_ownership)
677 return;
678 }
679 }
680 }
681
682 void done_X(void) {
683 int i;
684
685 if (ourwin != None)
686 XDestroyWindow (disp, ourwin);
687 if (disp)
688 XCloseDisplay (disp);
689 }
690
691 void do_paste(Window window, Atom property, int cutbuffer) {
692 Atom actual_type;
693 int actual_format, i;
694 unsigned long nitems, bytes_after, nread;
695 unsigned char *data;
696 int incremental = False;
697 XEvent ev;
698
699 nread = 0;
700 while (XGetWindowProperty(disp, window, property, nread / 4, sel_delta / 4,
701 !cutbuffer, AnyPropertyType, &actual_type,
702 &actual_format, &nitems, &bytes_after,
703 (unsigned char **) &data) == Success) {
704 if (verbose)
705 fprintf(stderr, "got %ld items of %d-byte data, type=%s;"
706 " %ld to go\n", nitems, actual_format,
707 translate_atom(disp, actual_type), bytes_after);
708
709 /*
710 * ICCCM 2.7.2: if we receive data with the type atom set to
711 * INCR, it indicates that the actual data will arrive in
712 * multiple chunks, terminating with a zero-length one.
713 * Between each pair, we must wait for a PropertyNotify event
714 * which tells us that the next chunk has arrived.
715 */
716 if (actual_type == incr_atom && !cutbuffer) {
717 incremental = True;
718 /*
719 * Immediately wait for the first chunk of real data.
720 */
721 do {
722 XMaskEvent(disp, PropertyChangeMask, &ev);
723 } while (ev.xproperty.state != PropertyNewValue);
724 /*
725 * And loop straight back round to read it.
726 */
727 continue;
728 }
729
730 if (nitems > 0) {
731 /*
732 * We expect all returned chunks of data to be
733 * multiples of 4 bytes (because we can only request
734 * the subsequent starting offset in 4-byte
735 * increments). Of course you can store an odd number
736 * of bytes in a selection, so this can't be the case
737 * every time XGetWindowProperty returns; but it
738 * should be the case every time it returns _and there
739 * is more data to come_.
740 *
741 * Hence, whenever XGetWindowProperty returns, we
742 * verify that the size of the data returned _last_
743 * time was divisible by 4.
744 */
745 if ((nread & 3) != 0) {
746 error("unexpected data size: %d read (not a multiple"
747 " of 4), but more to come", nread);
748 }
749
750 if (expected_type != (Atom)None && actual_type != expected_type) {
751 const char *expout = translate_atom(disp, expected_type);
752 const char *gotout = translate_atom(disp, actual_type);
753 error("unexpected data type: expected %s, got %s",
754 expout, gotout);
755 }
756 if (expected_format && expected_format != actual_format) {
757 error("unexpected data format: expected %d-bit, got %d-bit",
758 expected_format, actual_format);
759 }
760 }
761
762 if (nitems > 0) {
763 if (mode == TARGETS) {
764 assert(actual_format == 32);
765 int i;
766 for (i = 0; i < nitems; i++) {
767 Atom x = ((Atom *)data)[i];
768 printf("%s\n", translate_atom(disp, x));
769 }
770 } else if (mode == TIMESTAMP) {
771 assert(actual_format == 32);
772 Time x = ((Time *)data)[0];
773 printf("%lu\n", (unsigned long)x);
774 } else {
775 fwrite(data, actual_format / 8, nitems, stdout);
776 nread += nitems * actual_format / 8;
777 }
778 }
779 XFree(data);
780 if (bytes_after == 0) {
781 /*
782 * We've come to the end of the property we're reading.
783 */
784 if (incremental) {
785 /*
786 * For an incremental transfer, this means we wait for
787 * another property to be dumped in the same place on
788 * our window, and loop round again reading that. The
789 * exception is if the total length of the property we
790 * got was zero, which signals the end.
791 */
792 if (nread == 0 && nitems == 0)
793 break; /* all done */
794
795 /* otherwise wait for the next chunk */
796 do {
797 XMaskEvent(disp, PropertyChangeMask, &ev);
798 } while (ev.xproperty.state != PropertyNewValue);
799
800 /* which we read from the beginning */
801 nread = 0;
802 } else {
803 break;
804 }
805 }
806 }
807 }