Move some of my more useful utilities out from my all-purpose
[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
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
20int init_X(void);
21void run_X(void);
22void done_X(void);
23void full_redraw(void);
24void do_paste(Window window, Atom property, int Delete);
25
26char *pname; /* program name */
27
28void error (char *fmt, ...);
29
30/* set from command-line parameters */
31char *display = NULL;
32enum { STRING, CTEXT, UTF8 } mode = STRING;
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
43int main(int ac, char **av) {
44 int n;
45 int eventloop;
46
47 pname = *av;
48
49 /* parse the command line arguments */
50 while (--ac) {
51 char *p = *++av;
52
53 if (!strcmp(p, "-display") || !strcmp(p, "-disp")) {
54 if (!av[1])
55 error ("option `%s' expects a parameter", p);
56 display = *++av, --ac;
57 } else if (!strcmp(p, "-r")) {
58 reading = True;
59 } else if (!strcmp(p, "-u")) {
60 mode = UTF8;
61 } else if (!strcmp(p, "-c")) {
62 mode = CTEXT;
63 } else if (!strcmp(p, "-C")) {
64 convert_to_ctext = False;
65 } else if (*p=='-') {
66 error ("unrecognised option `%s'", p);
67 } else {
68 error ("no parameters required");
69 }
70 }
71
72 if (!reading) {
73 seltext = malloc(SELDELTA);
74 if (!seltext)
75 error ("out of memory");
76 selsize = SELDELTA;
77 sellen = 0;
78 do {
79 n = fread(seltext+sellen, 1, selsize-sellen, stdin);
80 sellen += n;
81 if (sellen >= selsize) {
82 seltext = realloc(seltext, selsize += SELDELTA);
83 if (!seltext)
84 error ("out of memory");
85 }
86 } while (n > 0);
87 if (sellen == selsize) {
88 seltext = realloc(seltext, selsize += SELDELTA);
89 if (!seltext)
90 error ("out of memory");
91 }
92 seltext[sellen] = '\0';
93 }
94
95 eventloop = init_X();
96 if (!reading) {
97 /*
98 * If we are writing the selection, we must go into the
99 * background now.
100 */
101 int pid = fork();
102 if (pid < 0) {
103 error("unable to fork: %s", strerror(errno));
104 } else if (pid > 0) {
105 /*
106 * we are the parent; just exit
107 */
108 return 0;
109 }
110 /*
111 * we are the child
112 */
113 close(0);
114 close(1);
115 close(2);
116 chdir("/");
117 }
118 if (eventloop)
119 run_X();
120 done_X();
121 return 0;
122}
123
124/* handle errors */
125
126void error (char *fmt, ...) {
127 va_list ap;
128 char errbuf[200];
129
130 done_X();
131 va_start (ap, fmt);
132 vsprintf (errbuf, fmt, ap);
133 va_end (ap);
134 fprintf (stderr, "%s: %s\n", pname, errbuf);
135 exit (1);
136}
137
138/* begin the X interface */
139
140char *lcasename = "xcopy";
141char *ucasename = "XCopy";
142
143Display *disp = NULL;
144Window ourwin = None;
145Atom compound_text_atom, targets_atom;
146int screen, wwidth, wheight;
147
148Atom strtype = XA_STRING;
149
150/*
151 * Returns TRUE if we need to enter an event loop, FALSE otherwise.
152 */
153int init_X(void) {
154 Window root;
155 int x = 0, y = 0, width = 512, height = 128;
156 int i, got = 0;
157 XWMHints wm_hints;
158 XSizeHints size_hints;
159 XClassHint class_hints;
160 XTextProperty textprop;
161 XGCValues gcv;
162
163 /* open the X display */
164 disp = XOpenDisplay (display);
165 if (!disp)
166 error ("unable to open display");
167
168 if (mode == UTF8) {
169 strtype = XInternAtom(disp, "UTF8_STRING", False);
170 if (!strtype)
171 error ("unable to get UTF8_STRING property");
172 } else if (mode == CTEXT) {
173 strtype = XInternAtom(disp, "COMPOUND_TEXT", False);
174 if (!strtype)
175 error ("unable to get COMPOUND_TEXT property");
176 }
177 targets_atom = XInternAtom(disp, "TARGETS", False);
178 if (!targets_atom)
179 error ("unable to get TARGETS property");
180
181 /* get the screen and root-window */
182 screen = DefaultScreen (disp);
183 root = RootWindow (disp, screen);
184
185 x = y = 0;
186 width = height = 10; /* doesn't really matter */
187
188 /* actually create the window */
189 ourwin = XCreateSimpleWindow (disp, root, x, y, width, height,0,
190 BlackPixel(disp, screen),
191 WhitePixel(disp, screen));
192
193 /* resource class name */
194 class_hints.res_name = lcasename;
195 class_hints.res_class = ucasename;
196 XSetClassHint (disp, ourwin, &class_hints);
197
198 /* do selection fiddling */
199 if (reading) {
200 /*
201 * We are reading the selection, so we must FIXME.
202 */
203 if (XGetSelectionOwner(disp, XA_PRIMARY) == None) {
204 /* No primary selection, so use the cut buffer. */
205 do_paste(DefaultRootWindow(disp), XA_CUT_BUFFER0, False);
206 return False;
207 } else {
208 Atom sel_property = XInternAtom(disp, "VT_SELECTION", False);
209 XConvertSelection(disp, XA_PRIMARY, strtype,
210 sel_property, ourwin, CurrentTime);
211 return True;
212 }
213 } else {
214 /*
215 * We are writing to the selection, so we establish
216 * ourselves as selection owner. Also place the data in
217 * CUT_BUFFER0, if it isn't of an exotic type (cut buffers
218 * can only take ordinary string data, it turns out).
219 */
220 XSetSelectionOwner (disp, XA_PRIMARY, ourwin, CurrentTime);
221 if (XGetSelectionOwner (disp, XA_PRIMARY) != ourwin)
222 error ("unable to obtain primary X selection\n");
223 compound_text_atom = XInternAtom(disp, "COMPOUND_TEXT", False);
224 if (strtype == XA_STRING) {
225 /*
226 * ICCCM-required cut buffer initialisation.
227 */
228 XChangeProperty(disp, root, XA_CUT_BUFFER0,
229 XA_STRING, 8, PropModeAppend, "", 0);
230 XChangeProperty(disp, root, XA_CUT_BUFFER1,
231 XA_STRING, 8, PropModeAppend, "", 0);
232 XChangeProperty(disp, root, XA_CUT_BUFFER2,
233 XA_STRING, 8, PropModeAppend, "", 0);
234 XChangeProperty(disp, root, XA_CUT_BUFFER3,
235 XA_STRING, 8, PropModeAppend, "", 0);
236 XChangeProperty(disp, root, XA_CUT_BUFFER4,
237 XA_STRING, 8, PropModeAppend, "", 0);
238 XChangeProperty(disp, root, XA_CUT_BUFFER5,
239 XA_STRING, 8, PropModeAppend, "", 0);
240 XChangeProperty(disp, root, XA_CUT_BUFFER6,
241 XA_STRING, 8, PropModeAppend, "", 0);
242 XChangeProperty(disp, root, XA_CUT_BUFFER7,
243 XA_STRING, 8, PropModeAppend, "", 0);
244 /*
245 * Rotate the cut buffers and add our text in CUT_BUFFER0.
246 */
247 XRotateBuffers(disp, 1);
248 XStoreBytes(disp, seltext, sellen);
249 }
250 return True;
251 }
252}
253
254void run_X(void) {
255 XEvent ev, e2;
256
257 while (1) {
258 XNextEvent (disp, &ev);
259 if (reading) {
260 switch (ev.type) {
261 case SelectionNotify:
262 if (ev.xselection.property != None)
263 do_paste(ev.xselection.requestor,
264 ev.xselection.property, True);
265 return;
266 }
267 } else {
268 switch (ev.type) {
269 case SelectionClear:
270 /* Selection has been cleared by another app. */
271 return;
272 case SelectionRequest:
273 e2.xselection.type = SelectionNotify;
274 e2.xselection.requestor = ev.xselectionrequest.requestor;
275 e2.xselection.selection = ev.xselectionrequest.selection;
276 e2.xselection.target = ev.xselectionrequest.target;
277 e2.xselection.time = ev.xselectionrequest.time;
278 if (ev.xselectionrequest.target == strtype) {
279 XChangeProperty (disp, ev.xselectionrequest.requestor,
280 ev.xselectionrequest.property, strtype,
281 8, PropModeReplace, seltext, sellen);
282 e2.xselection.property = ev.xselectionrequest.property;
283 } else if (ev.xselectionrequest.target == compound_text_atom &&
284 convert_to_ctext) {
285 XTextProperty tp;
286 XmbTextListToTextProperty (disp, &seltext, 1,
287 XCompoundTextStyle, &tp);
288 XChangeProperty (disp, ev.xselectionrequest.requestor,
289 ev.xselectionrequest.property,
290 ev.xselectionrequest.target,
291 tp.format, PropModeReplace,
292 tp.value, tp.nitems);
293 e2.xselection.property = ev.xselectionrequest.property;
294 } else if (ev.xselectionrequest.target == targets_atom) {
295 Atom targets[2];
296 int len = 0;
297 targets[len++] = strtype;
298 if (strtype != compound_text_atom && convert_to_ctext)
299 targets[len++] = compound_text_atom;
300 XChangeProperty (disp, ev.xselectionrequest.requestor,
301 ev.xselectionrequest.property,
302 ev.xselectionrequest.target,
303 32, PropModeReplace,
304 (unsigned char *)targets, len);
305 } else {
306 e2.xselection.property = None;
307 }
308 XSendEvent (disp, ev.xselectionrequest.requestor, False, 0, &e2);
309 }
310 }
311 }
312}
313
314void done_X(void) {
315 int i;
316
317 if (ourwin != None)
318 XDestroyWindow (disp, ourwin);
319 if (disp)
320 XCloseDisplay (disp);
321}
322
323void do_paste(Window window, Atom property, int Delete) {
324 Atom actual_type;
325 int actual_format, i;
326 long nitems, bytes_after, nread;
327 unsigned char *data;
328
329 nread = 0;
330 while (XGetWindowProperty(disp, window, property, nread / 4, SELDELTA,
331 Delete, AnyPropertyType, &actual_type,
332 &actual_format, &nitems, &bytes_after,
333 (unsigned char **) &data) == Success) {
334 /*
335 * We expect all returned chunks of data to be multiples of
336 * 4 bytes (because we can only request the subsequent
337 * starting offset in 4-byte increments). Of course you can
338 * store an odd number of bytes in a selection, so this
339 * can't be the case every time XGetWindowProperty returns;
340 * but it should be the case every time it returns _and
341 * there is more data to come_.
342 *
343 * Hence, whenever XGetWindowProperty returns, we verify
344 * that the size of the data returned _last_ time was
345 * divisible by 4.
346 */
347 if (nitems > 0)
348 assert((nread & 3) == 0);
349
350 if (actual_type == strtype && nitems > 0) {
351 assert(actual_format == 8);
352 fwrite(data, 1, nitems, stdout);
353 nread += nitems;
354 }
355 XFree(data);
356 if (actual_type != strtype || nitems == 0)
357 break;
358 }
359}