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 				&regProp);
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 }
585