Write-side support for INCR.
[sgt/utils] / xcopy / xcopy.c
CommitLineData
9acadc2b 1/*
2 * xcopy: quickly pipe text data into, or out of, the primary X
3 * selection
4 */
5
9acadc2b 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>
8fb6d89b 15#include <X11/Intrinsic.h>
9acadc2b 16#include <X11/Xlib.h>
17#include <X11/Xutil.h>
18#include <X11/Xatom.h>
19
20int init_X(void);
21void run_X(void);
22void done_X(void);
23void full_redraw(void);
24void do_paste(Window window, Atom property, int Delete);
25
26char *pname; /* program name */
27
28void error (char *fmt, ...);
29
30/* set from command-line parameters */
31char *display = NULL;
3ffcf184 32enum { STRING, CTEXT, UTF8, TARGETS, TIMESTAMP, CUSTOM } mode = STRING;
5e36d477 33int use_clipboard = False;
3ffcf184 34char *custom_seltype = NULL;
acc02b82 35int fork_when_writing = True;
9acadc2b 36
37/* selection data */
38char *seltext;
39int sellen, selsize;
2e37f64c 40#define SELDELTA 16384
9acadc2b 41
8fb6d89b 42/* incremental transfers still pending when we return to the event loop */
43struct incr {
44 Window window;
45 Atom property;
46 Atom type;
47 int format;
48 unsigned char *data;
49 size_t size;
50} *incrs = NULL;
51int nincrs = 0, incrsize = 0;
52
9acadc2b 53/* functional parameters */
54int reading; /* read instead of writing? */
55int convert_to_ctext = True; /* Xmb convert to compound text? */
6aa7b28f 56int verbose;
9acadc2b 57
da0f8522 58const char usagemsg[] =
59 "usage: xcopy [ -r ] [ -u | -c ] [ -C ]\n"
3ffcf184 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"
acc02b82 69 " -F do not fork in write mode\n"
6aa7b28f 70 " -v proceed verbosely when reading selection\n"
c52f9fb9 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"
da0f8522 74 ;
75
76void usage(void) {
77 fputs(usagemsg, stdout);
78}
79
80const char licencemsg[] =
b3d07da3 81 "xcopy is copyright 2001-2004,2008 Simon Tatham.\n"
da0f8522 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
104void licence(void) {
105 fputs(licencemsg, stdout);
106}
107
108void 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
9acadc2b 127int main(int ac, char **av) {
128 int n;
129 int eventloop;
130
131 pname = *av;
132
133 /* parse the command line arguments */
3ffcf184 134 while (--ac > 0) {
9acadc2b 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;
5e36d477 149 } else if (!strcmp(p, "-b")) {
150 use_clipboard = True;
3ffcf184 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;
acc02b82 161 } else if (!strcmp(p, "-F")) {
162 fork_when_writing = False;
6aa7b28f 163 } else if (!strcmp(p, "-v")) {
164 verbose = True;
da0f8522 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;
9acadc2b 174 } else if (*p=='-') {
175 error ("unrecognised option `%s'", p);
176 } else {
177 error ("no parameters required");
178 }
179 }
180
181 if (!reading) {
3ffcf184 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) {
9acadc2b 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();
acc02b82 214 if (!reading && fork_when_writing) {
9acadc2b 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
244void 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
258char *lcasename = "xcopy";
259char *ucasename = "XCopy";
260
261Display *disp = NULL;
262Window ourwin = None;
acc02b82 263Atom compound_text_atom, targets_atom, timestamp_atom, atom_atom, integer_atom;
633c13c1 264Atom multiple_atom, atom_pair_atom, incr_atom;
9acadc2b 265int screen, wwidth, wheight;
266
267Atom strtype = XA_STRING;
5e36d477 268Atom sel_atom = XA_PRIMARY;
3ffcf184 269Atom expected_type = (Atom)None;
270int expected_format = 8;
9acadc2b 271
6aa7b28f 272static 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
9acadc2b 280/*
281 * Returns TRUE if we need to enter an event loop, FALSE otherwise.
282 */
283int 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
3ffcf184 298 targets_atom = XInternAtom(disp, "TARGETS", False);
acc02b82 299 timestamp_atom = XInternAtom(disp, "TIMESTAMP", False);
300 atom_atom = XInternAtom(disp, "ATOM", False);
e1c1021f 301 atom_pair_atom = XInternAtom(disp, "ATOM_PAIR", False);
302 multiple_atom = XInternAtom(disp, "MULTIPLE", False);
acc02b82 303 integer_atom = XInternAtom(disp, "INTEGER", False);
633c13c1 304 incr_atom = XInternAtom(disp, "INCR", False);
9acadc2b 305 if (mode == UTF8) {
306 strtype = XInternAtom(disp, "UTF8_STRING", False);
9acadc2b 307 } else if (mode == CTEXT) {
308 strtype = XInternAtom(disp, "COMPOUND_TEXT", False);
3ffcf184 309 } else if (mode == TARGETS) {
310 strtype = targets_atom;
acc02b82 311 expected_type = atom_atom;
3ffcf184 312 expected_format = 32;
313 } else if (mode == TIMESTAMP) {
acc02b82 314 strtype = timestamp_atom;
315 expected_type = integer_atom;
3ffcf184 316 expected_format = 32;
317 } else if (mode == CUSTOM) {
acc02b82 318 strtype = XInternAtom(disp, custom_seltype, True);
319 if (!strtype)
320 error ("atom '%s' does not exist on the server", custom_seltype);
3ffcf184 321 expected_format = 0;
9acadc2b 322 }
5e36d477 323 if (use_clipboard) {
324 sel_atom = XInternAtom(disp, "CLIPBOARD", False);
5e36d477 325 }
9acadc2b 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 /*
3a3007a4 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
633c13c1 350 * receive the data from the selection owner. Also we need to
351 * make sure we receive PropertyNotify events, for INCR
352 * transfers.
3a3007a4 353 *
354 * If there is no selection owner, look in the cut buffer
355 * property on the root window.
9acadc2b 356 */
633c13c1 357 XSelectInput(disp, ourwin, PropertyChangeMask);
5e36d477 358 if (XGetSelectionOwner(disp, sel_atom) == None) {
9acadc2b 359 /* No primary selection, so use the cut buffer. */
6aa7b28f 360 if (verbose)
361 fprintf(stderr, "no selection owner: trying cut buffer\n");
3ffcf184 362 if (strtype == XA_STRING)
633c13c1 363 do_paste(DefaultRootWindow(disp), XA_CUT_BUFFER0, True);
9acadc2b 364 return False;
365 } else {
366 Atom sel_property = XInternAtom(disp, "VT_SELECTION", False);
6aa7b28f 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);
5e36d477 374 XConvertSelection(disp, sel_atom, strtype,
9acadc2b 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 */
5e36d477 385 XSetSelectionOwner (disp, sel_atom, ourwin, CurrentTime);
386 if (XGetSelectionOwner (disp, sel_atom) != ourwin)
b3d07da3 387 error ("unable to obtain primary X selection");
9acadc2b 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
8fb6d89b 419void 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
e1c1021f 473Atom convert_sel_inner(Window requestor, Atom target, Atom property) {
474 if (target == strtype) {
8fb6d89b 475 write_data(requestor, property, strtype, 8, seltext, sellen);
e1c1021f 476 return property;
477 } else if (target == compound_text_atom && convert_to_ctext) {
478 XTextProperty tp;
8fb6d89b 479 XmbTextListToTextProperty(disp, &seltext, 1, XCompoundTextStyle, &tp);
480 write_data(requestor, property, target, tp.format, tp.value,tp.nitems);
e1c1021f 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;
8fb6d89b 491 write_data(requestor, property, atom_atom, 32, targets, len);
e1c1021f 492 return property;
493 } else if (target == timestamp_atom) {
494 Time rettime = CurrentTime;
8fb6d89b 495 write_data(requestor, property, integer_atom, 32, &rettime, 1);
e1c1021f 496 return property;
497 } else {
498 return None;
499 }
500}
501
502Atom 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
b06414b8 518 if (property == (Atom)None)
519 return None; /* ICCCM says this isn't allowed */
520
e1c1021f 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) {
b06414b8 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]);
e1c1021f 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 {
b06414b8 555 if (property == (Atom)None)
556 property = target; /* ICCCM says this is a sensible default */
e1c1021f 557 return convert_sel_inner(requestor, target, property);
558 }
559}
560
9acadc2b 561void run_X(void) {
562 XEvent ev, e2;
8fb6d89b 563 int i, j;
9acadc2b 564
565 while (1) {
566 XNextEvent (disp, &ev);
567 if (reading) {
568 switch (ev.type) {
569 case SelectionNotify:
6aa7b28f 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
9acadc2b 578 if (ev.xselection.property != None)
579 do_paste(ev.xselection.requestor,
633c13c1 580 ev.xselection.property, False);
9acadc2b 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;
e1c1021f 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);
8fb6d89b 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;
9acadc2b 637 }
638 }
639 }
640}
641
642void done_X(void) {
643 int i;
644
645 if (ourwin != None)
646 XDestroyWindow (disp, ourwin);
647 if (disp)
648 XCloseDisplay (disp);
649}
650
633c13c1 651void do_paste(Window window, Atom property, int cutbuffer) {
9acadc2b 652 Atom actual_type;
653 int actual_format, i;
654 long nitems, bytes_after, nread;
655 unsigned char *data;
633c13c1 656 int incremental = False;
657 XEvent ev;
9acadc2b 658
659 nread = 0;
660 while (XGetWindowProperty(disp, window, property, nread / 4, SELDELTA,
633c13c1 661 !cutbuffer, AnyPropertyType, &actual_type,
9acadc2b 662 &actual_format, &nitems, &bytes_after,
663 (unsigned char **) &data) == Success) {
6aa7b28f 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
633c13c1 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
3ffcf184 690 if (nitems > 0) {
5340832d 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"
b3d07da3 707 " of 4), but more to come", nread);
5340832d 708 }
709
3ffcf184 710 if (expected_type != (Atom)None && actual_type != expected_type) {
6aa7b28f 711 const char *expout = translate_atom(disp, expected_type);
712 const char *gotout = translate_atom(disp, actual_type);
b3d07da3 713 error("unexpected data type: expected %s, got %s",
3ffcf184 714 expout, gotout);
715 }
716 if (expected_format && expected_format != actual_format) {
b3d07da3 717 error("unexpected data format: expected %d-bit, got %d-bit",
3ffcf184 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];
6aa7b28f 728 printf("%s\n", translate_atom(disp, x));
3ffcf184 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 }
9acadc2b 738 }
739 XFree(data);
05ecc810 740 if (bytes_after == 0) {
633c13c1 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 {
05ecc810 763 break;
633c13c1 764 }
05ecc810 765 }
9acadc2b 766 }
767}