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
} mode
= STRING
;
36 #define SELDELTA 16384
38 /* functional parameters */
39 int reading
; /* read instead of writing? */
40 int convert_to_ctext
= True
; /* Xmb convert to compound text? */
42 const char usagemsg
[] =
43 "usage: xcopy [ -r ] [ -u | -c ] [ -C ]\n"
44 "where: -r read X selection and print on stdout\n"
45 " no -r read stdin and store in X selection\n"
46 " -u work with UTF8_STRING type selections\n"
47 " -c work with COMPOUND_TEXT type selections\n"
48 " -C suppress automatic conversion to COMPOUND_TEXT\n"
49 " also: xcopy --version report version number\n"
50 " xcopy --help display this help text\n"
51 " xcopy --licence display the (MIT) licence text\n"
55 fputs(usagemsg
, stdout
);
58 const char licencemsg
[] =
59 "xcopy is copyright 2001-2004 Simon Tatham.\n"
61 "Permission is hereby granted, free of charge, to any person\n"
62 "obtaining a copy of this software and associated documentation files\n"
63 "(the \"Software\"), to deal in the Software without restriction,\n"
64 "including without limitation the rights to use, copy, modify, merge,\n"
65 "publish, distribute, sublicense, and/or sell copies of the Software,\n"
66 "and to permit persons to whom the Software is furnished to do so,\n"
67 "subject to the following conditions:\n"
69 "The above copyright notice and this permission notice shall be\n"
70 "included in all copies or substantial portions of the Software.\n"
72 "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n"
73 "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n"
74 "MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n"
75 "NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n"
76 "BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n"
77 "ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n"
78 "CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n"
83 fputs(licencemsg
, stdout
);
87 #define SVN_REV "$Revision$"
88 char rev
[sizeof(SVN_REV
)];
93 for (p
= rev
; *p
&& *p
!= ':'; p
++);
96 while (*p
&& isspace(*p
)) p
++;
97 for (q
= p
; *q
&& *q
!= '$'; q
++);
99 printf("xcopy revision %s\n", p
);
101 printf("xcopy: unknown version\n");
105 int main(int ac
, char **av
) {
111 /* parse the command line arguments */
115 if (!strcmp(p
, "-display") || !strcmp(p
, "-disp")) {
117 error ("option `%s' expects a parameter", p
);
118 display
= *++av
, --ac
;
119 } else if (!strcmp(p
, "-r")) {
121 } else if (!strcmp(p
, "-u")) {
123 } else if (!strcmp(p
, "-c")) {
125 } else if (!strcmp(p
, "-C")) {
126 convert_to_ctext
= False
;
127 } else if (!strcmp(p
, "--help")) {
130 } else if (!strcmp(p
, "--version")) {
133 } else if (!strcmp(p
, "--licence") || !strcmp(p
, "--license")) {
136 } else if (*p
=='-') {
137 error ("unrecognised option `%s'", p
);
139 error ("no parameters required");
144 seltext
= malloc(SELDELTA
);
146 error ("out of memory");
150 n
= fread(seltext
+sellen
, 1, selsize
-sellen
, stdin
);
152 if (sellen
>= selsize
) {
153 seltext
= realloc(seltext
, selsize
+= SELDELTA
);
155 error ("out of memory");
158 if (sellen
== selsize
) {
159 seltext
= realloc(seltext
, selsize
+= SELDELTA
);
161 error ("out of memory");
163 seltext
[sellen
] = '\0';
166 eventloop
= init_X();
169 * If we are writing the selection, we must go into the
174 error("unable to fork: %s", strerror(errno
));
175 } else if (pid
> 0) {
177 * we are the parent; just exit
197 void error (char *fmt
, ...) {
203 vsprintf (errbuf
, fmt
, ap
);
205 fprintf (stderr
, "%s: %s\n", pname
, errbuf
);
209 /* begin the X interface */
211 char *lcasename
= "xcopy";
212 char *ucasename
= "XCopy";
214 Display
*disp
= NULL
;
215 Window ourwin
= None
;
216 Atom compound_text_atom
, targets_atom
;
217 int screen
, wwidth
, wheight
;
219 Atom strtype
= XA_STRING
;
222 * Returns TRUE if we need to enter an event loop, FALSE otherwise.
226 int x
= 0, y
= 0, width
= 512, height
= 128;
229 XSizeHints size_hints
;
230 XClassHint class_hints
;
231 XTextProperty textprop
;
234 /* open the X display */
235 disp
= XOpenDisplay (display
);
237 error ("unable to open display");
240 strtype
= XInternAtom(disp
, "UTF8_STRING", False
);
242 error ("unable to get UTF8_STRING property");
243 } else if (mode
== CTEXT
) {
244 strtype
= XInternAtom(disp
, "COMPOUND_TEXT", False
);
246 error ("unable to get COMPOUND_TEXT property");
248 targets_atom
= XInternAtom(disp
, "TARGETS", False
);
250 error ("unable to get TARGETS property");
252 /* get the screen and root-window */
253 screen
= DefaultScreen (disp
);
254 root
= RootWindow (disp
, screen
);
257 width
= height
= 10; /* doesn't really matter */
259 /* actually create the window */
260 ourwin
= XCreateSimpleWindow (disp
, root
, x
, y
, width
, height
,0,
261 BlackPixel(disp
, screen
),
262 WhitePixel(disp
, screen
));
264 /* resource class name */
265 class_hints
.res_name
= lcasename
;
266 class_hints
.res_class
= ucasename
;
267 XSetClassHint (disp
, ourwin
, &class_hints
);
269 /* do selection fiddling */
272 * We are reading the selection, so we must FIXME.
274 if (XGetSelectionOwner(disp
, XA_PRIMARY
) == None
) {
275 /* No primary selection, so use the cut buffer. */
276 do_paste(DefaultRootWindow(disp
), XA_CUT_BUFFER0
, False
);
279 Atom sel_property
= XInternAtom(disp
, "VT_SELECTION", False
);
280 XConvertSelection(disp
, XA_PRIMARY
, strtype
,
281 sel_property
, ourwin
, CurrentTime
);
286 * We are writing to the selection, so we establish
287 * ourselves as selection owner. Also place the data in
288 * CUT_BUFFER0, if it isn't of an exotic type (cut buffers
289 * can only take ordinary string data, it turns out).
291 XSetSelectionOwner (disp
, XA_PRIMARY
, ourwin
, CurrentTime
);
292 if (XGetSelectionOwner (disp
, XA_PRIMARY
) != ourwin
)
293 error ("unable to obtain primary X selection\n");
294 compound_text_atom
= XInternAtom(disp
, "COMPOUND_TEXT", False
);
295 if (strtype
== XA_STRING
) {
297 * ICCCM-required cut buffer initialisation.
299 XChangeProperty(disp
, root
, XA_CUT_BUFFER0
,
300 XA_STRING
, 8, PropModeAppend
, "", 0);
301 XChangeProperty(disp
, root
, XA_CUT_BUFFER1
,
302 XA_STRING
, 8, PropModeAppend
, "", 0);
303 XChangeProperty(disp
, root
, XA_CUT_BUFFER2
,
304 XA_STRING
, 8, PropModeAppend
, "", 0);
305 XChangeProperty(disp
, root
, XA_CUT_BUFFER3
,
306 XA_STRING
, 8, PropModeAppend
, "", 0);
307 XChangeProperty(disp
, root
, XA_CUT_BUFFER4
,
308 XA_STRING
, 8, PropModeAppend
, "", 0);
309 XChangeProperty(disp
, root
, XA_CUT_BUFFER5
,
310 XA_STRING
, 8, PropModeAppend
, "", 0);
311 XChangeProperty(disp
, root
, XA_CUT_BUFFER6
,
312 XA_STRING
, 8, PropModeAppend
, "", 0);
313 XChangeProperty(disp
, root
, XA_CUT_BUFFER7
,
314 XA_STRING
, 8, PropModeAppend
, "", 0);
316 * Rotate the cut buffers and add our text in CUT_BUFFER0.
318 XRotateBuffers(disp
, 1);
319 XStoreBytes(disp
, seltext
, sellen
);
329 XNextEvent (disp
, &ev
);
332 case SelectionNotify
:
333 if (ev
.xselection
.property
!= None
)
334 do_paste(ev
.xselection
.requestor
,
335 ev
.xselection
.property
, True
);
341 /* Selection has been cleared by another app. */
343 case SelectionRequest
:
344 e2
.xselection
.type
= SelectionNotify
;
345 e2
.xselection
.requestor
= ev
.xselectionrequest
.requestor
;
346 e2
.xselection
.selection
= ev
.xselectionrequest
.selection
;
347 e2
.xselection
.target
= ev
.xselectionrequest
.target
;
348 e2
.xselection
.time
= ev
.xselectionrequest
.time
;
349 if (ev
.xselectionrequest
.target
== strtype
) {
350 XChangeProperty (disp
, ev
.xselectionrequest
.requestor
,
351 ev
.xselectionrequest
.property
, strtype
,
352 8, PropModeReplace
, seltext
, sellen
);
353 e2
.xselection
.property
= ev
.xselectionrequest
.property
;
354 } else if (ev
.xselectionrequest
.target
== compound_text_atom
&&
357 XmbTextListToTextProperty (disp
, &seltext
, 1,
358 XCompoundTextStyle
, &tp
);
359 XChangeProperty (disp
, ev
.xselectionrequest
.requestor
,
360 ev
.xselectionrequest
.property
,
361 ev
.xselectionrequest
.target
,
362 tp
.format
, PropModeReplace
,
363 tp
.value
, tp
.nitems
);
364 e2
.xselection
.property
= ev
.xselectionrequest
.property
;
365 } else if (ev
.xselectionrequest
.target
== targets_atom
) {
368 targets
[len
++] = strtype
;
369 if (strtype
!= compound_text_atom
&& convert_to_ctext
)
370 targets
[len
++] = compound_text_atom
;
371 XChangeProperty (disp
, ev
.xselectionrequest
.requestor
,
372 ev
.xselectionrequest
.property
,
373 ev
.xselectionrequest
.target
,
375 (unsigned char *)targets
, len
);
377 e2
.xselection
.property
= None
;
379 XSendEvent (disp
, ev
.xselectionrequest
.requestor
, False
, 0, &e2
);
389 XDestroyWindow (disp
, ourwin
);
391 XCloseDisplay (disp
);
394 void do_paste(Window window
, Atom property
, int Delete
) {
396 int actual_format
, i
;
397 long nitems
, bytes_after
, nread
;
401 while (XGetWindowProperty(disp
, window
, property
, nread
/ 4, SELDELTA
,
402 Delete
, AnyPropertyType
, &actual_type
,
403 &actual_format
, &nitems
, &bytes_after
,
404 (unsigned char **) &data
) == Success
) {
406 * We expect all returned chunks of data to be multiples of
407 * 4 bytes (because we can only request the subsequent
408 * starting offset in 4-byte increments). Of course you can
409 * store an odd number of bytes in a selection, so this
410 * can't be the case every time XGetWindowProperty returns;
411 * but it should be the case every time it returns _and
412 * there is more data to come_.
414 * Hence, whenever XGetWindowProperty returns, we verify
415 * that the size of the data returned _last_ time was
419 assert((nread
& 3) == 0);
421 if (actual_type
== strtype
&& nitems
> 0) {
422 assert(actual_format
== 8);
423 fwrite(data
, 1, nitems
, stdout
);
427 if (actual_type
!= strtype
|| nitems
== 0)