2 * xcopy: quickly pipe text data into, or out of, the primary X
16 #include <X11/Xutil.h>
17 #include <X11/Xatom.h>
22 void full_redraw(void);
23 void do_paste(Window window
, Atom property
, int Delete
);
25 char *pname
; /* program name */
27 void error (char *fmt
, ...);
29 /* set from command-line parameters */
31 enum { STRING
, CTEXT
, UTF8
, TARGETS
, TIMESTAMP
, CUSTOM
} mode
= STRING
;
32 int use_clipboard
= False
;
33 char *custom_seltype
= NULL
;
34 int fork_when_writing
= True
;
39 #define SELDELTA 16384
41 /* functional parameters */
42 int reading
; /* read instead of writing? */
43 int convert_to_ctext
= True
; /* Xmb convert to compound text? */
46 const char usagemsg
[] =
47 "usage: xcopy [ -r ] [ -u | -c ] [ -C ]\n"
48 "where: -r read X selection and print on stdout\n"
49 " no -r read stdin and store in X selection\n"
50 " -u work with UTF8_STRING type selections\n"
51 " -c work with COMPOUND_TEXT type selections\n"
52 " -C suppress automatic conversion to COMPOUND_TEXT\n"
53 " -b read the CLIPBOARD rather than the PRIMARY selection\n"
54 " -t get the list of targets available to retrieve\n"
55 " -T get the time stamp of the selection contents\n"
56 " -a atom get an arbitrary form of the selection data\n"
57 " -F do not fork in write mode\n"
58 " -v proceed verbosely when reading selection\n"
59 " also: xcopy --version report version number\n"
60 " xcopy --help display this help text\n"
61 " xcopy --licence display the (MIT) licence text\n"
65 fputs(usagemsg
, stdout
);
68 const char licencemsg
[] =
69 "xcopy is copyright 2001-2004,2008 Simon Tatham.\n"
71 "Permission is hereby granted, free of charge, to any person\n"
72 "obtaining a copy of this software and associated documentation files\n"
73 "(the \"Software\"), to deal in the Software without restriction,\n"
74 "including without limitation the rights to use, copy, modify, merge,\n"
75 "publish, distribute, sublicense, and/or sell copies of the Software,\n"
76 "and to permit persons to whom the Software is furnished to do so,\n"
77 "subject to the following conditions:\n"
79 "The above copyright notice and this permission notice shall be\n"
80 "included in all copies or substantial portions of the Software.\n"
82 "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n"
83 "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n"
84 "MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n"
85 "NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n"
86 "BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n"
87 "ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n"
88 "CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n"
93 fputs(licencemsg
, stdout
);
97 #define SVN_REV "$Revision$"
98 char rev
[sizeof(SVN_REV
)];
101 strcpy(rev
, SVN_REV
);
103 for (p
= rev
; *p
&& *p
!= ':'; p
++);
106 while (*p
&& isspace(*p
)) p
++;
107 for (q
= p
; *q
&& *q
!= '$'; q
++);
109 printf("xcopy revision %s\n", p
);
111 printf("xcopy: unknown version\n");
115 int main(int ac
, char **av
) {
121 /* parse the command line arguments */
125 if (!strcmp(p
, "-display") || !strcmp(p
, "-disp")) {
127 error ("option `%s' expects a parameter", p
);
128 display
= *++av
, --ac
;
129 } else if (!strcmp(p
, "-r")) {
131 } else if (!strcmp(p
, "-u")) {
133 } else if (!strcmp(p
, "-c")) {
135 } else if (!strcmp(p
, "-C")) {
136 convert_to_ctext
= False
;
137 } else if (!strcmp(p
, "-b")) {
138 use_clipboard
= True
;
139 } else if (!strcmp(p
, "-t")) {
141 } else if (!strcmp(p
, "-T")) {
143 } else if (!strcmp(p
, "-a")) {
145 custom_seltype
= *++av
;
147 error ("expected an argument to `-a'");
149 } else if (!strcmp(p
, "-F")) {
150 fork_when_writing
= False
;
151 } else if (!strcmp(p
, "-v")) {
153 } else if (!strcmp(p
, "--help")) {
156 } else if (!strcmp(p
, "--version")) {
159 } else if (!strcmp(p
, "--licence") || !strcmp(p
, "--license")) {
162 } else if (*p
=='-') {
163 error ("unrecognised option `%s'", p
);
165 error ("no parameters required");
170 if (mode
== TARGETS
|| mode
== TIMESTAMP
|| mode
== CUSTOM
) {
171 error ("%s not supported in writing mode; use -r",
172 (mode
== TARGETS ?
"-t" :
173 mode
== TIMESTAMP ?
"-T" :
174 /* mode == CUSTOM ? */ "-a"));
179 seltext
= malloc(SELDELTA
);
181 error ("out of memory");
185 n
= fread(seltext
+sellen
, 1, selsize
-sellen
, stdin
);
187 if (sellen
>= selsize
) {
188 seltext
= realloc(seltext
, selsize
+= SELDELTA
);
190 error ("out of memory");
193 if (sellen
== selsize
) {
194 seltext
= realloc(seltext
, selsize
+= SELDELTA
);
196 error ("out of memory");
198 seltext
[sellen
] = '\0';
201 eventloop
= init_X();
202 if (!reading
&& fork_when_writing
) {
204 * If we are writing the selection, we must go into the
209 error("unable to fork: %s", strerror(errno
));
210 } else if (pid
> 0) {
212 * we are the parent; just exit
232 void error (char *fmt
, ...) {
238 vsprintf (errbuf
, fmt
, ap
);
240 fprintf (stderr
, "%s: %s\n", pname
, errbuf
);
244 /* begin the X interface */
246 char *lcasename
= "xcopy";
247 char *ucasename
= "XCopy";
249 Display
*disp
= NULL
;
250 Window ourwin
= None
;
251 Atom compound_text_atom
, targets_atom
, timestamp_atom
, atom_atom
, integer_atom
;
252 Atom multiple_atom
, atom_pair_atom
;
253 int screen
, wwidth
, wheight
;
255 Atom strtype
= XA_STRING
;
256 Atom sel_atom
= XA_PRIMARY
;
257 Atom expected_type
= (Atom
)None
;
258 int expected_format
= 8;
260 static const char *translate_atom(Display
*disp
, Atom atom
)
265 return XGetAtomName(disp
, atom
);
269 * Returns TRUE if we need to enter an event loop, FALSE otherwise.
273 int x
= 0, y
= 0, width
= 512, height
= 128;
276 XSizeHints size_hints
;
277 XClassHint class_hints
;
278 XTextProperty textprop
;
281 /* open the X display */
282 disp
= XOpenDisplay (display
);
284 error ("unable to open display");
286 targets_atom
= XInternAtom(disp
, "TARGETS", False
);
287 timestamp_atom
= XInternAtom(disp
, "TIMESTAMP", False
);
288 atom_atom
= XInternAtom(disp
, "ATOM", False
);
289 atom_pair_atom
= XInternAtom(disp
, "ATOM_PAIR", False
);
290 multiple_atom
= XInternAtom(disp
, "MULTIPLE", False
);
291 integer_atom
= XInternAtom(disp
, "INTEGER", False
);
293 strtype
= XInternAtom(disp
, "UTF8_STRING", False
);
294 } else if (mode
== CTEXT
) {
295 strtype
= XInternAtom(disp
, "COMPOUND_TEXT", False
);
296 } else if (mode
== TARGETS
) {
297 strtype
= targets_atom
;
298 expected_type
= atom_atom
;
299 expected_format
= 32;
300 } else if (mode
== TIMESTAMP
) {
301 strtype
= timestamp_atom
;
302 expected_type
= integer_atom
;
303 expected_format
= 32;
304 } else if (mode
== CUSTOM
) {
305 strtype
= XInternAtom(disp
, custom_seltype
, True
);
307 error ("atom '%s' does not exist on the server", custom_seltype
);
311 sel_atom
= XInternAtom(disp
, "CLIPBOARD", False
);
314 /* get the screen and root-window */
315 screen
= DefaultScreen (disp
);
316 root
= RootWindow (disp
, screen
);
319 width
= height
= 10; /* doesn't really matter */
321 /* actually create the window */
322 ourwin
= XCreateSimpleWindow (disp
, root
, x
, y
, width
, height
,0,
323 BlackPixel(disp
, screen
),
324 WhitePixel(disp
, screen
));
326 /* resource class name */
327 class_hints
.res_name
= lcasename
;
328 class_hints
.res_class
= ucasename
;
329 XSetClassHint (disp
, ourwin
, &class_hints
);
331 /* do selection fiddling */
334 * We are reading the selection. Call XConvertSelection to
335 * request transmission of the selection data in the
336 * appropriate format; the X event loop will then wait to
337 * receive the data from the selection owner.
339 * If there is no selection owner, look in the cut buffer
340 * property on the root window.
342 if (XGetSelectionOwner(disp
, sel_atom
) == None
) {
343 /* No primary selection, so use the cut buffer. */
345 fprintf(stderr
, "no selection owner: trying cut buffer\n");
346 if (strtype
== XA_STRING
)
347 do_paste(DefaultRootWindow(disp
), XA_CUT_BUFFER0
, False
);
350 Atom sel_property
= XInternAtom(disp
, "VT_SELECTION", False
);
352 fprintf(stderr
, "calling XConvertSelection: selection=%s"
353 " target=%s property=%s requestor=%08lx\n",
354 translate_atom(disp
, sel_atom
),
355 translate_atom(disp
, strtype
),
356 translate_atom(disp
, sel_property
),
358 XConvertSelection(disp
, sel_atom
, strtype
,
359 sel_property
, ourwin
, CurrentTime
);
364 * We are writing to the selection, so we establish
365 * ourselves as selection owner. Also place the data in
366 * CUT_BUFFER0, if it isn't of an exotic type (cut buffers
367 * can only take ordinary string data, it turns out).
369 XSetSelectionOwner (disp
, sel_atom
, ourwin
, CurrentTime
);
370 if (XGetSelectionOwner (disp
, sel_atom
) != ourwin
)
371 error ("unable to obtain primary X selection");
372 compound_text_atom
= XInternAtom(disp
, "COMPOUND_TEXT", False
);
373 if (strtype
== XA_STRING
) {
375 * ICCCM-required cut buffer initialisation.
377 XChangeProperty(disp
, root
, XA_CUT_BUFFER0
,
378 XA_STRING
, 8, PropModeAppend
, "", 0);
379 XChangeProperty(disp
, root
, XA_CUT_BUFFER1
,
380 XA_STRING
, 8, PropModeAppend
, "", 0);
381 XChangeProperty(disp
, root
, XA_CUT_BUFFER2
,
382 XA_STRING
, 8, PropModeAppend
, "", 0);
383 XChangeProperty(disp
, root
, XA_CUT_BUFFER3
,
384 XA_STRING
, 8, PropModeAppend
, "", 0);
385 XChangeProperty(disp
, root
, XA_CUT_BUFFER4
,
386 XA_STRING
, 8, PropModeAppend
, "", 0);
387 XChangeProperty(disp
, root
, XA_CUT_BUFFER5
,
388 XA_STRING
, 8, PropModeAppend
, "", 0);
389 XChangeProperty(disp
, root
, XA_CUT_BUFFER6
,
390 XA_STRING
, 8, PropModeAppend
, "", 0);
391 XChangeProperty(disp
, root
, XA_CUT_BUFFER7
,
392 XA_STRING
, 8, PropModeAppend
, "", 0);
394 * Rotate the cut buffers and add our text in CUT_BUFFER0.
396 XRotateBuffers(disp
, 1);
397 XStoreBytes(disp
, seltext
, sellen
);
403 Atom
convert_sel_inner(Window requestor
, Atom target
, Atom property
) {
404 if (target
== strtype
) {
405 XChangeProperty (disp
, requestor
, property
, strtype
,
406 8, PropModeReplace
, seltext
, sellen
);
408 } else if (target
== compound_text_atom
&& convert_to_ctext
) {
410 XmbTextListToTextProperty (disp
, &seltext
, 1,
411 XCompoundTextStyle
, &tp
);
412 XChangeProperty (disp
, requestor
, property
, target
,
413 tp
.format
, PropModeReplace
,
414 tp
.value
, tp
.nitems
);
416 } else if (target
== targets_atom
) {
419 targets
[len
++] = timestamp_atom
;
420 targets
[len
++] = targets_atom
;
421 targets
[len
++] = multiple_atom
;
422 targets
[len
++] = strtype
;
423 if (strtype
!= compound_text_atom
&& convert_to_ctext
)
424 targets
[len
++] = compound_text_atom
;
425 XChangeProperty (disp
, requestor
, property
,
426 atom_atom
, 32, PropModeReplace
,
427 (unsigned char *)targets
, len
);
429 } else if (target
== timestamp_atom
) {
430 Time rettime
= CurrentTime
;
431 XChangeProperty (disp
, requestor
, property
,
432 integer_atom
, 32, PropModeReplace
,
433 (unsigned char *)&rettime
, 1);
440 Atom
convert_sel_outer(Window requestor
, Atom target
, Atom property
) {
441 if (target
== multiple_atom
) {
443 * Support for the MULTIPLE selection type, since it's
444 * specified as required in the ICCCM. Completely
445 * untested, though, because I have no idea of what X
446 * application might implement it for me to test against.
451 int actual_format
, i
;
452 long nitems
, bytes_after
, nread
;
456 if (property
== (Atom
)None
)
457 return None
; /* ICCCM says this isn't allowed */
460 * Fetch the requestor's window property giving a list of
461 * selection requests.
463 while (XGetWindowProperty(disp
, requestor
, property
, 0, size
,
464 False
, AnyPropertyType
, &actual_type
,
465 &actual_format
, &nitems
, &bytes_after
,
466 (unsigned char **) &data
) == Success
&&
467 nitems
* (actual_format
/ 8) == size
) {
472 if (actual_type
!= atom_pair_atom
|| actual_format
!= 32) {
477 adata
= (Atom
*)data
;
479 for (i
= 0; i
+1 < nitems
; i
+= 2) {
480 if (adata
[i
+1] != (Atom
)None
) /* ICCCM says this isn't allowed */
481 adata
[i
+1] = convert_sel_inner(requestor
, adata
[i
],
485 XChangeProperty (disp
, requestor
, property
,
486 atom_pair_atom
, 32, PropModeReplace
,
493 if (property
== (Atom
)None
)
494 property
= target
; /* ICCCM says this is a sensible default */
495 return convert_sel_inner(requestor
, target
, property
);
503 XNextEvent (disp
, &ev
);
506 case SelectionNotify
:
508 fprintf(stderr
, "got SelectionNotify: requestor=%08lx "
509 "selection=%s target=%s property=%s\n",
510 ev
.xselection
.requestor
,
511 translate_atom(disp
, ev
.xselection
.selection
),
512 translate_atom(disp
, ev
.xselection
.target
),
513 translate_atom(disp
, ev
.xselection
.property
));
515 if (ev
.xselection
.property
!= None
)
516 do_paste(ev
.xselection
.requestor
,
517 ev
.xselection
.property
, True
);
523 /* Selection has been cleared by another app. */
525 case SelectionRequest
:
526 e2
.xselection
.type
= SelectionNotify
;
527 e2
.xselection
.requestor
= ev
.xselectionrequest
.requestor
;
528 e2
.xselection
.selection
= ev
.xselectionrequest
.selection
;
529 e2
.xselection
.target
= ev
.xselectionrequest
.target
;
530 e2
.xselection
.time
= ev
.xselectionrequest
.time
;
531 e2
.xselection
.property
=
532 convert_sel_outer(ev
.xselectionrequest
.requestor
,
533 ev
.xselectionrequest
.target
,
534 ev
.xselectionrequest
.property
);
535 XSendEvent (disp
, ev
.xselectionrequest
.requestor
,
546 XDestroyWindow (disp
, ourwin
);
548 XCloseDisplay (disp
);
551 void do_paste(Window window
, Atom property
, int Delete
) {
553 int actual_format
, i
;
554 long nitems
, bytes_after
, nread
;
558 while (XGetWindowProperty(disp
, window
, property
, nread
/ 4, SELDELTA
,
559 Delete
, AnyPropertyType
, &actual_type
,
560 &actual_format
, &nitems
, &bytes_after
,
561 (unsigned char **) &data
) == Success
) {
563 fprintf(stderr
, "got %ld items of %d-byte data, type=%s;"
564 " %ld to go\n", nitems
, actual_format
,
565 translate_atom(disp
, actual_type
), bytes_after
);
569 * We expect all returned chunks of data to be
570 * multiples of 4 bytes (because we can only request
571 * the subsequent starting offset in 4-byte
572 * increments). Of course you can store an odd number
573 * of bytes in a selection, so this can't be the case
574 * every time XGetWindowProperty returns; but it
575 * should be the case every time it returns _and there
576 * is more data to come_.
578 * Hence, whenever XGetWindowProperty returns, we
579 * verify that the size of the data returned _last_
580 * time was divisible by 4.
582 if ((nread
& 3) != 0) {
583 error("unexpected data size: %d read (not a multiple"
584 " of 4), but more to come", nread
);
587 if (expected_type
!= (Atom
)None
&& actual_type
!= expected_type
) {
588 const char *expout
= translate_atom(disp
, expected_type
);
589 const char *gotout
= translate_atom(disp
, actual_type
);
590 error("unexpected data type: expected %s, got %s",
593 if (expected_format
&& expected_format
!= actual_format
) {
594 error("unexpected data format: expected %d-bit, got %d-bit",
595 expected_format
, actual_format
);
600 if (mode
== TARGETS
) {
601 assert(actual_format
== 32);
603 for (i
= 0; i
< nitems
; i
++) {
604 Atom x
= ((Atom
*)data
)[i
];
605 printf("%s\n", translate_atom(disp
, x
));
607 } else if (mode
== TIMESTAMP
) {
608 assert(actual_format
== 32);
609 Time x
= ((Time
*)data
)[0];
610 printf("%lu\n", (unsigned long)x
);
612 fwrite(data
, actual_format
/ 8, nitems
, stdout
);
613 nread
+= nitems
* actual_format
/ 8;