Some exciting new X diagnostic options: -t to return a list of
[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;
9acadc2b 34
35/* selection data */
36char *seltext;
37int sellen, selsize;
38#define SELDELTA 16384
39
40/* functional parameters */
41int reading; /* read instead of writing? */
42int convert_to_ctext = True; /* Xmb convert to compound text? */
43
da0f8522 44const char usagemsg[] =
45 "usage: xcopy [ -r ] [ -u | -c ] [ -C ]\n"
3ffcf184 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"
c52f9fb9 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"
da0f8522 58 ;
59
60void usage(void) {
61 fputs(usagemsg, stdout);
62}
63
64const char licencemsg[] =
65 "xcopy is copyright 2001-2004 Simon Tatham.\n"
66 "\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"
74 "\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"
77 "\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"
85 "SOFTWARE.\n"
86 ;
87
88void licence(void) {
89 fputs(licencemsg, stdout);
90}
91
92void version(void) {
93#define SVN_REV "$Revision$"
94 char rev[sizeof(SVN_REV)];
95 char *p, *q;
96
97 strcpy(rev, SVN_REV);
98
99 for (p = rev; *p && *p != ':'; p++);
100 if (*p) {
101 p++;
102 while (*p && isspace(*p)) p++;
103 for (q = p; *q && *q != '$'; q++);
104 if (*q) *q = '\0';
105 printf("xcopy revision %s\n", p);
106 } else {
107 printf("xcopy: unknown version\n");
108 }
109}
110
9acadc2b 111int main(int ac, char **av) {
112 int n;
113 int eventloop;
114
115 pname = *av;
116
117 /* parse the command line arguments */
3ffcf184 118 while (--ac > 0) {
9acadc2b 119 char *p = *++av;
120
121 if (!strcmp(p, "-display") || !strcmp(p, "-disp")) {
122 if (!av[1])
123 error ("option `%s' expects a parameter", p);
124 display = *++av, --ac;
125 } else if (!strcmp(p, "-r")) {
126 reading = True;
127 } else if (!strcmp(p, "-u")) {
128 mode = UTF8;
129 } else if (!strcmp(p, "-c")) {
130 mode = CTEXT;
131 } else if (!strcmp(p, "-C")) {
132 convert_to_ctext = False;
5e36d477 133 } else if (!strcmp(p, "-b")) {
134 use_clipboard = True;
3ffcf184 135 } else if (!strcmp(p, "-t")) {
136 mode = TARGETS;
137 } else if (!strcmp(p, "-T")) {
138 mode = TIMESTAMP;
139 } else if (!strcmp(p, "-a")) {
140 if (--ac > 0)
141 custom_seltype = *++av;
142 else
143 error ("expected an argument to `-a'");
144 mode = CUSTOM;
da0f8522 145 } else if (!strcmp(p, "--help")) {
146 usage();
147 return 0;
148 } else if (!strcmp(p, "--version")) {
149 version();
150 return 0;
151 } else if (!strcmp(p, "--licence") || !strcmp(p, "--license")) {
152 licence();
153 return 0;
9acadc2b 154 } else if (*p=='-') {
155 error ("unrecognised option `%s'", p);
156 } else {
157 error ("no parameters required");
158 }
159 }
160
161 if (!reading) {
3ffcf184 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"));
167 }
168 }
169
170 if (!reading) {
9acadc2b 171 seltext = malloc(SELDELTA);
172 if (!seltext)
173 error ("out of memory");
174 selsize = SELDELTA;
175 sellen = 0;
176 do {
177 n = fread(seltext+sellen, 1, selsize-sellen, stdin);
178 sellen += n;
179 if (sellen >= selsize) {
180 seltext = realloc(seltext, selsize += SELDELTA);
181 if (!seltext)
182 error ("out of memory");
183 }
184 } while (n > 0);
185 if (sellen == selsize) {
186 seltext = realloc(seltext, selsize += SELDELTA);
187 if (!seltext)
188 error ("out of memory");
189 }
190 seltext[sellen] = '\0';
191 }
192
193 eventloop = init_X();
194 if (!reading) {
195 /*
196 * If we are writing the selection, we must go into the
197 * background now.
198 */
199 int pid = fork();
200 if (pid < 0) {
201 error("unable to fork: %s", strerror(errno));
202 } else if (pid > 0) {
203 /*
204 * we are the parent; just exit
205 */
206 return 0;
207 }
208 /*
209 * we are the child
210 */
211 close(0);
212 close(1);
213 close(2);
214 chdir("/");
215 }
216 if (eventloop)
217 run_X();
218 done_X();
219 return 0;
220}
221
222/* handle errors */
223
224void error (char *fmt, ...) {
225 va_list ap;
226 char errbuf[200];
227
228 done_X();
229 va_start (ap, fmt);
230 vsprintf (errbuf, fmt, ap);
231 va_end (ap);
232 fprintf (stderr, "%s: %s\n", pname, errbuf);
233 exit (1);
234}
235
236/* begin the X interface */
237
238char *lcasename = "xcopy";
239char *ucasename = "XCopy";
240
241Display *disp = NULL;
242Window ourwin = None;
243Atom compound_text_atom, targets_atom;
244int screen, wwidth, wheight;
245
246Atom strtype = XA_STRING;
5e36d477 247Atom sel_atom = XA_PRIMARY;
3ffcf184 248Atom expected_type = (Atom)None;
249int expected_format = 8;
9acadc2b 250
251/*
252 * Returns TRUE if we need to enter an event loop, FALSE otherwise.
253 */
254int init_X(void) {
255 Window root;
256 int x = 0, y = 0, width = 512, height = 128;
257 int i, got = 0;
258 XWMHints wm_hints;
259 XSizeHints size_hints;
260 XClassHint class_hints;
261 XTextProperty textprop;
262 XGCValues gcv;
263
264 /* open the X display */
265 disp = XOpenDisplay (display);
266 if (!disp)
267 error ("unable to open display");
268
3ffcf184 269 targets_atom = XInternAtom(disp, "TARGETS", False);
270 if (!targets_atom)
271 error ("unable to get TARGETS property");
9acadc2b 272 if (mode == UTF8) {
273 strtype = XInternAtom(disp, "UTF8_STRING", False);
274 if (!strtype)
275 error ("unable to get UTF8_STRING property");
276 } else if (mode == CTEXT) {
277 strtype = XInternAtom(disp, "COMPOUND_TEXT", False);
278 if (!strtype)
279 error ("unable to get COMPOUND_TEXT property");
3ffcf184 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);
289 expected_format = 0;
9acadc2b 290 }
5e36d477 291 if (use_clipboard) {
292 sel_atom = XInternAtom(disp, "CLIPBOARD", False);
293 if (!sel_atom)
294 error ("unable to get CLIPBOARD property");
295 }
9acadc2b 296
297 /* get the screen and root-window */
298 screen = DefaultScreen (disp);
299 root = RootWindow (disp, screen);
300
301 x = y = 0;
302 width = height = 10; /* doesn't really matter */
303
304 /* actually create the window */
305 ourwin = XCreateSimpleWindow (disp, root, x, y, width, height,0,
306 BlackPixel(disp, screen),
307 WhitePixel(disp, screen));
308
309 /* resource class name */
310 class_hints.res_name = lcasename;
311 class_hints.res_class = ucasename;
312 XSetClassHint (disp, ourwin, &class_hints);
313
314 /* do selection fiddling */
315 if (reading) {
316 /*
317 * We are reading the selection, so we must FIXME.
318 */
5e36d477 319 if (XGetSelectionOwner(disp, sel_atom) == None) {
9acadc2b 320 /* No primary selection, so use the cut buffer. */
3ffcf184 321 if (strtype == XA_STRING)
322 do_paste(DefaultRootWindow(disp), XA_CUT_BUFFER0, False);
9acadc2b 323 return False;
324 } else {
325 Atom sel_property = XInternAtom(disp, "VT_SELECTION", False);
5e36d477 326 XConvertSelection(disp, sel_atom, strtype,
9acadc2b 327 sel_property, ourwin, CurrentTime);
328 return True;
329 }
330 } else {
331 /*
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).
336 */
5e36d477 337 XSetSelectionOwner (disp, sel_atom, ourwin, CurrentTime);
338 if (XGetSelectionOwner (disp, sel_atom) != ourwin)
9acadc2b 339 error ("unable to obtain primary X selection\n");
340 compound_text_atom = XInternAtom(disp, "COMPOUND_TEXT", False);
341 if (strtype == XA_STRING) {
342 /*
343 * ICCCM-required cut buffer initialisation.
344 */
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);
361 /*
362 * Rotate the cut buffers and add our text in CUT_BUFFER0.
363 */
364 XRotateBuffers(disp, 1);
365 XStoreBytes(disp, seltext, sellen);
366 }
367 return True;
368 }
369}
370
371void run_X(void) {
372 XEvent ev, e2;
373
374 while (1) {
375 XNextEvent (disp, &ev);
376 if (reading) {
377 switch (ev.type) {
378 case SelectionNotify:
379 if (ev.xselection.property != None)
380 do_paste(ev.xselection.requestor,
381 ev.xselection.property, True);
382 return;
383 }
384 } else {
385 switch (ev.type) {
386 case SelectionClear:
387 /* Selection has been cleared by another app. */
388 return;
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 &&
401 convert_to_ctext) {
402 XTextProperty tp;
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) {
412 Atom targets[2];
413 int len = 0;
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,
420 32, PropModeReplace,
421 (unsigned char *)targets, len);
422 } else {
423 e2.xselection.property = None;
424 }
425 XSendEvent (disp, ev.xselectionrequest.requestor, False, 0, &e2);
426 }
427 }
428 }
429}
430
431void done_X(void) {
432 int i;
433
434 if (ourwin != None)
435 XDestroyWindow (disp, ourwin);
436 if (disp)
437 XCloseDisplay (disp);
438}
439
440void do_paste(Window window, Atom property, int Delete) {
441 Atom actual_type;
442 int actual_format, i;
443 long nitems, bytes_after, nread;
444 unsigned char *data;
445
446 nread = 0;
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) {
451 /*
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_.
459 *
460 * Hence, whenever XGetWindowProperty returns, we verify
461 * that the size of the data returned _last_ time was
462 * divisible by 4.
463 */
464 if (nitems > 0)
465 assert((nread & 3) == 0);
466
3ffcf184 467 if (nitems > 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",
473 expout, gotout);
474 }
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);
478 }
479 }
480
481 if (nitems > 0) {
482 if (mode == TARGETS) {
483 assert(actual_format == 32);
484 int i;
485 for (i = 0; i < nitems; i++) {
486 Atom x = ((Atom *)data)[i];
487 printf("%s\n", XGetAtomName(disp, x));
488 }
489 } else if (mode == TIMESTAMP) {
490 assert(actual_format == 32);
491 Time x = ((Time *)data)[0];
492 printf("%lu\n", (unsigned long)x);
493 } else {
494 fwrite(data, actual_format / 8, nitems, stdout);
495 nread += nitems * actual_format / 8;
496 }
9acadc2b 497 }
498 XFree(data);
3ffcf184 499 if (nitems == 0)
9acadc2b 500 break;
501 }
502}