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? */
45 const char usagemsg
[] =
46 "usage: xcopy [ -r ] [ -u | -c ] [ -C ]\n"
47 "where: -r read X selection and print on stdout\n"
48 " no -r read stdin and store in X selection\n"
49 " -u work with UTF8_STRING type selections\n"
50 " -c work with COMPOUND_TEXT type selections\n"
51 " -C suppress automatic conversion to COMPOUND_TEXT\n"
52 " -b read the CLIPBOARD rather than the PRIMARY selection\n"
53 " -t get the list of targets available to retrieve\n"
54 " -T get the time stamp of the selection contents\n"
55 " -a atom get an arbitrary form of the selection data\n"
56 " -F do not fork in write mode\n"
57 " also: xcopy --version report version number\n"
58 " xcopy --help display this help text\n"
59 " xcopy --licence display the (MIT) licence text\n"
63 fputs(usagemsg
, stdout
);
66 const char licencemsg
[] =
67 "xcopy is copyright 2001-2004 Simon Tatham.\n"
69 "Permission is hereby granted, free of charge, to any person\n"
70 "obtaining a copy of this software and associated documentation files\n"
71 "(the \"Software\"), to deal in the Software without restriction,\n"
72 "including without limitation the rights to use, copy, modify, merge,\n"
73 "publish, distribute, sublicense, and/or sell copies of the Software,\n"
74 "and to permit persons to whom the Software is furnished to do so,\n"
75 "subject to the following conditions:\n"
77 "The above copyright notice and this permission notice shall be\n"
78 "included in all copies or substantial portions of the Software.\n"
80 "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n"
81 "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n"
82 "MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n"
83 "NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n"
84 "BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n"
85 "ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n"
86 "CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n"
91 fputs(licencemsg
, stdout
);
95 #define SVN_REV "$Revision$"
96 char rev
[sizeof(SVN_REV
)];
101 for (p
= rev
; *p
&& *p
!= ':'; p
++);
104 while (*p
&& isspace(*p
)) p
++;
105 for (q
= p
; *q
&& *q
!= '$'; q
++);
107 printf("xcopy revision %s\n", p
);
109 printf("xcopy: unknown version\n");
113 int main(int ac
, char **av
) {
119 /* parse the command line arguments */
123 if (!strcmp(p
, "-display") || !strcmp(p
, "-disp")) {
125 error ("option `%s' expects a parameter", p
);
126 display
= *++av
, --ac
;
127 } else if (!strcmp(p
, "-r")) {
129 } else if (!strcmp(p
, "-u")) {
131 } else if (!strcmp(p
, "-c")) {
133 } else if (!strcmp(p
, "-C")) {
134 convert_to_ctext
= False
;
135 } else if (!strcmp(p
, "-b")) {
136 use_clipboard
= True
;
137 } else if (!strcmp(p
, "-t")) {
139 } else if (!strcmp(p
, "-T")) {
141 } else if (!strcmp(p
, "-a")) {
143 custom_seltype
= *++av
;
145 error ("expected an argument to `-a'");
147 } else if (!strcmp(p
, "-F")) {
148 fork_when_writing
= False
;
149 } else if (!strcmp(p
, "--help")) {
152 } else if (!strcmp(p
, "--version")) {
155 } else if (!strcmp(p
, "--licence") || !strcmp(p
, "--license")) {
158 } else if (*p
=='-') {
159 error ("unrecognised option `%s'", p
);
161 error ("no parameters required");
166 if (mode
== TARGETS
|| mode
== TIMESTAMP
|| mode
== CUSTOM
) {
167 error ("%s not supported in writing mode; use -r",
168 (mode
== TARGETS ?
"-t" :
169 mode
== TIMESTAMP ?
"-T" :
170 /* mode == CUSTOM ? */ "-a"));
175 seltext
= malloc(SELDELTA
);
177 error ("out of memory");
181 n
= fread(seltext
+sellen
, 1, selsize
-sellen
, stdin
);
183 if (sellen
>= selsize
) {
184 seltext
= realloc(seltext
, selsize
+= SELDELTA
);
186 error ("out of memory");
189 if (sellen
== selsize
) {
190 seltext
= realloc(seltext
, selsize
+= SELDELTA
);
192 error ("out of memory");
194 seltext
[sellen
] = '\0';
197 eventloop
= init_X();
198 if (!reading
&& fork_when_writing
) {
200 * If we are writing the selection, we must go into the
205 error("unable to fork: %s", strerror(errno
));
206 } else if (pid
> 0) {
208 * we are the parent; just exit
228 void error (char *fmt
, ...) {
234 vsprintf (errbuf
, fmt
, ap
);
236 fprintf (stderr
, "%s: %s\n", pname
, errbuf
);
240 /* begin the X interface */
242 char *lcasename
= "xcopy";
243 char *ucasename
= "XCopy";
245 Display
*disp
= NULL
;
246 Window ourwin
= None
;
247 Atom compound_text_atom
, targets_atom
, timestamp_atom
, atom_atom
, integer_atom
;
248 int screen
, wwidth
, wheight
;
250 Atom strtype
= XA_STRING
;
251 Atom sel_atom
= XA_PRIMARY
;
252 Atom expected_type
= (Atom
)None
;
253 int expected_format
= 8;
256 * Returns TRUE if we need to enter an event loop, FALSE otherwise.
260 int x
= 0, y
= 0, width
= 512, height
= 128;
263 XSizeHints size_hints
;
264 XClassHint class_hints
;
265 XTextProperty textprop
;
268 /* open the X display */
269 disp
= XOpenDisplay (display
);
271 error ("unable to open display");
273 targets_atom
= XInternAtom(disp
, "TARGETS", False
);
274 timestamp_atom
= XInternAtom(disp
, "TIMESTAMP", False
);
275 atom_atom
= XInternAtom(disp
, "ATOM", False
);
276 integer_atom
= XInternAtom(disp
, "INTEGER", False
);
278 strtype
= XInternAtom(disp
, "UTF8_STRING", False
);
279 } else if (mode
== CTEXT
) {
280 strtype
= XInternAtom(disp
, "COMPOUND_TEXT", False
);
281 } else if (mode
== TARGETS
) {
282 strtype
= targets_atom
;
283 expected_type
= atom_atom
;
284 expected_format
= 32;
285 } else if (mode
== TIMESTAMP
) {
286 strtype
= timestamp_atom
;
287 expected_type
= integer_atom
;
288 expected_format
= 32;
289 } else if (mode
== CUSTOM
) {
290 strtype
= XInternAtom(disp
, custom_seltype
, True
);
292 error ("atom '%s' does not exist on the server", custom_seltype
);
296 sel_atom
= XInternAtom(disp
, "CLIPBOARD", False
);
299 /* get the screen and root-window */
300 screen
= DefaultScreen (disp
);
301 root
= RootWindow (disp
, screen
);
304 width
= height
= 10; /* doesn't really matter */
306 /* actually create the window */
307 ourwin
= XCreateSimpleWindow (disp
, root
, x
, y
, width
, height
,0,
308 BlackPixel(disp
, screen
),
309 WhitePixel(disp
, screen
));
311 /* resource class name */
312 class_hints
.res_name
= lcasename
;
313 class_hints
.res_class
= ucasename
;
314 XSetClassHint (disp
, ourwin
, &class_hints
);
316 /* do selection fiddling */
319 * We are reading the selection, so we must FIXME.
321 if (XGetSelectionOwner(disp
, sel_atom
) == None
) {
322 /* No primary selection, so use the cut buffer. */
323 if (strtype
== XA_STRING
)
324 do_paste(DefaultRootWindow(disp
), XA_CUT_BUFFER0
, False
);
327 Atom sel_property
= XInternAtom(disp
, "VT_SELECTION", False
);
328 XConvertSelection(disp
, sel_atom
, strtype
,
329 sel_property
, ourwin
, CurrentTime
);
334 * We are writing to the selection, so we establish
335 * ourselves as selection owner. Also place the data in
336 * CUT_BUFFER0, if it isn't of an exotic type (cut buffers
337 * can only take ordinary string data, it turns out).
339 XSetSelectionOwner (disp
, sel_atom
, ourwin
, CurrentTime
);
340 if (XGetSelectionOwner (disp
, sel_atom
) != ourwin
)
341 error ("unable to obtain primary X selection\n");
342 compound_text_atom
= XInternAtom(disp
, "COMPOUND_TEXT", False
);
343 if (strtype
== XA_STRING
) {
345 * ICCCM-required cut buffer initialisation.
347 XChangeProperty(disp
, root
, XA_CUT_BUFFER0
,
348 XA_STRING
, 8, PropModeAppend
, "", 0);
349 XChangeProperty(disp
, root
, XA_CUT_BUFFER1
,
350 XA_STRING
, 8, PropModeAppend
, "", 0);
351 XChangeProperty(disp
, root
, XA_CUT_BUFFER2
,
352 XA_STRING
, 8, PropModeAppend
, "", 0);
353 XChangeProperty(disp
, root
, XA_CUT_BUFFER3
,
354 XA_STRING
, 8, PropModeAppend
, "", 0);
355 XChangeProperty(disp
, root
, XA_CUT_BUFFER4
,
356 XA_STRING
, 8, PropModeAppend
, "", 0);
357 XChangeProperty(disp
, root
, XA_CUT_BUFFER5
,
358 XA_STRING
, 8, PropModeAppend
, "", 0);
359 XChangeProperty(disp
, root
, XA_CUT_BUFFER6
,
360 XA_STRING
, 8, PropModeAppend
, "", 0);
361 XChangeProperty(disp
, root
, XA_CUT_BUFFER7
,
362 XA_STRING
, 8, PropModeAppend
, "", 0);
364 * Rotate the cut buffers and add our text in CUT_BUFFER0.
366 XRotateBuffers(disp
, 1);
367 XStoreBytes(disp
, seltext
, sellen
);
377 XNextEvent (disp
, &ev
);
380 case SelectionNotify
:
381 if (ev
.xselection
.property
!= None
)
382 do_paste(ev
.xselection
.requestor
,
383 ev
.xselection
.property
, True
);
389 /* Selection has been cleared by another app. */
391 case SelectionRequest
:
392 e2
.xselection
.type
= SelectionNotify
;
393 e2
.xselection
.requestor
= ev
.xselectionrequest
.requestor
;
394 e2
.xselection
.selection
= ev
.xselectionrequest
.selection
;
395 e2
.xselection
.target
= ev
.xselectionrequest
.target
;
396 e2
.xselection
.time
= ev
.xselectionrequest
.time
;
397 if (ev
.xselectionrequest
.target
== strtype
) {
398 XChangeProperty (disp
, ev
.xselectionrequest
.requestor
,
399 ev
.xselectionrequest
.property
, strtype
,
400 8, PropModeReplace
, seltext
, sellen
);
401 e2
.xselection
.property
= ev
.xselectionrequest
.property
;
402 } else if (ev
.xselectionrequest
.target
== compound_text_atom
&&
405 XmbTextListToTextProperty (disp
, &seltext
, 1,
406 XCompoundTextStyle
, &tp
);
407 XChangeProperty (disp
, ev
.xselectionrequest
.requestor
,
408 ev
.xselectionrequest
.property
,
409 ev
.xselectionrequest
.target
,
410 tp
.format
, PropModeReplace
,
411 tp
.value
, tp
.nitems
);
412 e2
.xselection
.property
= ev
.xselectionrequest
.property
;
413 } else if (ev
.xselectionrequest
.target
== targets_atom
) {
416 targets
[len
++] = timestamp_atom
;
417 targets
[len
++] = targets_atom
;
418 targets
[len
++] = strtype
;
419 if (strtype
!= compound_text_atom
&& convert_to_ctext
)
420 targets
[len
++] = compound_text_atom
;
421 XChangeProperty (disp
, ev
.xselectionrequest
.requestor
,
422 ev
.xselectionrequest
.property
,
423 atom_atom
, 32, PropModeReplace
,
424 (unsigned char *)targets
, len
);
425 e2
.xselection
.property
= ev
.xselectionrequest
.property
;
426 } else if (ev
.xselectionrequest
.target
== timestamp_atom
) {
427 Time rettime
= CurrentTime
;
428 XChangeProperty (disp
, ev
.xselectionrequest
.requestor
,
429 ev
.xselectionrequest
.property
,
430 integer_atom
, 32, PropModeReplace
,
431 (unsigned char *)&rettime
, 1);
432 e2
.xselection
.property
= ev
.xselectionrequest
.property
;
434 e2
.xselection
.property
= None
;
436 XSendEvent (disp
, ev
.xselectionrequest
.requestor
, False
, 0, &e2
);
446 XDestroyWindow (disp
, ourwin
);
448 XCloseDisplay (disp
);
451 void do_paste(Window window
, Atom property
, int Delete
) {
453 int actual_format
, i
;
454 long nitems
, bytes_after
, nread
;
458 while (XGetWindowProperty(disp
, window
, property
, nread
/ 4, SELDELTA
,
459 Delete
, AnyPropertyType
, &actual_type
,
460 &actual_format
, &nitems
, &bytes_after
,
461 (unsigned char **) &data
) == Success
) {
464 * We expect all returned chunks of data to be
465 * multiples of 4 bytes (because we can only request
466 * the subsequent starting offset in 4-byte
467 * increments). Of course you can store an odd number
468 * of bytes in a selection, so this can't be the case
469 * every time XGetWindowProperty returns; but it
470 * should be the case every time it returns _and there
471 * is more data to come_.
473 * Hence, whenever XGetWindowProperty returns, we
474 * verify that the size of the data returned _last_
475 * time was divisible by 4.
477 if ((nread
& 3) != 0) {
478 error("unexpected data size: %d read (not a multiple"
479 " of 4), but more to come\n", nread
);
482 if (expected_type
!= (Atom
)None
&& actual_type
!= expected_type
) {
483 char *expout
= XGetAtomName(disp
, expected_type
);
484 char *gotout
= (actual_type
== (Atom
)None ?
"None" :
485 XGetAtomName(disp
, actual_type
));
486 error("unexpected data type: expected %s, got %s\n",
489 if (expected_format
&& expected_format
!= actual_format
) {
490 error("unexpected data format: expected %d-bit, got %d-bit\n",
491 expected_format
, actual_format
);
496 if (mode
== TARGETS
) {
497 assert(actual_format
== 32);
499 for (i
= 0; i
< nitems
; i
++) {
500 Atom x
= ((Atom
*)data
)[i
];
501 printf("%s\n", XGetAtomName(disp
, x
));
503 } else if (mode
== TIMESTAMP
) {
504 assert(actual_format
== 32);
505 Time x
= ((Time
*)data
)[0];
506 printf("%lu\n", (unsigned long)x
);
508 fwrite(data
, actual_format
/ 8, nitems
, stdout
);
509 nread
+= nitems
* actual_format
/ 8;