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