7
|
1 /* vi:set ts=8 sts=4 sw=4:
|
|
2 *
|
|
3 * VIM - Vi IMproved by Bram Moolenaar
|
|
4 * X-Windows communication by Flemming Madsen
|
|
5 *
|
|
6 * Do ":help uganda" in Vim to read copying and usage conditions.
|
|
7 * Do ":help credits" in Vim to see a list of people who contributed.
|
|
8 * See README.txt for an overview of the Vim source code.
|
|
9 *
|
|
10 * Client for sending commands to an '+xcmdsrv' enabled vim.
|
|
11 * This is mostly a de-Vimified version of if_xcmdsrv.c in vim.
|
|
12 * See that file for a protocol specification.
|
|
13 *
|
|
14 * You can make a test program with a Makefile like:
|
|
15 * xcmdsrv_client: xcmdsrv_client.c
|
|
16 * cc -o $@ -g -DMAIN -I/usr/X11R6/include -L/usr/X11R6/lib $< -lX11
|
|
17 *
|
|
18 */
|
|
19
|
|
20 #include <stdio.h>
|
|
21 #include <string.h>
|
|
22 #ifdef HAVE_SELECT
|
|
23 #include <sys/time.h>
|
|
24 #include <sys/types.h>
|
|
25 #include <unistd.h>
|
|
26 #else
|
|
27 #include <sys/poll.h>
|
|
28 #endif
|
|
29 #include <X11/Intrinsic.h>
|
|
30 #include <X11/Xatom.h>
|
|
31
|
|
32 #define __ARGS(x) x
|
|
33
|
|
34 /* Client API */
|
|
35 char * sendToVim __ARGS((Display *dpy, char *name, char *cmd, int asKeys, int *code));
|
|
36
|
|
37 #ifdef MAIN
|
|
38 /* A sample program */
|
|
39 main(int argc, char **argv)
|
|
40 {
|
|
41 char *res;
|
|
42 int code;
|
|
43
|
|
44 if (argc == 4)
|
|
45 {
|
|
46 if ((res = sendToVim(XOpenDisplay(NULL), argv[2], argv[3],
|
|
47 argv[1][0] != 'e', &code)) != NULL)
|
|
48 {
|
|
49 if (code)
|
|
50 printf("Error code returned: %d\n", code);
|
|
51 puts(res);
|
|
52 }
|
|
53 exit(0);
|
|
54 }
|
|
55 else
|
|
56 fprintf(stderr, "Usage: %s {k|e} <server> <command>", argv[0]);
|
|
57
|
|
58 exit(1);
|
|
59 }
|
|
60 #endif
|
|
61
|
|
62 /*
|
|
63 * Maximum size property that can be read at one time by
|
|
64 * this module:
|
|
65 */
|
|
66
|
|
67 #define MAX_PROP_WORDS 100000
|
|
68
|
|
69 /*
|
|
70 * Forward declarations for procedures defined later in this file:
|
|
71 */
|
|
72
|
|
73 static int x_error_check __ARGS((Display *dpy, XErrorEvent *error_event));
|
|
74 static int AppendPropCarefully __ARGS((Display *display,
|
|
75 Window window, Atom property, char *value, int length));
|
|
76 static Window LookupName __ARGS((Display *dpy, char *name,
|
|
77 int delete, char **loose));
|
|
78 static int SendInit __ARGS((Display *dpy));
|
|
79 static char *SendEventProc __ARGS((Display *dpy, XEvent *eventPtr,
|
|
80 int expect, int *code));
|
|
81 static int IsSerialName __ARGS((char *name));
|
|
82
|
|
83 /* Private variables */
|
|
84 static Atom registryProperty = None;
|
|
85 static Atom commProperty = None;
|
|
86 static Window commWindow = None;
|
|
87 static int got_x_error = FALSE;
|
|
88
|
|
89
|
|
90 /*
|
|
91 * sendToVim --
|
|
92 * Send to an instance of Vim via the X display.
|
|
93 *
|
|
94 * Results:
|
|
95 * A string with the result or NULL. Caller must free if non-NULL
|
|
96 */
|
|
97
|
|
98 char *
|
|
99 sendToVim(dpy, name, cmd, asKeys, code)
|
|
100 Display *dpy; /* Where to send. */
|
|
101 char *name; /* Where to send. */
|
|
102 char *cmd; /* What to send. */
|
|
103 int asKeys; /* Interpret as keystrokes or expr ? */
|
|
104 int *code; /* Return code. 0 => OK */
|
|
105 {
|
|
106 Window w;
|
|
107 Atom *plist;
|
|
108 XErrorHandler old_handler;
|
|
109 #define STATIC_SPACE 500
|
|
110 char *property, staticSpace[STATIC_SPACE];
|
|
111 int length;
|
|
112 int res;
|
|
113 static int serial = 0; /* Running count of sent commands.
|
|
114 * Used to give each command a
|
|
115 * different serial number. */
|
|
116 XEvent event;
|
|
117 XPropertyEvent *e = (XPropertyEvent *)&event;
|
|
118 time_t start;
|
|
119 char *result;
|
|
120 char *loosename = NULL;
|
|
121
|
|
122 if (commProperty == None && dpy != NULL)
|
|
123 {
|
|
124 if (SendInit(dpy) < 0)
|
|
125 return NULL;
|
|
126 }
|
|
127
|
|
128 /*
|
|
129 * Bind the server name to a communication window.
|
|
130 *
|
|
131 * Find any survivor with a serialno attached to the name if the
|
|
132 * original registrant of the wanted name is no longer present.
|
|
133 *
|
|
134 * Delete any lingering names from dead editors.
|
|
135 */
|
|
136
|
|
137 old_handler = XSetErrorHandler(x_error_check);
|
|
138 while (TRUE)
|
|
139 {
|
|
140 got_x_error = FALSE;
|
|
141 w = LookupName(dpy, name, 0, &loosename);
|
|
142 /* Check that the window is hot */
|
|
143 if (w != None)
|
|
144 {
|
|
145 plist = XListProperties(dpy, w, &res);
|
|
146 XSync(dpy, False);
|
|
147 if (plist != NULL)
|
|
148 XFree(plist);
|
|
149 if (got_x_error)
|
|
150 {
|
|
151 LookupName(dpy, loosename ? loosename : name,
|
|
152 /*DELETE=*/TRUE, NULL);
|
|
153 continue;
|
|
154 }
|
|
155 }
|
|
156 break;
|
|
157 }
|
|
158 if (w == None)
|
|
159 {
|
|
160 fprintf(stderr, "no registered server named %s\n", name);
|
|
161 return NULL;
|
|
162 }
|
|
163 else if (loosename != NULL)
|
|
164 name = loosename;
|
|
165
|
|
166 /*
|
|
167 * Send the command to target interpreter by appending it to the
|
|
168 * comm window in the communication window.
|
|
169 */
|
|
170
|
|
171 length = strlen(name) + strlen(cmd) + 10;
|
|
172 if (length <= STATIC_SPACE)
|
|
173 property = staticSpace;
|
|
174 else
|
|
175 property = (char *) malloc((unsigned) length);
|
|
176
|
|
177 serial++;
|
|
178 sprintf(property, "%c%c%c-n %s%c-s %s",
|
|
179 0, asKeys ? 'k' : 'c', 0, name, 0, cmd);
|
|
180 if (name == loosename)
|
|
181 free(loosename);
|
|
182 if (!asKeys)
|
|
183 {
|
|
184 /* Add a back reference to our comm window */
|
|
185 sprintf(property + length, "%c-r %x %d", 0, (uint) commWindow, serial);
|
|
186 length += strlen(property + length + 1) + 1;
|
|
187 }
|
|
188
|
|
189 res = AppendPropCarefully(dpy, w, commProperty, property, length + 1);
|
|
190 if (length > STATIC_SPACE)
|
|
191 free(property);
|
|
192 if (res < 0)
|
|
193 {
|
|
194 fprintf(stderr, "Failed to send command to the destination program\n");
|
|
195 return NULL;
|
|
196 }
|
|
197
|
|
198 if (asKeys) /* There is no answer for this - Keys are sent async */
|
|
199 return NULL;
|
|
200
|
|
201
|
|
202 /*
|
|
203 * Enter a loop processing X events & pooling chars until we see the result
|
|
204 */
|
|
205
|
|
206 #define SEND_MSEC_POLL 50
|
|
207
|
|
208 time(&start);
|
|
209 while ((time((time_t *) 0) - start) < 60)
|
|
210 {
|
|
211 /* Look out for the answer */
|
|
212 #ifndef HAVE_SELECT
|
|
213 struct pollfd fds;
|
|
214
|
|
215 fds.fd = ConnectionNumber(dpy);
|
|
216 fds.events = POLLIN;
|
|
217 if (poll(&fds, 1, SEND_MSEC_POLL) < 0)
|
|
218 break;
|
|
219 #else
|
|
220 fd_set fds;
|
|
221 struct timeval tv;
|
|
222
|
|
223 tv.tv_sec = 0;
|
|
224 tv.tv_usec = SEND_MSEC_POLL * 1000;
|
|
225 FD_ZERO(&fds);
|
|
226 FD_SET(ConnectionNumber(dpy), &fds);
|
|
227 if (select(ConnectionNumber(dpy) + 1, &fds, NULL, NULL, &tv) < 0)
|
|
228 break;
|
|
229 #endif
|
|
230 while (XEventsQueued(dpy, QueuedAfterReading) > 0)
|
|
231 {
|
|
232 XNextEvent(dpy, &event);
|
|
233 if (event.type == PropertyNotify && e->window == commWindow)
|
|
234 if ((result = SendEventProc(dpy, &event, serial, code)) != NULL)
|
|
235 return result;
|
|
236 }
|
|
237 }
|
|
238 return NULL;
|
|
239 }
|
|
240
|
|
241
|
|
242 /*
|
|
243 * SendInit --
|
|
244 * This procedure is called to initialize the
|
|
245 * communication channels for sending commands and
|
|
246 * receiving results.
|
|
247 */
|
|
248
|
|
249 static int
|
|
250 SendInit(dpy)
|
|
251 Display *dpy;
|
|
252 {
|
|
253 XErrorHandler old_handler;
|
|
254
|
|
255 /*
|
|
256 * Create the window used for communication, and set up an
|
|
257 * event handler for it.
|
|
258 */
|
|
259 old_handler = XSetErrorHandler(x_error_check);
|
|
260 got_x_error = FALSE;
|
|
261
|
|
262 commProperty = XInternAtom(dpy, "Comm", False);
|
|
263 /* Change this back to "InterpRegistry" to talk to tk processes */
|
|
264 registryProperty = XInternAtom(dpy, "VimRegistry", False);
|
|
265
|
|
266 if (commWindow == None)
|
|
267 {
|
|
268 commWindow =
|
|
269 XCreateSimpleWindow(dpy, XDefaultRootWindow(dpy),
|
|
270 getpid(), 0, 10, 10, 0,
|
|
271 WhitePixel(dpy, DefaultScreen(dpy)),
|
|
272 WhitePixel(dpy, DefaultScreen(dpy)));
|
|
273 XSelectInput(dpy, commWindow, PropertyChangeMask);
|
|
274 }
|
|
275
|
|
276 XSync(dpy, False);
|
|
277 (void) XSetErrorHandler(old_handler);
|
|
278
|
|
279 return got_x_error ? -1 : 0;
|
|
280 }
|
|
281
|
|
282 /*
|
|
283 * LookupName --
|
|
284 * Given an interpreter name, see if the name exists in
|
|
285 * the interpreter registry for a particular display.
|
|
286 *
|
|
287 * Results:
|
|
288 * If the given name is registered, return the ID of
|
|
289 * the window associated with the name. If the name
|
|
290 * isn't registered, then return 0.
|
|
291 */
|
|
292
|
|
293 static Window
|
|
294 LookupName(dpy, name, delete, loose)
|
|
295 Display *dpy; /* Display whose registry to check. */
|
|
296 char *name; /* Name of an interpreter. */
|
|
297 int delete; /* If non-zero, delete info about name. */
|
|
298 char **loose; /* Do another search matching -999 if not found
|
|
299 Return result here if a match is found */
|
|
300 {
|
|
301 unsigned char *regProp, *entry;
|
|
302 unsigned char *p;
|
|
303 int result, actualFormat;
|
|
304 unsigned long numItems, bytesAfter;
|
|
305 Atom actualType;
|
|
306 Window returnValue;
|
|
307
|
|
308 /*
|
|
309 * Read the registry property.
|
|
310 */
|
|
311
|
|
312 regProp = NULL;
|
|
313 result = XGetWindowProperty(dpy, RootWindow(dpy, 0), registryProperty, 0,
|
|
314 MAX_PROP_WORDS, False, XA_STRING, &actualType,
|
|
315 &actualFormat, &numItems, &bytesAfter,
|
|
316 ®Prop);
|
|
317
|
|
318 if (actualType == None)
|
|
319 return 0;
|
|
320
|
|
321 /*
|
|
322 * If the property is improperly formed, then delete it.
|
|
323 */
|
|
324
|
|
325 if ((result != Success) || (actualFormat != 8) || (actualType != XA_STRING))
|
|
326 {
|
|
327 if (regProp != NULL)
|
|
328 XFree(regProp);
|
|
329 XDeleteProperty(dpy, RootWindow(dpy, 0), registryProperty);
|
|
330 return 0;
|
|
331 }
|
|
332
|
|
333 /*
|
|
334 * Scan the property for the desired name.
|
|
335 */
|
|
336
|
|
337 returnValue = None;
|
|
338 entry = NULL; /* Not needed, but eliminates compiler warning. */
|
|
339 for (p = regProp; (p - regProp) < numItems; )
|
|
340 {
|
|
341 entry = p;
|
|
342 while ((*p != 0) && (!isspace(*p)))
|
|
343 p++;
|
|
344 if ((*p != 0) && (strcasecmp(name, p + 1) == 0))
|
|
345 {
|
|
346 sscanf(entry, "%x", (uint*) &returnValue);
|
|
347 break;
|
|
348 }
|
|
349 while (*p != 0)
|
|
350 p++;
|
|
351 p++;
|
|
352 }
|
|
353
|
|
354 if (loose != NULL && returnValue == None && !IsSerialName(name))
|
|
355 {
|
|
356 for (p = regProp; (p - regProp) < numItems; )
|
|
357 {
|
|
358 entry = p;
|
|
359 while ((*p != 0) && (!isspace(*p)))
|
|
360 p++;
|
|
361 if ((*p != 0) && IsSerialName(p + 1)
|
|
362 && (strncmp(name, p + 1, strlen(name)) == 0))
|
|
363 {
|
|
364 sscanf(entry, "%x", (uint*) &returnValue);
|
|
365 *loose = strdup(p + 1);
|
|
366 break;
|
|
367 }
|
|
368 while (*p != 0)
|
|
369 p++;
|
|
370 p++;
|
|
371 }
|
|
372 }
|
|
373
|
|
374 /*
|
|
375 * Delete the property, if that is desired (copy down the
|
|
376 * remainder of the registry property to overlay the deleted
|
|
377 * info, then rewrite the property).
|
|
378 */
|
|
379
|
|
380 if ((delete) && (returnValue != None))
|
|
381 {
|
|
382 int count;
|
|
383
|
|
384 while (*p != 0)
|
|
385 p++;
|
|
386 p++;
|
|
387 count = numItems - (p-regProp);
|
|
388 if (count > 0)
|
|
389 memcpy(entry, p, count);
|
|
390 XChangeProperty(dpy, RootWindow(dpy, 0), registryProperty, XA_STRING,
|
|
391 8, PropModeReplace, regProp,
|
|
392 (int) (numItems - (p-entry)));
|
|
393 XSync(dpy, False);
|
|
394 }
|
|
395
|
|
396 XFree(regProp);
|
|
397 return returnValue;
|
|
398 }
|
|
399
|
|
400 static char *
|
|
401 SendEventProc(dpy, eventPtr, expected, code)
|
|
402 Display *dpy;
|
|
403 XEvent *eventPtr; /* Information about event. */
|
|
404 int expected; /* The one were waiting for */
|
|
405 int *code; /* Return code. 0 => OK */
|
|
406 {
|
|
407 unsigned char *propInfo;
|
|
408 unsigned char *p;
|
|
409 int result, actualFormat;
|
|
410 int retCode;
|
|
411 unsigned long numItems, bytesAfter;
|
|
412 Atom actualType;
|
|
413
|
|
414 if ((eventPtr->xproperty.atom != commProperty)
|
|
415 || (eventPtr->xproperty.state != PropertyNewValue))
|
|
416 {
|
|
417 return;
|
|
418 }
|
|
419
|
|
420 /*
|
|
421 * Read the comm property and delete it.
|
|
422 */
|
|
423
|
|
424 propInfo = NULL;
|
|
425 result = XGetWindowProperty(dpy, commWindow, commProperty, 0,
|
|
426 MAX_PROP_WORDS, True, XA_STRING, &actualType,
|
|
427 &actualFormat, &numItems, &bytesAfter,
|
|
428 &propInfo);
|
|
429
|
|
430 /*
|
|
431 * If the property doesn't exist or is improperly formed
|
|
432 * then ignore it.
|
|
433 */
|
|
434
|
|
435 if ((result != Success) || (actualType != XA_STRING)
|
|
436 || (actualFormat != 8))
|
|
437 {
|
|
438 if (propInfo != NULL)
|
|
439 {
|
|
440 XFree(propInfo);
|
|
441 }
|
|
442 return;
|
|
443 }
|
|
444
|
|
445 /*
|
|
446 * Several commands and results could arrive in the property at
|
|
447 * one time; each iteration through the outer loop handles a
|
|
448 * single command or result.
|
|
449 */
|
|
450
|
|
451 for (p = propInfo; (p - propInfo) < numItems; )
|
|
452 {
|
|
453 /*
|
|
454 * Ignore leading NULs; each command or result starts with a
|
|
455 * NUL so that no matter how badly formed a preceding command
|
|
456 * is, we'll be able to tell that a new command/result is
|
|
457 * starting.
|
|
458 */
|
|
459
|
|
460 if (*p == 0)
|
|
461 {
|
|
462 p++;
|
|
463 continue;
|
|
464 }
|
|
465
|
|
466 if ((*p == 'r') && (p[1] == 0))
|
|
467 {
|
|
468 int serial, gotSerial;
|
|
469 char *res;
|
|
470
|
|
471 /*
|
|
472 * This is a reply to some command that we sent out. Iterate
|
|
473 * over all of its options. Stop when we reach the end of the
|
|
474 * property or something that doesn't look like an option.
|
|
475 */
|
|
476
|
|
477 p += 2;
|
|
478 gotSerial = 0;
|
|
479 res = "";
|
|
480 retCode = 0;
|
|
481 while (((p-propInfo) < numItems) && (*p == '-'))
|
|
482 {
|
|
483 switch (p[1])
|
|
484 {
|
|
485 case 'r':
|
|
486 if (p[2] == ' ')
|
|
487 res = p + 3;
|
|
488 break;
|
|
489 case 's':
|
|
490 if (sscanf(p + 2, " %d", &serial) == 1)
|
|
491 gotSerial = 1;
|
|
492 break;
|
|
493 case 'c':
|
|
494 if (sscanf(p + 2, " %d", &retCode) != 1)
|
|
495 retCode = 0;
|
|
496 break;
|
|
497 }
|
|
498 while (*p != 0)
|
|
499 p++;
|
|
500 p++;
|
|
501 }
|
|
502
|
|
503 if (!gotSerial)
|
|
504 continue;
|
|
505
|
|
506 if (code != NULL)
|
|
507 *code = retCode;
|
|
508 return serial == expected ? strdup(res) : NULL;
|
|
509 }
|
|
510 else
|
|
511 {
|
|
512 /*
|
|
513 * Didn't recognize this thing. Just skip through the next
|
|
514 * null character and try again.
|
|
515 * Also, throw away commands that we cant process anyway.
|
|
516 */
|
|
517
|
|
518 while (*p != 0)
|
|
519 p++;
|
|
520 p++;
|
|
521 }
|
|
522 }
|
|
523 XFree(propInfo);
|
|
524 }
|
|
525
|
|
526 /*
|
|
527 * AppendPropCarefully --
|
|
528 *
|
|
529 * Append a given property to a given window, but set up
|
|
530 * an X error handler so that if the append fails this
|
|
531 * procedure can return an error code rather than having
|
|
532 * Xlib panic.
|
|
533 *
|
|
534 * Return:
|
|
535 * 0 on OK - -1 on error
|
|
536 *--------------------------------------------------------------
|
|
537 */
|
|
538
|
|
539 static int
|
|
540 AppendPropCarefully(dpy, window, property, value, length)
|
|
541 Display *dpy; /* Display on which to operate. */
|
|
542 Window window; /* Window whose property is to
|
|
543 * be modified. */
|
|
544 Atom property; /* Name of property. */
|
|
545 char *value; /* Characters to append to property. */
|
|
546 int length; /* How much to append */
|
|
547 {
|
|
548 XErrorHandler old_handler;
|
|
549
|
|
550 old_handler = XSetErrorHandler(x_error_check);
|
|
551 got_x_error = FALSE;
|
|
552 XChangeProperty(dpy, window, property, XA_STRING, 8,
|
|
553 PropModeAppend, value, length);
|
|
554 XSync(dpy, False);
|
|
555 (void) XSetErrorHandler(old_handler);
|
|
556 return got_x_error ? -1 : 0;
|
|
557 }
|
|
558
|
|
559
|
|
560 /*
|
|
561 * Another X Error handler, just used to check for errors.
|
|
562 */
|
|
563 /* ARGSUSED */
|
|
564 static int
|
|
565 x_error_check(dpy, error_event)
|
|
566 Display *dpy;
|
|
567 XErrorEvent *error_event;
|
|
568 {
|
|
569 got_x_error = TRUE;
|
|
570 return 0;
|
|
571 }
|
|
572
|
|
573 /*
|
|
574 * Check if "str" looks like it had a serial number appended.
|
|
575 * Actually just checks if the name ends in a digit.
|
|
576 */
|
|
577 static int
|
|
578 IsSerialName(str)
|
|
579 char *str;
|
|
580 {
|
|
581 int len = strlen(str);
|
|
582
|
|
583 return (len > 1 && isdigit(str[len - 1]));
|
|
584 }
|