diff options
Diffstat (limited to 'runtime/tools/xcmdsrv_client.c')
-rw-r--r-- | runtime/tools/xcmdsrv_client.c | 578 |
1 files changed, 578 insertions, 0 deletions
diff --git a/runtime/tools/xcmdsrv_client.c b/runtime/tools/xcmdsrv_client.c new file mode 100644 index 0000000..e1aea10 --- /dev/null +++ b/runtime/tools/xcmdsrv_client.c @@ -0,0 +1,578 @@ +/* vi:set ts=8 sts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * X-Windows communication by Flemming Madsen + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + * + * Client for sending commands to an '+xcmdsrv' enabled vim. + * This is mostly a de-Vimified version of if_xcmdsrv.c in vim. + * See that file for a protocol specification. + * + * You can make a test program with a Makefile like: + * xcmdsrv_client: xcmdsrv_client.c + * cc -o $@ -g -DMAIN -I/usr/X11R6/include -L/usr/X11R6/lib $< -lX11 + * + */ + +#include <stdio.h> +#include <string.h> +#ifdef HAVE_SELECT +#include <sys/time.h> +#include <sys/types.h> +#include <unistd.h> +#else +#include <sys/poll.h> +#endif +#include <X11/Intrinsic.h> +#include <X11/Xatom.h> + +/* Client API */ +char * sendToVim(Display *dpy, char *name, char *cmd, int asKeys, int *code); + +#ifdef MAIN +/* A sample program */ +main(int argc, char **argv) +{ + char *res; + int code; + + if (argc == 4) + { + if ((res = sendToVim(XOpenDisplay(NULL), argv[2], argv[3], + argv[1][0] != 'e', &code)) != NULL) + { + if (code) + printf("Error code returned: %d\n", code); + puts(res); + } + exit(0); + } + else + fprintf(stderr, "Usage: %s {k|e} <server> <command>", argv[0]); + + exit(1); +} +#endif + +/* + * Maximum size property that can be read at one time by + * this module: + */ + +#define MAX_PROP_WORDS 100000 + +/* + * Forward declarations for procedures defined later in this file: + */ + +static int x_error_check(Display *dpy, XErrorEvent *error_event); +static int AppendPropCarefully(Display *display, + Window window, Atom property, char *value, int length); +static Window LookupName(Display *dpy, char *name, + int delete, char **loose); +static int SendInit(Display *dpy); +static char *SendEventProc(Display *dpy, XEvent *eventPtr, + int expect, int *code); +static int IsSerialName(char *name); + +/* Private variables */ +static Atom registryProperty = None; +static Atom commProperty = None; +static Window commWindow = None; +static int got_x_error = FALSE; + + +/* + * sendToVim -- + * Send to an instance of Vim via the X display. + * + * Results: + * A string with the result or NULL. Caller must free if non-NULL + */ + + char * +sendToVim( + Display *dpy, /* Where to send. */ + char *name, /* Where to send. */ + char *cmd, /* What to send. */ + int asKeys, /* Interpret as keystrokes or expr ? */ + int *code) /* Return code. 0 => OK */ +{ + Window w; + Atom *plist; + XErrorHandler old_handler; +#define STATIC_SPACE 500 + char *property, staticSpace[STATIC_SPACE]; + int length; + int res; + static int serial = 0; /* Running count of sent commands. + * Used to give each command a + * different serial number. */ + XEvent event; + XPropertyEvent *e = (XPropertyEvent *)&event; + time_t start; + char *result; + char *loosename = NULL; + + if (commProperty == None && dpy != NULL) + { + if (SendInit(dpy) < 0) + return NULL; + } + + /* + * Bind the server name to a communication window. + * + * Find any survivor with a serialno attached to the name if the + * original registrant of the wanted name is no longer present. + * + * Delete any lingering names from dead editors. + */ + + old_handler = XSetErrorHandler(x_error_check); + while (TRUE) + { + got_x_error = FALSE; + w = LookupName(dpy, name, 0, &loosename); + /* Check that the window is hot */ + if (w != None) + { + plist = XListProperties(dpy, w, &res); + XSync(dpy, False); + if (plist != NULL) + XFree(plist); + if (got_x_error) + { + LookupName(dpy, loosename ? loosename : name, + /*DELETE=*/TRUE, NULL); + continue; + } + } + break; + } + if (w == None) + { + fprintf(stderr, "no registered server named %s\n", name); + return NULL; + } + else if (loosename != NULL) + name = loosename; + + /* + * Send the command to target interpreter by appending it to the + * comm window in the communication window. + */ + + length = strlen(name) + strlen(cmd) + 10; + if (length <= STATIC_SPACE) + property = staticSpace; + else + property = (char *) malloc((unsigned) length); + + serial++; + sprintf(property, "%c%c%c-n %s%c-s %s", + 0, asKeys ? 'k' : 'c', 0, name, 0, cmd); + if (name == loosename) + free(loosename); + if (!asKeys) + { + /* Add a back reference to our comm window */ + sprintf(property + length, "%c-r %x %d", 0, (uint) commWindow, serial); + length += strlen(property + length + 1) + 1; + } + + res = AppendPropCarefully(dpy, w, commProperty, property, length + 1); + if (length > STATIC_SPACE) + free(property); + if (res < 0) + { + fprintf(stderr, "Failed to send command to the destination program\n"); + return NULL; + } + + if (asKeys) /* There is no answer for this - Keys are sent async */ + return NULL; + + + /* + * Enter a loop processing X events & pooling chars until we see the result + */ + +#define SEND_MSEC_POLL 50 + + time(&start); + while ((time((time_t *) 0) - start) < 60) + { + /* Look out for the answer */ +#ifndef HAVE_SELECT + struct pollfd fds; + + fds.fd = ConnectionNumber(dpy); + fds.events = POLLIN; + if (poll(&fds, 1, SEND_MSEC_POLL) < 0) + break; +#else + fd_set fds; + struct timeval tv; + + tv.tv_sec = 0; + tv.tv_usec = SEND_MSEC_POLL * 1000; + FD_ZERO(&fds); + FD_SET(ConnectionNumber(dpy), &fds); + if (select(ConnectionNumber(dpy) + 1, &fds, NULL, NULL, &tv) < 0) + break; +#endif + while (XEventsQueued(dpy, QueuedAfterReading) > 0) + { + XNextEvent(dpy, &event); + if (event.type == PropertyNotify && e->window == commWindow) + if ((result = SendEventProc(dpy, &event, serial, code)) != NULL) + return result; + } + } + return NULL; +} + + +/* + * SendInit -- + * This procedure is called to initialize the + * communication channels for sending commands and + * receiving results. + */ + + static int +SendInit(Display *dpy) +{ + XErrorHandler old_handler; + + /* + * Create the window used for communication, and set up an + * event handler for it. + */ + old_handler = XSetErrorHandler(x_error_check); + got_x_error = FALSE; + + commProperty = XInternAtom(dpy, "Comm", False); + /* Change this back to "InterpRegistry" to talk to tk processes */ + registryProperty = XInternAtom(dpy, "VimRegistry", False); + + if (commWindow == None) + { + commWindow = + XCreateSimpleWindow(dpy, XDefaultRootWindow(dpy), + getpid(), 0, 10, 10, 0, + WhitePixel(dpy, DefaultScreen(dpy)), + WhitePixel(dpy, DefaultScreen(dpy))); + XSelectInput(dpy, commWindow, PropertyChangeMask); + } + + XSync(dpy, False); + (void) XSetErrorHandler(old_handler); + + return got_x_error ? -1 : 0; +} + +/* + * LookupName -- + * Given an interpreter name, see if the name exists in + * the interpreter registry for a particular display. + * + * Results: + * If the given name is registered, return the ID of + * the window associated with the name. If the name + * isn't registered, then return 0. + */ + + static Window +LookupName( + Display *dpy, /* Display whose registry to check. */ + char *name, /* Name of an interpreter. */ + int delete, /* If non-zero, delete info about name. */ + char **loose) /* Do another search matching -999 if not found + Return result here if a match is found */ +{ + unsigned char *regProp, *entry; + unsigned char *p; + int result, actualFormat; + unsigned long numItems, bytesAfter; + Atom actualType; + Window returnValue; + + /* + * Read the registry property. + */ + + regProp = NULL; + result = XGetWindowProperty(dpy, RootWindow(dpy, 0), registryProperty, 0, + MAX_PROP_WORDS, False, XA_STRING, &actualType, + &actualFormat, &numItems, &bytesAfter, + ®Prop); + + if (actualType == None) + return 0; + + /* + * If the property is improperly formed, then delete it. + */ + + if ((result != Success) || (actualFormat != 8) || (actualType != XA_STRING)) + { + if (regProp != NULL) + XFree(regProp); + XDeleteProperty(dpy, RootWindow(dpy, 0), registryProperty); + return 0; + } + + /* + * Scan the property for the desired name. + */ + + returnValue = None; + entry = NULL; /* Not needed, but eliminates compiler warning. */ + for (p = regProp; (p - regProp) < numItems; ) + { + entry = p; + while ((*p != 0) && (!isspace(*p))) + p++; + if ((*p != 0) && (strcasecmp(name, p + 1) == 0)) + { + sscanf(entry, "%x", (uint*) &returnValue); + break; + } + while (*p != 0) + p++; + p++; + } + + if (loose != NULL && returnValue == None && !IsSerialName(name)) + { + for (p = regProp; (p - regProp) < numItems; ) + { + entry = p; + while ((*p != 0) && (!isspace(*p))) + p++; + if ((*p != 0) && IsSerialName(p + 1) + && (strncmp(name, p + 1, strlen(name)) == 0)) + { + sscanf(entry, "%x", (uint*) &returnValue); + *loose = strdup(p + 1); + break; + } + while (*p != 0) + p++; + p++; + } + } + + /* + * Delete the property, if that is desired (copy down the + * remainder of the registry property to overlay the deleted + * info, then rewrite the property). + */ + + if ((delete) && (returnValue != None)) + { + int count; + + while (*p != 0) + p++; + p++; + count = numItems - (p-regProp); + if (count > 0) + memcpy(entry, p, count); + XChangeProperty(dpy, RootWindow(dpy, 0), registryProperty, XA_STRING, + 8, PropModeReplace, regProp, + (int) (numItems - (p-entry))); + XSync(dpy, False); + } + + XFree(regProp); + return returnValue; +} + + static char * +SendEventProc( + Display *dpy, + XEvent *eventPtr, /* Information about event. */ + int expected, /* The one were waiting for */ + int *code) /* Return code. 0 => OK */ +{ + unsigned char *propInfo; + unsigned char *p; + int result, actualFormat; + int retCode; + unsigned long numItems, bytesAfter; + Atom actualType; + + if ((eventPtr->xproperty.atom != commProperty) + || (eventPtr->xproperty.state != PropertyNewValue)) + { + return; + } + + /* + * Read the comm property and delete it. + */ + + propInfo = NULL; + result = XGetWindowProperty(dpy, commWindow, commProperty, 0, + MAX_PROP_WORDS, True, XA_STRING, &actualType, + &actualFormat, &numItems, &bytesAfter, + &propInfo); + + /* + * If the property doesn't exist or is improperly formed + * then ignore it. + */ + + if ((result != Success) || (actualType != XA_STRING) + || (actualFormat != 8)) + { + if (propInfo != NULL) + { + XFree(propInfo); + } + return; + } + + /* + * Several commands and results could arrive in the property at + * one time; each iteration through the outer loop handles a + * single command or result. + */ + + for (p = propInfo; (p - propInfo) < numItems; ) + { + /* + * Ignore leading NULs; each command or result starts with a + * NUL so that no matter how badly formed a preceding command + * is, we'll be able to tell that a new command/result is + * starting. + */ + + if (*p == 0) + { + p++; + continue; + } + + if ((*p == 'r') && (p[1] == 0)) + { + int serial, gotSerial; + char *res; + + /* + * This is a reply to some command that we sent out. Iterate + * over all of its options. Stop when we reach the end of the + * property or something that doesn't look like an option. + */ + + p += 2; + gotSerial = 0; + res = ""; + retCode = 0; + while (((p-propInfo) < numItems) && (*p == '-')) + { + switch (p[1]) + { + case 'r': + if (p[2] == ' ') + res = p + 3; + break; + case 's': + if (sscanf(p + 2, " %d", &serial) == 1) + gotSerial = 1; + break; + case 'c': + if (sscanf(p + 2, " %d", &retCode) != 1) + retCode = 0; + break; + } + while (*p != 0) + p++; + p++; + } + + if (!gotSerial) + continue; + + if (code != NULL) + *code = retCode; + return serial == expected ? strdup(res) : NULL; + } + else + { + /* + * Didn't recognize this thing. Just skip through the next + * null character and try again. + * Also, throw away commands that we can't process anyway. + */ + + while (*p != 0) + p++; + p++; + } + } + XFree(propInfo); +} + +/* + * AppendPropCarefully -- + * + * Append a given property to a given window, but set up + * an X error handler so that if the append fails this + * procedure can return an error code rather than having + * Xlib panic. + * + * Return: + * 0 on OK - -1 on error + *-------------------------------------------------------------- + */ + + static int +AppendPropCarefully( + Display *dpy, /* Display on which to operate. */ + Window window, /* Window whose property is to + * be modified. */ + Atom property, /* Name of property. */ + char *value, /* Characters to append to property. */ + int length) /* How much to append */ +{ + XErrorHandler old_handler; + + old_handler = XSetErrorHandler(x_error_check); + got_x_error = FALSE; + XChangeProperty(dpy, window, property, XA_STRING, 8, + PropModeAppend, value, length); + XSync(dpy, False); + (void) XSetErrorHandler(old_handler); + return got_x_error ? -1 : 0; +} + + +/* + * Another X Error handler, just used to check for errors. + */ +/* ARGSUSED */ + static int +x_error_check(Display *dpy, XErrorEvent *error_event) +{ + got_x_error = TRUE; + return 0; +} + +/* + * Check if "str" looks like it had a serial number appended. + * Actually just checks if the name ends in a digit. + */ + static int +IsSerialName(char *str) +{ + int len = strlen(str); + + return (len > 1 && isdigit(str[len - 1])); +} |