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