diff src/if_xcmdsrv.c @ 7:3fc0f57ecb91 v7.0001

updated for version 7.0001
author vimboss
date Sun, 13 Jun 2004 20:20:40 +0000
parents
children db5102f7e29f
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/src/if_xcmdsrv.c
@@ -0,0 +1,1441 @@
+/* vi:set ts=8 sts=4 sw=4:
+ *
+ * VIM - Vi IMproved	by Bram Moolenaar
+ * X command server 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.
+ *
+ * if_xcmdsrv.c: Functions for passing commands through an X11 display.
+ *
+ */
+
+#include "vim.h"
+#include "version.h"
+
+#if defined(FEAT_CLIENTSERVER) || defined(PROTO)
+
+# ifdef FEAT_X11
+#  include <X11/Intrinsic.h>
+#  include <X11/Xatom.h>
+# endif
+
+# if defined(HAVE_SYS_SELECT_H) && \
+	(!defined(HAVE_SYS_TIME_H) || defined(SYS_SELECT_WITH_SYS_TIME))
+#  include <sys/select.h>
+# endif
+
+# ifndef HAVE_SELECT
+#  ifdef HAVE_SYS_POLL_H
+#   include <sys/poll.h>
+#  else
+#   ifdef HAVE_POLL_H
+#    include <poll.h>
+#   endif
+#  endif
+# endif
+
+/*
+ * This file provides procedures that implement the command server functionality
+ * of Vim when in contact with an X11 server.
+ *
+ * Adapted from TCL/TK's send command  in tkSend.c of the tk 3.6 distribution.
+ * Adapted for use in Vim by Flemming Madsen. Protocol changed to that of tk 4
+ */
+
+/*
+ * Copyright (c) 1989-1993 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and its documentation for any purpose, provided that the
+ * above copyright notice and the following two paragraphs appear in
+ * all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+
+/*
+ * When a result is being awaited from a sent command, one of
+ * the following structures is present on a list of all outstanding
+ * sent commands.  The information in the structure is used to
+ * process the result when it arrives.  You're probably wondering
+ * how there could ever be multiple outstanding sent commands.
+ * This could happen if Vim instances invoke each other recursively.
+ * It's unlikely, but possible.
+ */
+
+typedef struct PendingCommand
+{
+    int	    serial;	/* Serial number expected in result. */
+    int	    code;	/* Result Code. 0 is OK */
+    char_u  *result;	/* String result for command (malloc'ed).
+			 * NULL means command still pending. */
+    struct PendingCommand *nextPtr;
+			/* Next in list of all outstanding commands.
+			 * NULL means end of list. */
+} PendingCommand;
+
+static PendingCommand *pendingCommands = NULL;
+				/* List of all commands currently
+				 * being waited for. */
+
+/*
+ * The information below is used for communication between processes
+ * during "send" commands.  Each process keeps a private window, never
+ * even mapped, with one property, "Comm".  When a command is sent to
+ * an interpreter, the command is appended to the comm property of the
+ * communication window associated with the interp's process.  Similarly,
+ * when a result is returned from a sent command, it is also appended
+ * to the comm property.
+ *
+ * Each command and each result takes the form of ASCII text.  For a
+ * command, the text consists of a nul character followed by several
+ * nul-terminated ASCII strings.  The first string consists of the
+ * single letter "c" for an expression, or "k" for keystrokes.  Subsequent
+ * strings have the form "option value" where the following options are
+ * supported:
+ *
+ * -r commWindow serial
+ *
+ *	This option means that a response should be sent to the window
+ *	whose X identifier is "commWindow" (in hex), and the response should
+ *	be identified with the serial number given by "serial" (in decimal).
+ *	If this option isn't specified then the send is asynchronous and
+ *	no response is sent.
+ *
+ * -n name
+ *	"Name" gives the name of the application for which the command is
+ *	intended.  This option must be present.
+ *
+ * -s script
+ *	"Script" is the script to be executed.  This option must be
+ *	present.  Taken as a series of keystrokes in a "k" command where
+ *	<Key>'s are expanded
+ *
+ * The options may appear in any order.  The -n and -s options must be
+ * present, but -r may be omitted for asynchronous RPCs.  For compatibility
+ * with future releases that may add new features, there may be additional
+ * options present;  as long as they start with a "-" character, they will
+ * be ignored.
+ *
+ * A result also consists of a zero character followed by several null-
+ * terminated ASCII strings.  The first string consists of the single
+ * letter "r".  Subsequent strings have the form "option value" where
+ * the following options are supported:
+ *
+ * -s serial
+ *	Identifies the command for which this is the result.  It is the
+ *	same as the "serial" field from the -s option in the command.  This
+ *	option must be present.
+ *
+ * -r result
+ *	"Result" is the result string for the script, which may be either
+ *	a result or an error message.  If this field is omitted then it
+ *	defaults to an empty string.
+ *
+ * -c code
+ *	0: for OK. This is the default.
+ *	1: for error: Result is the last error
+ *
+ * -i errorInfo
+ * -e errorCode
+ *	Not applicable for Vim
+ *
+ * Options may appear in any order, and only the -s option must be
+ * present.  As with commands, there may be additional options besides
+ * these;  unknown options are ignored.
+ */
+
+/*
+ * Maximum size property that can be read at one time by
+ * this module:
+ */
+
+#define MAX_PROP_WORDS 100000
+
+struct ServerReply
+{
+    Window  id;
+    garray_T strings;
+};
+static garray_T serverReply = { 0, 0, 0, 0, 0 };
+enum ServerReplyOp { SROP_Find, SROP_Add, SROP_Delete };
+
+typedef int (*EndCond) __ARGS((void *));
+
+/*
+ * Forward declarations for procedures defined later in this file:
+ */
+
+static Window	LookupName __ARGS((Display *dpy, char_u *name, int delete, char_u **loose));
+static int	SendInit __ARGS((Display *dpy));
+static int	DoRegisterName __ARGS((Display *dpy, char_u *name));
+static void	DeleteAnyLingerer __ARGS((Display *dpy, Window w));
+static int	GetRegProp __ARGS((Display *dpy, char_u **regPropp, long_u *numItemsp, int domsg));
+static int	WaitForPend __ARGS((void *p));
+static int	WaitForReply __ARGS((void *p));
+static int	WindowValid __ARGS((Display *dpy, Window w));
+static void	ServerWait __ARGS((Display *dpy, Window w, EndCond endCond, void *endData, int localLoop, int seconds));
+static struct ServerReply *ServerReplyFind __ARGS((Window w, enum ServerReplyOp op));
+static int	AppendPropCarefully __ARGS((Display *display, Window window, Atom property, char_u *value, int length));
+static int	x_error_check __ARGS((Display *dpy, XErrorEvent *error_event));
+static int	IsSerialName __ARGS((char_u *name));
+
+/* Private variables for the "server" functionality */
+static Atom	registryProperty = None;
+static Atom	vimProperty = None;
+static int	got_x_error = FALSE;
+
+static char_u	*empty_prop = (char_u *)"";	/* empty GetRegProp() result */
+
+/*
+ * Associate an ASCII name with Vim.  Try real hard to get a unique one.
+ * Returns FAIL or OK.
+ */
+    int
+serverRegisterName(dpy, name)
+    Display	*dpy;		/* display to register with */
+    char_u	*name;		/* the name that will be used as a base */
+{
+    int		i;
+    int		res;
+    char_u	*p = NULL;
+
+    res = DoRegisterName(dpy, name);
+    if (res < 0)
+    {
+	i = 1;
+	do
+	{
+	    if (res < -1 || i >= 1000)
+	    {
+		MSG_ATTR(_("Unable to register a command server name"),
+							      hl_attr(HLF_W));
+		return FAIL;
+	    }
+	    if (p == NULL)
+		p = alloc(STRLEN(name) + 10);
+	    if (p == NULL)
+	    {
+		res = -10;
+		continue;
+	    }
+	    sprintf((char *)p, "%s%d", name, i++);
+	    res = DoRegisterName(dpy, p);
+	}
+	while (res < 0)
+	    ;
+	vim_free(p);
+    }
+    return OK;
+}
+
+    static int
+DoRegisterName(dpy, name)
+    Display	*dpy;
+    char_u	*name;
+{
+    Window	w;
+    XErrorHandler old_handler;
+#define MAX_NAME_LENGTH 100
+    char_u	propInfo[MAX_NAME_LENGTH + 20];
+
+    if (commProperty == None)
+    {
+	if (SendInit(dpy) < 0)
+	    return -2;
+    }
+
+    /*
+     * Make sure the name is unique, and append info about it to
+     * the registry property.  It's important to lock the server
+     * here to prevent conflicting changes to the registry property.
+     * WARNING: Do not step through this while debugging, it will hangup the X
+     * server!
+     */
+    XGrabServer(dpy);
+    w = LookupName(dpy, name, FALSE, NULL);
+    if (w != (Window)0)
+    {
+	Status		status;
+	int		dummyInt;
+	unsigned int	dummyUns;
+	Window		dummyWin;
+
+	/*
+	 * The name is currently registered.  See if the commWindow
+	 * associated with the name exists.  If not, or if the commWindow
+	 * is *our* commWindow, then just unregister the old name (this
+	 * could happen if an application dies without cleaning up the
+	 * registry).
+	 */
+	old_handler = XSetErrorHandler(x_error_check);
+	status = XGetGeometry(dpy, w, &dummyWin, &dummyInt, &dummyInt,
+				  &dummyUns, &dummyUns, &dummyUns, &dummyUns);
+	(void)XSetErrorHandler(old_handler);
+	if (status != Success && w != commWindow)
+	{
+	    XUngrabServer(dpy);
+	    XFlush(dpy);
+	    return -1;
+	}
+	(void)LookupName(dpy, name, /*delete=*/TRUE, NULL);
+    }
+    sprintf((char *)propInfo, "%x %.*s", (int_u)commWindow,
+						       MAX_NAME_LENGTH, name);
+    old_handler = XSetErrorHandler(x_error_check);
+    got_x_error = FALSE;
+    XChangeProperty(dpy, RootWindow(dpy, 0), registryProperty, XA_STRING, 8,
+		    PropModeAppend, propInfo, STRLEN(propInfo) + 1);
+    XUngrabServer(dpy);
+    XSync(dpy, False);
+    (void)XSetErrorHandler(old_handler);
+
+    if (!got_x_error)
+    {
+#ifdef FEAT_EVAL
+	set_vim_var_string(VV_SEND_SERVER, name, -1);
+#endif
+	serverName = vim_strsave(name);
+#ifdef FEAT_TITLE
+	need_maketitle = TRUE;
+#endif
+	return 0;
+    }
+    return -2;
+}
+
+#if defined(FEAT_GUI) || defined(PROTO)
+/*
+ * Clean out new ID from registry and set it as comm win.
+ * Change any registered window ID.
+ */
+    void
+serverChangeRegisteredWindow(dpy, newwin)
+    Display	*dpy;		/* Display to register with */
+    Window	newwin;		/* Re-register to this ID */
+{
+    char_u	propInfo[MAX_NAME_LENGTH + 20];
+
+    commWindow = newwin;
+
+    /* Always call SendInit() here, to make sure commWindow is marked as a Vim
+     * window. */
+    if (SendInit(dpy) < 0)
+	return;
+
+    /* WARNING: Do not step through this while debugging, it will hangup the X
+     * server! */
+    XGrabServer(dpy);
+    DeleteAnyLingerer(dpy, newwin);
+    if (serverName != NULL)
+    {
+	/* Reinsert name if we was already registered */
+	(void)LookupName(dpy, serverName, /*delete=*/TRUE, NULL);
+	sprintf((char *)propInfo, "%x %.*s",
+		(int_u)newwin, MAX_NAME_LENGTH, serverName);
+	XChangeProperty(dpy, RootWindow(dpy, 0), registryProperty, XA_STRING, 8,
+			PropModeAppend, (char_u *)propInfo,
+			STRLEN(propInfo) + 1);
+    }
+    XUngrabServer(dpy);
+}
+#endif
+
+/*
+ * Send to an instance of Vim via the X display.
+ * Returns 0 for OK, negative for an error.
+ */
+    int
+serverSendToVim(dpy, name, cmd,  result, server, asExpr, localLoop, silent)
+    Display	*dpy;			/* Where to send. */
+    char_u	*name;			/* Where to send. */
+    char_u	*cmd;			/* What to send. */
+    char_u	**result;		/* Result of eval'ed expression */
+    Window	*server;		/* Actual ID of receiving app */
+    Bool	asExpr;			/* Interpret as keystrokes or expr ? */
+    Bool	localLoop;		/* Throw away everything but result */
+    int		silent;			/* don't complain about no server */
+{
+    Window	    w;
+    char_u	    *property;
+    int		    length;
+    int		    res;
+    static int	    serial = 0;	/* Running count of sent commands.
+				 * Used to give each command a
+				 * different serial number. */
+    PendingCommand  pending;
+    char_u	    *loosename = NULL;
+
+    if (result != NULL)
+	*result = NULL;
+    if (name == NULL || *name == NUL)
+	name = (char_u *)"GVIM";    /* use a default name */
+
+    if (commProperty == None && dpy != NULL)
+    {
+	if (SendInit(dpy) < 0)
+	    return -1;
+    }
+
+    /* Execute locally if no display or target is ourselves */
+    if (dpy == NULL || (serverName != NULL && STRICMP(name, serverName) == 0))
+    {
+	if (asExpr)
+	{
+	    char_u *ret;
+
+	    ret = eval_client_expr_to_string(cmd);
+	    if (result != NULL)
+	    {
+		if (ret == NULL)
+		    *result = vim_strsave((char_u *)_(e_invexprmsg));
+		else
+		    *result = ret;
+	    }
+	    else
+		vim_free(ret);
+	    return ret == NULL ? -1 : 0;
+	}
+	else
+	    server_to_input_buf(cmd);
+	return 0;
+    }
+
+    /*
+     * 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.
+     */
+    while (TRUE)
+    {
+	w = LookupName(dpy, name, FALSE, &loosename);
+	/* Check that the window is hot */
+	if (w != None)
+	{
+	    if (!WindowValid(dpy, w))
+	    {
+		LookupName(dpy, loosename ? loosename : name,
+			   /*DELETE=*/TRUE, NULL);
+		continue;
+	    }
+	}
+	break;
+    }
+    if (w == None)
+    {
+	if (!silent)
+	    EMSG2(_(e_noserver), name);
+	return -1;
+    }
+    else if (loosename != NULL)
+	name = loosename;
+    if (server != NULL)
+	*server = w;
+
+    /*
+     * Send the command to target interpreter by appending it to the
+     * comm window in the communication window.
+     */
+    length = STRLEN(name) + STRLEN(cmd) + 10;
+    property = (char_u *)alloc((unsigned) length + 30);
+
+    sprintf((char *)property, "%c%c%c-n %s%c-s %s",
+		      0, asExpr ? 'c' : 'k', 0, name, 0, cmd);
+    if (name == loosename)
+	vim_free(loosename);
+    /* Add a back reference to our comm window */
+    serial++;
+    sprintf((char *)property + length, "%c-r %x %d",
+						0, (int_u)commWindow, serial);
+    length += STRLEN(property + length + 1) + 1;
+
+    res = AppendPropCarefully(dpy, w, commProperty, property, length + 1);
+    vim_free(property);
+    if (res < 0)
+    {
+	EMSG(_("E248: Failed to send command to the destination program"));
+	return -1;
+    }
+
+    if (!asExpr) /* There is no answer for this - Keys are sent async */
+	return 0;
+
+    /*
+     * Register the fact that we're waiting for a command to
+     * complete (this is needed by SendEventProc and by
+     * AppendErrorProc to pass back the command's results).
+     */
+    pending.serial = serial;
+    pending.code = 0;
+    pending.result = NULL;
+    pending.nextPtr = pendingCommands;
+    pendingCommands = &pending;
+
+    ServerWait(dpy, w, WaitForPend, &pending, localLoop, 600);
+
+    /*
+     * Unregister the information about the pending command
+     * and return the result.
+     */
+    if (pendingCommands == &pending)
+	pendingCommands = pending.nextPtr;
+    else
+    {
+	PendingCommand *pcPtr;
+
+	for (pcPtr = pendingCommands; pcPtr != NULL; pcPtr = pcPtr->nextPtr)
+	    if (pcPtr->nextPtr == &pending)
+	    {
+		pcPtr->nextPtr = pending.nextPtr;
+		break;
+	    }
+    }
+    if (result != NULL)
+	*result = pending.result;
+    else
+	vim_free(pending.result);
+
+    return pending.code == 0 ? 0 : -1;
+}
+
+    static int
+WaitForPend(p)
+    void    *p;
+{
+    PendingCommand *pending = (PendingCommand *) p;
+    return pending->result != NULL;
+}
+
+/*
+ * Return TRUE if window "w" exists and has a "Vim" property on it.
+ */
+    static int
+WindowValid(dpy, w)
+    Display     *dpy;
+    Window	w;
+{
+    XErrorHandler   old_handler;
+    Atom	    *plist;
+    int		    numProp;
+    int		    i;
+
+    old_handler = XSetErrorHandler(x_error_check);
+    got_x_error = 0;
+    plist = XListProperties(dpy, w, &numProp);
+    XSync(dpy, False);
+    XSetErrorHandler(old_handler);
+    if (plist == NULL || got_x_error)
+	return FALSE;
+
+    for (i = 0; i < numProp; i++)
+	if (plist[i] == vimProperty)
+	{
+	    XFree(plist);
+	    return TRUE;
+	}
+    XFree(plist);
+    return FALSE;
+}
+
+/*
+ * Enter a loop processing X events & polling chars until we see a result
+ */
+    static void
+ServerWait(dpy, w, endCond, endData, localLoop, seconds)
+    Display	*dpy;
+    Window	w;
+    EndCond	endCond;
+    void	*endData;
+    int		localLoop;
+    int		seconds;
+{
+    time_t	    start;
+    time_t	    now;
+    time_t	    lastChk = 0;
+    XEvent	    event;
+    XPropertyEvent *e = (XPropertyEvent *)&event;
+#   define SEND_MSEC_POLL 50
+
+    time(&start);
+    while (endCond(endData) == 0)
+    {
+	time(&now);
+	if (seconds >= 0 && (now - start) >= seconds)
+	    break;
+	if (now != lastChk)
+	{
+	    lastChk = now;
+	    if (!WindowValid(dpy, w))
+		break;
+	    /*
+	     * Sometimes the PropertyChange event doesn't come.
+	     * This can be seen in eg: vim -c 'echo remote_expr("gvim", "3+2")'
+	     */
+	    serverEventProc(dpy, NULL);
+	}
+	if (localLoop)
+	{
+	    /* Just look out for the answer without calling back into Vim */
+#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)
+		    serverEventProc(dpy, &event);
+	    }
+	}
+	else
+	{
+	    if (got_int)
+		break;
+	    ui_delay((long)SEND_MSEC_POLL, TRUE);
+	    ui_breakcheck();
+	}
+    }
+}
+
+
+/*
+ * Fetch a list of all the Vim instance names currently registered for the
+ * display.
+ *
+ * Returns a newline separated list in allocated memory or NULL.
+ */
+    char_u *
+serverGetVimNames(dpy)
+    Display	*dpy;
+{
+    char_u	*regProp;
+    char_u	*entry;
+    char_u	*p;
+    long_u	numItems;
+    int_u	w;
+    garray_T	ga;
+
+    if (registryProperty == None)
+    {
+	if (SendInit(dpy) < 0)
+	    return NULL;
+    }
+    ga_init2(&ga, 1, 100);
+
+    /*
+     * Read the registry property.
+     */
+    if (GetRegProp(dpy, &regProp, &numItems, TRUE) == FAIL)
+	return NULL;
+
+    /*
+     * Scan all of the names out of the property.
+     */
+    ga_init2(&ga, 1, 100);
+    for (p = regProp; (p - regProp) < numItems; p++)
+    {
+	entry = p;
+	while (*p != 0 && !isspace(*p))
+	    p++;
+	if (*p != 0)
+	{
+	    w = None;
+	    sscanf((char *)entry, "%x", &w);
+	    if (WindowValid(dpy, (Window)w))
+	    {
+		ga_concat(&ga, p + 1);
+		ga_concat(&ga, (char_u *)"\n");
+	    }
+	    while (*p != 0)
+		p++;
+	}
+    }
+    if (regProp != empty_prop)
+	XFree(regProp);
+    return ga.ga_data;
+}
+
+/* ----------------------------------------------------------
+ * Reply stuff
+ */
+
+    static struct ServerReply *
+ServerReplyFind(w, op)
+    Window  w;
+    enum ServerReplyOp op;
+{
+    struct ServerReply *p;
+    struct ServerReply e;
+    int		i;
+
+    p = (struct ServerReply *) serverReply.ga_data;
+    for (i = 0; i < serverReply.ga_len; i++, p++)
+	if (p->id == w)
+	    break;
+    if (i >= serverReply.ga_len)
+	p = NULL;
+
+    if (p == NULL && op == SROP_Add)
+    {
+	if (serverReply.ga_growsize == 0)
+	    ga_init2(&serverReply, sizeof(struct ServerReply), 1);
+	if (ga_grow(&serverReply, 1) == OK)
+	{
+	    p = ((struct ServerReply *) serverReply.ga_data)
+		+ serverReply.ga_len;
+	    e.id = w;
+	    ga_init2(&e.strings, 1, 100);
+	    memcpy(p, &e, sizeof(e));
+	    serverReply.ga_len++;
+	    serverReply.ga_room--;
+	}
+    }
+    else if (p != NULL && op == SROP_Delete)
+    {
+	ga_clear(&p->strings);
+	mch_memmove(p, p + 1, (serverReply.ga_len - i - 1) * sizeof(*p));
+	serverReply.ga_len--;
+	serverReply.ga_room++;
+    }
+
+    return p;
+}
+
+/*
+ * Convert string to windowid.
+ * Issue an error if the id is invalid.
+ */
+    Window
+serverStrToWin(str)
+    char_u  *str;
+{
+    unsigned  id = None;
+
+    sscanf((char *)str, "0x%x", &id);
+    if (id == None)
+	EMSG2(_("E573: Invalid server id used: %s"), str);
+
+    return (Window)id;
+}
+
+/*
+ * Send a reply string to client with id "name".
+ * Return -1 if the window is invalid.
+ */
+    int
+serverSendReply(name, str)
+    char_u	*name;
+    char_u	*str;
+{
+    char_u	*property;
+    int		length;
+    int		res;
+    Display	*dpy = X_DISPLAY;
+    Window	win = serverStrToWin(name);
+
+    if (commProperty == None)
+    {
+	if (SendInit(dpy) < 0)
+	    return -2;
+    }
+    if (!WindowValid(dpy, win))
+	return -1;
+
+    length = STRLEN(str) + 7;
+    if ((property = (char_u *)alloc((unsigned) length + 30)) != NULL)
+    {
+	sprintf((char *)property, "%c%c%c-n %s%c-w %x",
+			  0, 'n', 0, str, 0, (unsigned int)commWindow);
+	length += STRLEN(property + length);
+	res = AppendPropCarefully(dpy, win, commProperty, property, length + 1);
+	vim_free(property);
+	return res;
+    }
+    return -1;
+}
+
+    static int
+WaitForReply(p)
+    void    *p;
+{
+    Window  *w = (Window *) p;
+    return ServerReplyFind(*w, SROP_Find) != NULL;
+}
+
+/*
+ * Wait for replies from id (win)
+ * Return 0 and the malloc'ed string when a reply is available.
+ * Return -1 if the window becomes invalid while waiting.
+ */
+    int
+serverReadReply(dpy, win, str, localLoop)
+    Display	*dpy;
+    Window	win;
+    char_u	**str;
+    int		localLoop;
+{
+    int		len;
+    char_u	*s;
+    struct	ServerReply *p;
+
+    ServerWait(dpy, win, WaitForReply, &win, localLoop, -1);
+
+    if ((p = ServerReplyFind(win, SROP_Find)) != NULL && p->strings.ga_len > 0)
+    {
+	*str = vim_strsave(p->strings.ga_data);
+	len = STRLEN(*str) + 1;
+	if (len < p->strings.ga_len)
+	{
+	    s = (char_u *) p->strings.ga_data;
+	    mch_memmove(s, s + len, p->strings.ga_len - len);
+	    p->strings.ga_room += len;
+	    p->strings.ga_len -= len;
+	}
+	else
+	{
+	    /* Last string read.  Remove from list */
+	    ga_clear(&p->strings);
+	    ServerReplyFind(win, SROP_Delete);
+	}
+	return 0;
+    }
+    return -1;
+}
+
+/*
+ * Check for replies from id (win).
+ * Return TRUE and a non-malloc'ed string if there is.  Else return FALSE.
+ */
+    int
+serverPeekReply(dpy, win, str)
+    Display *dpy;
+    Window win;
+    char_u **str;
+{
+    struct ServerReply *p;
+
+    if ((p = ServerReplyFind(win, SROP_Find)) != NULL && p->strings.ga_len > 0)
+    {
+	if (str != NULL)
+	    *str = p->strings.ga_data;
+	return 1;
+    }
+    if (!WindowValid(dpy, win))
+	return -1;
+    return 0;
+}
+
+
+/*
+ * Initialize the communication channels for sending commands and receiving
+ * results.
+ */
+    static int
+SendInit(dpy)
+    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;
+
+    if (commProperty == None)
+	commProperty = XInternAtom(dpy, "Comm", False);
+    if (vimProperty == None)
+	vimProperty = XInternAtom(dpy, "Vim", False);
+    if (registryProperty == None)
+	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);
+	/* WARNING: Do not step through this while debugging, it will hangup
+	 * the X server! */
+	XGrabServer(dpy);
+	DeleteAnyLingerer(dpy, commWindow);
+	XUngrabServer(dpy);
+    }
+
+    /* Make window recognizable as a vim window */
+    XChangeProperty(dpy, commWindow, vimProperty, XA_STRING,
+		    8, PropModeReplace, (char_u *)VIM_VERSION_SHORT,
+			(int)STRLEN(VIM_VERSION_SHORT) + 1);
+
+    XSync(dpy, False);
+    (void)XSetErrorHandler(old_handler);
+
+    return got_x_error ? -1 : 0;
+}
+
+/*
+ * Given a server name, see if the name exists in the registry for a
+ * particular display.
+ *
+ * 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.
+ *
+ * Side effects:
+ *	If the registry property is improperly formed, then it is deleted.
+ *	If "delete" is non-zero, then if the named server is found it is
+ *	removed from the registry property.
+ */
+    static Window
+LookupName(dpy, name, delete, loose)
+    Display	*dpy;	    /* Display whose registry to check. */
+    char_u	*name;	    /* Name of a server. */
+    int		delete;	    /* If non-zero, delete info about name. */
+    char_u	**loose;    /* Do another search matching -999 if not found
+			       Return result here if a match is found */
+{
+    char_u	*regProp, *entry;
+    char_u	*p;
+    long_u	numItems;
+    int_u	returnValue;
+
+    /*
+     * Read the registry property.
+     */
+    if (GetRegProp(dpy, &regProp, &numItems, FALSE) == FAIL)
+	return 0;
+
+    /*
+     * Scan the property for the desired name.
+     */
+    returnValue = (int_u)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 && STRICMP(name, p + 1) == 0)
+	{
+	    sscanf((char *)entry, "%x", &returnValue);
+	    break;
+	}
+	while (*p != 0)
+	    p++;
+	p++;
+    }
+
+    if (loose != NULL && returnValue == (int_u)None && !IsSerialName(name))
+    {
+	for (p = regProp; (p - regProp) < numItems; )
+	{
+	    entry = p;
+	    while (*p != 0 && !isspace(*p))
+		p++;
+	    if (*p != 0 && IsSerialName(p + 1)
+		    && STRNICMP(name, p + 1, STRLEN(name)) == 0)
+	    {
+		sscanf((char *)entry, "%x", &returnValue);
+		*loose = vim_strsave(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 != (int_u)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);
+    }
+
+    if (regProp != empty_prop)
+	XFree(regProp);
+    return (Window)returnValue;
+}
+
+/*
+ * Delete any lingering occurences of window id.  We promise that any
+ * occurences is not ours since it is not yet put into the registry (by us)
+ *
+ * This is necessary in the following scenario:
+ * 1. There is an old windowid for an exit'ed vim in the registry
+ * 2. We get that id for our commWindow but only want to send, not register.
+ * 3. The window will mistakenly be regarded valid because of own commWindow
+ */
+    static void
+DeleteAnyLingerer(dpy, win)
+    Display	*dpy;	/* Display whose registry to check. */
+    Window	win;	/* Window to remove */
+{
+    char_u	*regProp, *entry = NULL;
+    char_u	*p;
+    long_u	numItems;
+    Window	wwin;
+
+    /*
+     * Read the registry property.
+     */
+    if (GetRegProp(dpy, &regProp, &numItems, FALSE) == FAIL)
+	return;
+
+    /* Scan the property for the window id.  */
+    for (p = regProp; (p - regProp) < numItems; )
+    {
+	if (*p != 0)
+	{
+	    sscanf((char *)p, "%x", (int_u *)&wwin);
+	    if (wwin == win)
+	    {
+		int lastHalf;
+
+		/* Copy down the remainder to delete entry */
+		entry = p;
+		while (*p != 0)
+		    p++;
+		p++;
+		lastHalf = numItems - (p - regProp);
+		if (lastHalf > 0)
+		    memcpy(entry, p, lastHalf);
+		numItems = (entry - regProp) + lastHalf;
+		p = entry;
+		continue;
+	    }
+	}
+	while (*p != 0)
+	    p++;
+	p++;
+    }
+
+    if (entry != NULL)
+    {
+	XChangeProperty(dpy, RootWindow(dpy, 0), registryProperty,
+			XA_STRING, 8, PropModeReplace, regProp,
+			(int)(p - regProp));
+	XSync(dpy, False);
+    }
+
+    if (regProp != empty_prop)
+	XFree(regProp);
+}
+
+/*
+ * Read the registry property.  Delete it when it's formatted wrong.
+ * Return the property in "regPropp".  "empty_prop" is used when it doesn't
+ * exist yet.
+ * Return OK when successful.
+ */
+    static int
+GetRegProp(dpy, regPropp, numItemsp, domsg)
+    Display	*dpy;
+    char_u	**regPropp;
+    long_u	*numItemsp;
+    int		domsg;		/* When TRUE give error message. */
+{
+    int		result, actualFormat;
+    long_u	bytesAfter;
+    Atom	actualType;
+
+    *regPropp = NULL;
+    result = XGetWindowProperty(dpy, RootWindow(dpy, 0), registryProperty, 0L,
+				(long)MAX_PROP_WORDS, False,
+				XA_STRING, &actualType,
+				&actualFormat, numItemsp, &bytesAfter,
+				regPropp);
+
+    if (actualType == None)
+    {
+	/* No prop yet. Logically equal to the empty list */
+	*numItemsp = 0;
+	*regPropp = empty_prop;
+	return OK;
+    }
+
+    /* If the property is improperly formed, then delete it. */
+    if (result != Success || actualFormat != 8 || actualType != XA_STRING)
+    {
+	if (*regPropp != NULL)
+	    XFree(*regPropp);
+	XDeleteProperty(dpy, RootWindow(dpy, 0), registryProperty);
+	if (domsg)
+	    EMSG(_("E251: VIM instance registry property is badly formed.  Deleted!"));
+	return FAIL;
+    }
+    return OK;
+}
+
+/*
+ * This procedure is invoked by the varous X event loops throughout Vims when
+ * a property changes on the communication window.  This procedure reads the
+ * property and handles command requests and responses.
+ */
+    void
+serverEventProc(dpy, eventPtr)
+    Display	*dpy;
+    XEvent	*eventPtr;		/* Information about event. */
+{
+    char_u	*propInfo;
+    char_u	*p;
+    int		result, actualFormat, code;
+    long_u	numItems, bytesAfter;
+    Atom	actualType;
+
+    if (eventPtr != NULL)
+    {
+	if (eventPtr->xproperty.atom != commProperty
+		|| eventPtr->xproperty.state != PropertyNewValue)
+	    return;
+    }
+
+    /*
+     * Read the comm property and delete it.
+     */
+    propInfo = NULL;
+    result = XGetWindowProperty(dpy, commWindow, commProperty, 0L,
+				(long)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 == 'c' || *p == 'k') && (p[1] == 0))
+	{
+	    Window	resWindow;
+	    char_u	*name, *script, *serial, *end, *res;
+	    Bool	asKeys = *p == 'k';
+	    garray_T	reply;
+
+	    /*
+	     * This is an incoming command from some other application.
+	     * 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;
+	    name = NULL;
+	    resWindow = None;
+	    serial = (char_u *)"";
+	    script = NULL;
+	    while (p - propInfo < numItems && *p == '-')
+	    {
+		switch (p[1])
+		{
+		    case 'r':
+			end = skipwhite(p + 2);
+			resWindow = 0;
+			while (vim_isxdigit(*end))
+			{
+			    resWindow = 16 * resWindow + (long_u)hex2nr(*end);
+			    ++end;
+			}
+			if (end == p + 2 || *end != ' ')
+			    resWindow = None;
+			else
+			{
+			    p = serial = end + 1;
+			    clientWindow = resWindow; /* Remember in global */
+			}
+			break;
+		    case 'n':
+			if (p[2] == ' ')
+			    name = p + 3;
+			break;
+		    case 's':
+			if (p[2] == ' ')
+			    script = p + 3;
+			break;
+		}
+		while (*p != 0)
+		    p++;
+		p++;
+	    }
+
+	    if (script == NULL || name == NULL)
+		continue;
+
+	    /*
+	     * Initialize the result property, so that we're ready at any
+	     * time if we need to return an error.
+	     */
+	    if (resWindow != None)
+	    {
+		ga_init2(&reply, 1, 100);
+		ga_grow(&reply, 50);
+		sprintf(reply.ga_data, "%cr%c-s %s%c-r ", 0, 0, serial, 0);
+		reply.ga_len = 10 + STRLEN(serial);
+		reply.ga_room -= reply.ga_len;
+	    }
+	    res = NULL;
+	    if (serverName != NULL && STRICMP(name, serverName) == 0)
+	    {
+		if (asKeys)
+		    server_to_input_buf(script);
+		else
+		    res = eval_client_expr_to_string(script);
+	    }
+	    if (resWindow != None)
+	    {
+		if (res != NULL)
+		    ga_concat(&reply, res);
+		else if (asKeys == 0)
+		{
+		    ga_concat(&reply, (char_u *)_(e_invexprmsg));
+		    ga_append(&reply, 0);
+		    ga_concat(&reply, (char_u *)"-c 1");
+		}
+		ga_append(&reply, 0);
+		(void)AppendPropCarefully(dpy, resWindow, commProperty,
+					   reply.ga_data, reply.ga_len);
+	    }
+	    vim_free(res);
+	}
+	else if (*p == 'r' && p[1] == 0)
+	{
+	    int	    serial, gotSerial;
+	    char_u  *res;
+	    PendingCommand *pcPtr;
+
+	    /*
+	     * 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 = (char_u *)"";
+	    code = 0;
+	    while ((p-propInfo) < numItems && *p == '-')
+	    {
+		switch (p[1])
+		{
+		    case 'r':
+			if (p[2] == ' ')
+			    res = p + 3;
+			break;
+		    case 's':
+			if (sscanf((char *)p + 2, " %d", &serial) == 1)
+			    gotSerial = 1;
+			break;
+		    case 'c':
+			if (sscanf((char *)p + 2, " %d", &code) != 1)
+			    code = 0;
+			break;
+		}
+		while (*p != 0)
+		    p++;
+		p++;
+	    }
+
+	    if (!gotSerial)
+		continue;
+
+	    /*
+	     * Give the result information to anyone who's
+	     * waiting for it.
+	     */
+	    for (pcPtr = pendingCommands; pcPtr != NULL; pcPtr = pcPtr->nextPtr)
+	    {
+		if (serial != pcPtr->serial || pcPtr->result != NULL)
+		    continue;
+
+		pcPtr->code = code;
+		if (res != NULL)
+		    pcPtr->result = vim_strsave(res);
+		else
+		    pcPtr->result = vim_strsave((char_u *)"");
+		break;
+	    }
+	}
+	else if (*p == 'n' && p[1] == 0)
+	{
+	    Window	win = 0;
+	    unsigned int u;
+	    int		gotWindow;
+	    char_u	*str;
+	    char_u	winstr[30];
+	    struct	ServerReply *r;
+
+	    /*
+	     * This is a (n)otification.  Sent with serverreply_send in VimL.
+	     * Execute any autocommand and save it for later retrieval
+	     */
+	    p += 2;
+	    gotWindow = 0;
+	    str = (char_u *)"";
+	    while ((p-propInfo) < numItems && *p == '-')
+	    {
+		switch (p[1])
+		{
+		    case 'n':
+			if (p[2] == ' ')
+			    str = p + 3;
+			break;
+		    case 'w':
+			if (sscanf((char *)p + 2, " %x", &u) == 1)
+			{
+			    win = u;
+			    gotWindow = 1;
+			}
+			break;
+		}
+		while (*p != 0)
+		    p++;
+		p++;
+	    }
+
+	    if (!gotWindow)
+		continue;
+	    if ((r = ServerReplyFind(win, SROP_Add)) != NULL)
+	    {
+		ga_concat(&(r->strings), str);
+		ga_append(&(r->strings), 0);
+	    }
+#ifdef FEAT_AUTOCMD
+	    sprintf((char *)winstr, "0x%x", (unsigned int)win);
+	    apply_autocmds(EVENT_REMOTEREPLY, winstr, str, TRUE, curbuf);
+#endif
+
+	}
+	else
+	{
+	    /*
+	     * Didn't recognize this thing.  Just skip through the next
+	     * null character and try again.
+	     * Even if we get an 'r'(eply) we will throw it away as we
+	     * never specify (and thus expect) one
+	     */
+	    while (*p != 0)
+		p++;
+	    p++;
+	}
+    }
+    XFree(propInfo);
+}
+
+/*
+ * 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 for OK, -1 for error
+ */
+    static int
+AppendPropCarefully(dpy, window, property, value, length)
+    Display	*dpy;		/* Display on which to operate. */
+    Window	window;		/* Window whose property is to be modified. */
+    Atom	property;	/* Name of property. */
+    char_u	*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(dpy, error_event)
+    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(str)
+    char_u	*str;
+{
+    int len = STRLEN(str);
+
+    return (len > 1 && vim_isdigit(str[len - 1]));
+}
+#endif	/* FEAT_CLIENTSERVER */