a06789a97ab8fa985db61386839d0b2836fec682
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
;
38 #define SELDELTA 16384
40 /* functional parameters */
41 int reading
; /* read instead of writing? */
42 int convert_to_ctext
= True
; /* Xmb convert to compound text? */
44 const char usagemsg
[] =
45 "usage: xcopy [ -r ] [ -u | -c ] [ -C ]\n"
46 "where: -r read X selection and print on stdout\n"
47 " no -r read stdin and store in X selection\n"
48 " -u work with UTF8_STRING type selections\n"
49 " -c work with COMPOUND_TEXT type selections\n"
50 " -C suppress automatic conversion to COMPOUND_TEXT\n"
51 " -b read the CLIPBOARD rather than the PRIMARY selection\n"
52 " -t get the list of targets available to retrieve\n"
53 " -T get the time stamp of the selection contents\n"
54 " -a atom get an arbitrary form of the selection data\n"
55 " also: xcopy --version report version number\n"
56 " xcopy --help display this help text\n"
57 " xcopy --licence display the (MIT) licence text\n"
61 fputs(usagemsg
, stdout
);
64 const char licencemsg
[] =
65 "xcopy is copyright 2001-2004 Simon Tatham.\n"
67 "Permission is hereby granted, free of charge, to any person\n"
68 "obtaining a copy of this software and associated documentation files\n"
69 "(the \"Software\"), to deal in the Software without restriction,\n"
70 "including without limitation the rights to use, copy, modify, merge,\n"
71 "publish, distribute, sublicense, and/or sell copies of the Software,\n"
72 "and to permit persons to whom the Software is furnished to do so,\n"
73 "subject to the following conditions:\n"
75 "The above copyright notice and this permission notice shall be\n"
76 "included in all copies or substantial portions of the Software.\n"
78 "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n"
79 "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n"
80 "MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n"
81 "NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n"
82 "BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n"
83 "ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n"
84 "CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n"
89 fputs(licencemsg
, stdout
);
93 #define SVN_REV "$Revision$"
94 char rev
[sizeof(SVN_REV
)];
99 for (p
= rev
; *p
&& *p
!= ':'; p
++);
102 while (*p
&& isspace(*p
)) p
++;
103 for (q
= p
; *q
&& *q
!= '$'; q
++);
105 printf("xcopy revision %s\n", p
);
107 printf("xcopy: unknown version\n");
111 int main(int ac
, char **av
) {
117 /* parse the command line arguments */
121 if (!strcmp(p
, "-display") || !strcmp(p
, "-disp")) {
123 error ("option `%s' expects a parameter", p
);
124 display
= *++av
, --ac
;
125 } else if (!strcmp(p
, "-r")) {
127 } else if (!strcmp(p
, "-u")) {
129 } else if (!strcmp(p
, "-c")) {
131 } else if (!strcmp(p
, "-C")) {
132 convert_to_ctext
= False
;
133 } else if (!strcmp(p
, "-b")) {
134 use_clipboard
= True
;
135 } else if (!strcmp(p
, "-t")) {
137 } else if (!strcmp(p
, "-T")) {
139 } else if (!strcmp(p
, "-a")) {
141 custom_seltype
= *++av
;
143 error ("expected an argument to `-a'");
145 } else if (!strcmp(p
, "--help")) {
148 } else if (!strcmp(p
, "--version")) {
151 } else if (!strcmp(p
, "--licence") || !strcmp(p
, "--license")) {
154 } else if (*p
=='-') {
155 error ("unrecognised option `%s'", p
);
157 error ("no parameters required");
162 if (mode
== TARGETS
|| mode
== TIMESTAMP
|| mode
== CUSTOM
) {
163 error ("%s not supported in writing mode; use -r",
164 (mode
== TARGETS ?
"-t" :
165 mode
== TIMESTAMP ?
"-T" :
166 /* mode == CUSTOM ? */ "-a"));
171 seltext
= malloc(SELDELTA
);
173 error ("out of memory");
177 n
= fread(seltext
+sellen
, 1, selsize
-sellen
, stdin
);
179 if (sellen
>= selsize
) {
180 seltext
= realloc(seltext
, selsize
+= SELDELTA
);
182 error ("out of memory");
185 if (sellen
== selsize
) {
186 seltext
= realloc(seltext
, selsize
+= SELDELTA
);
188 error ("out of memory");
190 seltext
[sellen
] = '\0';
193 eventloop
= init_X();
196 * If we are writing the selection, we must go into the
201 error("unable to fork: %s", strerror(errno
));
202 } else if (pid
> 0) {
204 * we are the parent; just exit
224 void error (char *fmt
, ...) {
230 vsprintf (errbuf
, fmt
, ap
);
232 fprintf (stderr
, "%s: %s\n", pname
, errbuf
);
236 /* begin the X interface */
238 char *lcasename
= "xcopy";
239 char *ucasename
= "XCopy";
241 Display
*disp
= NULL
;
242 Window ourwin
= None
;
243 Atom compound_text_atom
, targets_atom
;
244 int screen
, wwidth
, wheight
;
246 Atom strtype
= XA_STRING
;
247 Atom sel_atom
= XA_PRIMARY
;
248 Atom expected_type
= (Atom
)None
;
249 int expected_format
= 8;
252 * Returns TRUE if we need to enter an event loop, FALSE otherwise.
256 int x
= 0, y
= 0, width
= 512, height
= 128;
259 XSizeHints size_hints
;
260 XClassHint class_hints
;
261 XTextProperty textprop
;
264 /* open the X display */
265 disp
= XOpenDisplay (display
);
267 error ("unable to open display");
269 targets_atom
= XInternAtom(disp
, "TARGETS", False
);
271 error ("unable to get TARGETS property");
273 strtype
= XInternAtom(disp
, "UTF8_STRING", False
);
275 error ("unable to get UTF8_STRING property");
276 } else if (mode
== CTEXT
) {
277 strtype
= XInternAtom(disp
, "COMPOUND_TEXT", False
);
279 error ("unable to get COMPOUND_TEXT property");
280 } else if (mode
== TARGETS
) {
281 strtype
= targets_atom
;
282 expected_type
= XInternAtom(disp
, "ATOM", False
);
283 expected_format
= 32;
284 } else if (mode
== TIMESTAMP
) {
285 strtype
= XInternAtom(disp
, "TIMESTAMP", False
);
286 expected_format
= 32;
287 } else if (mode
== CUSTOM
) {
288 strtype
= XInternAtom(disp
, custom_seltype
, False
);
292 sel_atom
= XInternAtom(disp
, "CLIPBOARD", False
);
294 error ("unable to get CLIPBOARD property");
297 /* get the screen and root-window */
298 screen
= DefaultScreen (disp
);
299 root
= RootWindow (disp
, screen
);
302 width
= height
= 10; /* doesn't really matter */
304 /* actually create the window */
305 ourwin
= XCreateSimpleWindow (disp
, root
, x
, y
, width
, height
,0,
306 BlackPixel(disp
, screen
),
307 WhitePixel(disp
, screen
));
309 /* resource class name */
310 class_hints
.res_name
= lcasename
;
311 class_hints
.res_class
= ucasename
;
312 XSetClassHint (disp
, ourwin
, &class_hints
);
314 /* do selection fiddling */
317 * We are reading the selection, so we must FIXME.
319 if (XGetSelectionOwner(disp
, sel_atom
) == None
) {
320 /* No primary selection, so use the cut buffer. */
321 if (strtype
== XA_STRING
)
322 do_paste(DefaultRootWindow(disp
), XA_CUT_BUFFER0
, False
);
325 Atom sel_property
= XInternAtom(disp
, "VT_SELECTION", False
);
326 XConvertSelection(disp
, sel_atom
, strtype
,
327 sel_property
, ourwin
, CurrentTime
);
332 * We are writing to the selection, so we establish
333 * ourselves as selection owner. Also place the data in
334 * CUT_BUFFER0, if it isn't of an exotic type (cut buffers
335 * can only take ordinary string data, it turns out).
337 XSetSelectionOwner (disp
, sel_atom
, ourwin
, CurrentTime
);
338 if (XGetSelectionOwner (disp
, sel_atom
) != ourwin
)
339 error ("unable to obtain primary X selection\n");
340 compound_text_atom
= XInternAtom(disp
, "COMPOUND_TEXT", False
);
341 if (strtype
== XA_STRING
) {
343 * ICCCM-required cut buffer initialisation.
345 XChangeProperty(disp
, root
, XA_CUT_BUFFER0
,
346 XA_STRING
, 8, PropModeAppend
, "", 0);
347 XChangeProperty(disp
, root
, XA_CUT_BUFFER1
,
348 XA_STRING
, 8, PropModeAppend
, "", 0);
349 XChangeProperty(disp
, root
, XA_CUT_BUFFER2
,
350 XA_STRING
, 8, PropModeAppend
, "", 0);
351 XChangeProperty(disp
, root
, XA_CUT_BUFFER3
,
352 XA_STRING
, 8, PropModeAppend
, "", 0);
353 XChangeProperty(disp
, root
, XA_CUT_BUFFER4
,
354 XA_STRING
, 8, PropModeAppend
, "", 0);
355 XChangeProperty(disp
, root
, XA_CUT_BUFFER5
,
356 XA_STRING
, 8, PropModeAppend
, "", 0);
357 XChangeProperty(disp
, root
, XA_CUT_BUFFER6
,
358 XA_STRING
, 8, PropModeAppend
, "", 0);
359 XChangeProperty(disp
, root
, XA_CUT_BUFFER7
,
360 XA_STRING
, 8, PropModeAppend
, "", 0);
362 * Rotate the cut buffers and add our text in CUT_BUFFER0.
364 XRotateBuffers(disp
, 1);
365 XStoreBytes(disp
, seltext
, sellen
);
375 XNextEvent (disp
, &ev
);
378 case SelectionNotify
:
379 if (ev
.xselection
.property
!= None
)
380 do_paste(ev
.xselection
.requestor
,
381 ev
.xselection
.property
, True
);
387 /* Selection has been cleared by another app. */
389 case SelectionRequest
:
390 e2
.xselection
.type
= SelectionNotify
;
391 e2
.xselection
.requestor
= ev
.xselectionrequest
.requestor
;
392 e2
.xselection
.selection
= ev
.xselectionrequest
.selection
;
393 e2
.xselection
.target
= ev
.xselectionrequest
.target
;
394 e2
.xselection
.time
= ev
.xselectionrequest
.time
;
395 if (ev
.xselectionrequest
.target
== strtype
) {
396 XChangeProperty (disp
, ev
.xselectionrequest
.requestor
,
397 ev
.xselectionrequest
.property
, strtype
,
398 8, PropModeReplace
, seltext
, sellen
);
399 e2
.xselection
.property
= ev
.xselectionrequest
.property
;
400 } else if (ev
.xselectionrequest
.target
== compound_text_atom
&&
403 XmbTextListToTextProperty (disp
, &seltext
, 1,
404 XCompoundTextStyle
, &tp
);
405 XChangeProperty (disp
, ev
.xselectionrequest
.requestor
,
406 ev
.xselectionrequest
.property
,
407 ev
.xselectionrequest
.target
,
408 tp
.format
, PropModeReplace
,
409 tp
.value
, tp
.nitems
);
410 e2
.xselection
.property
= ev
.xselectionrequest
.property
;
411 } else if (ev
.xselectionrequest
.target
== targets_atom
) {
414 targets
[len
++] = strtype
;
415 if (strtype
!= compound_text_atom
&& convert_to_ctext
)
416 targets
[len
++] = compound_text_atom
;
417 XChangeProperty (disp
, ev
.xselectionrequest
.requestor
,
418 ev
.xselectionrequest
.property
,
419 ev
.xselectionrequest
.target
,
421 (unsigned char *)targets
, len
);
423 e2
.xselection
.property
= None
;
425 XSendEvent (disp
, ev
.xselectionrequest
.requestor
, False
, 0, &e2
);
435 XDestroyWindow (disp
, ourwin
);
437 XCloseDisplay (disp
);
440 void do_paste(Window window
, Atom property
, int Delete
) {
442 int actual_format
, i
;
443 long nitems
, bytes_after
, nread
;
447 while (XGetWindowProperty(disp
, window
, property
, nread
/ 4, SELDELTA
,
448 Delete
, AnyPropertyType
, &actual_type
,
449 &actual_format
, &nitems
, &bytes_after
,
450 (unsigned char **) &data
) == Success
) {
452 * We expect all returned chunks of data to be multiples of
453 * 4 bytes (because we can only request the subsequent
454 * starting offset in 4-byte increments). Of course you can
455 * store an odd number of bytes in a selection, so this
456 * can't be the case every time XGetWindowProperty returns;
457 * but it should be the case every time it returns _and
458 * there is more data to come_.
460 * Hence, whenever XGetWindowProperty returns, we verify
461 * that the size of the data returned _last_ time was
465 assert((nread
& 3) == 0);
468 if (expected_type
!= (Atom
)None
&& actual_type
!= expected_type
) {
469 char *expout
= XGetAtomName(disp
, expected_type
);
470 char *gotout
= (actual_type
== (Atom
)None ?
"None" :
471 XGetAtomName(disp
, actual_type
));
472 error("unexpected data type: expected %s, got %s\n",
475 if (expected_format
&& expected_format
!= actual_format
) {
476 error("unexpected data format: expected %d-bit, got %d-bit\n",
477 expected_format
, actual_format
);
482 if (mode
== TARGETS
) {
483 assert(actual_format
== 32);
485 for (i
= 0; i
< nitems
; i
++) {
486 Atom x
= ((Atom
*)data
)[i
];
487 printf("%s\n", XGetAtomName(disp
, x
));
489 } else if (mode
== TIMESTAMP
) {
490 assert(actual_format
== 32);
491 Time x
= ((Time
*)data
)[0];
492 printf("%lu\n", (unsigned long)x
);
494 fwrite(data
, actual_format
/ 8, nitems
, stdout
);
495 nread
+= nitems
* actual_format
/ 8;