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 | |
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 | int 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 | |
126 | void 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 | |
140 | char *lcasename = "xcopy"; |
141 | char *ucasename = "XCopy"; |
142 | |
143 | Display *disp = NULL; |
144 | Window ourwin = None; |
145 | Atom compound_text_atom, targets_atom; |
146 | int screen, wwidth, wheight; |
147 | |
148 | Atom strtype = XA_STRING; |
149 | |
150 | /* |
151 | * Returns TRUE if we need to enter an event loop, FALSE otherwise. |
152 | */ |
153 | int 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 | |
254 | void 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 | |
314 | void done_X(void) { |
315 | int i; |
316 | |
317 | if (ourwin != None) |
318 | XDestroyWindow (disp, ourwin); |
319 | if (disp) |
320 | XCloseDisplay (disp); |
321 | } |
322 | |
323 | void 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 | } |