f1678f1b9f92a60995abd10b23a8b6ad51c120bc
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 Atom multiple_atom
, atom_pair_atom
;
249 int screen
, wwidth
, wheight
;
251 Atom strtype
= XA_STRING
;
252 Atom sel_atom
= XA_PRIMARY
;
253 Atom expected_type
= (Atom
)None
;
254 int expected_format
= 8;
257 * Returns TRUE if we need to enter an event loop, FALSE otherwise.
261 int x
= 0, y
= 0, width
= 512, height
= 128;
264 XSizeHints size_hints
;
265 XClassHint class_hints
;
266 XTextProperty textprop
;
269 /* open the X display */
270 disp
= XOpenDisplay (display
);
272 error ("unable to open display");
274 targets_atom
= XInternAtom(disp
, "TARGETS", False
);
275 timestamp_atom
= XInternAtom(disp
, "TIMESTAMP", False
);
276 atom_atom
= XInternAtom(disp
, "ATOM", False
);
277 atom_pair_atom
= XInternAtom(disp
, "ATOM_PAIR", False
);
278 multiple_atom
= XInternAtom(disp
, "MULTIPLE", False
);
279 integer_atom
= XInternAtom(disp
, "INTEGER", False
);
281 strtype
= XInternAtom(disp
, "UTF8_STRING", False
);
282 } else if (mode
== CTEXT
) {
283 strtype
= XInternAtom(disp
, "COMPOUND_TEXT", False
);
284 } else if (mode
== TARGETS
) {
285 strtype
= targets_atom
;
286 expected_type
= atom_atom
;
287 expected_format
= 32;
288 } else if (mode
== TIMESTAMP
) {
289 strtype
= timestamp_atom
;
290 expected_type
= integer_atom
;
291 expected_format
= 32;
292 } else if (mode
== CUSTOM
) {
293 strtype
= XInternAtom(disp
, custom_seltype
, True
);
295 error ("atom '%s' does not exist on the server", custom_seltype
);
299 sel_atom
= XInternAtom(disp
, "CLIPBOARD", False
);
302 /* get the screen and root-window */
303 screen
= DefaultScreen (disp
);
304 root
= RootWindow (disp
, screen
);
307 width
= height
= 10; /* doesn't really matter */
309 /* actually create the window */
310 ourwin
= XCreateSimpleWindow (disp
, root
, x
, y
, width
, height
,0,
311 BlackPixel(disp
, screen
),
312 WhitePixel(disp
, screen
));
314 /* resource class name */
315 class_hints
.res_name
= lcasename
;
316 class_hints
.res_class
= ucasename
;
317 XSetClassHint (disp
, ourwin
, &class_hints
);
319 /* do selection fiddling */
322 * We are reading the selection, so we must FIXME.
324 if (XGetSelectionOwner(disp
, sel_atom
) == None
) {
325 /* No primary selection, so use the cut buffer. */
326 if (strtype
== XA_STRING
)
327 do_paste(DefaultRootWindow(disp
), XA_CUT_BUFFER0
, False
);
330 Atom sel_property
= XInternAtom(disp
, "VT_SELECTION", False
);
331 XConvertSelection(disp
, sel_atom
, strtype
,
332 sel_property
, ourwin
, CurrentTime
);
337 * We are writing to the selection, so we establish
338 * ourselves as selection owner. Also place the data in
339 * CUT_BUFFER0, if it isn't of an exotic type (cut buffers
340 * can only take ordinary string data, it turns out).
342 XSetSelectionOwner (disp
, sel_atom
, ourwin
, CurrentTime
);
343 if (XGetSelectionOwner (disp
, sel_atom
) != ourwin
)
344 error ("unable to obtain primary X selection\n");
345 compound_text_atom
= XInternAtom(disp
, "COMPOUND_TEXT", False
);
346 if (strtype
== XA_STRING
) {
348 * ICCCM-required cut buffer initialisation.
350 XChangeProperty(disp
, root
, XA_CUT_BUFFER0
,
351 XA_STRING
, 8, PropModeAppend
, "", 0);
352 XChangeProperty(disp
, root
, XA_CUT_BUFFER1
,
353 XA_STRING
, 8, PropModeAppend
, "", 0);
354 XChangeProperty(disp
, root
, XA_CUT_BUFFER2
,
355 XA_STRING
, 8, PropModeAppend
, "", 0);
356 XChangeProperty(disp
, root
, XA_CUT_BUFFER3
,
357 XA_STRING
, 8, PropModeAppend
, "", 0);
358 XChangeProperty(disp
, root
, XA_CUT_BUFFER4
,
359 XA_STRING
, 8, PropModeAppend
, "", 0);
360 XChangeProperty(disp
, root
, XA_CUT_BUFFER5
,
361 XA_STRING
, 8, PropModeAppend
, "", 0);
362 XChangeProperty(disp
, root
, XA_CUT_BUFFER6
,
363 XA_STRING
, 8, PropModeAppend
, "", 0);
364 XChangeProperty(disp
, root
, XA_CUT_BUFFER7
,
365 XA_STRING
, 8, PropModeAppend
, "", 0);
367 * Rotate the cut buffers and add our text in CUT_BUFFER0.
369 XRotateBuffers(disp
, 1);
370 XStoreBytes(disp
, seltext
, sellen
);
376 Atom
convert_sel_inner(Window requestor
, Atom target
, Atom property
) {
377 if (target
== strtype
) {
378 XChangeProperty (disp
, requestor
, property
, strtype
,
379 8, PropModeReplace
, seltext
, sellen
);
381 } else if (target
== compound_text_atom
&& convert_to_ctext
) {
383 XmbTextListToTextProperty (disp
, &seltext
, 1,
384 XCompoundTextStyle
, &tp
);
385 XChangeProperty (disp
, requestor
, property
, target
,
386 tp
.format
, PropModeReplace
,
387 tp
.value
, tp
.nitems
);
389 } else if (target
== targets_atom
) {
392 targets
[len
++] = timestamp_atom
;
393 targets
[len
++] = targets_atom
;
394 targets
[len
++] = multiple_atom
;
395 targets
[len
++] = strtype
;
396 if (strtype
!= compound_text_atom
&& convert_to_ctext
)
397 targets
[len
++] = compound_text_atom
;
398 XChangeProperty (disp
, requestor
, property
,
399 atom_atom
, 32, PropModeReplace
,
400 (unsigned char *)targets
, len
);
402 } else if (target
== timestamp_atom
) {
403 Time rettime
= CurrentTime
;
404 XChangeProperty (disp
, requestor
, property
,
405 integer_atom
, 32, PropModeReplace
,
406 (unsigned char *)&rettime
, 1);
413 Atom
convert_sel_outer(Window requestor
, Atom target
, Atom property
) {
414 if (target
== multiple_atom
) {
416 * Support for the MULTIPLE selection type, since it's
417 * specified as required in the ICCCM. Completely
418 * untested, though, because I have no idea of what X
419 * application might implement it for me to test against.
424 int actual_format
, i
;
425 long nitems
, bytes_after
, nread
;
430 * Fetch the requestor's window property giving a list of
431 * selection requests.
433 while (XGetWindowProperty(disp
, requestor
, property
, 0, size
,
434 False
, AnyPropertyType
, &actual_type
,
435 &actual_format
, &nitems
, &bytes_after
,
436 (unsigned char **) &data
) == Success
&&
437 nitems
* (actual_format
/ 8) == size
) {
442 if (actual_type
!= atom_pair_atom
|| actual_format
!= 32) {
447 adata
= (Atom
*)data
;
449 for (i
= 0; i
+1 < nitems
; i
+= 2) {
450 adata
[i
+1] = convert_sel_inner(requestor
, adata
[i
], adata
[i
+1]);
453 XChangeProperty (disp
, requestor
, property
,
454 atom_pair_atom
, 32, PropModeReplace
,
461 return convert_sel_inner(requestor
, target
, property
);
469 XNextEvent (disp
, &ev
);
472 case SelectionNotify
:
473 if (ev
.xselection
.property
!= None
)
474 do_paste(ev
.xselection
.requestor
,
475 ev
.xselection
.property
, True
);
481 /* Selection has been cleared by another app. */
483 case SelectionRequest
:
484 e2
.xselection
.type
= SelectionNotify
;
485 e2
.xselection
.requestor
= ev
.xselectionrequest
.requestor
;
486 e2
.xselection
.selection
= ev
.xselectionrequest
.selection
;
487 e2
.xselection
.target
= ev
.xselectionrequest
.target
;
488 e2
.xselection
.time
= ev
.xselectionrequest
.time
;
489 e2
.xselection
.property
=
490 convert_sel_outer(ev
.xselectionrequest
.requestor
,
491 ev
.xselectionrequest
.target
,
492 ev
.xselectionrequest
.property
);
493 XSendEvent (disp
, ev
.xselectionrequest
.requestor
,
504 XDestroyWindow (disp
, ourwin
);
506 XCloseDisplay (disp
);
509 void do_paste(Window window
, Atom property
, int Delete
) {
511 int actual_format
, i
;
512 long nitems
, bytes_after
, nread
;
516 while (XGetWindowProperty(disp
, window
, property
, nread
/ 4, SELDELTA
,
517 Delete
, AnyPropertyType
, &actual_type
,
518 &actual_format
, &nitems
, &bytes_after
,
519 (unsigned char **) &data
) == Success
) {
522 * We expect all returned chunks of data to be
523 * multiples of 4 bytes (because we can only request
524 * the subsequent starting offset in 4-byte
525 * increments). Of course you can store an odd number
526 * of bytes in a selection, so this can't be the case
527 * every time XGetWindowProperty returns; but it
528 * should be the case every time it returns _and there
529 * is more data to come_.
531 * Hence, whenever XGetWindowProperty returns, we
532 * verify that the size of the data returned _last_
533 * time was divisible by 4.
535 if ((nread
& 3) != 0) {
536 error("unexpected data size: %d read (not a multiple"
537 " of 4), but more to come\n", nread
);
540 if (expected_type
!= (Atom
)None
&& actual_type
!= expected_type
) {
541 char *expout
= XGetAtomName(disp
, expected_type
);
542 char *gotout
= (actual_type
== (Atom
)None ?
"None" :
543 XGetAtomName(disp
, actual_type
));
544 error("unexpected data type: expected %s, got %s\n",
547 if (expected_format
&& expected_format
!= actual_format
) {
548 error("unexpected data format: expected %d-bit, got %d-bit\n",
549 expected_format
, actual_format
);
554 if (mode
== TARGETS
) {
555 assert(actual_format
== 32);
557 for (i
= 0; i
< nitems
; i
++) {
558 Atom x
= ((Atom
*)data
)[i
];
559 printf("%s\n", XGetAtomName(disp
, x
));
561 } else if (mode
== TIMESTAMP
) {
562 assert(actual_format
== 32);
563 Time x
= ((Time
*)data
)[0];
564 printf("%lu\n", (unsigned long)x
);
566 fwrite(data
, actual_format
/ 8, nitems
, stdout
);
567 nread
+= nitems
* actual_format
/ 8;