2 * xcopy: quickly pipe text data into, or out of, the primary X
15 #include <X11/Intrinsic.h>
17 #include <X11/Xutil.h>
18 #include <X11/Xatom.h>
23 void full_redraw(void);
24 void do_paste(Window window
, Atom property
, int Delete
);
26 char *pname
; /* program name */
28 void error (char *fmt
, ...);
30 /* set from command-line parameters */
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;
44 /* incremental transfers still pending when we return to the event loop */
53 int nincrs
= 0, incrsize
= 0;
55 /* functional parameters */
56 int reading
; /* read instead of writing? */
57 int convert_to_ctext
= True
; /* Xmb convert to compound text? */
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"
82 fputs(usagemsg
, stdout
);
85 const char licencemsg
[] =
86 "xcopy is copyright 2001-2004,2008 Simon Tatham.\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"
96 "The above copyright notice and this permission notice shall be\n"
97 "included in all copies or substantial portions of the Software.\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"
110 fputs(licencemsg
, stdout
);
114 #define SVN_REV "$Revision$"
115 char rev
[sizeof(SVN_REV
)];
118 strcpy(rev
, SVN_REV
);
120 for (p
= rev
; *p
&& *p
!= ':'; p
++);
123 while (*p
&& isspace(*p
)) p
++;
124 for (q
= p
; *q
&& *q
!= '$'; q
++);
126 printf("xcopy revision %s\n", p
);
128 printf("xcopy: unknown version\n");
132 int main(int ac
, char **av
) {
138 /* parse the command line arguments */
142 if (!strcmp(p
, "-display") || !strcmp(p
, "-disp")) {
144 error ("option `%s' expects a parameter", p
);
145 display
= *++av
, --ac
;
146 } else if (!strcmp(p
, "-r")) {
148 } else if (!strcmp(p
, "-u")) {
150 } else if (!strcmp(p
, "-c")) {
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")) {
158 } else if (!strcmp(p
, "-T")) {
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")) {
166 custom_seltype
= *++av
;
168 error ("expected an argument to `-a'");
170 } else if (!strcmp(p
, "-d")) {
172 sel_delta
= atoi(*++av
);
174 error ("expected an argument to `-d'");
175 } else if (!strcmp(p
, "-F")) {
176 fork_when_writing
= False
;
177 } else if (!strcmp(p
, "-v")) {
179 } else if (!strcmp(p
, "--help")) {
182 } else if (!strcmp(p
, "--version")) {
185 } else if (!strcmp(p
, "--licence") || !strcmp(p
, "--license")) {
188 } else if (*p
=='-') {
189 error ("unrecognised option `%s'", p
);
191 error ("no parameters required");
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"));
205 seltext
= malloc(sel_delta
);
207 error ("out of memory");
211 n
= fread(seltext
+sellen
, 1, selsize
-sellen
, stdin
);
213 if (sellen
>= selsize
) {
214 seltext
= realloc(seltext
, selsize
+= sel_delta
);
216 error ("out of memory");
219 if (sellen
== selsize
) {
220 seltext
= realloc(seltext
, selsize
+= sel_delta
);
222 error ("out of memory");
224 seltext
[sellen
] = '\0';
227 eventloop
= init_X();
228 if (!reading
&& fork_when_writing
) {
230 * If we are writing the selection, we must go into the
235 error("unable to fork: %s", strerror(errno
));
236 } else if (pid
> 0) {
238 * we are the parent; just exit
258 void error (char *fmt
, ...) {
264 vsprintf (errbuf
, fmt
, ap
);
266 fprintf (stderr
, "%s: %s\n", pname
, errbuf
);
270 /* begin the X interface */
272 char *lcasename
= "xcopy";
273 char *ucasename
= "XCopy";
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
;
281 Atom strtype
= XA_STRING
;
282 Atom sel_atom
= XA_PRIMARY
;
283 Atom expected_type
= (Atom
)None
;
284 int expected_format
= 8;
286 static const char *translate_atom(Display
*disp
, Atom atom
)
291 return XGetAtomName(disp
, atom
);
295 * Returns TRUE if we need to enter an event loop, FALSE otherwise.
299 int x
= 0, y
= 0, width
= 512, height
= 128;
302 XSizeHints size_hints
;
303 XClassHint class_hints
;
304 XTextProperty textprop
;
307 /* open the X display */
308 disp
= XOpenDisplay (display
);
310 error ("unable to open display");
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
);
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
);
334 error ("atom '%s' does not exist on the server", custom_seltype
);
338 sel_atom
= XInternAtom(disp
, "CLIPBOARD", False
);
341 /* get the screen and root-window */
342 screen
= DefaultScreen (disp
);
343 root
= RootWindow (disp
, screen
);
346 width
= height
= 10; /* doesn't really matter */
348 /* actually create the window */
349 ourwin
= XCreateSimpleWindow (disp
, root
, x
, y
, width
, height
,0,
350 BlackPixel(disp
, screen
),
351 WhitePixel(disp
, screen
));
353 /* resource class name */
354 class_hints
.res_name
= lcasename
;
355 class_hints
.res_class
= ucasename
;
356 XSetClassHint (disp
, ourwin
, &class_hints
);
358 /* do selection fiddling */
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
368 * If there is no selection owner, look in the cut buffer
369 * property on the root window.
371 XSelectInput(disp
, ourwin
, PropertyChangeMask
);
372 if (XGetSelectionOwner(disp
, sel_atom
) != None
) {
373 Atom sel_property
= XInternAtom(disp
, "VT_SELECTION", False
);
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
),
381 XConvertSelection(disp
, sel_atom
, strtype
,
382 sel_property
, ourwin
, CurrentTime
);
384 } else if (use_cutbuffers
) {
385 /* No primary selection, so use the cut buffer. */
387 fprintf(stderr
, "no selection owner: trying cut buffer\n");
388 if (strtype
== XA_STRING
)
389 do_paste(DefaultRootWindow(disp
), XA_CUT_BUFFER0
, True
);
392 /* Last fallback: do nothing. */
397 * We are writing to the selection, so we establish ourselves
398 * as selection owner.
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.
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
) {
410 * ICCCM-required cut buffer initialisation.
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);
429 * Rotate the cut buffers and add our text in CUT_BUFFER0.
431 XRotateBuffers(disp
, 1);
432 XStoreBytes(disp
, seltext
, sellen
);
438 void write_data(Window requestor
, Atom property
, Atom type
, int format
,
439 void *vdata
, size_t size
)
441 int bformat
= format
/ 8; /* bytes per element */
442 unsigned char *data
= (unsigned char *)vdata
;
445 if (!use_outgoing_incr
|| size
* bformat
<= sel_delta
) {
446 XChangeProperty(disp
, requestor
, property
, type
, format
,
447 PropModeReplace
, data
, size
);
450 * For large data, an incremental transfer as per ICCCM 2.7.2.
452 Cardinal totalsize
= size
* bformat
;
453 Cardinal sent
, thissize
;
456 * We're going to need PropertyNotify events on the target
457 * window to tell us when to send the next chunk.
459 XSelectInput(disp
, requestor
, PropertyChangeMask
);
462 * Start by sending a single 32-bit word with type INCR giving
463 * the total size in bytes.
465 XChangeProperty(disp
, requestor
, property
, incr_atom
, 32,
466 PropModeReplace
, (unsigned char *)&totalsize
, 1);
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.
473 if (nincrs
>= incrsize
) {
474 incrsize
= nincrs
* 9 / 8 + 16;
475 incrs
= realloc(incrs
, incrsize
* sizeof(*incrs
));
477 error ("out of memory");
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
);
492 Atom
convert_sel_inner(Window requestor
, Atom target
, Atom property
) {
493 if (target
== strtype
) {
494 write_data(requestor
, property
, strtype
, 8, seltext
, sellen
);
496 } else if (target
== compound_text_atom
&& convert_to_ctext
) {
498 XmbTextListToTextProperty(disp
, &seltext
, 1, XCompoundTextStyle
, &tp
);
499 write_data(requestor
, property
, target
, tp
.format
, tp
.value
,tp
.nitems
);
501 } else if (target
== targets_atom
) {
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
);
512 } else if (target
== timestamp_atom
) {
513 Time rettime
= CurrentTime
;
514 write_data(requestor
, property
, integer_atom
, 32, &rettime
, 1);
521 Atom
convert_sel_outer(Window requestor
, Atom target
, Atom property
) {
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.
529 if (property
== None
)
532 if (target
== multiple_atom
) {
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.
540 int size
= sel_delta
;
542 int actual_format
, i
;
543 long nitems
, bytes_after
, nread
;
547 if (property
== (Atom
)None
)
548 return None
; /* ICCCM says this isn't allowed */
551 * Fetch the requestor's window property giving a list of
552 * selection requests.
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
) {
563 if (actual_type
!= atom_pair_atom
|| actual_format
!= 32) {
568 adata
= (Atom
*)data
;
570 for (i
= 0; i
+1 < nitems
; i
+= 2) {
571 if (adata
[i
+1] != (Atom
)None
) /* ICCCM says this isn't allowed */
572 adata
[i
+1] = convert_sel_inner(requestor
, adata
[i
],
576 XChangeProperty (disp
, requestor
, property
,
577 atom_pair_atom
, 32, PropModeReplace
,
584 if (property
== (Atom
)None
)
585 property
= target
; /* ICCCM says this is a sensible default */
586 return convert_sel_inner(requestor
, target
, property
);
595 XNextEvent (disp
, &ev
);
598 case SelectionNotify
:
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
));
607 if (ev
.xselection
.property
!= None
)
608 do_paste(ev
.xselection
.requestor
,
609 ev
.xselection
.property
, False
);
613 int have_ownership
= True
;
617 /* Selection has been cleared by another app. */
618 have_ownership
= False
;
620 case SelectionRequest
:
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
,
636 for (i
= j
= 0; i
< nincrs
; i
++) {
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
;
642 if (thissize
> sel_delta
)
643 thissize
= sel_delta
;
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));
653 * If we've just sent a zero-length block,
654 * the incremental transfer is over and we
655 * should delete this entry.
660 incrs
[i
].data
+= thissize
;
661 incrs
[i
].size
-= thissize
;
672 if (nincrs
== 0 && !have_ownership
)
682 XDestroyWindow (disp
, ourwin
);
684 XCloseDisplay (disp
);
687 void do_paste(Window window
, Atom property
, int cutbuffer
) {
689 int actual_format
, i
;
690 long nitems
, bytes_after
, nread
;
692 int incremental
= False
;
696 while (XGetWindowProperty(disp
, window
, property
, nread
/ 4, sel_delta
/ 4,
697 !cutbuffer
, AnyPropertyType
, &actual_type
,
698 &actual_format
, &nitems
, &bytes_after
,
699 (unsigned char **) &data
) == Success
) {
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
);
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.
712 if (actual_type
== incr_atom
&& !cutbuffer
) {
715 * Immediately wait for the first chunk of real data.
718 XMaskEvent(disp
, PropertyChangeMask
, &ev
);
719 } while (ev
.xproperty
.state
!= PropertyNewValue
);
721 * And loop straight back round to read it.
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_.
737 * Hence, whenever XGetWindowProperty returns, we
738 * verify that the size of the data returned _last_
739 * time was divisible by 4.
741 if ((nread
& 3) != 0) {
742 error("unexpected data size: %d read (not a multiple"
743 " of 4), but more to come", nread
);
746 if (expected_type
!= (Atom
)None
&& actual_type
!= expected_type
) {
747 const char *expout
= translate_atom(disp
, expected_type
);
748 const char *gotout
= translate_atom(disp
, actual_type
);
749 error("unexpected data type: expected %s, got %s",
752 if (expected_format
&& expected_format
!= actual_format
) {
753 error("unexpected data format: expected %d-bit, got %d-bit",
754 expected_format
, actual_format
);
759 if (mode
== TARGETS
) {
760 assert(actual_format
== 32);
762 for (i
= 0; i
< nitems
; i
++) {
763 Atom x
= ((Atom
*)data
)[i
];
764 printf("%s\n", translate_atom(disp
, x
));
766 } else if (mode
== TIMESTAMP
) {
767 assert(actual_format
== 32);
768 Time x
= ((Time
*)data
)[0];
769 printf("%lu\n", (unsigned long)x
);
771 fwrite(data
, actual_format
/ 8, nitems
, stdout
);
772 nread
+= nitems
* actual_format
/ 8;
776 if (bytes_after
== 0) {
778 * We've come to the end of the property we're reading.
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.
788 if (nread
== 0 && nitems
== 0)
789 break; /* all done */
791 /* otherwise wait for the next chunk */
793 XMaskEvent(disp
, PropertyChangeMask
, &ev
);
794 } while (ev
.xproperty
.state
!= PropertyNewValue
);
796 /* which we read from the beginning */