Oops, no, that's wrong. When we lose the selection, we must stop
[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;
7d4520e0 36int use_cutbuffers = True;
37int use_outgoing_incr = True;
38int sel_delta = 16384;
9acadc2b 39
40/* selection data */
41char *seltext;
42int sellen, selsize;
9acadc2b 43
8fb6d89b 44/* incremental transfers still pending when we return to the event loop */
45struct incr {
46 Window window;
47 Atom property;
48 Atom type;
49 int format;
50 unsigned char *data;
51 size_t size;
52} *incrs = NULL;
53int nincrs = 0, incrsize = 0;
54
9acadc2b 55/* functional parameters */
56int reading; /* read instead of writing? */
57int convert_to_ctext = True; /* Xmb convert to compound text? */
6aa7b28f 58int verbose;
9acadc2b 59
da0f8522 60const char usagemsg[] =
61 "usage: xcopy [ -r ] [ -u | -c ] [ -C ]\n"
7d4520e0 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"
3ffcf184 65 " -T get the time stamp of the selection contents\n"
66 " -a atom get an arbitrary form of the selection data\n"
7d4520e0 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"
c52f9fb9 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"
da0f8522 79 ;
80
81void usage(void) {
82 fputs(usagemsg, stdout);
83}
84
85const char licencemsg[] =
b3d07da3 86 "xcopy is copyright 2001-2004,2008 Simon Tatham.\n"
da0f8522 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
109void licence(void) {
110 fputs(licencemsg, stdout);
111}
112
113void 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
9acadc2b 132int main(int ac, char **av) {
133 int n;
134 int eventloop;
135
136 pname = *av;
137
138 /* parse the command line arguments */
3ffcf184 139 while (--ac > 0) {
9acadc2b 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;
5e36d477 154 } else if (!strcmp(p, "-b")) {
155 use_clipboard = True;
3ffcf184 156 } else if (!strcmp(p, "-t")) {
157 mode = TARGETS;
158 } else if (!strcmp(p, "-T")) {
159 mode = TIMESTAMP;
7d4520e0 160 } else if (!strcmp(p, "-I")) {
161 use_outgoing_incr = False;
162 } else if (!strcmp(p, "-B")) {
163 use_cutbuffers = False;
3ffcf184 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;
7d4520e0 170 } else if (!strcmp(p, "-d")) {
171 if (--ac > 0)
172 sel_delta = atoi(*++av);
173 else
174 error ("expected an argument to `-d'");
acc02b82 175 } else if (!strcmp(p, "-F")) {
176 fork_when_writing = False;
6aa7b28f 177 } else if (!strcmp(p, "-v")) {
178 verbose = True;
da0f8522 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;
9acadc2b 188 } else if (*p=='-') {
189 error ("unrecognised option `%s'", p);
190 } else {
191 error ("no parameters required");
192 }
193 }
194
195 if (!reading) {
3ffcf184 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) {
7d4520e0 205 seltext = malloc(sel_delta);
9acadc2b 206 if (!seltext)
207 error ("out of memory");
7d4520e0 208 selsize = sel_delta;
9acadc2b 209 sellen = 0;
210 do {
211 n = fread(seltext+sellen, 1, selsize-sellen, stdin);
212 sellen += n;
213 if (sellen >= selsize) {
7d4520e0 214 seltext = realloc(seltext, selsize += sel_delta);
9acadc2b 215 if (!seltext)
216 error ("out of memory");
217 }
218 } while (n > 0);
219 if (sellen == selsize) {
7d4520e0 220 seltext = realloc(seltext, selsize += sel_delta);
9acadc2b 221 if (!seltext)
222 error ("out of memory");
223 }
224 seltext[sellen] = '\0';
225 }
226
227 eventloop = init_X();
acc02b82 228 if (!reading && fork_when_writing) {
9acadc2b 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
258void 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
272char *lcasename = "xcopy";
273char *ucasename = "XCopy";
274
275Display *disp = NULL;
276Window ourwin = None;
acc02b82 277Atom compound_text_atom, targets_atom, timestamp_atom, atom_atom, integer_atom;
633c13c1 278Atom multiple_atom, atom_pair_atom, incr_atom;
9acadc2b 279int screen, wwidth, wheight;
280
281Atom strtype = XA_STRING;
5e36d477 282Atom sel_atom = XA_PRIMARY;
3ffcf184 283Atom expected_type = (Atom)None;
284int expected_format = 8;
9acadc2b 285
6aa7b28f 286static 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
9acadc2b 294/*
295 * Returns TRUE if we need to enter an event loop, FALSE otherwise.
296 */
297int 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
3ffcf184 312 targets_atom = XInternAtom(disp, "TARGETS", False);
acc02b82 313 timestamp_atom = XInternAtom(disp, "TIMESTAMP", False);
314 atom_atom = XInternAtom(disp, "ATOM", False);
e1c1021f 315 atom_pair_atom = XInternAtom(disp, "ATOM_PAIR", False);
316 multiple_atom = XInternAtom(disp, "MULTIPLE", False);
acc02b82 317 integer_atom = XInternAtom(disp, "INTEGER", False);
633c13c1 318 incr_atom = XInternAtom(disp, "INCR", False);
9acadc2b 319 if (mode == UTF8) {
320 strtype = XInternAtom(disp, "UTF8_STRING", False);
9acadc2b 321 } else if (mode == CTEXT) {
322 strtype = XInternAtom(disp, "COMPOUND_TEXT", False);
3ffcf184 323 } else if (mode == TARGETS) {
324 strtype = targets_atom;
acc02b82 325 expected_type = atom_atom;
3ffcf184 326 expected_format = 32;
327 } else if (mode == TIMESTAMP) {
acc02b82 328 strtype = timestamp_atom;
329 expected_type = integer_atom;
3ffcf184 330 expected_format = 32;
331 } else if (mode == CUSTOM) {
acc02b82 332 strtype = XInternAtom(disp, custom_seltype, True);
333 if (!strtype)
334 error ("atom '%s' does not exist on the server", custom_seltype);
3ffcf184 335 expected_format = 0;
9acadc2b 336 }
5e36d477 337 if (use_clipboard) {
338 sel_atom = XInternAtom(disp, "CLIPBOARD", False);
5e36d477 339 }
9acadc2b 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 /*
3a3007a4 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
633c13c1 364 * receive the data from the selection owner. Also we need to
365 * make sure we receive PropertyNotify events, for INCR
366 * transfers.
3a3007a4 367 *
368 * If there is no selection owner, look in the cut buffer
369 * property on the root window.
9acadc2b 370 */
633c13c1 371 XSelectInput(disp, ourwin, PropertyChangeMask);
7d4520e0 372 if (XGetSelectionOwner(disp, sel_atom) != None) {
9acadc2b 373 Atom sel_property = XInternAtom(disp, "VT_SELECTION", False);
6aa7b28f 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);
5e36d477 381 XConvertSelection(disp, sel_atom, strtype,
9acadc2b 382 sel_property, ourwin, CurrentTime);
383 return True;
7d4520e0 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;
9acadc2b 394 }
395 } else {
396 /*
7d4520e0 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.
9acadc2b 403 */
5e36d477 404 XSetSelectionOwner (disp, sel_atom, ourwin, CurrentTime);
405 if (XGetSelectionOwner (disp, sel_atom) != ourwin)
b3d07da3 406 error ("unable to obtain primary X selection");
9acadc2b 407 compound_text_atom = XInternAtom(disp, "COMPOUND_TEXT", False);
7d4520e0 408 if (strtype == XA_STRING && sellen <= sel_delta && use_cutbuffers) {
9acadc2b 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
8fb6d89b 438void 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
7d4520e0 445 if (!use_outgoing_incr || size * bformat <= sel_delta) {
8fb6d89b 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
e1c1021f 492Atom convert_sel_inner(Window requestor, Atom target, Atom property) {
493 if (target == strtype) {
8fb6d89b 494 write_data(requestor, property, strtype, 8, seltext, sellen);
e1c1021f 495 return property;
496 } else if (target == compound_text_atom && convert_to_ctext) {
497 XTextProperty tp;
8fb6d89b 498 XmbTextListToTextProperty(disp, &seltext, 1, XCompoundTextStyle, &tp);
499 write_data(requestor, property, target, tp.format, tp.value,tp.nitems);
e1c1021f 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;
8fb6d89b 510 write_data(requestor, property, atom_atom, 32, targets, len);
e1c1021f 511 return property;
512 } else if (target == timestamp_atom) {
513 Time rettime = CurrentTime;
8fb6d89b 514 write_data(requestor, property, integer_atom, 32, &rettime, 1);
e1c1021f 515 return property;
516 } else {
517 return None;
518 }
519}
520
521Atom convert_sel_outer(Window requestor, Atom target, Atom property) {
461ec27a 522 /*
523 * ICCCM 2.2 says that obsolete clients requesting the selection
524 * request may not specify a property name under which they want
525 * the data written to their window; selection owners are
526 * encouraged to support such clients by reusing the selection
527 * target name as the property.
528 */
529 if (property == None)
530 property = target;
531
e1c1021f 532 if (target == multiple_atom) {
533 /*
534 * Support for the MULTIPLE selection type, since it's
535 * specified as required in the ICCCM. Completely
536 * untested, though, because I have no idea of what X
537 * application might implement it for me to test against.
538 */
539
7d4520e0 540 int size = sel_delta;
e1c1021f 541 Atom actual_type;
542 int actual_format, i;
543 long nitems, bytes_after, nread;
544 unsigned char *data;
545 Atom *adata;
546
b06414b8 547 if (property == (Atom)None)
548 return None; /* ICCCM says this isn't allowed */
549
e1c1021f 550 /*
551 * Fetch the requestor's window property giving a list of
552 * selection requests.
553 */
554 while (XGetWindowProperty(disp, requestor, property, 0, size,
555 False, AnyPropertyType, &actual_type,
556 &actual_format, &nitems, &bytes_after,
557 (unsigned char **) &data) == Success &&
558 nitems * (actual_format / 8) == size) {
559 XFree(data);
560 size *= 3 / 2;
561 }
562
563 if (actual_type != atom_pair_atom || actual_format != 32) {
564 XFree(data);
565 return None;
566 }
567
568 adata = (Atom *)data;
569
570 for (i = 0; i+1 < nitems; i += 2) {
b06414b8 571 if (adata[i+1] != (Atom)None) /* ICCCM says this isn't allowed */
572 adata[i+1] = convert_sel_inner(requestor, adata[i],
573 adata[i+1]);
e1c1021f 574 }
575
576 XChangeProperty (disp, requestor, property,
577 atom_pair_atom, 32, PropModeReplace,
578 data, nitems);
579
580 XFree(data);
581
582 return property;
583 } else {
b06414b8 584 if (property == (Atom)None)
585 property = target; /* ICCCM says this is a sensible default */
e1c1021f 586 return convert_sel_inner(requestor, target, property);
587 }
588}
589
9acadc2b 590void run_X(void) {
591 XEvent ev, e2;
8fb6d89b 592 int i, j;
9acadc2b 593
594 while (1) {
595 XNextEvent (disp, &ev);
596 if (reading) {
597 switch (ev.type) {
598 case SelectionNotify:
6aa7b28f 599 if (verbose)
600 fprintf(stderr, "got SelectionNotify: requestor=%08lx "
601 "selection=%s target=%s property=%s\n",
602 ev.xselection.requestor,
603 translate_atom(disp, ev.xselection.selection),
604 translate_atom(disp, ev.xselection.target),
605 translate_atom(disp, ev.xselection.property));
606
9acadc2b 607 if (ev.xselection.property != None)
608 do_paste(ev.xselection.requestor,
633c13c1 609 ev.xselection.property, False);
9acadc2b 610 return;
611 }
612 } else {
4bf3e4f3 613 int have_ownership = True;
f8381827 614
9acadc2b 615 switch (ev.type) {
616 case SelectionClear:
617 /* Selection has been cleared by another app. */
4bf3e4f3 618 have_ownership = False;
f8381827 619 break;
9acadc2b 620 case SelectionRequest:
4bf3e4f3 621 if (have_ownership) {
622 e2.xselection.type = SelectionNotify;
623 e2.xselection.requestor = ev.xselectionrequest.requestor;
624 e2.xselection.selection = ev.xselectionrequest.selection;
625 e2.xselection.target = ev.xselectionrequest.target;
626 e2.xselection.time = ev.xselectionrequest.time;
627 e2.xselection.property =
628 convert_sel_outer(ev.xselectionrequest.requestor,
629 ev.xselectionrequest.target,
630 ev.xselectionrequest.property);
631 XSendEvent (disp, ev.xselectionrequest.requestor,
632 False, 0, &e2);
633 }
8fb6d89b 634 break;
635 case PropertyNotify:
636 for (i = j = 0; i < nincrs; i++) {
637 int keep = True;
638 if (incrs[i].window == ev.xproperty.window &&
639 incrs[i].property == ev.xproperty.atom &&
640 ev.xproperty.state == PropertyDelete) {
641 size_t thissize = incrs[i].size;
7d4520e0 642 if (thissize > sel_delta)
643 thissize = sel_delta;
8fb6d89b 644
645 XChangeProperty(disp,
646 incrs[i].window, incrs[i].property,
647 incrs[i].type, incrs[i].format,
648 PropModeReplace, incrs[i].data,
649 thissize / (incrs[i].format/8));
650
651 if (thissize == 0) {
652 /*
653 * If we've just sent a zero-length block,
654 * the incremental transfer is over and we
655 * should delete this entry.
656 */
657 keep = False;
658 }
659
660 incrs[i].data += thissize;
661 incrs[i].size -= thissize;
662 }
663 if (keep) {
664 if (j != i)
665 incrs[j] = incrs[i];
666 j++;
667 }
668 }
669 nincrs = j;
670 break;
9acadc2b 671 }
4bf3e4f3 672 if (nincrs == 0 && !have_ownership)
f8381827 673 return;
9acadc2b 674 }
675 }
676}
677
678void done_X(void) {
679 int i;
680
681 if (ourwin != None)
682 XDestroyWindow (disp, ourwin);
683 if (disp)
684 XCloseDisplay (disp);
685}
686
633c13c1 687void do_paste(Window window, Atom property, int cutbuffer) {
9acadc2b 688 Atom actual_type;
689 int actual_format, i;
690 long nitems, bytes_after, nread;
691 unsigned char *data;
633c13c1 692 int incremental = False;
693 XEvent ev;
9acadc2b 694
695 nread = 0;
7d4520e0 696 while (XGetWindowProperty(disp, window, property, nread / 4, sel_delta / 4,
633c13c1 697 !cutbuffer, AnyPropertyType, &actual_type,
9acadc2b 698 &actual_format, &nitems, &bytes_after,
699 (unsigned char **) &data) == Success) {
6aa7b28f 700 if (verbose)
701 fprintf(stderr, "got %ld items of %d-byte data, type=%s;"
702 " %ld to go\n", nitems, actual_format,
703 translate_atom(disp, actual_type), bytes_after);
704
633c13c1 705 /*
706 * ICCCM 2.7.2: if we receive data with the type atom set to
707 * INCR, it indicates that the actual data will arrive in
708 * multiple chunks, terminating with a zero-length one.
709 * Between each pair, we must wait for a PropertyNotify event
710 * which tells us that the next chunk has arrived.
711 */
712 if (actual_type == incr_atom && !cutbuffer) {
713 incremental = True;
714 /*
715 * Immediately wait for the first chunk of real data.
716 */
717 do {
718 XMaskEvent(disp, PropertyChangeMask, &ev);
719 } while (ev.xproperty.state != PropertyNewValue);
720 /*
721 * And loop straight back round to read it.
722 */
723 continue;
724 }
725
3ffcf184 726 if (nitems > 0) {
5340832d 727 /*
728 * We expect all returned chunks of data to be
729 * multiples of 4 bytes (because we can only request
730 * the subsequent starting offset in 4-byte
731 * increments). Of course you can store an odd number
732 * of bytes in a selection, so this can't be the case
733 * every time XGetWindowProperty returns; but it
734 * should be the case every time it returns _and there
735 * is more data to come_.
736 *
737 * Hence, whenever XGetWindowProperty returns, we
738 * verify that the size of the data returned _last_
739 * time was divisible by 4.
740 */
741 if ((nread & 3) != 0) {
742 error("unexpected data size: %d read (not a multiple"
b3d07da3 743 " of 4), but more to come", nread);
5340832d 744 }
745
3ffcf184 746 if (expected_type != (Atom)None && actual_type != expected_type) {
6aa7b28f 747 const char *expout = translate_atom(disp, expected_type);
748 const char *gotout = translate_atom(disp, actual_type);
b3d07da3 749 error("unexpected data type: expected %s, got %s",
3ffcf184 750 expout, gotout);
751 }
752 if (expected_format && expected_format != actual_format) {
b3d07da3 753 error("unexpected data format: expected %d-bit, got %d-bit",
3ffcf184 754 expected_format, actual_format);
755 }
756 }
757
758 if (nitems > 0) {
759 if (mode == TARGETS) {
760 assert(actual_format == 32);
761 int i;
762 for (i = 0; i < nitems; i++) {
763 Atom x = ((Atom *)data)[i];
6aa7b28f 764 printf("%s\n", translate_atom(disp, x));
3ffcf184 765 }
766 } else if (mode == TIMESTAMP) {
767 assert(actual_format == 32);
768 Time x = ((Time *)data)[0];
769 printf("%lu\n", (unsigned long)x);
770 } else {
771 fwrite(data, actual_format / 8, nitems, stdout);
772 nread += nitems * actual_format / 8;
773 }
9acadc2b 774 }
775 XFree(data);
05ecc810 776 if (bytes_after == 0) {
633c13c1 777 /*
778 * We've come to the end of the property we're reading.
779 */
780 if (incremental) {
781 /*
782 * For an incremental transfer, this means we wait for
783 * another property to be dumped in the same place on
784 * our window, and loop round again reading that. The
785 * exception is if the total length of the property we
786 * got was zero, which signals the end.
787 */
788 if (nread == 0 && nitems == 0)
789 break; /* all done */
790
791 /* otherwise wait for the next chunk */
792 do {
793 XMaskEvent(disp, PropertyChangeMask, &ev);
794 } while (ev.xproperty.state != PropertyNewValue);
795
796 /* which we read from the beginning */
797 nread = 0;
798 } else {
05ecc810 799 break;
633c13c1 800 }
05ecc810 801 }
9acadc2b 802 }
803}