Add a vital missing paragraph to the man page, and update the header
[sgt/utils] / xcopy / xcopy.c
CommitLineData
9acadc2b 1/*
2 * xcopy: quickly pipe text data into, or out of, the primary X
3 * selection
4 */
5
9acadc2b 6#include <stdio.h>
7#include <stdlib.h>
8#include <string.h>
9#include <stdarg.h>
10#include <math.h>
11#include <errno.h>
12#include <assert.h>
13
14#include <X11/X.h>
15#include <X11/Xlib.h>
16#include <X11/Xutil.h>
17#include <X11/Xatom.h>
18
19int init_X(void);
20void run_X(void);
21void done_X(void);
22void full_redraw(void);
23void do_paste(Window window, Atom property, int Delete);
24
25char *pname; /* program name */
26
27void error (char *fmt, ...);
28
29/* set from command-line parameters */
30char *display = NULL;
3ffcf184 31enum { STRING, CTEXT, UTF8, TARGETS, TIMESTAMP, CUSTOM } mode = STRING;
5e36d477 32int use_clipboard = False;
3ffcf184 33char *custom_seltype = NULL;
acc02b82 34int fork_when_writing = True;
9acadc2b 35
36/* selection data */
37char *seltext;
38int sellen, selsize;
39#define SELDELTA 16384
40
41/* functional parameters */
42int reading; /* read instead of writing? */
43int convert_to_ctext = True; /* Xmb convert to compound text? */
6aa7b28f 44int verbose;
9acadc2b 45
da0f8522 46const char usagemsg[] =
47 "usage: xcopy [ -r ] [ -u | -c ] [ -C ]\n"
3ffcf184 48 "where: -r read X selection and print on stdout\n"
49 " no -r read stdin and store in X selection\n"
50 " -u work with UTF8_STRING type selections\n"
51 " -c work with COMPOUND_TEXT type selections\n"
52 " -C suppress automatic conversion to COMPOUND_TEXT\n"
53 " -b read the CLIPBOARD rather than the PRIMARY selection\n"
54 " -t get the list of targets available to retrieve\n"
55 " -T get the time stamp of the selection contents\n"
56 " -a atom get an arbitrary form of the selection data\n"
acc02b82 57 " -F do not fork in write mode\n"
6aa7b28f 58 " -v proceed verbosely when reading selection\n"
c52f9fb9 59 " also: xcopy --version report version number\n"
60 " xcopy --help display this help text\n"
61 " xcopy --licence display the (MIT) licence text\n"
da0f8522 62 ;
63
64void usage(void) {
65 fputs(usagemsg, stdout);
66}
67
68const char licencemsg[] =
b3d07da3 69 "xcopy is copyright 2001-2004,2008 Simon Tatham.\n"
da0f8522 70 "\n"
71 "Permission is hereby granted, free of charge, to any person\n"
72 "obtaining a copy of this software and associated documentation files\n"
73 "(the \"Software\"), to deal in the Software without restriction,\n"
74 "including without limitation the rights to use, copy, modify, merge,\n"
75 "publish, distribute, sublicense, and/or sell copies of the Software,\n"
76 "and to permit persons to whom the Software is furnished to do so,\n"
77 "subject to the following conditions:\n"
78 "\n"
79 "The above copyright notice and this permission notice shall be\n"
80 "included in all copies or substantial portions of the Software.\n"
81 "\n"
82 "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n"
83 "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n"
84 "MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n"
85 "NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n"
86 "BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n"
87 "ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n"
88 "CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n"
89 "SOFTWARE.\n"
90 ;
91
92void licence(void) {
93 fputs(licencemsg, stdout);
94}
95
96void version(void) {
97#define SVN_REV "$Revision$"
98 char rev[sizeof(SVN_REV)];
99 char *p, *q;
100
101 strcpy(rev, SVN_REV);
102
103 for (p = rev; *p && *p != ':'; p++);
104 if (*p) {
105 p++;
106 while (*p && isspace(*p)) p++;
107 for (q = p; *q && *q != '$'; q++);
108 if (*q) *q = '\0';
109 printf("xcopy revision %s\n", p);
110 } else {
111 printf("xcopy: unknown version\n");
112 }
113}
114
9acadc2b 115int main(int ac, char **av) {
116 int n;
117 int eventloop;
118
119 pname = *av;
120
121 /* parse the command line arguments */
3ffcf184 122 while (--ac > 0) {
9acadc2b 123 char *p = *++av;
124
125 if (!strcmp(p, "-display") || !strcmp(p, "-disp")) {
126 if (!av[1])
127 error ("option `%s' expects a parameter", p);
128 display = *++av, --ac;
129 } else if (!strcmp(p, "-r")) {
130 reading = True;
131 } else if (!strcmp(p, "-u")) {
132 mode = UTF8;
133 } else if (!strcmp(p, "-c")) {
134 mode = CTEXT;
135 } else if (!strcmp(p, "-C")) {
136 convert_to_ctext = False;
5e36d477 137 } else if (!strcmp(p, "-b")) {
138 use_clipboard = True;
3ffcf184 139 } else if (!strcmp(p, "-t")) {
140 mode = TARGETS;
141 } else if (!strcmp(p, "-T")) {
142 mode = TIMESTAMP;
143 } else if (!strcmp(p, "-a")) {
144 if (--ac > 0)
145 custom_seltype = *++av;
146 else
147 error ("expected an argument to `-a'");
148 mode = CUSTOM;
acc02b82 149 } else if (!strcmp(p, "-F")) {
150 fork_when_writing = False;
6aa7b28f 151 } else if (!strcmp(p, "-v")) {
152 verbose = True;
da0f8522 153 } else if (!strcmp(p, "--help")) {
154 usage();
155 return 0;
156 } else if (!strcmp(p, "--version")) {
157 version();
158 return 0;
159 } else if (!strcmp(p, "--licence") || !strcmp(p, "--license")) {
160 licence();
161 return 0;
9acadc2b 162 } else if (*p=='-') {
163 error ("unrecognised option `%s'", p);
164 } else {
165 error ("no parameters required");
166 }
167 }
168
169 if (!reading) {
3ffcf184 170 if (mode == TARGETS || mode == TIMESTAMP || mode == CUSTOM) {
171 error ("%s not supported in writing mode; use -r",
172 (mode == TARGETS ? "-t" :
173 mode == TIMESTAMP ? "-T" :
174 /* mode == CUSTOM ? */ "-a"));
175 }
176 }
177
178 if (!reading) {
9acadc2b 179 seltext = malloc(SELDELTA);
180 if (!seltext)
181 error ("out of memory");
182 selsize = SELDELTA;
183 sellen = 0;
184 do {
185 n = fread(seltext+sellen, 1, selsize-sellen, stdin);
186 sellen += n;
187 if (sellen >= selsize) {
188 seltext = realloc(seltext, selsize += SELDELTA);
189 if (!seltext)
190 error ("out of memory");
191 }
192 } while (n > 0);
193 if (sellen == selsize) {
194 seltext = realloc(seltext, selsize += SELDELTA);
195 if (!seltext)
196 error ("out of memory");
197 }
198 seltext[sellen] = '\0';
199 }
200
201 eventloop = init_X();
acc02b82 202 if (!reading && fork_when_writing) {
9acadc2b 203 /*
204 * If we are writing the selection, we must go into the
205 * background now.
206 */
207 int pid = fork();
208 if (pid < 0) {
209 error("unable to fork: %s", strerror(errno));
210 } else if (pid > 0) {
211 /*
212 * we are the parent; just exit
213 */
214 return 0;
215 }
216 /*
217 * we are the child
218 */
219 close(0);
220 close(1);
221 close(2);
222 chdir("/");
223 }
224 if (eventloop)
225 run_X();
226 done_X();
227 return 0;
228}
229
230/* handle errors */
231
232void error (char *fmt, ...) {
233 va_list ap;
234 char errbuf[200];
235
236 done_X();
237 va_start (ap, fmt);
238 vsprintf (errbuf, fmt, ap);
239 va_end (ap);
240 fprintf (stderr, "%s: %s\n", pname, errbuf);
241 exit (1);
242}
243
244/* begin the X interface */
245
246char *lcasename = "xcopy";
247char *ucasename = "XCopy";
248
249Display *disp = NULL;
250Window ourwin = None;
acc02b82 251Atom compound_text_atom, targets_atom, timestamp_atom, atom_atom, integer_atom;
e1c1021f 252Atom multiple_atom, atom_pair_atom;
9acadc2b 253int screen, wwidth, wheight;
254
255Atom strtype = XA_STRING;
5e36d477 256Atom sel_atom = XA_PRIMARY;
3ffcf184 257Atom expected_type = (Atom)None;
258int expected_format = 8;
9acadc2b 259
6aa7b28f 260static const char *translate_atom(Display *disp, Atom atom)
261{
262 if (atom == None)
263 return "None";
264 else
265 return XGetAtomName(disp, atom);
266}
267
9acadc2b 268/*
269 * Returns TRUE if we need to enter an event loop, FALSE otherwise.
270 */
271int init_X(void) {
272 Window root;
273 int x = 0, y = 0, width = 512, height = 128;
274 int i, got = 0;
275 XWMHints wm_hints;
276 XSizeHints size_hints;
277 XClassHint class_hints;
278 XTextProperty textprop;
279 XGCValues gcv;
280
281 /* open the X display */
282 disp = XOpenDisplay (display);
283 if (!disp)
284 error ("unable to open display");
285
3ffcf184 286 targets_atom = XInternAtom(disp, "TARGETS", False);
acc02b82 287 timestamp_atom = XInternAtom(disp, "TIMESTAMP", False);
288 atom_atom = XInternAtom(disp, "ATOM", False);
e1c1021f 289 atom_pair_atom = XInternAtom(disp, "ATOM_PAIR", False);
290 multiple_atom = XInternAtom(disp, "MULTIPLE", False);
acc02b82 291 integer_atom = XInternAtom(disp, "INTEGER", False);
9acadc2b 292 if (mode == UTF8) {
293 strtype = XInternAtom(disp, "UTF8_STRING", False);
9acadc2b 294 } else if (mode == CTEXT) {
295 strtype = XInternAtom(disp, "COMPOUND_TEXT", False);
3ffcf184 296 } else if (mode == TARGETS) {
297 strtype = targets_atom;
acc02b82 298 expected_type = atom_atom;
3ffcf184 299 expected_format = 32;
300 } else if (mode == TIMESTAMP) {
acc02b82 301 strtype = timestamp_atom;
302 expected_type = integer_atom;
3ffcf184 303 expected_format = 32;
304 } else if (mode == CUSTOM) {
acc02b82 305 strtype = XInternAtom(disp, custom_seltype, True);
306 if (!strtype)
307 error ("atom '%s' does not exist on the server", custom_seltype);
3ffcf184 308 expected_format = 0;
9acadc2b 309 }
5e36d477 310 if (use_clipboard) {
311 sel_atom = XInternAtom(disp, "CLIPBOARD", False);
5e36d477 312 }
9acadc2b 313
314 /* get the screen and root-window */
315 screen = DefaultScreen (disp);
316 root = RootWindow (disp, screen);
317
318 x = y = 0;
319 width = height = 10; /* doesn't really matter */
320
321 /* actually create the window */
322 ourwin = XCreateSimpleWindow (disp, root, x, y, width, height,0,
323 BlackPixel(disp, screen),
324 WhitePixel(disp, screen));
325
326 /* resource class name */
327 class_hints.res_name = lcasename;
328 class_hints.res_class = ucasename;
329 XSetClassHint (disp, ourwin, &class_hints);
330
331 /* do selection fiddling */
332 if (reading) {
333 /*
3a3007a4 334 * We are reading the selection. Call XConvertSelection to
335 * request transmission of the selection data in the
336 * appropriate format; the X event loop will then wait to
337 * receive the data from the selection owner.
338 *
339 * If there is no selection owner, look in the cut buffer
340 * property on the root window.
9acadc2b 341 */
5e36d477 342 if (XGetSelectionOwner(disp, sel_atom) == None) {
9acadc2b 343 /* No primary selection, so use the cut buffer. */
6aa7b28f 344 if (verbose)
345 fprintf(stderr, "no selection owner: trying cut buffer\n");
3ffcf184 346 if (strtype == XA_STRING)
347 do_paste(DefaultRootWindow(disp), XA_CUT_BUFFER0, False);
9acadc2b 348 return False;
349 } else {
350 Atom sel_property = XInternAtom(disp, "VT_SELECTION", False);
6aa7b28f 351 if (verbose)
352 fprintf(stderr, "calling XConvertSelection: selection=%s"
353 " target=%s property=%s requestor=%08lx\n",
354 translate_atom(disp, sel_atom),
355 translate_atom(disp, strtype),
356 translate_atom(disp, sel_property),
357 ourwin);
5e36d477 358 XConvertSelection(disp, sel_atom, strtype,
9acadc2b 359 sel_property, ourwin, CurrentTime);
360 return True;
361 }
362 } else {
363 /*
364 * We are writing to the selection, so we establish
365 * ourselves as selection owner. Also place the data in
366 * CUT_BUFFER0, if it isn't of an exotic type (cut buffers
367 * can only take ordinary string data, it turns out).
368 */
5e36d477 369 XSetSelectionOwner (disp, sel_atom, ourwin, CurrentTime);
370 if (XGetSelectionOwner (disp, sel_atom) != ourwin)
b3d07da3 371 error ("unable to obtain primary X selection");
9acadc2b 372 compound_text_atom = XInternAtom(disp, "COMPOUND_TEXT", False);
373 if (strtype == XA_STRING) {
374 /*
375 * ICCCM-required cut buffer initialisation.
376 */
377 XChangeProperty(disp, root, XA_CUT_BUFFER0,
378 XA_STRING, 8, PropModeAppend, "", 0);
379 XChangeProperty(disp, root, XA_CUT_BUFFER1,
380 XA_STRING, 8, PropModeAppend, "", 0);
381 XChangeProperty(disp, root, XA_CUT_BUFFER2,
382 XA_STRING, 8, PropModeAppend, "", 0);
383 XChangeProperty(disp, root, XA_CUT_BUFFER3,
384 XA_STRING, 8, PropModeAppend, "", 0);
385 XChangeProperty(disp, root, XA_CUT_BUFFER4,
386 XA_STRING, 8, PropModeAppend, "", 0);
387 XChangeProperty(disp, root, XA_CUT_BUFFER5,
388 XA_STRING, 8, PropModeAppend, "", 0);
389 XChangeProperty(disp, root, XA_CUT_BUFFER6,
390 XA_STRING, 8, PropModeAppend, "", 0);
391 XChangeProperty(disp, root, XA_CUT_BUFFER7,
392 XA_STRING, 8, PropModeAppend, "", 0);
393 /*
394 * Rotate the cut buffers and add our text in CUT_BUFFER0.
395 */
396 XRotateBuffers(disp, 1);
397 XStoreBytes(disp, seltext, sellen);
398 }
399 return True;
400 }
401}
402
e1c1021f 403Atom convert_sel_inner(Window requestor, Atom target, Atom property) {
404 if (target == strtype) {
405 XChangeProperty (disp, requestor, property, strtype,
406 8, PropModeReplace, seltext, sellen);
407 return property;
408 } else if (target == compound_text_atom && convert_to_ctext) {
409 XTextProperty tp;
410 XmbTextListToTextProperty (disp, &seltext, 1,
411 XCompoundTextStyle, &tp);
412 XChangeProperty (disp, requestor, property, target,
413 tp.format, PropModeReplace,
414 tp.value, tp.nitems);
415 return property;
416 } else if (target == targets_atom) {
417 Atom targets[16];
418 int len = 0;
419 targets[len++] = timestamp_atom;
420 targets[len++] = targets_atom;
421 targets[len++] = multiple_atom;
422 targets[len++] = strtype;
423 if (strtype != compound_text_atom && convert_to_ctext)
424 targets[len++] = compound_text_atom;
425 XChangeProperty (disp, requestor, property,
426 atom_atom, 32, PropModeReplace,
427 (unsigned char *)targets, len);
428 return property;
429 } else if (target == timestamp_atom) {
430 Time rettime = CurrentTime;
431 XChangeProperty (disp, requestor, property,
432 integer_atom, 32, PropModeReplace,
433 (unsigned char *)&rettime, 1);
434 return property;
435 } else {
436 return None;
437 }
438}
439
440Atom convert_sel_outer(Window requestor, Atom target, Atom property) {
441 if (target == multiple_atom) {
442 /*
443 * Support for the MULTIPLE selection type, since it's
444 * specified as required in the ICCCM. Completely
445 * untested, though, because I have no idea of what X
446 * application might implement it for me to test against.
447 */
448
449 int size = SELDELTA;
450 Atom actual_type;
451 int actual_format, i;
452 long nitems, bytes_after, nread;
453 unsigned char *data;
454 Atom *adata;
455
b06414b8 456 if (property == (Atom)None)
457 return None; /* ICCCM says this isn't allowed */
458
e1c1021f 459 /*
460 * Fetch the requestor's window property giving a list of
461 * selection requests.
462 */
463 while (XGetWindowProperty(disp, requestor, property, 0, size,
464 False, AnyPropertyType, &actual_type,
465 &actual_format, &nitems, &bytes_after,
466 (unsigned char **) &data) == Success &&
467 nitems * (actual_format / 8) == size) {
468 XFree(data);
469 size *= 3 / 2;
470 }
471
472 if (actual_type != atom_pair_atom || actual_format != 32) {
473 XFree(data);
474 return None;
475 }
476
477 adata = (Atom *)data;
478
479 for (i = 0; i+1 < nitems; i += 2) {
b06414b8 480 if (adata[i+1] != (Atom)None) /* ICCCM says this isn't allowed */
481 adata[i+1] = convert_sel_inner(requestor, adata[i],
482 adata[i+1]);
e1c1021f 483 }
484
485 XChangeProperty (disp, requestor, property,
486 atom_pair_atom, 32, PropModeReplace,
487 data, nitems);
488
489 XFree(data);
490
491 return property;
492 } else {
b06414b8 493 if (property == (Atom)None)
494 property = target; /* ICCCM says this is a sensible default */
e1c1021f 495 return convert_sel_inner(requestor, target, property);
496 }
497}
498
9acadc2b 499void run_X(void) {
500 XEvent ev, e2;
501
502 while (1) {
503 XNextEvent (disp, &ev);
504 if (reading) {
505 switch (ev.type) {
506 case SelectionNotify:
6aa7b28f 507 if (verbose)
508 fprintf(stderr, "got SelectionNotify: requestor=%08lx "
509 "selection=%s target=%s property=%s\n",
510 ev.xselection.requestor,
511 translate_atom(disp, ev.xselection.selection),
512 translate_atom(disp, ev.xselection.target),
513 translate_atom(disp, ev.xselection.property));
514
9acadc2b 515 if (ev.xselection.property != None)
516 do_paste(ev.xselection.requestor,
517 ev.xselection.property, True);
518 return;
519 }
520 } else {
521 switch (ev.type) {
522 case SelectionClear:
523 /* Selection has been cleared by another app. */
524 return;
525 case SelectionRequest:
526 e2.xselection.type = SelectionNotify;
527 e2.xselection.requestor = ev.xselectionrequest.requestor;
528 e2.xselection.selection = ev.xselectionrequest.selection;
529 e2.xselection.target = ev.xselectionrequest.target;
530 e2.xselection.time = ev.xselectionrequest.time;
e1c1021f 531 e2.xselection.property =
532 convert_sel_outer(ev.xselectionrequest.requestor,
533 ev.xselectionrequest.target,
534 ev.xselectionrequest.property);
535 XSendEvent (disp, ev.xselectionrequest.requestor,
536 False, 0, &e2);
9acadc2b 537 }
538 }
539 }
540}
541
542void done_X(void) {
543 int i;
544
545 if (ourwin != None)
546 XDestroyWindow (disp, ourwin);
547 if (disp)
548 XCloseDisplay (disp);
549}
550
551void do_paste(Window window, Atom property, int Delete) {
552 Atom actual_type;
553 int actual_format, i;
554 long nitems, bytes_after, nread;
555 unsigned char *data;
556
557 nread = 0;
558 while (XGetWindowProperty(disp, window, property, nread / 4, SELDELTA,
559 Delete, AnyPropertyType, &actual_type,
560 &actual_format, &nitems, &bytes_after,
561 (unsigned char **) &data) == Success) {
6aa7b28f 562 if (verbose)
563 fprintf(stderr, "got %ld items of %d-byte data, type=%s;"
564 " %ld to go\n", nitems, actual_format,
565 translate_atom(disp, actual_type), bytes_after);
566
3ffcf184 567 if (nitems > 0) {
5340832d 568 /*
569 * We expect all returned chunks of data to be
570 * multiples of 4 bytes (because we can only request
571 * the subsequent starting offset in 4-byte
572 * increments). Of course you can store an odd number
573 * of bytes in a selection, so this can't be the case
574 * every time XGetWindowProperty returns; but it
575 * should be the case every time it returns _and there
576 * is more data to come_.
577 *
578 * Hence, whenever XGetWindowProperty returns, we
579 * verify that the size of the data returned _last_
580 * time was divisible by 4.
581 */
582 if ((nread & 3) != 0) {
583 error("unexpected data size: %d read (not a multiple"
b3d07da3 584 " of 4), but more to come", nread);
5340832d 585 }
586
3ffcf184 587 if (expected_type != (Atom)None && actual_type != expected_type) {
6aa7b28f 588 const char *expout = translate_atom(disp, expected_type);
589 const char *gotout = translate_atom(disp, actual_type);
b3d07da3 590 error("unexpected data type: expected %s, got %s",
3ffcf184 591 expout, gotout);
592 }
593 if (expected_format && expected_format != actual_format) {
b3d07da3 594 error("unexpected data format: expected %d-bit, got %d-bit",
3ffcf184 595 expected_format, actual_format);
596 }
597 }
598
599 if (nitems > 0) {
600 if (mode == TARGETS) {
601 assert(actual_format == 32);
602 int i;
603 for (i = 0; i < nitems; i++) {
604 Atom x = ((Atom *)data)[i];
6aa7b28f 605 printf("%s\n", translate_atom(disp, x));
3ffcf184 606 }
607 } else if (mode == TIMESTAMP) {
608 assert(actual_format == 32);
609 Time x = ((Time *)data)[0];
610 printf("%lu\n", (unsigned long)x);
611 } else {
612 fwrite(data, actual_format / 8, nitems, stdout);
613 nread += nitems * actual_format / 8;
614 }
9acadc2b 615 }
616 XFree(data);
3ffcf184 617 if (nitems == 0)
9acadc2b 618 break;
619 }
620}