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
;
40 #define SELDELTA 16384
42 /* incremental transfers still pending when we return to the event loop */
51 int nincrs
= 0, incrsize
= 0;
53 /* functional parameters */
54 int reading
; /* read instead of writing? */
55 int convert_to_ctext
= True
; /* Xmb convert to compound text? */
58 const char usagemsg
[] =
59 "usage: xcopy [ -r ] [ -u | -c ] [ -C ]\n"
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"
69 " -F do not fork in write mode\n"
70 " -v proceed verbosely when reading selection\n"
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"
77 fputs(usagemsg
, stdout
);
80 const char licencemsg
[] =
81 "xcopy is copyright 2001-2004,2008 Simon Tatham.\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"
91 "The above copyright notice and this permission notice shall be\n"
92 "included in all copies or substantial portions of the Software.\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"
105 fputs(licencemsg
, stdout
);
109 #define SVN_REV "$Revision$"
110 char rev
[sizeof(SVN_REV
)];
113 strcpy(rev
, SVN_REV
);
115 for (p
= rev
; *p
&& *p
!= ':'; p
++);
118 while (*p
&& isspace(*p
)) p
++;
119 for (q
= p
; *q
&& *q
!= '$'; q
++);
121 printf("xcopy revision %s\n", p
);
123 printf("xcopy: unknown version\n");
127 int main(int ac
, char **av
) {
133 /* parse the command line arguments */
137 if (!strcmp(p
, "-display") || !strcmp(p
, "-disp")) {
139 error ("option `%s' expects a parameter", p
);
140 display
= *++av
, --ac
;
141 } else if (!strcmp(p
, "-r")) {
143 } else if (!strcmp(p
, "-u")) {
145 } else if (!strcmp(p
, "-c")) {
147 } else if (!strcmp(p
, "-C")) {
148 convert_to_ctext
= False
;
149 } else if (!strcmp(p
, "-b")) {
150 use_clipboard
= True
;
151 } else if (!strcmp(p
, "-t")) {
153 } else if (!strcmp(p
, "-T")) {
155 } else if (!strcmp(p
, "-a")) {
157 custom_seltype
= *++av
;
159 error ("expected an argument to `-a'");
161 } else if (!strcmp(p
, "-F")) {
162 fork_when_writing
= False
;
163 } else if (!strcmp(p
, "-v")) {
165 } else if (!strcmp(p
, "--help")) {
168 } else if (!strcmp(p
, "--version")) {
171 } else if (!strcmp(p
, "--licence") || !strcmp(p
, "--license")) {
174 } else if (*p
=='-') {
175 error ("unrecognised option `%s'", p
);
177 error ("no parameters required");
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"));
191 seltext
= malloc(SELDELTA
);
193 error ("out of memory");
197 n
= fread(seltext
+sellen
, 1, selsize
-sellen
, stdin
);
199 if (sellen
>= selsize
) {
200 seltext
= realloc(seltext
, selsize
+= SELDELTA
);
202 error ("out of memory");
205 if (sellen
== selsize
) {
206 seltext
= realloc(seltext
, selsize
+= SELDELTA
);
208 error ("out of memory");
210 seltext
[sellen
] = '\0';
213 eventloop
= init_X();
214 if (!reading
&& fork_when_writing
) {
216 * If we are writing the selection, we must go into the
221 error("unable to fork: %s", strerror(errno
));
222 } else if (pid
> 0) {
224 * we are the parent; just exit
244 void error (char *fmt
, ...) {
250 vsprintf (errbuf
, fmt
, ap
);
252 fprintf (stderr
, "%s: %s\n", pname
, errbuf
);
256 /* begin the X interface */
258 char *lcasename
= "xcopy";
259 char *ucasename
= "XCopy";
261 Display
*disp
= NULL
;
262 Window ourwin
= None
;
263 Atom compound_text_atom
, targets_atom
, timestamp_atom
, atom_atom
, integer_atom
;
264 Atom multiple_atom
, atom_pair_atom
, incr_atom
;
265 int screen
, wwidth
, wheight
;
267 Atom strtype
= XA_STRING
;
268 Atom sel_atom
= XA_PRIMARY
;
269 Atom expected_type
= (Atom
)None
;
270 int expected_format
= 8;
272 static const char *translate_atom(Display
*disp
, Atom atom
)
277 return XGetAtomName(disp
, atom
);
281 * Returns TRUE if we need to enter an event loop, FALSE otherwise.
285 int x
= 0, y
= 0, width
= 512, height
= 128;
288 XSizeHints size_hints
;
289 XClassHint class_hints
;
290 XTextProperty textprop
;
293 /* open the X display */
294 disp
= XOpenDisplay (display
);
296 error ("unable to open display");
298 targets_atom
= XInternAtom(disp
, "TARGETS", False
);
299 timestamp_atom
= XInternAtom(disp
, "TIMESTAMP", False
);
300 atom_atom
= XInternAtom(disp
, "ATOM", False
);
301 atom_pair_atom
= XInternAtom(disp
, "ATOM_PAIR", False
);
302 multiple_atom
= XInternAtom(disp
, "MULTIPLE", False
);
303 integer_atom
= XInternAtom(disp
, "INTEGER", False
);
304 incr_atom
= XInternAtom(disp
, "INCR", False
);
306 strtype
= XInternAtom(disp
, "UTF8_STRING", False
);
307 } else if (mode
== CTEXT
) {
308 strtype
= XInternAtom(disp
, "COMPOUND_TEXT", False
);
309 } else if (mode
== TARGETS
) {
310 strtype
= targets_atom
;
311 expected_type
= atom_atom
;
312 expected_format
= 32;
313 } else if (mode
== TIMESTAMP
) {
314 strtype
= timestamp_atom
;
315 expected_type
= integer_atom
;
316 expected_format
= 32;
317 } else if (mode
== CUSTOM
) {
318 strtype
= XInternAtom(disp
, custom_seltype
, True
);
320 error ("atom '%s' does not exist on the server", custom_seltype
);
324 sel_atom
= XInternAtom(disp
, "CLIPBOARD", False
);
327 /* get the screen and root-window */
328 screen
= DefaultScreen (disp
);
329 root
= RootWindow (disp
, screen
);
332 width
= height
= 10; /* doesn't really matter */
334 /* actually create the window */
335 ourwin
= XCreateSimpleWindow (disp
, root
, x
, y
, width
, height
,0,
336 BlackPixel(disp
, screen
),
337 WhitePixel(disp
, screen
));
339 /* resource class name */
340 class_hints
.res_name
= lcasename
;
341 class_hints
.res_class
= ucasename
;
342 XSetClassHint (disp
, ourwin
, &class_hints
);
344 /* do selection fiddling */
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
350 * receive the data from the selection owner. Also we need to
351 * make sure we receive PropertyNotify events, for INCR
354 * If there is no selection owner, look in the cut buffer
355 * property on the root window.
357 XSelectInput(disp
, ourwin
, PropertyChangeMask
);
358 if (XGetSelectionOwner(disp
, sel_atom
) == None
) {
359 /* No primary selection, so use the cut buffer. */
361 fprintf(stderr
, "no selection owner: trying cut buffer\n");
362 if (strtype
== XA_STRING
)
363 do_paste(DefaultRootWindow(disp
), XA_CUT_BUFFER0
, True
);
366 Atom sel_property
= XInternAtom(disp
, "VT_SELECTION", False
);
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
),
374 XConvertSelection(disp
, sel_atom
, strtype
,
375 sel_property
, ourwin
, CurrentTime
);
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).
385 XSetSelectionOwner (disp
, sel_atom
, ourwin
, CurrentTime
);
386 if (XGetSelectionOwner (disp
, sel_atom
) != ourwin
)
387 error ("unable to obtain primary X selection");
388 compound_text_atom
= XInternAtom(disp
, "COMPOUND_TEXT", False
);
389 if (strtype
== XA_STRING
) {
391 * ICCCM-required cut buffer initialisation.
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);
410 * Rotate the cut buffers and add our text in CUT_BUFFER0.
412 XRotateBuffers(disp
, 1);
413 XStoreBytes(disp
, seltext
, sellen
);
419 void write_data(Window requestor
, Atom property
, Atom type
, int format
,
420 void *vdata
, size_t size
)
422 int bformat
= format
/ 8; /* bytes per element */
423 unsigned char *data
= (unsigned char *)vdata
;
426 if (size
* bformat
<= SELDELTA
) {
427 XChangeProperty(disp
, requestor
, property
, type
, format
,
428 PropModeReplace
, data
, size
);
431 * For large data, an incremental transfer as per ICCCM 2.7.2.
433 Cardinal totalsize
= size
* bformat
;
434 Cardinal sent
, thissize
;
437 * We're going to need PropertyNotify events on the target
438 * window to tell us when to send the next chunk.
440 XSelectInput(disp
, requestor
, PropertyChangeMask
);
443 * Start by sending a single 32-bit word with type INCR giving
444 * the total size in bytes.
446 XChangeProperty(disp
, requestor
, property
, incr_atom
, 32,
447 PropModeReplace
, (unsigned char *)&totalsize
, 1);
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.
454 if (nincrs
>= incrsize
) {
455 incrsize
= nincrs
* 9 / 8 + 16;
456 incrs
= realloc(incrs
, incrsize
* sizeof(*incrs
));
458 error ("out of memory");
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
);
473 Atom
convert_sel_inner(Window requestor
, Atom target
, Atom property
) {
474 if (target
== strtype
) {
475 write_data(requestor
, property
, strtype
, 8, seltext
, sellen
);
477 } else if (target
== compound_text_atom
&& convert_to_ctext
) {
479 XmbTextListToTextProperty(disp
, &seltext
, 1, XCompoundTextStyle
, &tp
);
480 write_data(requestor
, property
, target
, tp
.format
, tp
.value
,tp
.nitems
);
482 } else if (target
== targets_atom
) {
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
;
491 write_data(requestor
, property
, atom_atom
, 32, targets
, len
);
493 } else if (target
== timestamp_atom
) {
494 Time rettime
= CurrentTime
;
495 write_data(requestor
, property
, integer_atom
, 32, &rettime
, 1);
502 Atom
convert_sel_outer(Window requestor
, Atom target
, Atom property
) {
503 if (target
== multiple_atom
) {
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.
513 int actual_format
, i
;
514 long nitems
, bytes_after
, nread
;
518 if (property
== (Atom
)None
)
519 return None
; /* ICCCM says this isn't allowed */
522 * Fetch the requestor's window property giving a list of
523 * selection requests.
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
) {
534 if (actual_type
!= atom_pair_atom
|| actual_format
!= 32) {
539 adata
= (Atom
*)data
;
541 for (i
= 0; i
+1 < nitems
; i
+= 2) {
542 if (adata
[i
+1] != (Atom
)None
) /* ICCCM says this isn't allowed */
543 adata
[i
+1] = convert_sel_inner(requestor
, adata
[i
],
547 XChangeProperty (disp
, requestor
, property
,
548 atom_pair_atom
, 32, PropModeReplace
,
555 if (property
== (Atom
)None
)
556 property
= target
; /* ICCCM says this is a sensible default */
557 return convert_sel_inner(requestor
, target
, property
);
566 XNextEvent (disp
, &ev
);
569 case SelectionNotify
:
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
));
578 if (ev
.xselection
.property
!= None
)
579 do_paste(ev
.xselection
.requestor
,
580 ev
.xselection
.property
, False
);
586 /* Selection has been cleared by another app. */
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
;
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
,
602 for (i
= j
= 0; i
< nincrs
; i
++) {
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
)
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));
619 * If we've just sent a zero-length block,
620 * the incremental transfer is over and we
621 * should delete this entry.
626 incrs
[i
].data
+= thissize
;
627 incrs
[i
].size
-= thissize
;
646 XDestroyWindow (disp
, ourwin
);
648 XCloseDisplay (disp
);
651 void do_paste(Window window
, Atom property
, int cutbuffer
) {
653 int actual_format
, i
;
654 long nitems
, bytes_after
, nread
;
656 int incremental
= False
;
660 while (XGetWindowProperty(disp
, window
, property
, nread
/ 4, SELDELTA
,
661 !cutbuffer
, AnyPropertyType
, &actual_type
,
662 &actual_format
, &nitems
, &bytes_after
,
663 (unsigned char **) &data
) == Success
) {
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
);
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.
676 if (actual_type
== incr_atom
&& !cutbuffer
) {
679 * Immediately wait for the first chunk of real data.
682 XMaskEvent(disp
, PropertyChangeMask
, &ev
);
683 } while (ev
.xproperty
.state
!= PropertyNewValue
);
685 * And loop straight back round to read it.
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_.
701 * Hence, whenever XGetWindowProperty returns, we
702 * verify that the size of the data returned _last_
703 * time was divisible by 4.
705 if ((nread
& 3) != 0) {
706 error("unexpected data size: %d read (not a multiple"
707 " of 4), but more to come", nread
);
710 if (expected_type
!= (Atom
)None
&& actual_type
!= expected_type
) {
711 const char *expout
= translate_atom(disp
, expected_type
);
712 const char *gotout
= translate_atom(disp
, actual_type
);
713 error("unexpected data type: expected %s, got %s",
716 if (expected_format
&& expected_format
!= actual_format
) {
717 error("unexpected data format: expected %d-bit, got %d-bit",
718 expected_format
, actual_format
);
723 if (mode
== TARGETS
) {
724 assert(actual_format
== 32);
726 for (i
= 0; i
< nitems
; i
++) {
727 Atom x
= ((Atom
*)data
)[i
];
728 printf("%s\n", translate_atom(disp
, x
));
730 } else if (mode
== TIMESTAMP
) {
731 assert(actual_format
== 32);
732 Time x
= ((Time
*)data
)[0];
733 printf("%lu\n", (unsigned long)x
);
735 fwrite(data
, actual_format
/ 8, nitems
, stdout
);
736 nread
+= nitems
* actual_format
/ 8;
740 if (bytes_after
== 0) {
742 * We've come to the end of the property we're reading.
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.
752 if (nread
== 0 && nitems
== 0)
753 break; /* all done */
755 /* otherwise wait for the next chunk */
757 XMaskEvent(disp
, PropertyChangeMask
, &ev
);
758 } while (ev
.xproperty
.state
!= PropertyNewValue
);
760 /* which we read from the beginning */