A bit more uniformity in those --help messages wouldn't go amiss.
[sgt/utils] / xcopy / xcopy.c
1 /*
2 * xcopy: quickly pipe text data into, or out of, the primary X
3 * selection
4 */
5
6 #include <malloc.h>
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <stdarg.h>
11 #include <math.h>
12 #include <errno.h>
13 #include <assert.h>
14
15 #include <X11/X.h>
16 #include <X11/Xlib.h>
17 #include <X11/Xutil.h>
18 #include <X11/Xatom.h>
19
20 int init_X(void);
21 void run_X(void);
22 void done_X(void);
23 void full_redraw(void);
24 void do_paste(Window window, Atom property, int Delete);
25
26 char *pname; /* program name */
27
28 void error (char *fmt, ...);
29
30 /* set from command-line parameters */
31 char *display = NULL;
32 enum { STRING, CTEXT, UTF8 } mode = STRING;
33
34 /* selection data */
35 char *seltext;
36 int sellen, selsize;
37 #define SELDELTA 16384
38
39 /* functional parameters */
40 int reading; /* read instead of writing? */
41 int convert_to_ctext = True; /* Xmb convert to compound text? */
42
43 const char usagemsg[] =
44 "usage: xcopy [ -r ] [ -u | -c ] [ -C ]\n"
45 "where: -r read X selection and print on stdout\n"
46 " no -r read stdin and store in X selection\n"
47 " -u work with UTF8_STRING type selections\n"
48 " -c work with COMPOUND_TEXT type selections\n"
49 " -C suppress automatic conversion to COMPOUND_TEXT\n"
50 " also: xcopy --version report version number\n"
51 " xcopy --help display this help text\n"
52 " xcopy --licence display the (MIT) licence text\n"
53 ;
54
55 void usage(void) {
56 fputs(usagemsg, stdout);
57 }
58
59 const char licencemsg[] =
60 "xcopy is copyright 2001-2004 Simon Tatham.\n"
61 "\n"
62 "Permission is hereby granted, free of charge, to any person\n"
63 "obtaining a copy of this software and associated documentation files\n"
64 "(the \"Software\"), to deal in the Software without restriction,\n"
65 "including without limitation the rights to use, copy, modify, merge,\n"
66 "publish, distribute, sublicense, and/or sell copies of the Software,\n"
67 "and to permit persons to whom the Software is furnished to do so,\n"
68 "subject to the following conditions:\n"
69 "\n"
70 "The above copyright notice and this permission notice shall be\n"
71 "included in all copies or substantial portions of the Software.\n"
72 "\n"
73 "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n"
74 "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n"
75 "MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n"
76 "NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n"
77 "BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n"
78 "ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n"
79 "CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n"
80 "SOFTWARE.\n"
81 ;
82
83 void licence(void) {
84 fputs(licencemsg, stdout);
85 }
86
87 void version(void) {
88 #define SVN_REV "$Revision$"
89 char rev[sizeof(SVN_REV)];
90 char *p, *q;
91
92 strcpy(rev, SVN_REV);
93
94 for (p = rev; *p && *p != ':'; p++);
95 if (*p) {
96 p++;
97 while (*p && isspace(*p)) p++;
98 for (q = p; *q && *q != '$'; q++);
99 if (*q) *q = '\0';
100 printf("xcopy revision %s\n", p);
101 } else {
102 printf("xcopy: unknown version\n");
103 }
104 }
105
106 int main(int ac, char **av) {
107 int n;
108 int eventloop;
109
110 pname = *av;
111
112 /* parse the command line arguments */
113 while (--ac) {
114 char *p = *++av;
115
116 if (!strcmp(p, "-display") || !strcmp(p, "-disp")) {
117 if (!av[1])
118 error ("option `%s' expects a parameter", p);
119 display = *++av, --ac;
120 } else if (!strcmp(p, "-r")) {
121 reading = True;
122 } else if (!strcmp(p, "-u")) {
123 mode = UTF8;
124 } else if (!strcmp(p, "-c")) {
125 mode = CTEXT;
126 } else if (!strcmp(p, "-C")) {
127 convert_to_ctext = False;
128 } else if (!strcmp(p, "--help")) {
129 usage();
130 return 0;
131 } else if (!strcmp(p, "--version")) {
132 version();
133 return 0;
134 } else if (!strcmp(p, "--licence") || !strcmp(p, "--license")) {
135 licence();
136 return 0;
137 } else if (*p=='-') {
138 error ("unrecognised option `%s'", p);
139 } else {
140 error ("no parameters required");
141 }
142 }
143
144 if (!reading) {
145 seltext = malloc(SELDELTA);
146 if (!seltext)
147 error ("out of memory");
148 selsize = SELDELTA;
149 sellen = 0;
150 do {
151 n = fread(seltext+sellen, 1, selsize-sellen, stdin);
152 sellen += n;
153 if (sellen >= selsize) {
154 seltext = realloc(seltext, selsize += SELDELTA);
155 if (!seltext)
156 error ("out of memory");
157 }
158 } while (n > 0);
159 if (sellen == selsize) {
160 seltext = realloc(seltext, selsize += SELDELTA);
161 if (!seltext)
162 error ("out of memory");
163 }
164 seltext[sellen] = '\0';
165 }
166
167 eventloop = init_X();
168 if (!reading) {
169 /*
170 * If we are writing the selection, we must go into the
171 * background now.
172 */
173 int pid = fork();
174 if (pid < 0) {
175 error("unable to fork: %s", strerror(errno));
176 } else if (pid > 0) {
177 /*
178 * we are the parent; just exit
179 */
180 return 0;
181 }
182 /*
183 * we are the child
184 */
185 close(0);
186 close(1);
187 close(2);
188 chdir("/");
189 }
190 if (eventloop)
191 run_X();
192 done_X();
193 return 0;
194 }
195
196 /* handle errors */
197
198 void error (char *fmt, ...) {
199 va_list ap;
200 char errbuf[200];
201
202 done_X();
203 va_start (ap, fmt);
204 vsprintf (errbuf, fmt, ap);
205 va_end (ap);
206 fprintf (stderr, "%s: %s\n", pname, errbuf);
207 exit (1);
208 }
209
210 /* begin the X interface */
211
212 char *lcasename = "xcopy";
213 char *ucasename = "XCopy";
214
215 Display *disp = NULL;
216 Window ourwin = None;
217 Atom compound_text_atom, targets_atom;
218 int screen, wwidth, wheight;
219
220 Atom strtype = XA_STRING;
221
222 /*
223 * Returns TRUE if we need to enter an event loop, FALSE otherwise.
224 */
225 int init_X(void) {
226 Window root;
227 int x = 0, y = 0, width = 512, height = 128;
228 int i, got = 0;
229 XWMHints wm_hints;
230 XSizeHints size_hints;
231 XClassHint class_hints;
232 XTextProperty textprop;
233 XGCValues gcv;
234
235 /* open the X display */
236 disp = XOpenDisplay (display);
237 if (!disp)
238 error ("unable to open display");
239
240 if (mode == UTF8) {
241 strtype = XInternAtom(disp, "UTF8_STRING", False);
242 if (!strtype)
243 error ("unable to get UTF8_STRING property");
244 } else if (mode == CTEXT) {
245 strtype = XInternAtom(disp, "COMPOUND_TEXT", False);
246 if (!strtype)
247 error ("unable to get COMPOUND_TEXT property");
248 }
249 targets_atom = XInternAtom(disp, "TARGETS", False);
250 if (!targets_atom)
251 error ("unable to get TARGETS property");
252
253 /* get the screen and root-window */
254 screen = DefaultScreen (disp);
255 root = RootWindow (disp, screen);
256
257 x = y = 0;
258 width = height = 10; /* doesn't really matter */
259
260 /* actually create the window */
261 ourwin = XCreateSimpleWindow (disp, root, x, y, width, height,0,
262 BlackPixel(disp, screen),
263 WhitePixel(disp, screen));
264
265 /* resource class name */
266 class_hints.res_name = lcasename;
267 class_hints.res_class = ucasename;
268 XSetClassHint (disp, ourwin, &class_hints);
269
270 /* do selection fiddling */
271 if (reading) {
272 /*
273 * We are reading the selection, so we must FIXME.
274 */
275 if (XGetSelectionOwner(disp, XA_PRIMARY) == None) {
276 /* No primary selection, so use the cut buffer. */
277 do_paste(DefaultRootWindow(disp), XA_CUT_BUFFER0, False);
278 return False;
279 } else {
280 Atom sel_property = XInternAtom(disp, "VT_SELECTION", False);
281 XConvertSelection(disp, XA_PRIMARY, strtype,
282 sel_property, ourwin, CurrentTime);
283 return True;
284 }
285 } else {
286 /*
287 * We are writing to the selection, so we establish
288 * ourselves as selection owner. Also place the data in
289 * CUT_BUFFER0, if it isn't of an exotic type (cut buffers
290 * can only take ordinary string data, it turns out).
291 */
292 XSetSelectionOwner (disp, XA_PRIMARY, ourwin, CurrentTime);
293 if (XGetSelectionOwner (disp, XA_PRIMARY) != ourwin)
294 error ("unable to obtain primary X selection\n");
295 compound_text_atom = XInternAtom(disp, "COMPOUND_TEXT", False);
296 if (strtype == XA_STRING) {
297 /*
298 * ICCCM-required cut buffer initialisation.
299 */
300 XChangeProperty(disp, root, XA_CUT_BUFFER0,
301 XA_STRING, 8, PropModeAppend, "", 0);
302 XChangeProperty(disp, root, XA_CUT_BUFFER1,
303 XA_STRING, 8, PropModeAppend, "", 0);
304 XChangeProperty(disp, root, XA_CUT_BUFFER2,
305 XA_STRING, 8, PropModeAppend, "", 0);
306 XChangeProperty(disp, root, XA_CUT_BUFFER3,
307 XA_STRING, 8, PropModeAppend, "", 0);
308 XChangeProperty(disp, root, XA_CUT_BUFFER4,
309 XA_STRING, 8, PropModeAppend, "", 0);
310 XChangeProperty(disp, root, XA_CUT_BUFFER5,
311 XA_STRING, 8, PropModeAppend, "", 0);
312 XChangeProperty(disp, root, XA_CUT_BUFFER6,
313 XA_STRING, 8, PropModeAppend, "", 0);
314 XChangeProperty(disp, root, XA_CUT_BUFFER7,
315 XA_STRING, 8, PropModeAppend, "", 0);
316 /*
317 * Rotate the cut buffers and add our text in CUT_BUFFER0.
318 */
319 XRotateBuffers(disp, 1);
320 XStoreBytes(disp, seltext, sellen);
321 }
322 return True;
323 }
324 }
325
326 void run_X(void) {
327 XEvent ev, e2;
328
329 while (1) {
330 XNextEvent (disp, &ev);
331 if (reading) {
332 switch (ev.type) {
333 case SelectionNotify:
334 if (ev.xselection.property != None)
335 do_paste(ev.xselection.requestor,
336 ev.xselection.property, True);
337 return;
338 }
339 } else {
340 switch (ev.type) {
341 case SelectionClear:
342 /* Selection has been cleared by another app. */
343 return;
344 case SelectionRequest:
345 e2.xselection.type = SelectionNotify;
346 e2.xselection.requestor = ev.xselectionrequest.requestor;
347 e2.xselection.selection = ev.xselectionrequest.selection;
348 e2.xselection.target = ev.xselectionrequest.target;
349 e2.xselection.time = ev.xselectionrequest.time;
350 if (ev.xselectionrequest.target == strtype) {
351 XChangeProperty (disp, ev.xselectionrequest.requestor,
352 ev.xselectionrequest.property, strtype,
353 8, PropModeReplace, seltext, sellen);
354 e2.xselection.property = ev.xselectionrequest.property;
355 } else if (ev.xselectionrequest.target == compound_text_atom &&
356 convert_to_ctext) {
357 XTextProperty tp;
358 XmbTextListToTextProperty (disp, &seltext, 1,
359 XCompoundTextStyle, &tp);
360 XChangeProperty (disp, ev.xselectionrequest.requestor,
361 ev.xselectionrequest.property,
362 ev.xselectionrequest.target,
363 tp.format, PropModeReplace,
364 tp.value, tp.nitems);
365 e2.xselection.property = ev.xselectionrequest.property;
366 } else if (ev.xselectionrequest.target == targets_atom) {
367 Atom targets[2];
368 int len = 0;
369 targets[len++] = strtype;
370 if (strtype != compound_text_atom && convert_to_ctext)
371 targets[len++] = compound_text_atom;
372 XChangeProperty (disp, ev.xselectionrequest.requestor,
373 ev.xselectionrequest.property,
374 ev.xselectionrequest.target,
375 32, PropModeReplace,
376 (unsigned char *)targets, len);
377 } else {
378 e2.xselection.property = None;
379 }
380 XSendEvent (disp, ev.xselectionrequest.requestor, False, 0, &e2);
381 }
382 }
383 }
384 }
385
386 void done_X(void) {
387 int i;
388
389 if (ourwin != None)
390 XDestroyWindow (disp, ourwin);
391 if (disp)
392 XCloseDisplay (disp);
393 }
394
395 void do_paste(Window window, Atom property, int Delete) {
396 Atom actual_type;
397 int actual_format, i;
398 long nitems, bytes_after, nread;
399 unsigned char *data;
400
401 nread = 0;
402 while (XGetWindowProperty(disp, window, property, nread / 4, SELDELTA,
403 Delete, AnyPropertyType, &actual_type,
404 &actual_format, &nitems, &bytes_after,
405 (unsigned char **) &data) == Success) {
406 /*
407 * We expect all returned chunks of data to be multiples of
408 * 4 bytes (because we can only request the subsequent
409 * starting offset in 4-byte increments). Of course you can
410 * store an odd number of bytes in a selection, so this
411 * can't be the case every time XGetWindowProperty returns;
412 * but it should be the case every time it returns _and
413 * there is more data to come_.
414 *
415 * Hence, whenever XGetWindowProperty returns, we verify
416 * that the size of the data returned _last_ time was
417 * divisible by 4.
418 */
419 if (nitems > 0)
420 assert((nread & 3) == 0);
421
422 if (actual_type == strtype && nitems > 0) {
423 assert(actual_format == 8);
424 fwrite(data, 1, nitems, stdout);
425 nread += nitems;
426 }
427 XFree(data);
428 if (actual_type != strtype || nitems == 0)
429 break;
430 }
431 }