Stop using physical tabs in Python. (I was goaded into doing this by
[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? */
44
da0f8522 45const char usagemsg[] =
46 "usage: xcopy [ -r ] [ -u | -c ] [ -C ]\n"
3ffcf184 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"
acc02b82 56 " -F do not fork in write mode\n"
c52f9fb9 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"
da0f8522 60 ;
61
62void usage(void) {
63 fputs(usagemsg, stdout);
64}
65
66const char licencemsg[] =
b3d07da3 67 "xcopy is copyright 2001-2004,2008 Simon Tatham.\n"
da0f8522 68 "\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"
76 "\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"
79 "\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"
87 "SOFTWARE.\n"
88 ;
89
90void licence(void) {
91 fputs(licencemsg, stdout);
92}
93
94void version(void) {
95#define SVN_REV "$Revision$"
96 char rev[sizeof(SVN_REV)];
97 char *p, *q;
98
99 strcpy(rev, SVN_REV);
100
101 for (p = rev; *p && *p != ':'; p++);
102 if (*p) {
103 p++;
104 while (*p && isspace(*p)) p++;
105 for (q = p; *q && *q != '$'; q++);
106 if (*q) *q = '\0';
107 printf("xcopy revision %s\n", p);
108 } else {
109 printf("xcopy: unknown version\n");
110 }
111}
112
9acadc2b 113int main(int ac, char **av) {
114 int n;
115 int eventloop;
116
117 pname = *av;
118
119 /* parse the command line arguments */
3ffcf184 120 while (--ac > 0) {
9acadc2b 121 char *p = *++av;
122
123 if (!strcmp(p, "-display") || !strcmp(p, "-disp")) {
124 if (!av[1])
125 error ("option `%s' expects a parameter", p);
126 display = *++av, --ac;
127 } else if (!strcmp(p, "-r")) {
128 reading = True;
129 } else if (!strcmp(p, "-u")) {
130 mode = UTF8;
131 } else if (!strcmp(p, "-c")) {
132 mode = CTEXT;
133 } else if (!strcmp(p, "-C")) {
134 convert_to_ctext = False;
5e36d477 135 } else if (!strcmp(p, "-b")) {
136 use_clipboard = True;
3ffcf184 137 } else if (!strcmp(p, "-t")) {
138 mode = TARGETS;
139 } else if (!strcmp(p, "-T")) {
140 mode = TIMESTAMP;
141 } else if (!strcmp(p, "-a")) {
142 if (--ac > 0)
143 custom_seltype = *++av;
144 else
145 error ("expected an argument to `-a'");
146 mode = CUSTOM;
acc02b82 147 } else if (!strcmp(p, "-F")) {
148 fork_when_writing = False;
da0f8522 149 } else if (!strcmp(p, "--help")) {
150 usage();
151 return 0;
152 } else if (!strcmp(p, "--version")) {
153 version();
154 return 0;
155 } else if (!strcmp(p, "--licence") || !strcmp(p, "--license")) {
156 licence();
157 return 0;
9acadc2b 158 } else if (*p=='-') {
159 error ("unrecognised option `%s'", p);
160 } else {
161 error ("no parameters required");
162 }
163 }
164
165 if (!reading) {
3ffcf184 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"));
171 }
172 }
173
174 if (!reading) {
9acadc2b 175 seltext = malloc(SELDELTA);
176 if (!seltext)
177 error ("out of memory");
178 selsize = SELDELTA;
179 sellen = 0;
180 do {
181 n = fread(seltext+sellen, 1, selsize-sellen, stdin);
182 sellen += n;
183 if (sellen >= selsize) {
184 seltext = realloc(seltext, selsize += SELDELTA);
185 if (!seltext)
186 error ("out of memory");
187 }
188 } while (n > 0);
189 if (sellen == selsize) {
190 seltext = realloc(seltext, selsize += SELDELTA);
191 if (!seltext)
192 error ("out of memory");
193 }
194 seltext[sellen] = '\0';
195 }
196
197 eventloop = init_X();
acc02b82 198 if (!reading && fork_when_writing) {
9acadc2b 199 /*
200 * If we are writing the selection, we must go into the
201 * background now.
202 */
203 int pid = fork();
204 if (pid < 0) {
205 error("unable to fork: %s", strerror(errno));
206 } else if (pid > 0) {
207 /*
208 * we are the parent; just exit
209 */
210 return 0;
211 }
212 /*
213 * we are the child
214 */
215 close(0);
216 close(1);
217 close(2);
218 chdir("/");
219 }
220 if (eventloop)
221 run_X();
222 done_X();
223 return 0;
224}
225
226/* handle errors */
227
228void error (char *fmt, ...) {
229 va_list ap;
230 char errbuf[200];
231
232 done_X();
233 va_start (ap, fmt);
234 vsprintf (errbuf, fmt, ap);
235 va_end (ap);
236 fprintf (stderr, "%s: %s\n", pname, errbuf);
237 exit (1);
238}
239
240/* begin the X interface */
241
242char *lcasename = "xcopy";
243char *ucasename = "XCopy";
244
245Display *disp = NULL;
246Window ourwin = None;
acc02b82 247Atom compound_text_atom, targets_atom, timestamp_atom, atom_atom, integer_atom;
e1c1021f 248Atom multiple_atom, atom_pair_atom;
9acadc2b 249int screen, wwidth, wheight;
250
251Atom strtype = XA_STRING;
5e36d477 252Atom sel_atom = XA_PRIMARY;
3ffcf184 253Atom expected_type = (Atom)None;
254int expected_format = 8;
9acadc2b 255
256/*
257 * Returns TRUE if we need to enter an event loop, FALSE otherwise.
258 */
259int init_X(void) {
260 Window root;
261 int x = 0, y = 0, width = 512, height = 128;
262 int i, got = 0;
263 XWMHints wm_hints;
264 XSizeHints size_hints;
265 XClassHint class_hints;
266 XTextProperty textprop;
267 XGCValues gcv;
268
269 /* open the X display */
270 disp = XOpenDisplay (display);
271 if (!disp)
272 error ("unable to open display");
273
3ffcf184 274 targets_atom = XInternAtom(disp, "TARGETS", False);
acc02b82 275 timestamp_atom = XInternAtom(disp, "TIMESTAMP", False);
276 atom_atom = XInternAtom(disp, "ATOM", False);
e1c1021f 277 atom_pair_atom = XInternAtom(disp, "ATOM_PAIR", False);
278 multiple_atom = XInternAtom(disp, "MULTIPLE", False);
acc02b82 279 integer_atom = XInternAtom(disp, "INTEGER", False);
9acadc2b 280 if (mode == UTF8) {
281 strtype = XInternAtom(disp, "UTF8_STRING", False);
9acadc2b 282 } else if (mode == CTEXT) {
283 strtype = XInternAtom(disp, "COMPOUND_TEXT", False);
3ffcf184 284 } else if (mode == TARGETS) {
285 strtype = targets_atom;
acc02b82 286 expected_type = atom_atom;
3ffcf184 287 expected_format = 32;
288 } else if (mode == TIMESTAMP) {
acc02b82 289 strtype = timestamp_atom;
290 expected_type = integer_atom;
3ffcf184 291 expected_format = 32;
292 } else if (mode == CUSTOM) {
acc02b82 293 strtype = XInternAtom(disp, custom_seltype, True);
294 if (!strtype)
295 error ("atom '%s' does not exist on the server", custom_seltype);
3ffcf184 296 expected_format = 0;
9acadc2b 297 }
5e36d477 298 if (use_clipboard) {
299 sel_atom = XInternAtom(disp, "CLIPBOARD", False);
5e36d477 300 }
9acadc2b 301
302 /* get the screen and root-window */
303 screen = DefaultScreen (disp);
304 root = RootWindow (disp, screen);
305
306 x = y = 0;
307 width = height = 10; /* doesn't really matter */
308
309 /* actually create the window */
310 ourwin = XCreateSimpleWindow (disp, root, x, y, width, height,0,
311 BlackPixel(disp, screen),
312 WhitePixel(disp, screen));
313
314 /* resource class name */
315 class_hints.res_name = lcasename;
316 class_hints.res_class = ucasename;
317 XSetClassHint (disp, ourwin, &class_hints);
318
319 /* do selection fiddling */
320 if (reading) {
321 /*
322 * We are reading the selection, so we must FIXME.
323 */
5e36d477 324 if (XGetSelectionOwner(disp, sel_atom) == None) {
9acadc2b 325 /* No primary selection, so use the cut buffer. */
3ffcf184 326 if (strtype == XA_STRING)
327 do_paste(DefaultRootWindow(disp), XA_CUT_BUFFER0, False);
9acadc2b 328 return False;
329 } else {
330 Atom sel_property = XInternAtom(disp, "VT_SELECTION", False);
5e36d477 331 XConvertSelection(disp, sel_atom, strtype,
9acadc2b 332 sel_property, ourwin, CurrentTime);
333 return True;
334 }
335 } else {
336 /*
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).
341 */
5e36d477 342 XSetSelectionOwner (disp, sel_atom, ourwin, CurrentTime);
343 if (XGetSelectionOwner (disp, sel_atom) != ourwin)
b3d07da3 344 error ("unable to obtain primary X selection");
9acadc2b 345 compound_text_atom = XInternAtom(disp, "COMPOUND_TEXT", False);
346 if (strtype == XA_STRING) {
347 /*
348 * ICCCM-required cut buffer initialisation.
349 */
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);
366 /*
367 * Rotate the cut buffers and add our text in CUT_BUFFER0.
368 */
369 XRotateBuffers(disp, 1);
370 XStoreBytes(disp, seltext, sellen);
371 }
372 return True;
373 }
374}
375
e1c1021f 376Atom convert_sel_inner(Window requestor, Atom target, Atom property) {
377 if (target == strtype) {
378 XChangeProperty (disp, requestor, property, strtype,
379 8, PropModeReplace, seltext, sellen);
380 return property;
381 } else if (target == compound_text_atom && convert_to_ctext) {
382 XTextProperty tp;
383 XmbTextListToTextProperty (disp, &seltext, 1,
384 XCompoundTextStyle, &tp);
385 XChangeProperty (disp, requestor, property, target,
386 tp.format, PropModeReplace,
387 tp.value, tp.nitems);
388 return property;
389 } else if (target == targets_atom) {
390 Atom targets[16];
391 int len = 0;
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);
401 return property;
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);
407 return property;
408 } else {
409 return None;
410 }
411}
412
413Atom convert_sel_outer(Window requestor, Atom target, Atom property) {
414 if (target == multiple_atom) {
415 /*
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.
420 */
421
422 int size = SELDELTA;
423 Atom actual_type;
424 int actual_format, i;
425 long nitems, bytes_after, nread;
426 unsigned char *data;
427 Atom *adata;
428
b06414b8 429 if (property == (Atom)None)
430 return None; /* ICCCM says this isn't allowed */
431
e1c1021f 432 /*
433 * Fetch the requestor's window property giving a list of
434 * selection requests.
435 */
436 while (XGetWindowProperty(disp, requestor, property, 0, size,
437 False, AnyPropertyType, &actual_type,
438 &actual_format, &nitems, &bytes_after,
439 (unsigned char **) &data) == Success &&
440 nitems * (actual_format / 8) == size) {
441 XFree(data);
442 size *= 3 / 2;
443 }
444
445 if (actual_type != atom_pair_atom || actual_format != 32) {
446 XFree(data);
447 return None;
448 }
449
450 adata = (Atom *)data;
451
452 for (i = 0; i+1 < nitems; i += 2) {
b06414b8 453 if (adata[i+1] != (Atom)None) /* ICCCM says this isn't allowed */
454 adata[i+1] = convert_sel_inner(requestor, adata[i],
455 adata[i+1]);
e1c1021f 456 }
457
458 XChangeProperty (disp, requestor, property,
459 atom_pair_atom, 32, PropModeReplace,
460 data, nitems);
461
462 XFree(data);
463
464 return property;
465 } else {
b06414b8 466 if (property == (Atom)None)
467 property = target; /* ICCCM says this is a sensible default */
e1c1021f 468 return convert_sel_inner(requestor, target, property);
469 }
470}
471
9acadc2b 472void run_X(void) {
473 XEvent ev, e2;
474
475 while (1) {
476 XNextEvent (disp, &ev);
477 if (reading) {
478 switch (ev.type) {
479 case SelectionNotify:
480 if (ev.xselection.property != None)
481 do_paste(ev.xselection.requestor,
482 ev.xselection.property, True);
483 return;
484 }
485 } else {
486 switch (ev.type) {
487 case SelectionClear:
488 /* Selection has been cleared by another app. */
489 return;
490 case SelectionRequest:
491 e2.xselection.type = SelectionNotify;
492 e2.xselection.requestor = ev.xselectionrequest.requestor;
493 e2.xselection.selection = ev.xselectionrequest.selection;
494 e2.xselection.target = ev.xselectionrequest.target;
495 e2.xselection.time = ev.xselectionrequest.time;
e1c1021f 496 e2.xselection.property =
497 convert_sel_outer(ev.xselectionrequest.requestor,
498 ev.xselectionrequest.target,
499 ev.xselectionrequest.property);
500 XSendEvent (disp, ev.xselectionrequest.requestor,
501 False, 0, &e2);
9acadc2b 502 }
503 }
504 }
505}
506
507void done_X(void) {
508 int i;
509
510 if (ourwin != None)
511 XDestroyWindow (disp, ourwin);
512 if (disp)
513 XCloseDisplay (disp);
514}
515
516void do_paste(Window window, Atom property, int Delete) {
517 Atom actual_type;
518 int actual_format, i;
519 long nitems, bytes_after, nread;
520 unsigned char *data;
521
522 nread = 0;
523 while (XGetWindowProperty(disp, window, property, nread / 4, SELDELTA,
524 Delete, AnyPropertyType, &actual_type,
525 &actual_format, &nitems, &bytes_after,
526 (unsigned char **) &data) == Success) {
3ffcf184 527 if (nitems > 0) {
5340832d 528 /*
529 * We expect all returned chunks of data to be
530 * multiples of 4 bytes (because we can only request
531 * the subsequent starting offset in 4-byte
532 * increments). Of course you can store an odd number
533 * of bytes in a selection, so this can't be the case
534 * every time XGetWindowProperty returns; but it
535 * should be the case every time it returns _and there
536 * is more data to come_.
537 *
538 * Hence, whenever XGetWindowProperty returns, we
539 * verify that the size of the data returned _last_
540 * time was divisible by 4.
541 */
542 if ((nread & 3) != 0) {
543 error("unexpected data size: %d read (not a multiple"
b3d07da3 544 " of 4), but more to come", nread);
5340832d 545 }
546
3ffcf184 547 if (expected_type != (Atom)None && actual_type != expected_type) {
548 char *expout = XGetAtomName(disp, expected_type);
549 char *gotout = (actual_type == (Atom)None ? "None" :
550 XGetAtomName(disp, actual_type));
b3d07da3 551 error("unexpected data type: expected %s, got %s",
3ffcf184 552 expout, gotout);
553 }
554 if (expected_format && expected_format != actual_format) {
b3d07da3 555 error("unexpected data format: expected %d-bit, got %d-bit",
3ffcf184 556 expected_format, actual_format);
557 }
558 }
559
560 if (nitems > 0) {
561 if (mode == TARGETS) {
562 assert(actual_format == 32);
563 int i;
564 for (i = 0; i < nitems; i++) {
565 Atom x = ((Atom *)data)[i];
566 printf("%s\n", XGetAtomName(disp, x));
567 }
568 } else if (mode == TIMESTAMP) {
569 assert(actual_format == 32);
570 Time x = ((Time *)data)[0];
571 printf("%lu\n", (unsigned long)x);
572 } else {
573 fwrite(data, actual_format / 8, nitems, stdout);
574 nread += nitems * actual_format / 8;
575 }
9acadc2b 576 }
577 XFree(data);
3ffcf184 578 if (nitems == 0)
9acadc2b 579 break;
580 }
581}