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