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