2 * xcopy: quickly pipe text data into, or out of, the primary X
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
} mode
= STRING
;
37 #define SELDELTA 16384
39 /* functional parameters */
40 int reading
; /* read instead of writing? */
41 int convert_to_ctext
= True
; /* Xmb convert to compound text? */
43 const char usagemsg
[] =
44 "usage: xcopy [ -r ] [ -u | -c ] [ -C ]\n"
45 "where: -r read X selection and print on stdout\n"
46 " no -r read stdin and store in X selection\n"
47 " -u work with UTF8_STRING type selections\n"
48 " -c work with COMPOUND_TEXT type selections\n"
49 " -C suppress automatic conversion to COMPOUND_TEXT\n"
50 " also: xcopy --version report version number\n"
51 " xcopy --help display this help text\n"
52 " xcopy --licence display the (MIT) licence text\n"
56 fputs(usagemsg
, stdout
);
59 const char licencemsg
[] =
60 "xcopy is copyright 2001-2004 Simon Tatham.\n"
62 "Permission is hereby granted, free of charge, to any person\n"
63 "obtaining a copy of this software and associated documentation files\n"
64 "(the \"Software\"), to deal in the Software without restriction,\n"
65 "including without limitation the rights to use, copy, modify, merge,\n"
66 "publish, distribute, sublicense, and/or sell copies of the Software,\n"
67 "and to permit persons to whom the Software is furnished to do so,\n"
68 "subject to the following conditions:\n"
70 "The above copyright notice and this permission notice shall be\n"
71 "included in all copies or substantial portions of the Software.\n"
73 "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n"
74 "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n"
75 "MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n"
76 "NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n"
77 "BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n"
78 "ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n"
79 "CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n"
84 fputs(licencemsg
, stdout
);
88 #define SVN_REV "$Revision$"
89 char rev
[sizeof(SVN_REV
)];
94 for (p
= rev
; *p
&& *p
!= ':'; p
++);
97 while (*p
&& isspace(*p
)) p
++;
98 for (q
= p
; *q
&& *q
!= '$'; q
++);
100 printf("xcopy revision %s\n", p
);
102 printf("xcopy: unknown version\n");
106 int main(int ac
, char **av
) {
112 /* parse the command line arguments */
116 if (!strcmp(p
, "-display") || !strcmp(p
, "-disp")) {
118 error ("option `%s' expects a parameter", p
);
119 display
= *++av
, --ac
;
120 } else if (!strcmp(p
, "-r")) {
122 } else if (!strcmp(p
, "-u")) {
124 } else if (!strcmp(p
, "-c")) {
126 } else if (!strcmp(p
, "-C")) {
127 convert_to_ctext
= False
;
128 } else if (!strcmp(p
, "--help")) {
131 } else if (!strcmp(p
, "--version")) {
134 } else if (!strcmp(p
, "--licence") || !strcmp(p
, "--license")) {
137 } else if (*p
=='-') {
138 error ("unrecognised option `%s'", p
);
140 error ("no parameters required");
145 seltext
= malloc(SELDELTA
);
147 error ("out of memory");
151 n
= fread(seltext
+sellen
, 1, selsize
-sellen
, stdin
);
153 if (sellen
>= selsize
) {
154 seltext
= realloc(seltext
, selsize
+= SELDELTA
);
156 error ("out of memory");
159 if (sellen
== selsize
) {
160 seltext
= realloc(seltext
, selsize
+= SELDELTA
);
162 error ("out of memory");
164 seltext
[sellen
] = '\0';
167 eventloop
= init_X();
170 * If we are writing the selection, we must go into the
175 error("unable to fork: %s", strerror(errno
));
176 } else if (pid
> 0) {
178 * we are the parent; just exit
198 void error (char *fmt
, ...) {
204 vsprintf (errbuf
, fmt
, ap
);
206 fprintf (stderr
, "%s: %s\n", pname
, errbuf
);
210 /* begin the X interface */
212 char *lcasename
= "xcopy";
213 char *ucasename
= "XCopy";
215 Display
*disp
= NULL
;
216 Window ourwin
= None
;
217 Atom compound_text_atom
, targets_atom
;
218 int screen
, wwidth
, wheight
;
220 Atom strtype
= XA_STRING
;
223 * Returns TRUE if we need to enter an event loop, FALSE otherwise.
227 int x
= 0, y
= 0, width
= 512, height
= 128;
230 XSizeHints size_hints
;
231 XClassHint class_hints
;
232 XTextProperty textprop
;
235 /* open the X display */
236 disp
= XOpenDisplay (display
);
238 error ("unable to open display");
241 strtype
= XInternAtom(disp
, "UTF8_STRING", False
);
243 error ("unable to get UTF8_STRING property");
244 } else if (mode
== CTEXT
) {
245 strtype
= XInternAtom(disp
, "COMPOUND_TEXT", False
);
247 error ("unable to get COMPOUND_TEXT property");
249 targets_atom
= XInternAtom(disp
, "TARGETS", False
);
251 error ("unable to get TARGETS property");
253 /* get the screen and root-window */
254 screen
= DefaultScreen (disp
);
255 root
= RootWindow (disp
, screen
);
258 width
= height
= 10; /* doesn't really matter */
260 /* actually create the window */
261 ourwin
= XCreateSimpleWindow (disp
, root
, x
, y
, width
, height
,0,
262 BlackPixel(disp
, screen
),
263 WhitePixel(disp
, screen
));
265 /* resource class name */
266 class_hints
.res_name
= lcasename
;
267 class_hints
.res_class
= ucasename
;
268 XSetClassHint (disp
, ourwin
, &class_hints
);
270 /* do selection fiddling */
273 * We are reading the selection, so we must FIXME.
275 if (XGetSelectionOwner(disp
, XA_PRIMARY
) == None
) {
276 /* No primary selection, so use the cut buffer. */
277 do_paste(DefaultRootWindow(disp
), XA_CUT_BUFFER0
, False
);
280 Atom sel_property
= XInternAtom(disp
, "VT_SELECTION", False
);
281 XConvertSelection(disp
, XA_PRIMARY
, strtype
,
282 sel_property
, ourwin
, CurrentTime
);
287 * We are writing to the selection, so we establish
288 * ourselves as selection owner. Also place the data in
289 * CUT_BUFFER0, if it isn't of an exotic type (cut buffers
290 * can only take ordinary string data, it turns out).
292 XSetSelectionOwner (disp
, XA_PRIMARY
, ourwin
, CurrentTime
);
293 if (XGetSelectionOwner (disp
, XA_PRIMARY
) != ourwin
)
294 error ("unable to obtain primary X selection\n");
295 compound_text_atom
= XInternAtom(disp
, "COMPOUND_TEXT", False
);
296 if (strtype
== XA_STRING
) {
298 * ICCCM-required cut buffer initialisation.
300 XChangeProperty(disp
, root
, XA_CUT_BUFFER0
,
301 XA_STRING
, 8, PropModeAppend
, "", 0);
302 XChangeProperty(disp
, root
, XA_CUT_BUFFER1
,
303 XA_STRING
, 8, PropModeAppend
, "", 0);
304 XChangeProperty(disp
, root
, XA_CUT_BUFFER2
,
305 XA_STRING
, 8, PropModeAppend
, "", 0);
306 XChangeProperty(disp
, root
, XA_CUT_BUFFER3
,
307 XA_STRING
, 8, PropModeAppend
, "", 0);
308 XChangeProperty(disp
, root
, XA_CUT_BUFFER4
,
309 XA_STRING
, 8, PropModeAppend
, "", 0);
310 XChangeProperty(disp
, root
, XA_CUT_BUFFER5
,
311 XA_STRING
, 8, PropModeAppend
, "", 0);
312 XChangeProperty(disp
, root
, XA_CUT_BUFFER6
,
313 XA_STRING
, 8, PropModeAppend
, "", 0);
314 XChangeProperty(disp
, root
, XA_CUT_BUFFER7
,
315 XA_STRING
, 8, PropModeAppend
, "", 0);
317 * Rotate the cut buffers and add our text in CUT_BUFFER0.
319 XRotateBuffers(disp
, 1);
320 XStoreBytes(disp
, seltext
, sellen
);
330 XNextEvent (disp
, &ev
);
333 case SelectionNotify
:
334 if (ev
.xselection
.property
!= None
)
335 do_paste(ev
.xselection
.requestor
,
336 ev
.xselection
.property
, True
);
342 /* Selection has been cleared by another app. */
344 case SelectionRequest
:
345 e2
.xselection
.type
= SelectionNotify
;
346 e2
.xselection
.requestor
= ev
.xselectionrequest
.requestor
;
347 e2
.xselection
.selection
= ev
.xselectionrequest
.selection
;
348 e2
.xselection
.target
= ev
.xselectionrequest
.target
;
349 e2
.xselection
.time
= ev
.xselectionrequest
.time
;
350 if (ev
.xselectionrequest
.target
== strtype
) {
351 XChangeProperty (disp
, ev
.xselectionrequest
.requestor
,
352 ev
.xselectionrequest
.property
, strtype
,
353 8, PropModeReplace
, seltext
, sellen
);
354 e2
.xselection
.property
= ev
.xselectionrequest
.property
;
355 } else if (ev
.xselectionrequest
.target
== compound_text_atom
&&
358 XmbTextListToTextProperty (disp
, &seltext
, 1,
359 XCompoundTextStyle
, &tp
);
360 XChangeProperty (disp
, ev
.xselectionrequest
.requestor
,
361 ev
.xselectionrequest
.property
,
362 ev
.xselectionrequest
.target
,
363 tp
.format
, PropModeReplace
,
364 tp
.value
, tp
.nitems
);
365 e2
.xselection
.property
= ev
.xselectionrequest
.property
;
366 } else if (ev
.xselectionrequest
.target
== targets_atom
) {
369 targets
[len
++] = strtype
;
370 if (strtype
!= compound_text_atom
&& convert_to_ctext
)
371 targets
[len
++] = compound_text_atom
;
372 XChangeProperty (disp
, ev
.xselectionrequest
.requestor
,
373 ev
.xselectionrequest
.property
,
374 ev
.xselectionrequest
.target
,
376 (unsigned char *)targets
, len
);
378 e2
.xselection
.property
= None
;
380 XSendEvent (disp
, ev
.xselectionrequest
.requestor
, False
, 0, &e2
);
390 XDestroyWindow (disp
, ourwin
);
392 XCloseDisplay (disp
);
395 void do_paste(Window window
, Atom property
, int Delete
) {
397 int actual_format
, i
;
398 long nitems
, bytes_after
, nread
;
402 while (XGetWindowProperty(disp
, window
, property
, nread
/ 4, SELDELTA
,
403 Delete
, AnyPropertyType
, &actual_type
,
404 &actual_format
, &nitems
, &bytes_after
,
405 (unsigned char **) &data
) == Success
) {
407 * We expect all returned chunks of data to be multiples of
408 * 4 bytes (because we can only request the subsequent
409 * starting offset in 4-byte increments). Of course you can
410 * store an odd number of bytes in a selection, so this
411 * can't be the case every time XGetWindowProperty returns;
412 * but it should be the case every time it returns _and
413 * there is more data to come_.
415 * Hence, whenever XGetWindowProperty returns, we verify
416 * that the size of the data returned _last_ time was
420 assert((nread
& 3) == 0);
422 if (actual_type
== strtype
&& nitems
> 0) {
423 assert(actual_format
== 8);
424 fwrite(data
, 1, nitems
, stdout
);
428 if (actual_type
!= strtype
|| nitems
== 0)