2 * xcopy: quickly pipe text data into, or out of, the primary X
18 #include <X11/Intrinsic.h>
20 #include <X11/Xutil.h>
21 #include <X11/Xatom.h>
26 void full_redraw(void);
27 void do_paste(Window window
, Atom property
, int Delete
);
29 char *pname
; /* program name */
31 void error (char *fmt
, ...);
33 /* set from command-line parameters */
35 enum { STRING
, CTEXT
, UTF8
, TARGETS
, TIMESTAMP
, CUSTOM
} mode
= STRING
;
36 int use_clipboard
= False
;
37 char *custom_seltype
= NULL
;
38 int fork_when_writing
= True
;
39 int use_cutbuffers
= True
;
40 int use_outgoing_incr
= True
;
41 int sel_delta
= 16384;
47 /* incremental transfers still pending when we return to the event loop */
56 int nincrs
= 0, incrsize
= 0;
58 /* functional parameters */
59 int reading
; /* read instead of writing? */
60 int convert_to_ctext
= True
; /* Xmb convert to compound text? */
63 const char usagemsg
[] =
64 "usage: xcopy [ -r ] [ -u | -c ] [ -C ]\n"
65 "modes: -r read X selection and print on stdout\n"
66 " no -r write to X selection from stdin\n"
67 " read: -t get the list of targets available to retrieve\n"
68 " -T get the time stamp of the selection contents\n"
69 " -a atom get an arbitrary form of the selection data\n"
70 " -v verbosely report progress in reading selection\n"
71 "write: -F remain in foreground until selection reclaimed\n"
72 " -I do not attempt incremental transfers (INCR)\n"
73 " both: -u work with UTF8_STRING type selections\n"
74 " -c work with COMPOUND_TEXT type selections\n"
75 " -C suppress automatic conversion to COMPOUND_TEXT\n"
76 " -b use the CLIPBOARD rather than the PRIMARY selection\n"
77 " -d size transfer data in chunks of at most <size> (default 16384)\n"
78 " -B do not use root window cut buffers\n"
79 " also: xcopy --version report version number\n"
80 " xcopy --help display this help text\n"
81 " xcopy --licence display the (MIT) licence text\n"
85 fputs(usagemsg
, stdout
);
88 const char licencemsg
[] =
89 "xcopy is copyright 2001-2004,2008 Simon Tatham.\n"
91 "Permission is hereby granted, free of charge, to any person\n"
92 "obtaining a copy of this software and associated documentation files\n"
93 "(the \"Software\"), to deal in the Software without restriction,\n"
94 "including without limitation the rights to use, copy, modify, merge,\n"
95 "publish, distribute, sublicense, and/or sell copies of the Software,\n"
96 "and to permit persons to whom the Software is furnished to do so,\n"
97 "subject to the following conditions:\n"
99 "The above copyright notice and this permission notice shall be\n"
100 "included in all copies or substantial portions of the Software.\n"
102 "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n"
103 "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n"
104 "MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n"
105 "NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n"
106 "BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n"
107 "ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n"
108 "CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n"
113 fputs(licencemsg
, stdout
);
117 #define SVN_REV "$Revision$"
118 char rev
[sizeof(SVN_REV
)];
121 strcpy(rev
, SVN_REV
);
123 for (p
= rev
; *p
&& *p
!= ':'; p
++);
126 while (*p
&& isspace(*p
)) p
++;
127 for (q
= p
; *q
&& *q
!= '$'; q
++);
129 printf("xcopy revision %s\n", p
);
131 printf("xcopy: unknown version\n");
135 int main(int ac
, char **av
) {
141 /* parse the command line arguments */
145 if (!strcmp(p
, "-display") || !strcmp(p
, "-disp")) {
147 error ("option `%s' expects a parameter", p
);
148 display
= *++av
, --ac
;
149 } else if (!strcmp(p
, "-r")) {
151 } else if (!strcmp(p
, "-u")) {
153 } else if (!strcmp(p
, "-c")) {
155 } else if (!strcmp(p
, "-C")) {
156 convert_to_ctext
= False
;
157 } else if (!strcmp(p
, "-b")) {
158 use_clipboard
= True
;
159 } else if (!strcmp(p
, "-t")) {
161 } else if (!strcmp(p
, "-T")) {
163 } else if (!strcmp(p
, "-I")) {
164 use_outgoing_incr
= False
;
165 } else if (!strcmp(p
, "-B")) {
166 use_cutbuffers
= False
;
167 } else if (!strcmp(p
, "-a")) {
169 custom_seltype
= *++av
;
171 error ("expected an argument to `-a'");
173 } else if (!strcmp(p
, "-d")) {
175 sel_delta
= atoi(*++av
);
177 error ("expected an argument to `-d'");
178 } else if (!strcmp(p
, "-F")) {
179 fork_when_writing
= False
;
180 } else if (!strcmp(p
, "-v")) {
182 } else if (!strcmp(p
, "--help")) {
185 } else if (!strcmp(p
, "--version")) {
188 } else if (!strcmp(p
, "--licence") || !strcmp(p
, "--license")) {
191 } else if (*p
=='-') {
192 error ("unrecognised option `%s'", p
);
194 error ("no parameters required");
199 if (mode
== TARGETS
|| mode
== TIMESTAMP
|| mode
== CUSTOM
) {
200 error ("%s not supported in writing mode; use -r",
201 (mode
== TARGETS ?
"-t" :
202 mode
== TIMESTAMP ?
"-T" :
203 /* mode == CUSTOM ? */ "-a"));
208 seltext
= malloc(sel_delta
);
210 error ("out of memory");
214 n
= fread(seltext
+sellen
, 1, selsize
-sellen
, stdin
);
216 if (sellen
>= selsize
) {
217 seltext
= realloc(seltext
, selsize
+= sel_delta
);
219 error ("out of memory");
222 if (sellen
== selsize
) {
223 seltext
= realloc(seltext
, selsize
+= sel_delta
);
225 error ("out of memory");
227 seltext
[sellen
] = '\0';
230 eventloop
= init_X();
231 if (!reading
&& fork_when_writing
) {
233 * If we are writing the selection, we must go into the
238 error("unable to fork: %s", strerror(errno
));
239 } else if (pid
> 0) {
241 * we are the parent; just exit
261 void error (char *fmt
, ...) {
267 vsprintf (errbuf
, fmt
, ap
);
269 fprintf (stderr
, "%s: %s\n", pname
, errbuf
);
273 /* begin the X interface */
275 char *lcasename
= "xcopy";
276 char *ucasename
= "XCopy";
278 Display
*disp
= NULL
;
279 Window ourwin
= None
;
280 Atom compound_text_atom
, targets_atom
, timestamp_atom
, atom_atom
, integer_atom
;
281 Atom multiple_atom
, atom_pair_atom
, incr_atom
;
282 int screen
, wwidth
, wheight
;
284 Atom strtype
= XA_STRING
;
285 Atom sel_atom
= XA_PRIMARY
;
286 Atom expected_type
= (Atom
)None
;
287 int expected_format
= 8;
289 static const char *translate_atom(Display
*disp
, Atom atom
)
294 return XGetAtomName(disp
, atom
);
298 * Returns TRUE if we need to enter an event loop, FALSE otherwise.
302 int x
= 0, y
= 0, width
= 512, height
= 128;
305 XSizeHints size_hints
;
306 XClassHint class_hints
;
307 XTextProperty textprop
;
310 /* open the X display */
311 disp
= XOpenDisplay (display
);
313 error ("unable to open display");
315 targets_atom
= XInternAtom(disp
, "TARGETS", False
);
316 timestamp_atom
= XInternAtom(disp
, "TIMESTAMP", False
);
317 atom_atom
= XInternAtom(disp
, "ATOM", False
);
318 atom_pair_atom
= XInternAtom(disp
, "ATOM_PAIR", False
);
319 multiple_atom
= XInternAtom(disp
, "MULTIPLE", False
);
320 integer_atom
= XInternAtom(disp
, "INTEGER", False
);
321 incr_atom
= XInternAtom(disp
, "INCR", False
);
323 strtype
= XInternAtom(disp
, "UTF8_STRING", False
);
324 } else if (mode
== CTEXT
) {
325 strtype
= XInternAtom(disp
, "COMPOUND_TEXT", False
);
326 } else if (mode
== TARGETS
) {
327 strtype
= targets_atom
;
328 expected_type
= atom_atom
;
329 expected_format
= 32;
330 } else if (mode
== TIMESTAMP
) {
331 strtype
= timestamp_atom
;
332 expected_type
= integer_atom
;
333 expected_format
= 32;
334 } else if (mode
== CUSTOM
) {
335 strtype
= XInternAtom(disp
, custom_seltype
, True
);
337 error ("atom '%s' does not exist on the server", custom_seltype
);
341 sel_atom
= XInternAtom(disp
, "CLIPBOARD", False
);
344 /* get the screen and root-window */
345 screen
= DefaultScreen (disp
);
346 root
= RootWindow (disp
, screen
);
349 width
= height
= 10; /* doesn't really matter */
351 /* actually create the window */
352 ourwin
= XCreateSimpleWindow (disp
, root
, x
, y
, width
, height
,0,
353 BlackPixel(disp
, screen
),
354 WhitePixel(disp
, screen
));
356 /* resource class name */
357 class_hints
.res_name
= lcasename
;
358 class_hints
.res_class
= ucasename
;
359 XSetClassHint (disp
, ourwin
, &class_hints
);
361 /* do selection fiddling */
364 * We are reading the selection. Call XConvertSelection to
365 * request transmission of the selection data in the
366 * appropriate format; the X event loop will then wait to
367 * receive the data from the selection owner. Also we need to
368 * make sure we receive PropertyNotify events, for INCR
371 * If there is no selection owner, look in the cut buffer
372 * property on the root window.
374 XSelectInput(disp
, ourwin
, PropertyChangeMask
);
375 if (XGetSelectionOwner(disp
, sel_atom
) != None
) {
376 Atom sel_property
= XInternAtom(disp
, "VT_SELECTION", False
);
378 fprintf(stderr
, "calling XConvertSelection: selection=%s"
379 " target=%s property=%s requestor=%08lx\n",
380 translate_atom(disp
, sel_atom
),
381 translate_atom(disp
, strtype
),
382 translate_atom(disp
, sel_property
),
384 XConvertSelection(disp
, sel_atom
, strtype
,
385 sel_property
, ourwin
, CurrentTime
);
387 } else if (use_cutbuffers
) {
388 /* No primary selection, so use the cut buffer. */
390 fprintf(stderr
, "no selection owner: trying cut buffer\n");
391 if (strtype
== XA_STRING
)
392 do_paste(DefaultRootWindow(disp
), XA_CUT_BUFFER0
, True
);
395 /* Last fallback: do nothing. */
400 * We are writing to the selection, so we establish ourselves
401 * as selection owner.
403 * Also place the data in CUT_BUFFER0, if it isn't of an
404 * exotic type (cut buffers can only take ordinary string
405 * data, it turns out) or bigger than our maximum chunk size.
407 XSetSelectionOwner (disp
, sel_atom
, ourwin
, CurrentTime
);
408 if (XGetSelectionOwner (disp
, sel_atom
) != ourwin
)
409 error ("unable to obtain primary X selection");
410 compound_text_atom
= XInternAtom(disp
, "COMPOUND_TEXT", False
);
411 if (strtype
== XA_STRING
&& sellen
<= sel_delta
&& use_cutbuffers
) {
413 * ICCCM-required cut buffer initialisation.
415 static const unsigned char emptystring
[] = {0};
416 XChangeProperty(disp
, root
, XA_CUT_BUFFER0
,
417 XA_STRING
, 8, PropModeAppend
, emptystring
, 0);
418 XChangeProperty(disp
, root
, XA_CUT_BUFFER1
,
419 XA_STRING
, 8, PropModeAppend
, emptystring
, 0);
420 XChangeProperty(disp
, root
, XA_CUT_BUFFER2
,
421 XA_STRING
, 8, PropModeAppend
, emptystring
, 0);
422 XChangeProperty(disp
, root
, XA_CUT_BUFFER3
,
423 XA_STRING
, 8, PropModeAppend
, emptystring
, 0);
424 XChangeProperty(disp
, root
, XA_CUT_BUFFER4
,
425 XA_STRING
, 8, PropModeAppend
, emptystring
, 0);
426 XChangeProperty(disp
, root
, XA_CUT_BUFFER5
,
427 XA_STRING
, 8, PropModeAppend
, emptystring
, 0);
428 XChangeProperty(disp
, root
, XA_CUT_BUFFER6
,
429 XA_STRING
, 8, PropModeAppend
, emptystring
, 0);
430 XChangeProperty(disp
, root
, XA_CUT_BUFFER7
,
431 XA_STRING
, 8, PropModeAppend
, emptystring
, 0);
433 * Rotate the cut buffers and add our text in CUT_BUFFER0.
435 XRotateBuffers(disp
, 1);
436 XStoreBytes(disp
, seltext
, sellen
);
442 void write_data(Window requestor
, Atom property
, Atom type
, int format
,
443 void *vdata
, size_t size
)
445 int bformat
= format
/ 8; /* bytes per element */
446 unsigned char *data
= (unsigned char *)vdata
;
449 if (!use_outgoing_incr
|| size
* bformat
<= sel_delta
) {
450 XChangeProperty(disp
, requestor
, property
, type
, format
,
451 PropModeReplace
, data
, size
);
454 * For large data, an incremental transfer as per ICCCM 2.7.2.
456 Cardinal totalsize
= size
* bformat
;
457 Cardinal sent
, thissize
;
460 * We're going to need PropertyNotify events on the target
461 * window to tell us when to send the next chunk.
463 XSelectInput(disp
, requestor
, PropertyChangeMask
);
466 * Start by sending a single 32-bit word with type INCR giving
467 * the total size in bytes.
469 XChangeProperty(disp
, requestor
, property
, incr_atom
, 32,
470 PropModeReplace
, (unsigned char *)&totalsize
, 1);
473 * Now set up an entry in our list of ongoing incremental
474 * transfers, so that whenever that property is deleted, we'll
475 * send the next batch.
477 if (nincrs
>= incrsize
) {
478 incrsize
= nincrs
* 9 / 8 + 16;
479 incrs
= realloc(incrs
, incrsize
* sizeof(*incrs
));
481 error ("out of memory");
483 incrs
[nincrs
].window
= requestor
;
484 incrs
[nincrs
].property
= property
;
485 incrs
[nincrs
].type
= type
;
486 incrs
[nincrs
].format
= format
;
487 incrs
[nincrs
].size
= totalsize
;
488 incrs
[nincrs
].data
= malloc(totalsize
);
489 if (!incrs
[nincrs
].data
)
490 error("out of memory");
491 memcpy(incrs
[nincrs
].data
, data
, size
);
496 Atom
convert_sel_inner(Window requestor
, Atom target
, Atom property
) {
497 if (target
== strtype
) {
498 write_data(requestor
, property
, strtype
, 8, seltext
, sellen
);
500 } else if (target
== compound_text_atom
&& convert_to_ctext
) {
502 XmbTextListToTextProperty(disp
, &seltext
, 1, XCompoundTextStyle
, &tp
);
503 write_data(requestor
, property
, target
, tp
.format
, tp
.value
,tp
.nitems
);
505 } else if (target
== targets_atom
) {
508 targets
[len
++] = timestamp_atom
;
509 targets
[len
++] = targets_atom
;
510 targets
[len
++] = multiple_atom
;
511 targets
[len
++] = strtype
;
512 if (strtype
!= compound_text_atom
&& convert_to_ctext
)
513 targets
[len
++] = compound_text_atom
;
514 write_data(requestor
, property
, atom_atom
, 32, targets
, len
);
516 } else if (target
== timestamp_atom
) {
517 Time rettime
= CurrentTime
;
518 write_data(requestor
, property
, integer_atom
, 32, &rettime
, 1);
525 Atom
convert_sel_outer(Window requestor
, Atom target
, Atom property
) {
527 * ICCCM 2.2 says that obsolete clients requesting the selection
528 * request may not specify a property name under which they want
529 * the data written to their window; selection owners are
530 * encouraged to support such clients by reusing the selection
531 * target name as the property.
533 if (property
== None
)
536 if (target
== multiple_atom
) {
538 * Support for the MULTIPLE selection type, since it's
539 * specified as required in the ICCCM. Completely
540 * untested, though, because I have no idea of what X
541 * application might implement it for me to test against.
544 int size
= sel_delta
;
546 int actual_format
, i
;
547 unsigned long nitems
, bytes_after
, nread
;
551 if (property
== (Atom
)None
)
552 return None
; /* ICCCM says this isn't allowed */
555 * Fetch the requestor's window property giving a list of
556 * selection requests.
558 while (XGetWindowProperty(disp
, requestor
, property
, 0, size
,
559 False
, AnyPropertyType
, &actual_type
,
560 &actual_format
, &nitems
, &bytes_after
,
561 (unsigned char **) &data
) == Success
&&
562 nitems
* (actual_format
/ 8) == size
) {
567 if (actual_type
!= atom_pair_atom
|| actual_format
!= 32) {
572 adata
= (Atom
*)data
;
574 for (i
= 0; i
+1 < (long)nitems
; i
+= 2) {
575 if (adata
[i
+1] != (Atom
)None
) /* ICCCM says this isn't allowed */
576 adata
[i
+1] = convert_sel_inner(requestor
, adata
[i
],
580 XChangeProperty (disp
, requestor
, property
,
581 atom_pair_atom
, 32, PropModeReplace
,
588 if (property
== (Atom
)None
)
589 property
= target
; /* ICCCM says this is a sensible default */
590 return convert_sel_inner(requestor
, target
, property
);
599 XNextEvent (disp
, &ev
);
602 case SelectionNotify
:
604 fprintf(stderr
, "got SelectionNotify: requestor=%08lx "
605 "selection=%s target=%s property=%s\n",
606 ev
.xselection
.requestor
,
607 translate_atom(disp
, ev
.xselection
.selection
),
608 translate_atom(disp
, ev
.xselection
.target
),
609 translate_atom(disp
, ev
.xselection
.property
));
611 if (ev
.xselection
.property
!= None
)
612 do_paste(ev
.xselection
.requestor
,
613 ev
.xselection
.property
, False
);
617 int have_ownership
= True
;
621 /* Selection has been cleared by another app. */
622 have_ownership
= False
;
624 case SelectionRequest
:
625 if (have_ownership
) {
626 e2
.xselection
.type
= SelectionNotify
;
627 e2
.xselection
.requestor
= ev
.xselectionrequest
.requestor
;
628 e2
.xselection
.selection
= ev
.xselectionrequest
.selection
;
629 e2
.xselection
.target
= ev
.xselectionrequest
.target
;
630 e2
.xselection
.time
= ev
.xselectionrequest
.time
;
631 e2
.xselection
.property
=
632 convert_sel_outer(ev
.xselectionrequest
.requestor
,
633 ev
.xselectionrequest
.target
,
634 ev
.xselectionrequest
.property
);
635 XSendEvent (disp
, ev
.xselectionrequest
.requestor
,
640 for (i
= j
= 0; i
< nincrs
; i
++) {
642 if (incrs
[i
].window
== ev
.xproperty
.window
&&
643 incrs
[i
].property
== ev
.xproperty
.atom
&&
644 ev
.xproperty
.state
== PropertyDelete
) {
645 size_t thissize
= incrs
[i
].size
;
646 if (thissize
> sel_delta
)
647 thissize
= sel_delta
;
649 XChangeProperty(disp
,
650 incrs
[i
].window
, incrs
[i
].property
,
651 incrs
[i
].type
, incrs
[i
].format
,
652 PropModeReplace
, incrs
[i
].data
,
653 thissize
/ (incrs
[i
].format
/8));
657 * If we've just sent a zero-length block,
658 * the incremental transfer is over and we
659 * should delete this entry.
664 incrs
[i
].data
+= thissize
;
665 incrs
[i
].size
-= thissize
;
676 if (nincrs
== 0 && !have_ownership
)
686 XDestroyWindow (disp
, ourwin
);
688 XCloseDisplay (disp
);
691 void do_paste(Window window
, Atom property
, int cutbuffer
) {
693 int actual_format
, i
;
694 unsigned long nitems
, bytes_after
, nread
;
696 int incremental
= False
;
700 while (XGetWindowProperty(disp
, window
, property
, nread
/ 4, sel_delta
/ 4,
701 !cutbuffer
, AnyPropertyType
, &actual_type
,
702 &actual_format
, &nitems
, &bytes_after
,
703 (unsigned char **) &data
) == Success
) {
705 fprintf(stderr
, "got %ld items of %d-byte data, type=%s;"
706 " %ld to go\n", nitems
, actual_format
,
707 translate_atom(disp
, actual_type
), bytes_after
);
710 * ICCCM 2.7.2: if we receive data with the type atom set to
711 * INCR, it indicates that the actual data will arrive in
712 * multiple chunks, terminating with a zero-length one.
713 * Between each pair, we must wait for a PropertyNotify event
714 * which tells us that the next chunk has arrived.
716 if (actual_type
== incr_atom
&& !cutbuffer
) {
719 * Immediately wait for the first chunk of real data.
722 XMaskEvent(disp
, PropertyChangeMask
, &ev
);
723 } while (ev
.xproperty
.state
!= PropertyNewValue
);
725 * And loop straight back round to read it.
732 * We expect all returned chunks of data to be
733 * multiples of 4 bytes (because we can only request
734 * the subsequent starting offset in 4-byte
735 * increments). Of course you can store an odd number
736 * of bytes in a selection, so this can't be the case
737 * every time XGetWindowProperty returns; but it
738 * should be the case every time it returns _and there
739 * is more data to come_.
741 * Hence, whenever XGetWindowProperty returns, we
742 * verify that the size of the data returned _last_
743 * time was divisible by 4.
745 if ((nread
& 3) != 0) {
746 error("unexpected data size: %d read (not a multiple"
747 " of 4), but more to come", nread
);
750 if (expected_type
!= (Atom
)None
&& actual_type
!= expected_type
) {
751 const char *expout
= translate_atom(disp
, expected_type
);
752 const char *gotout
= translate_atom(disp
, actual_type
);
753 error("unexpected data type: expected %s, got %s",
756 if (expected_format
&& expected_format
!= actual_format
) {
757 error("unexpected data format: expected %d-bit, got %d-bit",
758 expected_format
, actual_format
);
763 if (mode
== TARGETS
) {
764 assert(actual_format
== 32);
766 for (i
= 0; i
< nitems
; i
++) {
767 Atom x
= ((Atom
*)data
)[i
];
768 printf("%s\n", translate_atom(disp
, x
));
770 } else if (mode
== TIMESTAMP
) {
771 assert(actual_format
== 32);
772 Time x
= ((Time
*)data
)[0];
773 printf("%lu\n", (unsigned long)x
);
775 fwrite(data
, actual_format
/ 8, nitems
, stdout
);
776 nread
+= nitems
* actual_format
/ 8;
780 if (bytes_after
== 0) {
782 * We've come to the end of the property we're reading.
786 * For an incremental transfer, this means we wait for
787 * another property to be dumped in the same place on
788 * our window, and loop round again reading that. The
789 * exception is if the total length of the property we
790 * got was zero, which signals the end.
792 if (nread
== 0 && nitems
== 0)
793 break; /* all done */
795 /* otherwise wait for the next chunk */
797 XMaskEvent(disp
, PropertyChangeMask
, &ev
);
798 } while (ev
.xproperty
.state
!= PropertyNewValue
);
800 /* which we read from the beginning */