Aha! Reading the ICCCM more closely, the return value for the
[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[] =
67 "xcopy is copyright 2001-2004 Simon Tatham.\n"
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;
9acadc2b 248int screen, wwidth, wheight;
249
250Atom strtype = XA_STRING;
5e36d477 251Atom sel_atom = XA_PRIMARY;
3ffcf184 252Atom expected_type = (Atom)None;
253int expected_format = 8;
9acadc2b 254
255/*
256 * Returns TRUE if we need to enter an event loop, FALSE otherwise.
257 */
258int init_X(void) {
259 Window root;
260 int x = 0, y = 0, width = 512, height = 128;
261 int i, got = 0;
262 XWMHints wm_hints;
263 XSizeHints size_hints;
264 XClassHint class_hints;
265 XTextProperty textprop;
266 XGCValues gcv;
267
268 /* open the X display */
269 disp = XOpenDisplay (display);
270 if (!disp)
271 error ("unable to open display");
272
3ffcf184 273 targets_atom = XInternAtom(disp, "TARGETS", False);
acc02b82 274 timestamp_atom = XInternAtom(disp, "TIMESTAMP", False);
275 atom_atom = XInternAtom(disp, "ATOM", False);
276 integer_atom = XInternAtom(disp, "INTEGER", False);
9acadc2b 277 if (mode == UTF8) {
278 strtype = XInternAtom(disp, "UTF8_STRING", False);
9acadc2b 279 } else if (mode == CTEXT) {
280 strtype = XInternAtom(disp, "COMPOUND_TEXT", False);
3ffcf184 281 } else if (mode == TARGETS) {
282 strtype = targets_atom;
acc02b82 283 expected_type = atom_atom;
3ffcf184 284 expected_format = 32;
285 } else if (mode == TIMESTAMP) {
acc02b82 286 strtype = timestamp_atom;
287 expected_type = integer_atom;
3ffcf184 288 expected_format = 32;
289 } else if (mode == CUSTOM) {
acc02b82 290 strtype = XInternAtom(disp, custom_seltype, True);
291 if (!strtype)
292 error ("atom '%s' does not exist on the server", custom_seltype);
3ffcf184 293 expected_format = 0;
9acadc2b 294 }
5e36d477 295 if (use_clipboard) {
296 sel_atom = XInternAtom(disp, "CLIPBOARD", False);
5e36d477 297 }
9acadc2b 298
299 /* get the screen and root-window */
300 screen = DefaultScreen (disp);
301 root = RootWindow (disp, screen);
302
303 x = y = 0;
304 width = height = 10; /* doesn't really matter */
305
306 /* actually create the window */
307 ourwin = XCreateSimpleWindow (disp, root, x, y, width, height,0,
308 BlackPixel(disp, screen),
309 WhitePixel(disp, screen));
310
311 /* resource class name */
312 class_hints.res_name = lcasename;
313 class_hints.res_class = ucasename;
314 XSetClassHint (disp, ourwin, &class_hints);
315
316 /* do selection fiddling */
317 if (reading) {
318 /*
319 * We are reading the selection, so we must FIXME.
320 */
5e36d477 321 if (XGetSelectionOwner(disp, sel_atom) == None) {
9acadc2b 322 /* No primary selection, so use the cut buffer. */
3ffcf184 323 if (strtype == XA_STRING)
324 do_paste(DefaultRootWindow(disp), XA_CUT_BUFFER0, False);
9acadc2b 325 return False;
326 } else {
327 Atom sel_property = XInternAtom(disp, "VT_SELECTION", False);
5e36d477 328 XConvertSelection(disp, sel_atom, strtype,
9acadc2b 329 sel_property, ourwin, CurrentTime);
330 return True;
331 }
332 } else {
333 /*
334 * We are writing to the selection, so we establish
335 * ourselves as selection owner. Also place the data in
336 * CUT_BUFFER0, if it isn't of an exotic type (cut buffers
337 * can only take ordinary string data, it turns out).
338 */
5e36d477 339 XSetSelectionOwner (disp, sel_atom, ourwin, CurrentTime);
340 if (XGetSelectionOwner (disp, sel_atom) != ourwin)
9acadc2b 341 error ("unable to obtain primary X selection\n");
342 compound_text_atom = XInternAtom(disp, "COMPOUND_TEXT", False);
343 if (strtype == XA_STRING) {
344 /*
345 * ICCCM-required cut buffer initialisation.
346 */
347 XChangeProperty(disp, root, XA_CUT_BUFFER0,
348 XA_STRING, 8, PropModeAppend, "", 0);
349 XChangeProperty(disp, root, XA_CUT_BUFFER1,
350 XA_STRING, 8, PropModeAppend, "", 0);
351 XChangeProperty(disp, root, XA_CUT_BUFFER2,
352 XA_STRING, 8, PropModeAppend, "", 0);
353 XChangeProperty(disp, root, XA_CUT_BUFFER3,
354 XA_STRING, 8, PropModeAppend, "", 0);
355 XChangeProperty(disp, root, XA_CUT_BUFFER4,
356 XA_STRING, 8, PropModeAppend, "", 0);
357 XChangeProperty(disp, root, XA_CUT_BUFFER5,
358 XA_STRING, 8, PropModeAppend, "", 0);
359 XChangeProperty(disp, root, XA_CUT_BUFFER6,
360 XA_STRING, 8, PropModeAppend, "", 0);
361 XChangeProperty(disp, root, XA_CUT_BUFFER7,
362 XA_STRING, 8, PropModeAppend, "", 0);
363 /*
364 * Rotate the cut buffers and add our text in CUT_BUFFER0.
365 */
366 XRotateBuffers(disp, 1);
367 XStoreBytes(disp, seltext, sellen);
368 }
369 return True;
370 }
371}
372
373void run_X(void) {
374 XEvent ev, e2;
375
376 while (1) {
377 XNextEvent (disp, &ev);
378 if (reading) {
379 switch (ev.type) {
380 case SelectionNotify:
381 if (ev.xselection.property != None)
382 do_paste(ev.xselection.requestor,
383 ev.xselection.property, True);
384 return;
385 }
386 } else {
387 switch (ev.type) {
388 case SelectionClear:
389 /* Selection has been cleared by another app. */
390 return;
391 case SelectionRequest:
392 e2.xselection.type = SelectionNotify;
393 e2.xselection.requestor = ev.xselectionrequest.requestor;
394 e2.xselection.selection = ev.xselectionrequest.selection;
395 e2.xselection.target = ev.xselectionrequest.target;
396 e2.xselection.time = ev.xselectionrequest.time;
397 if (ev.xselectionrequest.target == strtype) {
398 XChangeProperty (disp, ev.xselectionrequest.requestor,
399 ev.xselectionrequest.property, strtype,
400 8, PropModeReplace, seltext, sellen);
401 e2.xselection.property = ev.xselectionrequest.property;
402 } else if (ev.xselectionrequest.target == compound_text_atom &&
403 convert_to_ctext) {
404 XTextProperty tp;
405 XmbTextListToTextProperty (disp, &seltext, 1,
406 XCompoundTextStyle, &tp);
407 XChangeProperty (disp, ev.xselectionrequest.requestor,
408 ev.xselectionrequest.property,
409 ev.xselectionrequest.target,
410 tp.format, PropModeReplace,
411 tp.value, tp.nitems);
412 e2.xselection.property = ev.xselectionrequest.property;
413 } else if (ev.xselectionrequest.target == targets_atom) {
acc02b82 414 Atom targets[16];
9acadc2b 415 int len = 0;
acc02b82 416 targets[len++] = timestamp_atom;
417 targets[len++] = targets_atom;
9acadc2b 418 targets[len++] = strtype;
419 if (strtype != compound_text_atom && convert_to_ctext)
420 targets[len++] = compound_text_atom;
421 XChangeProperty (disp, ev.xselectionrequest.requestor,
422 ev.xselectionrequest.property,
acc02b82 423 atom_atom, 32, PropModeReplace,
9acadc2b 424 (unsigned char *)targets, len);
acc02b82 425 e2.xselection.property = ev.xselectionrequest.property;
426 } else if (ev.xselectionrequest.target == timestamp_atom) {
427 Time rettime = CurrentTime;
428 XChangeProperty (disp, ev.xselectionrequest.requestor,
429 ev.xselectionrequest.property,
430 integer_atom, 32, PropModeReplace,
431 (unsigned char *)&rettime, 1);
432 e2.xselection.property = ev.xselectionrequest.property;
9acadc2b 433 } else {
434 e2.xselection.property = None;
435 }
436 XSendEvent (disp, ev.xselectionrequest.requestor, False, 0, &e2);
437 }
438 }
439 }
440}
441
442void done_X(void) {
443 int i;
444
445 if (ourwin != None)
446 XDestroyWindow (disp, ourwin);
447 if (disp)
448 XCloseDisplay (disp);
449}
450
451void do_paste(Window window, Atom property, int Delete) {
452 Atom actual_type;
453 int actual_format, i;
454 long nitems, bytes_after, nread;
455 unsigned char *data;
456
457 nread = 0;
458 while (XGetWindowProperty(disp, window, property, nread / 4, SELDELTA,
459 Delete, AnyPropertyType, &actual_type,
460 &actual_format, &nitems, &bytes_after,
461 (unsigned char **) &data) == Success) {
3ffcf184 462 if (nitems > 0) {
5340832d 463 /*
464 * We expect all returned chunks of data to be
465 * multiples of 4 bytes (because we can only request
466 * the subsequent starting offset in 4-byte
467 * increments). Of course you can store an odd number
468 * of bytes in a selection, so this can't be the case
469 * every time XGetWindowProperty returns; but it
470 * should be the case every time it returns _and there
471 * is more data to come_.
472 *
473 * Hence, whenever XGetWindowProperty returns, we
474 * verify that the size of the data returned _last_
475 * time was divisible by 4.
476 */
477 if ((nread & 3) != 0) {
478 error("unexpected data size: %d read (not a multiple"
479 " of 4), but more to come\n", nread);
480 }
481
3ffcf184 482 if (expected_type != (Atom)None && actual_type != expected_type) {
483 char *expout = XGetAtomName(disp, expected_type);
484 char *gotout = (actual_type == (Atom)None ? "None" :
485 XGetAtomName(disp, actual_type));
486 error("unexpected data type: expected %s, got %s\n",
487 expout, gotout);
488 }
489 if (expected_format && expected_format != actual_format) {
490 error("unexpected data format: expected %d-bit, got %d-bit\n",
491 expected_format, actual_format);
492 }
493 }
494
495 if (nitems > 0) {
496 if (mode == TARGETS) {
497 assert(actual_format == 32);
498 int i;
499 for (i = 0; i < nitems; i++) {
500 Atom x = ((Atom *)data)[i];
501 printf("%s\n", XGetAtomName(disp, x));
502 }
503 } else if (mode == TIMESTAMP) {
504 assert(actual_format == 32);
505 Time x = ((Time *)data)[0];
506 printf("%lu\n", (unsigned long)x);
507 } else {
508 fwrite(data, actual_format / 8, nitems, stdout);
509 nread += nitems * actual_format / 8;
510 }
9acadc2b 511 }
512 XFree(data);
3ffcf184 513 if (nitems == 0)
9acadc2b 514 break;
515 }
516}