- features: normal
compiler: gcc
extra: [vimtags]
+ - features: huge
+ compiler: gcc
+ extra: [no_x11]
+ - features: huge
+ compiler: gcc
+ extra: [socketserver]
steps:
- name: Checkout repository from github
tiny)
echo "TEST=testtiny"
if ${{ contains(matrix.extra, 'nogui') }}; then
- echo "CONFOPT=--disable-gui"
+ CONFOPT="--disable-gui"
fi
;;
normal)
PYTHON3_CONFOPT="--with-python3-stable-abi=3.8"
fi
# The ubuntu-24.04 CI runner does not provide a python2 package.
- echo "CONFOPT=--enable-perlinterp=${INTERFACE} --enable-pythoninterp=no --enable-python3interp=${INTERFACE} --enable-rubyinterp=${INTERFACE} --enable-luainterp=${INTERFACE} --enable-tclinterp=${INTERFACE} ${PYTHON3_CONFOPT}"
+ CONFOPT="--enable-perlinterp=${INTERFACE} --enable-pythoninterp=no --enable-python3interp=${INTERFACE} --enable-rubyinterp=${INTERFACE} --enable-luainterp=${INTERFACE} --enable-tclinterp=${INTERFACE} ${PYTHON3_CONFOPT}"
;;
esac
+ if ${{ contains(matrix.extra, 'no_x11') }}; then
+ CONFOPT="${CONFOPT} --without-x --disable-gui"
+ fi
+ if ${{ contains(matrix.extra, 'socketserver') }}; then
+ CONFOPT="${CONFOPT} --enable-socketserver"
+ fi
if ${{ matrix.coverage == true }}; then
CFLAGS="${CFLAGS} --coverage -DUSE_GCOV_FLUSH"
echo "LDFLAGS=--coverage"
echo "TEST=-C runtime/doc vimtags VIMEXE=../../${SRCDIR}/vim"
fi
echo "CFLAGS=${CFLAGS}"
+ echo "CONFOPT=${CONFOPT}"
# Disables GTK attempt to integrate with the accessibility service that does run in CI.
echo "NO_AT_BRIDGE=1"
) >> $GITHUB_ENV
showcmd Compiled with 'showcmd' support.
signs Compiled with |:sign| support.
smartindent Compiled with 'smartindent' support. (always true)
+socketserver Compiled with socket server functionality. (Unix only)
sodium Compiled with libsodium for better crypt support
sound Compiled with sound support, e.g. `sound_playevent()`
spell Compiled with spell checking support |spell|.
-*remote.txt* For Vim version 9.1. Last change: 2022 Feb 17
+*remote.txt* For Vim version 9.1. Last change: 2025 Aug 18
VIM REFERENCE MANUAL by Bram Moolenaar
--servername {name} Become the server {name}. When used together
with one of the --remote commands: connect to
server {name} instead of the default (see
- below). The name used will be uppercase.
+ below). The name used will be uppercase. If
+ using the socketserver, you can specify a
+ path, see |socketserver-name| for more
+ details.
*--remote-send*
--remote-send {keys} Send {keys} to server and exit. The {keys}
are not mapped. Special key names are
on stdout.
*--serverlist*
--serverlist Output a list of server names.
+ *--clientserver*
+ --clientserver {method} Use the specified method {method} as the
+ backend for clientserver functionality. Can
+ either be "socket" or "x11".
+ {only available when Vim is compiled with both
+ |+X11| and |+socketserver| features}
Examples ~
encountered, i.e. "gvim1" for the second invocation of gvim on a particular
X-server. The resulting name is available in the servername builtin variable
|v:servername|. The case of the server name is ignored, thus "gvim" and
-"GVIM" are considered equal.
+"GVIM" are considered equal. Note if a socket server is being used, there are
+some differences, see |socketserver-differences|.
When Vim is invoked with --remote, --remote-wait or --remote-send it will try
to locate the server name determined by the invocation name and --servername
when sending command to it.
The --serverlist argument will cause Vim to print a list of registered command
-servers on the standard output (stdout) and exit.
+servers on the standard output (stdout) and exit. If a socket server is being
+used, there are caveats, see |socketserver-differences|.
*{server}*
The {server} argument is used by several functions. When this is an empty
string then on Unix the default server name is used, which is "GVIM". On
start /w gvim --remote-wait file.txt
<
+==============================================================================
+3. Socket server specific items *socketserver-clientserver*
+ *E1563* *E1564* *E1565* *E1566* *E1567*
+
+The communication between client and server is done using Unix domain sockets.
+These sockets are either placed in these directories in the following order of
+availability:
+ 1. "$XDG_RUTIME_DIR/vim" if $XDG_RUNTIME_DIR is set in the environment.
+ 2. "$TMPDIR/vim-[uid]", where "[uid]" is the uid of the user. This
+ directory will have the access permissions set to 700 so only the user
+ can read or write from/to it. If $TMPDIR is not set, "/tmp" is used.
+
+ *socketserver-name*
+When specifying the server id/name, it can be taken as a generic name or an
+absolute or relative path. If the server id starts with either a "/"
+(absolute) or "./" | "../" (relative), then it is taken as path to the socket.
+Otherwise the server id will be the filename of the socket which will be
+placed in the above common directories. Note that a server id/name can only
+contain slashes "/" if it is taken as a path, so names such as "abc/dir" will
+be invalid.
+
+Socket server functionality is available in both GTK GUI and terminal versions of
+Vim. Unless Vim is compiled with |+autoservername| feature, the socket server
+will have to started explicitly, just like X11, even in the GUI.
+
+If Vim crashes or does not exit cleanly, the socket server will not remove the
+socket file and it will be left around. This is generally not a problem,
+because if a socket name is taken, Vim checks if the socket in its place is
+dead (not attached to any process), and can replace it instead of finding a
+new name.
+
+To send commands to a Vim socket server from another application, read the
+source file src/os_unix.c, there is detailed description of the protocol used.
+
+ *socketserver-differences*
+Most of the functionality is the same as X11, however unlike X11, where the
+client does not need to be a server in order to communicate with another
+server, the socket server requires the server to be running even as a client.
+The exception is |serverlist()| or the |--serverlist| argument, which does not
+require the server to be running.
+
+Additionally, the server id or client id will not be a number like X11 or
+MS-Windows (shown in hex representation), instead it is the absolute path to
+the socket. This can be seen via the |v:servername| variable.
+
+The |--serverlist| argument will act just like X11, however it only checks the
+given common directories above. If a custom path is used for a socket, it
+will not be detected, such as a path either not in $XDG_RUNTIME_DIR or
+<$TMPDIR or /tmp>/vim of the |--serverlist| Vim process.
+
+If you have both |+socketserver| and |+X11| compiled, you will need to add
+|--clientserver| set to "socket" in combination with |--serverlist| to list
+the available servers. You cannot list both types of backends in one command.
+
+ *socketserver-x11*
+If Vim is compiled with both |+X11| and |+socketserver|, then deciding which
+backend to use is done at startup time, via the |--clientserver| argument. By
+default if it is not specified, then X11 will be used. A Vim instance using a
+socket server cannot communicate with one using X11.
+
vim:tw=78:sw=4:ts=8:noet:ft=help:norl:
+scrollbind various.txt /*+scrollbind*
+signs various.txt /*+signs*
+smartindent various.txt /*+smartindent*
++socketserver various.txt /*+socketserver*
+sodium various.txt /*+sodium*
+sound various.txt /*+sound*
+spell various.txt /*+spell*
-- starting.txt /*--*
--- starting.txt /*---*
--clean starting.txt /*--clean*
+--clientserver remote.txt /*--clientserver*
--cmd starting.txt /*--cmd*
--echo-wid starting.txt /*--echo-wid*
--gui-dialog-file starting.txt /*--gui-dialog-file*
E1560 vim9.txt /*E1560*
E1561 vim9.txt /*E1561*
E1562 options.txt /*E1562*
+E1563 remote.txt /*E1563*
+E1564 remote.txt /*E1564*
+E1565 remote.txt /*E1565*
+E1566 remote.txt /*E1566*
+E1567 remote.txt /*E1567*
E157 sign.txt /*E157*
E158 sign.txt /*E158*
E159 sign.txt /*E159*
slow-start starting.txt /*slow-start*
slow-terminal term.txt /*slow-terminal*
socket-interface channel.txt /*socket-interface*
+socketserver-clientserver remote.txt /*socketserver-clientserver*
+socketserver-differences remote.txt /*socketserver-differences*
+socketserver-name remote.txt /*socketserver-name*
+socketserver-x11 remote.txt /*socketserver-x11*
sort() builtin.txt /*sort()*
sorting change.txt /*sorting*
sound-functions usr_41.txt /*sound-functions*
-*various.txt* For Vim version 9.1. Last change: 2025 Aug 06
+*various.txt* For Vim version 9.1. Last change: 2025 Aug 18
VIM REFERENCE MANUAL by Bram Moolenaar
T *+scrollbind* 'scrollbind'
N *+signs* |:sign|
T *+smartindent* 'smartindent'
+N *+socketserver* Unix only: socket server backend for clientserver
+ functionality
H *+sodium* compiled with libsodium for better encryption support
H *+sound* |sound_playevent()|, |sound_playfile()| functions, etc.
N *+spell* spell checking support, see |spell|
Unicode 16.
- Two additional digraphs have been added: LEFT ANGLE BRACKET "<[" and RIGHT
ANGLE BRACKET "]>".
+- Support for Unix domain sockets have been added for the clientserver
+ feature, see |socketserver-clientserver|.
+
+Platform specific ~
- MS-Winodws: Paths like "\Windows" and "/Windows" are now considered to be
absolute paths (to the current drive) and no longer relative.
Vim Arguments: ~
|-Y| Do not connect to the Wayland compositor.
+|--clientserver| Specify backend for clientserver functionality.
==============================================================================
.TP
\-\-servername {name}
Use {name} as the server name. Used for the current Vim, unless used with a
-\-\-remote argument, then it's the name of the server to connect to.
+\-\-remote argument, then it's the name of the server to connect to. If the
+socketserver backend is being used, if the name starts with "/", "./", or "../",
+it is taken as either an absolute, relative or relative path to the socket.
+.TP
+\-\-clientserver {backend}
+Use {backend} as the backend for clientserver functionality, either "socket" or
+"x11" respectively. Only available when compiled with both socketserver and X11
+features present
.TP
\-\-socketid {id}
GTK GUI only: Use the GtkPlug mechanism to run gVim in another window.
--servername {name}
Use {name} as the server name. Used for the current Vim,
unless used with a --remote argument, then it's the name of
- the server to connect to.
+ the server to connect to. If the socketserver backend is
+ being used, if the name starts with "/", "./", or "../", it
+ is taken as either an absolute, relative or relative path
+ to the socket.
+
+ --clientserver {backend}
+ Use {backend} as the backend for clientserver functional‐
+ ity, either "socket" or "x11" respectively. Only available
+ when compiled with both socketserver and X11 features
+ present
--socketid {id}
GTK GUI only: Use the GtkPlug mechanism to run gVim in an‐
let max = argc()
let id = expand("<client>")
- if id == 0
+ if (type(id) == v:t_number && id == 0) || (type(id) == v:t_string && id == '')
return
endif
+
while cnt < max
" Handle same file from more clients and file being more than once
" on the command line by encoding this stuff in the group name
" Language: Vim script
" Maintainer: Hirohito Higashi <h.east.727 ATMARK gmail.com>
" Doug Kearns <dougkearns@gmail.com>
-" Last Change: 2025 Aug 16
+" Last Change: 2025 Aug 18
" Former Maintainer: Charles E. Campbell
" DO NOT CHANGE DIRECTLY.
enable_channel
enable_terminal
enable_autoservername
+enable_socketserver
enable_multibyte
enable_rightleft
enable_arabic
--disable-channel Disable process communication support.
--enable-terminal Enable terminal emulation support.
--enable-autoservername Automatically define servername at vim startup.
+ --enable-socketserver Use sockets for clientserver communication.
--enable-multibyte Include multibyte editing support.
--disable-rightleft Do not include Right-to-Left language support.
--disable-arabic Do not include Arabic language support.
fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking --enable-socketserver argument" >&5
+printf %s "checking --enable-socketserver argument... " >&6; }
+# Check whether --enable-socketserver was given.
+if test ${enable_socketserver+y}
+then :
+ enableval=$enable_socketserver; enable_socketserver=$enableval
+else case e in #(
+ e) if test "x$features" = xtiny
+then :
+ enable_socketserver=no_auto
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: cannot use socketserver with tiny features" >&5
+printf "%s\n" "cannot use socketserver with tiny features" >&6; }
+else case e in #(
+ e) enable_socketserver=auto ;;
+esac
+fi ;;
+esac
+fi
+
+if test "$enable_socketserver" = "yes"; then
+ printf "%s\n" "#define WANT_SOCKETSERVER 1" >>confdefs.h
+
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }
+elif test "$enable_socketserver" = "auto"; then
+ printf "%s\n" "#define MAYBE_SOCKETSERVER 1" >>confdefs.h
+
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: auto" >&5
+printf "%s\n" "auto" >&6; }
+elif test "$enable_socketserver" = "no"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking --enable-multibyte argument" >&5
printf %s "checking --enable-multibyte argument... " >&6; }
# Check whether --enable-multibyte was given.
#if defined(FEAT_CLIENTSERVER) || defined(PROTO)
+#ifdef FEAT_SOCKETSERVER
+# include <sys/socket.h>
+# include "sys/un.h"
+#endif
+
static void cmdsrv_main(int *argc, char **argv, char_u *serverName_arg, char_u **serverStr);
static char_u *serverMakeName(char_u *arg, char *cmd);
void
exec_on_server(mparm_T *parmp)
{
+ int made_name = FALSE;
+
if (parmp->serverName_arg != NULL && *parmp->serverName_arg == NUL)
return;
serverInitMessaging();
# endif
+#ifdef FEAT_SOCKETSERVER
+ // If servername is specified and we are using sockets, always init the
+ // sockt server. We may need to receive replies back to us. If --serverlist
+ // is passed, the socket server will be uninitialized before listing
+ // sockets then initialized after. This is so we don't add our own socket
+ // in the list. This does not happen in serverlist().
+ if ((parmp->serverArg || parmp->serverName_arg != NULL) &&
+ clientserver_method == CLIENTSERVER_METHOD_SOCKET)
+ {
+ parmp->servername = serverMakeName(parmp->serverName_arg,
+ parmp->argv[0]);
+ if (socket_server_init(parmp->servername) == OK)
+ TIME_MSG("initialize socket server");
+ made_name = TRUE;
+ }
+#endif
+
/*
* When a command server argument was found, execute it. This may
* exit Vim when it was successful. Otherwise it's executed further
// If we're still running, get the name to register ourselves.
// On Win32 can register right now, for X11 need to setup the
// clipboard first, it's further down.
- parmp->servername = serverMakeName(parmp->serverName_arg,
- parmp->argv[0]);
+ if (!made_name && parmp->servername == NULL)
+ parmp->servername = serverMakeName(parmp->serverName_arg,
+ parmp->argv[0]);
# ifdef MSWIN
if (parmp->servername != NULL)
{
}
# endif
}
-
/*
* Prepare for running as a Vim server.
*/
void
prepare_server(mparm_T *parmp)
{
-# if defined(FEAT_X11)
+# if defined(FEAT_X11) || defined(FEAT_SOCKETSERVER)
/*
* Register for remote command execution with :serversend and --remote
* unless there was a -X or a --servername '' on the command line.
* or when compiling with autoservername.
* When running as root --servername is also required.
*/
- if (X_DISPLAY != NULL && parmp->servername != NULL && (
+
+ if (
+# ifdef FEAT_X11
+ X_DISPLAY != NULL &&
+# endif
+
+ parmp->servername != NULL && (
# if defined(FEAT_AUTOSERVERNAME) || defined(FEAT_GUI)
(
# if defined(FEAT_AUTOSERVERNAME)
# endif
parmp->serverName_arg != NULL))
{
- (void)serverRegisterName(X_DISPLAY, parmp->servername);
+# ifdef FEAT_SOCKETSERVER
+ if (clientserver_method == CLIENTSERVER_METHOD_SOCKET)
+ {
+ if (socket_server_init(parmp->servername) == OK)
+ TIME_MSG("initialize socket server");
+ }
+# endif
+# ifdef FEAT_X11
+ if (clientserver_method == CLIENTSERVER_METHOD_X11)
+ {
+ (void)serverRegisterName(X_DISPLAY, parmp->servername);
+ TIME_MSG("register x11 server name");
+ }
+# endif
vim_free(parmp->servername);
- TIME_MSG("register server name");
}
+#ifdef FEAT_X11
else
serverDelayedStartName = parmp->servername;
+#endif
# endif
/*
#define ARGTYPE_SEND 3
int silent = FALSE;
int tabs = FALSE;
-# ifndef FEAT_X11
+#ifdef FEAT_SOCKETSERVER
+ char_u *receiver;
+#endif
+# ifdef MSWIN
HWND srv;
-# else
+# elif defined(FEAT_X11)
Window srv;
setup_term_clip();
}
Argc = i;
}
-# ifdef FEAT_X11
- if (xterm_dpy == NULL)
+
+#ifdef FEAT_SOCKETSERVER
+ if (clientserver_method == CLIENTSERVER_METHOD_SOCKET)
+ ret = socket_server_send(
+ sname, *serverStr, NULL, &receiver,
+ 0, -1, silent);
+#endif
+#ifdef FEAT_X11
+ if (clientserver_method == CLIENTSERVER_METHOD_X11)
{
- mch_errmsg(_("No display"));
- ret = -1;
+ if (xterm_dpy == NULL)
+ {
+ mch_errmsg(_("No display"));
+ ret = -1;
+ }
+ else
+ ret = serverSendToVim(xterm_dpy, sname, *serverStr,
+ NULL, &srv, 0, 0, 0, silent);
}
- else
- ret = serverSendToVim(xterm_dpy, sname, *serverStr,
- NULL, &srv, 0, 0, 0, silent);
-# else
+#endif
+#ifdef MSWIN
// Win32 always works?
ret = serverSendToVim(sname, *serverStr, NULL, &srv, 0, 0, silent);
# endif
vim_memset(done, 0, numFiles);
while (memchr(done, 0, numFiles) != NULL)
{
- char_u *p;
+ char_u *p = NULL;
int j;
# ifdef MSWIN
p = serverGetReply(srv, NULL, TRUE, TRUE, 0);
if (p == NULL)
break;
# else
- if (serverReadReply(xterm_dpy, srv, &p, TRUE, -1) < 0)
+# ifdef FEAT_SOCKETSERVER
+ if (clientserver_method == CLIENTSERVER_METHOD_SOCKET
+ && socket_server_read_reply(receiver, &p, -1) == FAIL)
+ break;
+# endif
+# ifdef FEAT_X11
+ if (clientserver_method == CLIENTSERVER_METHOD_X11
+ && serverReadReply(xterm_dpy, srv, &p, TRUE, -1) < 0)
+ break;
+# endif
+ if (p == NULL)
break;
# endif
j = atoi((char *)p);
if (serverSendToVim(sname, (char_u *)argv[i + 1],
&res, NULL, 1, 0, FALSE) < 0)
# else
- if (xterm_dpy == NULL)
- mch_errmsg(_("No display: Send expression failed.\n"));
- else if (serverSendToVim(xterm_dpy, sname, (char_u *)argv[i + 1],
- &res, NULL, 1, 0, 1, FALSE) < 0)
+# ifdef FEAT_SOCKETSERVER
+ if (clientserver_method == CLIENTSERVER_METHOD_SOCKET)
+ {
+ if (!socket_server_valid())
+ mch_errmsg(_("Socket server not online:"
+ "Send expression failed"));
+ else if (socket_server_send(sname, (char_u *)argv[i + 1],
+ &res, NULL, 1, 0, FALSE) < 0)
+ goto expr_fail;
+ }
+# endif
+# ifdef FEAT_X11
+ if (clientserver_method == CLIENTSERVER_METHOD_X11)
+ {
+ if (xterm_dpy == NULL)
+ mch_errmsg(_("No display: Send expression failed.\n"));
+ else if (serverSendToVim(xterm_dpy, sname,
+ (char_u *)argv[i + 1], &res,
+ NULL, 1, 0, 1, FALSE) < 0)
+ goto expr_fail;
+ }
+# endif
+ if (FALSE)
# endif
{
+# if !defined(MSWIN)
+expr_fail:
+# endif
if (res != NULL && *res != NUL)
{
// Output error from remote
// Win32 always works?
res = serverGetVimNames();
# else
- if (xterm_dpy != NULL)
+# ifdef FEAT_SOCKETSERVER
+ if (clientserver_method == CLIENTSERVER_METHOD_SOCKET)
+ {
+ int was_init = socket_server_valid();
+
+ // Don't want to add ourselves to the list. So shutdown the
+ // server before listing then startup back again.
+ socket_server_uninit();
+ res = socket_server_list_sockets();
+
+ if (was_init)
+ socket_server_init(NULL);
+ }
+# endif
+# ifdef FEAT_X11
+ if (clientserver_method == CLIENTSERVER_METHOD_X11 &&
+ xterm_dpy != NULL)
res = serverGetVimNames(xterm_dpy);
+# endif
# endif
if (did_emsg)
mch_errmsg("\n");
if (didone)
{
+#ifdef FEAT_SOCKETSERVER
+ socket_server_uninit();
+#endif
display_errors(); // display any collected messages
exit(exiterr); // Mission accomplished - get out
}
char_u *p;
if (arg != NULL && *arg != NUL)
+ {
+#ifdef FEAT_SOCKETSERVER
+ // If we are using a socket server, we want to preserve the original
+ // name if it is a path, else uppercase it if its just a generic name.
+ if (clientserver_method == CLIENTSERVER_METHOD_SOCKET)
+ {
+ if (arg[0] == '/' || STRNCMP(arg, "./", 2) == 0 ||
+ STRNCMP(arg, "../", 3) == 0)
+ p = vim_strsave(arg);
+ else
+ p = vim_strsave_up(arg);
+ }
+ else
+ p = vim_strsave_up(arg);
+#else
p = vim_strsave_up(arg);
+#endif
+ }
else
{
p = vim_strsave_up(gettail((char_u *)cmd));
# ifdef MSWIN
HWND w;
# else
+#ifdef FEAT_X11
Window w;
+#endif
+#ifdef FEAT_SOCKETSERVER
+ char_u *client = NULL;
+#endif
# endif
if (check_restricted() || check_secure())
# ifdef MSWIN
if (serverSendToVim(server_name, keys, &r, &w, expr, timeout, TRUE) < 0)
# else
- if (serverSendToVim(X_DISPLAY, server_name, keys, &r, &w, expr, timeout,
- 0, TRUE) < 0)
+#ifdef FEAT_SOCKETSERVER
+ if (clientserver_method == CLIENTSERVER_METHOD_SOCKET)
+ if (socket_server_send(server_name, keys, &r, &client, expr,
+ timeout * 1000, TRUE) < 0)
+ goto stuff;
+#endif
+#ifdef FEAT_X11
+ if (clientserver_method == CLIENTSERVER_METHOD_X11)
+ if (serverSendToVim(X_DISPLAY, server_name, keys, &r, &w, expr, timeout,
+ 0, TRUE) < 0)
+ goto stuff;
+#endif
# endif
+#if !defined(MSWIN)
+ if (FALSE)
{
+stuff:
+#else
+ {
+#endif
if (r != NULL)
{
emsg((char *)r); // sending worked but evaluation failed
vim_free(r);
+#ifdef FEAT_SOCKETSERVER
+ vim_free(client);
+#endif
}
else
semsg(_(e_unable_to_send_to_str), server_name);
if (argvars[2].v_type != VAR_UNKNOWN)
{
dictitem_T v;
+#if defined(FEAT_SOCKETSERVER)
+ struct sockaddr_un addr;
+ char_u str[sizeof(addr.sun_path)];
+#else
char_u str[30];
+#endif
char_u *idvar;
idvar = tv_get_string_chk(&argvars[2]);
if (idvar != NULL && *idvar != NUL)
{
+#ifdef MSWIN
sprintf((char *)str, PRINTF_HEX_LONG_U, (long_u)w);
+#else
+#ifdef FEAT_X11
+ if (clientserver_method == CLIENTSERVER_METHOD_X11)
+ sprintf((char *)str, PRINTF_HEX_LONG_U, (long_u)w);
+#endif
+#ifdef FEAT_SOCKETSERVER
+ if (clientserver_method == CLIENTSERVER_METHOD_SOCKET)
+ vim_snprintf((char *)str, sizeof(addr.sun_path),
+ "%s", client);
+#endif
+#endif
v.di_tv.v_type = VAR_STRING;
v.di_tv.vval.v_string = vim_strsave(str);
set_var(idvar, &v.di_tv, FALSE);
vim_free(v.di_tv.vval.v_string);
}
}
+#ifdef FEAT_SOCKETSERVER
+ vim_free(client);
+#endif
}
#endif
rettv->vval.v_number = (s != NULL);
}
# else
- if (check_connection() == FAIL)
- return;
+# ifdef FEAT_SOCKETSERVER
+ if (clientserver_method == CLIENTSERVER_METHOD_SOCKET)
+ rettv->vval.v_number = socket_server_peek_reply(serverid, &s);
+# endif
+# ifdef FEAT_X11
+ if (clientserver_method == CLIENTSERVER_METHOD_X11)
+ {
+ if (check_connection() == FAIL)
+ return;
- rettv->vval.v_number = serverPeekReply(X_DISPLAY,
- serverStrToWin(serverid), &s);
+ rettv->vval.v_number = serverPeekReply(X_DISPLAY,
+ serverStrToWin(serverid), &s);
+ }
+# endif
# endif
if (argvars[1].v_type != VAR_UNKNOWN && rettv->vval.v_number > 0)
if (n != 0)
r = serverGetReply((HWND)n, FALSE, TRUE, TRUE, timeout);
if (r == NULL)
+ emsg(_(e_unable_to_read_server_reply));
# else
- if (check_connection() == FAIL
+# ifdef FEAT_SOCKETSERVER
+ if (clientserver_method == CLIENTSERVER_METHOD_SOCKET &&
+ socket_server_read_reply(serverid, &r, timeout * 1000) == FAIL)
+ emsg(_(e_unable_to_read_server_reply));
+# endif
+# ifdef FEAT_X11
+ if (clientserver_method == CLIENTSERVER_METHOD_X11 &&
+ (check_connection() == FAIL
|| serverReadReply(X_DISPLAY, serverStrToWin(serverid),
- &r, FALSE, timeout) < 0)
-# endif
+ &r, FALSE, timeout) < 0))
emsg(_(e_unable_to_read_server_reply));
+# endif
+# endif
}
#endif
rettv->v_type = VAR_STRING;
}
char_u *server = tv_get_string_chk(&argvars[0]);
+# ifdef MSWIN
+ serverSetName(server);
+# else
+# ifdef FEAT_SOCKETSERVER
+ if (clientserver_method == CLIENTSERVER_METHOD_SOCKET)
+ socket_server_init(server);
+# endif
# ifdef FEAT_X11
- if (check_connection() == OK)
+ if (clientserver_method == CLIENTSERVER_METHOD_X11 &&
+ check_connection() == OK)
serverRegisterName(X_DISPLAY, server);
-# else
- serverSetName(server);
+# endif
# endif
#else
if (server == NULL || reply == NULL)
return;
-# ifdef FEAT_X11
- if (check_connection() == FAIL)
+#ifdef FEAT_SOCKETSERVER
+ if (clientserver_method == CLIENTSERVER_METHOD_SOCKET &&
+ socket_server_send_reply(server, reply) == FAIL)
+ goto fail;
+#endif
+
+#ifdef FEAT_X11
+ if (clientserver_method == CLIENTSERVER_METHOD_X11 &&
+ check_connection() == FAIL)
return;
-# endif
+ if (clientserver_method == CLIENTSERVER_METHOD_X11 &&
+ serverSendReply(server, reply) < 0)
+#endif
+#ifdef MSWIN
if (serverSendReply(server, reply) < 0)
+#endif
+#if defined(FEAT_SOCKETSERVER) && !defined(FEAT_X11) && !defined(MSWIN)
+ if (FALSE)
+#endif
{
+#ifdef FEAT_SOCKETSERVER
+fail:
+#endif
emsg(_(e_unable_to_send_to_client));
return;
}
# ifdef MSWIN
r = serverGetVimNames();
# else
+# ifdef FEAT_SOCKETSERVER
+ if (clientserver_method == CLIENTSERVER_METHOD_SOCKET)
+ r = socket_server_list_sockets();
+# endif
+# ifdef FEAT_X11
+ if (clientserver_method == CLIENTSERVER_METHOD_X11)
+ {
make_connection();
if (X_DISPLAY != NULL)
r = serverGetVimNames(X_DISPLAY);
+ }
+# endif
# endif
#endif
rettv->v_type = VAR_STRING;
/* Define if you want to always define a server name at vim startup. */
#undef FEAT_AUTOSERVERNAME
+/* Define if you want to use sockets for clientserver communication. */
+#undef WANT_SOCKETSERVER
+
+/* Define if you want to use sockets for clientserver communication if it makes sense. */
+#undef MAYBE_SOCKETSERVER
+
/* Define if you want to include fontset support. */
#undef FEAT_XFONTSET
AC_DEFINE(FEAT_AUTOSERVERNAME)
fi
+AC_MSG_CHECKING(--enable-socketserver argument)
+AC_ARG_ENABLE(socketserver,
+ [ --enable-socketserver Use sockets for clientserver communication.],
+ [enable_socketserver=$enableval],
+ AS_IF([test "x$features" = xtiny],
+ [enable_socketserver=no_auto
+ AC_MSG_RESULT([cannot use socketserver with tiny features])],
+ [enable_socketserver=auto]))
+if test "$enable_socketserver" = "yes"; then
+ AC_DEFINE(WANT_SOCKETSERVER)
+ AC_MSG_RESULT([yes])
+elif test "$enable_socketserver" = "auto"; then
+ AC_DEFINE(MAYBE_SOCKETSERVER)
+ AC_MSG_RESULT([auto])
+elif test "$enable_socketserver" = "no"; then
+ AC_MSG_RESULT([no])
+fi
+
AC_MSG_CHECKING(--enable-multibyte argument)
AC_ARG_ENABLE(multibyte,
[ --enable-multibyte Include multibyte editing support.], ,
EXTERN char e_diff_anchors_with_hidden_windows[]
INIT(= N_("E1562: Diff anchors cannot be used with hidden diff windows"));
#endif
+#ifdef FEAT_SOCKETSERVER
+EXTERN char e_socket_path_too_big[]
+ INIT(= N_("E1563: Socket path is too big"));
+EXTERN char e_socket_name_no_slashes[]
+ INIT(= N_("E1564: Socket name cannot have slashes in it without being a path"));
+EXTERN char e_socket_server_not_online[]
+ INIT(= N_("E1565: Socket server is not online, call remote_startserver() first"));
+EXTERN char e_socket_server_failed_connecting[]
+ INIT(= N_("E1566: Failed connecting to socket %s: %s"));
+EXTERN char e_socket_server_unavailable[]
+ INIT(= N_("E1567: Cannot start socket server, socket path is unavailable"));
+#endif
1
#else
0
+#endif
+ },
+ {"socketserver",
+#ifdef FEAT_SOCKETSERVER
+ 1
+#else
+ 0
#endif
},
{"balloon_eval",
#ifdef FEAT_CLIENTSERVER
case SPEC_CLIENT: // Source of last submitted input
+#ifdef MSWIN
sprintf((char *)strbuf, PRINTF_HEX_LONG_U,
(long_u)clientWindow);
result = strbuf;
+#else
+# ifdef FEAT_SOCKETSERVER
+ if (clientserver_method == CLIENTSERVER_METHOD_SOCKET)
+ {
+ if (client_socket == NULL)
+ result = (char_u *)"";
+ else
+ result = client_socket;
+ }
+# endif
+# ifdef FEAT_X11
+ if (clientserver_method == CLIENTSERVER_METHOD_X11)
+ {
+ sprintf((char *)strbuf, PRINTF_HEX_LONG_U,
+ (long_u)clientWindow);
+ result = strbuf;
+ }
+# endif
+#endif
break;
#endif
# define FIND_REPLACE_DIALOG 1
#endif
+/*
+ * +socketserver Use UNIX domain sockets for clientserver communication
+ */
+#if defined(UNIX) && (defined(WANT_SOCKETSERVER) || \
+ (defined(MAYBE_SOCKETSERVER) && !defined(HAVE_X11)))
+#define FEAT_SOCKETSERVER
+#endif
+
/*
* +clientserver Remote control via the remote_send() function
* and the --remote argument
*/
-#if (defined(MSWIN) || defined(FEAT_XCLIPBOARD)) && defined(FEAT_EVAL)
+#if (defined(MSWIN) || defined(FEAT_XCLIPBOARD) || defined(FEAT_SOCKETSERVER)) \
+ && defined(FEAT_EVAL)
# define FEAT_CLIENTSERVER
#endif
EXTERN Window clientWindow INIT(= None);
EXTERN Atom commProperty INIT(= None);
EXTERN char_u *serverDelayedStartName INIT(= NULL);
-# else
+# elif defined(MSWIN)
# ifdef PROTO
typedef int HWND;
# endif
EXTERN int wayland_display_fd;
#endif
+
+#if defined(FEAT_CLIENTSERVER) && !defined(MSWIN)
+
+// Backend for clientserver functionality
+typedef enum {
+ CLIENTSERVER_METHOD_NONE,
+ CLIENTSERVER_METHOD_X11,
+ CLIENTSERVER_METHOD_SOCKET
+} clientserver_method_T;
+
+// Default to X11 if compiled with support for it, else use socket server.
+# if defined(FEAT_X11) && defined(FEAT_SOCKETSERVER)
+EXTERN clientserver_method_T clientserver_method
+# else
+// Since we aren't going to be changing clientserver_method, make it constant to
+// allow compiler optimizations.
+EXTERN const clientserver_method_T clientserver_method
+# endif
+# ifdef FEAT_X11
+INIT(= CLIENTSERVER_METHOD_X11);
+# elif defined(FEAT_SOCKETSERVER)
+INIT(= CLIENTSERVER_METHOD_SOCKET);
+# else
+INIT(= CLIENTSERVER_METHOD_NONE);
+# endif
+
+#endif
+
+#ifdef FEAT_SOCKETSERVER
+// Path to socket of last client that communicated with us
+EXTERN char_u *client_socket INIT(= NULL);
+#endif
// Reset clipmethod to CLIPMETHOD_NONE
choose_clipmethod();
+#ifdef FEAT_SOCKETSERVER
+ // Install socket server listening socket if we are running it
+ if (socket_server_valid())
+ gui_gtk_init_socket_server();
+#endif
+
vim_free(old_term);
// If the GUI started successfully, trigger the GUIEnter event, otherwise
# include <X11/Sunkeysym.h>
#endif
+#ifdef FEAT_SOCKETSERVER
+# include <glib-unix.h>
+
+// Used to track the source for the listening socket
+static uint socket_server_source_id = 0;
+#endif
+
/*
* Easy-to-use macro for multihead support.
*/
}
#endif // !USE_GNOME_SESSION
+#ifdef FEAT_SOCKETSERVER
+
+/*
+ * Callback for new events from the socket server listening socket
+ */
+ static int
+socket_server_poll_in(int fd UNUSED, GIOCondition cond, void *user_data UNUSED)
+{
+ if (cond & G_IO_IN)
+ socket_server_accept_client();
+ else if (cond & (G_IO_ERR | G_IO_HUP))
+ {
+ socket_server_uninit();
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/*
+ * Initialize socket server for use in the GUI (does not actually initialize the
+ * socket server, only attaches a source).
+ */
+ void
+gui_gtk_init_socket_server(void)
+{
+ if (socket_server_source_id > 0)
+ return;
+ // Register source for file descriptor to global default context
+ socket_server_source_id = g_unix_fd_add(socket_server_get_fd(),
+ G_IO_IN | G_IO_ERR | G_IO_HUP, socket_server_poll_in, NULL);
+}
+
+/*
+ * Remove the source for the socket server listening socket.
+ */
+ void
+gui_gtk_uninit_socket_server(void)
+{
+ if (socket_server_source_id > 0)
+ {
+ g_source_remove(socket_server_source_id);
+ socket_server_source_id = 0;
+ }
+}
+
+#endif
/*
* Setup the window icon & xcmdsrv comm after the main window has been realized.
setup_save_yourself();
#ifdef FEAT_CLIENTSERVER
- if (gui_mch_get_display())
+ if (clientserver_method == CLIENTSERVER_METHOD_X11 && gui_mch_get_display())
{
if (serverName == NULL && serverDelayedStartName != NULL)
{
#include "vim.h"
#include "version.h"
-#if defined(FEAT_CLIENTSERVER) || defined(PROTO)
+#if (defined(FEAT_CLIENTSERVER) && defined(FEAT_X11)) || defined(PROTO)
# ifdef FEAT_X11
# include <X11/Intrinsic.h>
* Get the name of the display, before gui_prepare() removes it from
* argv[]. Used for the xterm-clipboard display.
*
- * Also find the --server... arguments and --socketid and --windowid
+ * Also find the --server, --clientserver... arguments and --socketid and
+ * --windowid
*/
static void
early_arg_scan(mparm_T *parmp UNUSED)
gui.dofork = FALSE;
# endif
}
+# if defined(FEAT_X11) && defined(FEAT_SOCKETSERVER)
+ else if (STRNICMP(argv[i], "--clientserver", 14) == 0)
+ {
+ char_u *arg;
+ if (i == argc - 1)
+ mainerr_arg_missing((char_u *)argv[i]);
+ arg = (char_u *)argv[++i];
+
+ if (STRICMP(arg, "socket") == 0)
+ clientserver_method = CLIENTSERVER_METHOD_SOCKET;
+ else if (STRICMP(arg, "x11") == 0)
+ clientserver_method = CLIENTSERVER_METHOD_X11;
+ else
+ mainerr(ME_UNKNOWN_OPTION, arg);
+ }
+# endif
# endif
# if defined(FEAT_GUI_GTK) || defined(FEAT_GUI_MSWIN)
else if (STRNICMP(argv[0] + argv_idx, "serverlist", 10) == 0)
; // already processed -- no arg
else if (STRNICMP(argv[0] + argv_idx, "servername", 10) == 0
- || STRNICMP(argv[0] + argv_idx, "serversend", 10) == 0)
+ || STRNICMP(argv[0] + argv_idx, "serversend", 10) == 0
+# if defined(FEAT_X11) && defined(FEAT_SOCKETSERVER)
+ || STRNICMP(argv[0] + argv_idx, "clientserver", 12) == 0
+# endif
+ )
{
// already processed -- snatch the following arg
if (argc > 1)
main_msg(_("-Y\t\t\tDo not connect to Wayland compositor"));
#endif
#ifdef FEAT_CLIENTSERVER
+# if defined(FEAT_X11) && defined(FEAT_SOCKETSERVER)
+ main_msg(_("--clientserver <socket|x11> Backend for clientserver communication"));
+# endif
main_msg(_("--remote <files>\tEdit <files> in a Vim server if possible"));
main_msg(_("--remote-silent <files> Same, don't complain if there is no server"));
main_msg(_("--remote-wait <files> As --remote but wait for files to have been edited"));
Display *x11_display = NULL;
#endif
+#ifdef FEAT_SOCKETSERVER
+# include <sys/socket.h>
+# include <sys/un.h>
+
+# define SOCKET_SERVER_MAX_BACKLOG 5
+# define SOCKET_SERVER_MAX_CMD_SIZE 16384
+# define SOCKET_SERVER_MAX_MSG 6
+
+static int socket_server_fd = -1;
+static char_u *socket_server_path = NULL;
+
+typedef enum {
+ SS_MSG_TYPE_ENCODING = 'e', // Encoding of message.
+ SS_MSG_TYPE_STRING = 'c', // Script to execute or reply string.
+ SS_MSG_TYPE_SERIAL = 's', // Serial of pending command
+ SS_MSG_TYPE_CODE = 'r', // Result code for an expression sent
+ SS_MSG_TYPE_SENDER = 'd' // Location of socket for the client that
+ // sent the command.
+} ss_msg_type_T;
+
+typedef enum {
+ SS_CMD_TYPE_EXPR = 'E', // An expression
+ SS_CMD_TYPE_KEYSTROKES = 'K', // Series of keystrokes
+ SS_CMD_TYPE_REPLY = 'R', // Reply from an expression
+ SS_CMD_TYPE_NOTIFY = 'N', // A notification
+ SS_CMD_TYPE_ALIVE = 'A', // Check if server is still responsive
+} ss_cmd_type_T;
+
+// Represents a message in a command. A command can contain multiple messages.
+// Each message starts with a single byte representing the type, then a uint32
+// representing the length of the contents, and then the actual contents.
+// Everything is in native byte order.
+//
+// While contents may contain NULL characters, such as when it is a number, it
+// is always NULL terminated. Note that the NULL terminator does not count in
+// the length.
+typedef struct {
+ char_u msg_type; // Type of message
+ uint32_t msg_len; // Total length of contents
+ char_u *msg_contents; // Actual contents of message
+} ss_msg_T;
+
+// Represents a command sent over a socket. Each socket starts with a byte
+// representing the type, then a uint32 representing the number of messages,
+// then a uint32 representing the total size of the messages in bytes, and then
+// the actual messages. Everything is in native byte order.
+typedef struct {
+ char_u cmd_type; // Type of command
+ uint32_t cmd_num; // Number of messages
+ uint32_t cmd_len; // Combined size of all
+ // messages
+ ss_msg_T cmd_msgs[SOCKET_SERVER_MAX_MSG]; // Array of messages
+} ss_cmd_T;
+
+#define SS_CMD_INFO_SIZE (sizeof(char_u) + (sizeof(uint32_t) * 2))
+#define SS_MSG_INFO_SIZE (sizeof(char_u) + sizeof(uint32_t))
+
+// Represents a pending reply from a command sent to a Vim server. When a
+// command is sent out, we generate unique serial number with it. When we
+// receive any reply, we check which pending command has a matching serial
+// number, and is therefore the reply for that pending command.
+//
+// The reason we just don't use the existing fd created by the connect() call,
+// and communicate using that, is that it can't handle recursive calls, ex:
+// call remote_expr('B', 'remote_expr("A", "<expr>")')
+//
+// This idea is taken from the existing X server functionality
+typedef struct ss_pending_cmd_S {
+ uint32_t serial; // Serial number expected in result
+ char_u code; // Result code, can be 0 or -1.
+ char_u *result; // Result of command
+
+ struct ss_pending_cmd_S *next; // Next in list
+} ss_pending_cmd_T;
+
+ss_pending_cmd_T *ss_pending_cmds;
+
+// Serial is always greater than zero
+static uint32_t ss_serial = 0;
+
+// Represents a reply from a server2client call. Each client that calls a
+// server2client call to us has its own ss_reply_T. Each time a client sends
+// data using server2client, Vim creates a ss_reply_T if it doesn't exist and
+// adds the string to the array. When remote_read is called, the server id is
+// used to find the specific ss_reply_T, and a single string is popped from the
+// array.
+//
+// This idea is taken from the existing X server functionality
+typedef struct {
+ char_u *sender;
+ garray_T strings;
+} ss_reply_T;
+
+static garray_T ss_replies;
+
+static char_u *socket_server_get_path_from_name(char_u *name);
+static int socket_server_connect(char_u *name, char_u **path, int silent);
+static void socket_server_init_pending_cmd(ss_pending_cmd_T *pending);
+static void socket_server_pop_pending_cmd(ss_pending_cmd_T *pending);
+static void socket_server_init_cmd(ss_cmd_T *cmd, ss_cmd_type_T type);
+static int socket_server_append_msg(ss_cmd_T *cmd, char_u type,
+ char_u *contents, int len);
+static void socket_server_free_cmd(ss_cmd_T *cmd);
+static char_u *socket_server_encode_cmd(ss_cmd_T *cmd, size_t *sz);
+static int socket_server_decode_cmd(ss_cmd_T *cmd, int socket_fd, int timeout);
+static int socket_server_write(int sock_fd, char_u *data, size_t sz,
+ int timeout);
+static ss_reply_T *socket_server_get_reply(char_u *sender, int *index);
+static ss_reply_T *socket_server_add_reply(char_u *sender);
+static void socket_server_remove_reply(char_u *sender);
+static void socket_server_exec_cmd(ss_cmd_T *cmd, int fd);
+static int socket_server_dispatch(int timeout);
+static int socket_server_check_alive(char_u *name);
+static int socket_server_name_is_valid(char_u *name);
+
+#endif // FEAT_SOCKETSERVER
+
static int ignore_sigtstp = FALSE;
static int get_x11_title(int);
x11_export_final_selection();
#endif
+#ifdef FEAT_SOCKETSERVER
+ socket_server_uninit();
+#endif
+
#ifdef FEAT_GUI
if (!gui.in_use)
#endif
// each channel may use in, out and err
struct pollfd fds[7 + 3 * MAX_OPEN_CHANNELS];
int nfd;
+# ifdef FEAT_SOCKETSERVER
+ int socket_server_idx = -1;
+# endif
# ifdef FEAT_WAYLAND_CLIPBOARD
int wayland_idx = -1;
# endif
fds[0].events = POLLIN;
nfd = 1;
+# ifdef FEAT_SOCKETSERVER
+ if (socket_server_fd != -1)
+ {
+ socket_server_idx = nfd;
+ fds[nfd].fd = socket_server_fd;
+ fds[nfd].events = POLLIN;
+ nfd++;
+ }
+# endif
+
# ifdef FEAT_WAYLAND_CLIPBOARD
if (wayland_may_restore_connection())
{
finished = FALSE;
# endif
+# ifdef FEAT_SOCKETSERVER
+ if (socket_server_fd != -1)
+ {
+ if (fds[socket_server_idx].revents & POLLIN)
+ socket_server_accept_client();
+ else if (fds[socket_server_idx].revents & (POLLHUP | POLLERR))
+ socket_server_uninit();
+ }
+
+# endif
+
# ifdef FEAT_WAYLAND_CLIPBOARD
// Technically we should first call wl_display_prepare_read() before
// polling the fd, then read and dispatch after we poll. However that is
# endif
maxfd = fd;
+# ifdef FEAT_SOCKETSERVER
+ if (socket_server_fd != -1)
+ {
+ FD_SET(socket_server_fd, &rfds);
+
+ if (maxfd < socket_server_fd)
+ maxfd = socket_server_fd;
+ }
+# endif
+
# ifdef FEAT_WAYLAND_CLIPBOARD
if (wayland_may_restore_connection())
finished = FALSE;
# endif
+# ifdef FEAT_SOCKETSERVER
+ if (socket_server_fd != -1 && ret > 0)
+ {
+ if (FD_ISSET(socket_server_fd, &rfds))
+ socket_server_accept_client();
+ else if (FD_ISSET(socket_server_fd, &efds))
+ socket_server_uninit();
+ }
+# endif
+
# ifdef FEAT_WAYLAND_CLIPBOARD
// Technically we should first call wl_display_prepare_read() before
// polling the fd, then read and dispatch after we poll. However that is
if (finished || msec == 0)
break;
-# ifdef FEAT_CLIENTSERVER
- if (server_waiting())
+# if defined(FEAT_CLIENTSERVER)
+# ifdef FEAT_X11
+ if (clientserver_method == CLIENTSERVER_METHOD_X11 && server_waiting())
break;
+# endif
+# ifdef FEAT_SOCKETSERVER
+ if (clientserver_method == CLIENTSERVER_METHOD_SOCKET &&
+ socket_server_waiting_accept())
+ break;
+# endif
# endif
// We're going to loop around again, find out for how long
}
return fd;
}
+
+#ifdef FEAT_SOCKETSERVER
+
+/*
+ * Initialize socket server called "name" (the socket filename). If "name" is a
+ * path (starts with a '/', './', or '../'), it is assumed to be the path to
+ * the desired socket. If the socket path is already taken, append an
+ * incrementing number to the path until we find a socket filename that can be
+ * used. If NULL is passed as the name, the previous socket path is used (only
+ * if not NULL). Returns OK on success and FAIL on failure.
+ */
+ int
+socket_server_init(char_u *name)
+{
+ struct sockaddr_un addr;
+ char_u *path;
+ int num_printed;
+ int fd;
+ int i = 1;
+
+ if (socket_server_valid() || (name == NULL && socket_server_path == NULL))
+ return FAIL;
+ if (name == NULL)
+ name = socket_server_path;
+
+ path = alloc(sizeof(addr.sun_path));
+
+ if (path == NULL)
+ return FAIL;
+
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+
+ if (fd == -1)
+ {
+ vim_free(path);
+ return FAIL;
+ }
+
+ addr.sun_family = AF_UNIX;
+
+ // If name is not a path, find a common directory to place the
+ // socket.
+ if (name[0] == '/' || STRNCMP(name, "./", 2) == 0 ||
+ STRNCMP(name, "../", 3) == 0)
+ num_printed =
+ vim_snprintf((char *)path, sizeof(addr.sun_path), "%s", name);
+ else
+ {
+ const char_u *dir;
+ char_u *buf;
+
+ // Check if there are slashes in the name
+ if (vim_strchr(name, '/') != NULL)
+ {
+ emsg(_(e_socket_name_no_slashes));
+ goto fail;
+ }
+
+ dir = mch_getenv("XDG_RUNTIME_DIR");
+
+ if (dir == NULL)
+ {
+ // Use $TMPDIR or /tmp if $XDG_RUNTIME_DIR is not set.
+ const char_u *tmpdir = mch_getenv("TMPDIR");
+ size_t sz;
+
+ if (tmpdir != NULL)
+ dir = tmpdir;
+ else
+ dir = (char_u *)"/tmp";
+
+ sz = STRLEN(dir) + 25;
+ buf = alloc(sz);
+
+ if (buf == NULL)
+ goto fail;
+
+ vim_snprintf((char *)buf, sz, "%s/vim-%lu", dir,
+ (unsigned long int)getuid());
+ }
+ else
+ {
+ buf = alloc(STRLEN(dir) + STRLEN("vim") + 2);
+
+ if (buf == NULL)
+ goto fail;
+
+ sprintf((char *)buf, "%s/vim", dir);
+ }
+
+ // Always set directory permissions to 0700 for security
+ if (vim_mkdir(buf, 0700) == -1 && errno != EEXIST)
+ {
+ semsg(_("Failed creating socket directory: %s"), strerror(errno));
+ vim_free(buf);
+ goto fail;
+ }
+
+ num_printed = vim_snprintf((char *)path, sizeof(addr.sun_path),
+ "%s/%s", buf, name);
+
+ vim_free(buf);
+ }
+
+ // Check if path was too big
+ if ((size_t)num_printed >= sizeof(addr.sun_path))
+ {
+ emsg(_(e_socket_path_too_big));
+ goto fail;
+ }
+
+ vim_snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", path);
+
+ // Bind to a suitable path/address
+ while (i < 1000)
+ {
+ if (bind(fd, (struct sockaddr *)&addr, sizeof(addr))
+ == -1)
+ {
+ int fd2;
+
+ if (errno != EADDRINUSE)
+ {
+ emsg(_(e_socket_server_unavailable));
+ goto fail;
+ }
+
+ // If the socket is dead, remove it and try again
+ fd2 = socket_server_connect((char_u *)addr.sun_path, NULL, TRUE);
+
+ if (fd2 == -1)
+ {
+ mch_remove(addr.sun_path);
+ continue;
+ }
+ else
+ close(fd2);
+ }
+ else
+ break;
+
+ num_printed = vim_snprintf(addr.sun_path, sizeof(addr.sun_path),
+ "%s%d", path, i);
+
+ if ((size_t)num_printed >= sizeof(addr.sun_path))
+ {
+ // Address too big
+ emsg(_(e_socket_path_too_big));
+ goto fail;
+ }
+
+ i++;
+ }
+
+ if (i >= 1000)
+ {
+ emsg(_(e_socket_server_unavailable));
+ goto fail;
+ }
+
+ // Start listening for connections
+ if (listen(fd, SOCKET_SERVER_MAX_BACKLOG) == -1)
+ goto fail;
+
+ // Set global path and vvar to the absolute path
+ if ((socket_server_path = alloc(MAXPATHL)) == NULL)
+ goto fail;
+
+ socket_server_path[0] = NUL;
+
+ if (mch_FullName((char_u *)addr.sun_path, socket_server_path,
+ MAXPATHL, FALSE) == FAIL)
+ {
+ vim_free(socket_server_path);
+ goto fail;
+ }
+
+ serverName = vim_strsave(socket_server_path);
+#ifdef FEAT_EVAL
+ set_vim_var_string(VV_SEND_SERVER, serverName, -1);
+#endif
+
+ socket_server_fd = fd;
+
+#ifdef FEAT_GUI_GTK
+ if (gui.in_use)
+ // Initialize source for GUI if we are using it
+ gui_gtk_init_socket_server();
+#endif
+
+ vim_free(path);
+ return OK;
+fail:
+ vim_free(path);
+ socket_server_uninit();
+ return FAIL;
+}
+
+ void
+socket_server_uninit(void)
+{
+ if (socket_server_fd != -1)
+ {
+ close(socket_server_fd);
+ socket_server_fd = -1;
+ }
+
+ if (socket_server_path != NULL)
+ {
+ mch_remove(socket_server_path);
+ vim_free(socket_server_path);
+ socket_server_path = NULL;
+ }
+#ifdef FEAT_GUI_GTK
+ if (gui.in_use)
+ gui_gtk_uninit_socket_server();
+#endif
+}
+
+/*
+ * List available sockets that can be connected to, only in common directories
+ * that Vim knows about. Vim instances with custom socket paths will not be
+ * detected. Returns a newline separated string on success and NULL on failure.
+ */
+ char_u *
+socket_server_list_sockets(void)
+{
+ garray_T str;
+ char_u *buf;
+ char_u *path;
+ DIR *dirp;
+ struct dirent *dp;
+ struct sockaddr_un addr;
+ char_u *known_dirs[] = {
+ mch_getenv("XDG_RUNTIME_DIR"),
+ mch_getenv("TMPDIR"),
+ (char_u *)"/tmp"
+ };
+
+ if ((buf = alloc(sizeof(addr.sun_path))) == NULL)
+ return NULL;
+ if ((path = alloc(sizeof(addr.sun_path))) == NULL)
+ {
+ vim_free(buf);
+ return NULL;
+ }
+
+ ga_init2(&str, 1, 100);
+
+ for (size_t i = 0 ; i < ARRAY_LENGTH(known_dirs); i++)
+ {
+ char_u *dir = known_dirs[i];
+
+ if (dir == NULL)
+ continue;
+
+ if (STRCMP(dir, "/tmp") == 0 ||
+ (known_dirs[1] != NULL && STRCMP(dir, known_dirs[1]) == 0))
+ vim_snprintf((char *)path, sizeof(addr.sun_path), "%s/vim-%lu",
+ dir, (unsigned long int)getuid());
+ else
+ vim_snprintf((char *)path, sizeof(addr.sun_path), "%s/vim", dir);
+
+ dir = path;
+
+ dirp = opendir((char *)dir);
+
+ if (dirp == NULL)
+ continue;
+
+ // Loop through directory
+ while ((dp = readdir(dirp)) != NULL)
+ {
+ if (STRCMP(dp->d_name, ".") == 0 || STRCMP(dp->d_name, "..") == 0)
+ continue;
+
+ vim_snprintf((char *)buf, sizeof(addr.sun_path), "%s/%s",
+ dir, dp->d_name);
+
+ // Try sending an ALIVE command. This is more assuring than a
+ // simple connect, and *also seems to make tests less flaky*.
+ if (!socket_server_check_alive(buf))
+ continue;
+
+ ga_concat(&str, (char_u *)dp->d_name);
+ ga_append(&str, '\n');
+ }
+
+ closedir(dirp);
+
+ break;
+ }
+
+ vim_free(path);
+ vim_free(buf);
+
+ ga_append(&str, NUL);
+
+ return str.ga_data;
+}
+
+/*
+ * Called when the server has received a new command. If so, parse it and do the
+ * stuff it says, and possibly send back a reply.
+ */
+ void
+socket_server_accept_client(void)
+{
+ int fd = accept(socket_server_fd, NULL, NULL);
+ ss_cmd_T cmd;
+
+ if (fd == -1)
+ return;
+
+ if (socket_server_decode_cmd(&cmd, fd, 1000) == FAIL)
+ goto exit;
+
+#ifdef FEAT_EVAL
+ ch_log(NULL, "accepted new client on socket %s", socket_server_path);
+#endif
+
+ socket_server_exec_cmd(&cmd, fd);
+ socket_server_free_cmd(&cmd);
+
+exit:
+ close(fd);
+}
+
+/*
+ * Check if socket server is able to be used
+ */
+ int
+socket_server_valid(void)
+{
+ return socket_server_fd != -1 && socket_server_path != NULL;
+}
+
+/*
+ * If "name" is a pathless name such as "VIM", search known directories for the
+ * socket named "name", and return the alloc'ed path to it. If "name" starts
+ * with a '/', './' or '../', then a copy of "name" is returned. Returns NULL
+ * on failure or if no socket was found.
+ */
+ static char_u *
+socket_server_get_path_from_name(char_u *name)
+{
+ char_u *buf;
+ stat_T s;
+ const char_u *known_dirs[] = {
+ mch_getenv("XDG_RUNTIME_DIR"),
+ mch_getenv("TMPDIR"),
+ (char_u *)"/tmp"
+ };
+
+ if (name == NULL)
+ return NULL;
+
+ // Ignore if name is a path
+ if (name[0] == '/' || STRNCMP(name, "./", 2) == 0 ||
+ STRNCMP(name, "../", 3) == 0)
+ return vim_strsave(name);
+
+ buf = alloc(MAXPATHL);
+
+ if (buf == NULL)
+ return NULL;
+
+ for (size_t i = 0; i < ARRAY_LENGTH(known_dirs); i++)
+ {
+ const char_u *dir = known_dirs[i];
+
+ if (dir == NULL)
+ continue;
+ else if (STRCMP(dir, "/tmp") == 0 ||
+ (known_dirs[1] != NULL && STRCMP(dir, known_dirs[1]) == 0))
+ vim_snprintf((char *)buf, MAXPATHL, "%s/vim-%lu/%s", dir,
+ (unsigned long int)getuid(), name);
+ else
+ vim_snprintf((char *)buf, MAXPATHL, "%s/vim/%s", dir, name);
+
+ if (mch_stat((char *)buf,&s) == 0 && S_ISSOCK(s.st_mode))
+ {
+ if (STRCMP(buf, socket_server_path) == 0)
+ // Can't connect to itself
+ break;
+ return buf;
+ }
+ }
+
+ vim_free(buf);
+ return NULL;
+}
+
+/*
+ * Send command to socket named "name". Returns 0 for OK, -1 on error.
+ */
+ int
+socket_server_send(
+ char_u *name, // Socket path or a general name
+ char_u *str, // What to send
+ char_u **result, // Set to result of expr
+ char_u **receiver, // Full path of "name"
+ int is_expr, // Is it an expresison or keystrokes?
+ int timeout, // In milliseconds
+ int silent) // Don't complain if socket doesn't exist
+{
+ ss_cmd_T cmd;
+ int socket_fd;
+ size_t sz;
+ char_u *final;
+ char_u *path;
+ struct timeval start, now;
+
+
+ if (!socket_server_valid())
+ {
+ emsg(_(e_socket_server_not_online));
+ return -1;
+ }
+
+ socket_fd = socket_server_connect(name, &path, silent);
+
+ if (socket_fd == -1)
+ return -1;
+
+#ifdef FEAT_EVAL
+ ch_log(NULL, "socket_server_send(%s, %s)", path, str);
+#endif
+
+ // Execute locally if target is ourselves
+ if (serverName != NULL && STRICMP(path, serverName) == 0)
+ {
+ vim_free(path);
+ close(socket_fd);
+ return sendToLocalVim(str, is_expr, result);
+ }
+
+ socket_server_init_cmd(&cmd,
+ is_expr ? SS_CMD_TYPE_EXPR : SS_CMD_TYPE_KEYSTROKES);
+
+ socket_server_append_msg(&cmd, SS_MSG_TYPE_ENCODING, p_enc, STRLEN(p_enc));
+
+ // Add +1 in case of empty string
+ socket_server_append_msg(&cmd, SS_MSG_TYPE_STRING, str, STRLEN(str) + 1);
+
+ // Tell server who we are so it can save our socket path internally for
+ // later use with server2client
+ socket_server_append_msg(&cmd, SS_MSG_TYPE_SENDER, socket_server_path,
+ STRLEN(socket_server_path));
+
+ if (is_expr)
+ {
+ ss_serial++;
+ socket_server_append_msg(&cmd, SS_MSG_TYPE_SERIAL,
+ (char_u *)&ss_serial, sizeof(ss_serial));
+ }
+
+ final = socket_server_encode_cmd(&cmd, &sz);
+
+ if (final == NULL ||
+ socket_server_write(socket_fd, final, sz, 1000) == FAIL)
+ {
+ if (final != NULL)
+ emsg(_(e_failed_to_send_command_to_destination_program));
+
+ vim_free(path);
+ socket_server_free_cmd(&cmd);
+ close(socket_fd);
+ vim_free(final);
+ return -1;
+ }
+ socket_server_free_cmd(&cmd);
+ vim_free(final);
+
+
+ close(socket_fd);
+ if (!is_expr)
+ {
+ if (receiver != NULL)
+ *receiver = path;
+ else
+ vim_free(path);
+
+ // Exit, we aren't waiting for a reponse
+ return 0;
+ }
+
+ ss_pending_cmd_T pending;
+
+ socket_server_init_pending_cmd(&pending);
+
+ gettimeofday(&start, NULL);
+
+ // Wait for server to send back result
+ while (socket_server_dispatch(500) >= 0)
+ {
+ if (pending.result != NULL)
+ break;
+
+ gettimeofday(&now, NULL);
+
+ if ((now.tv_sec * 1000000 + now.tv_usec) -
+ (start.tv_sec * 1000000 + start.tv_usec) >=
+ (timeout > 0 ? timeout * 1000 : 1000 * 1000))
+ break;
+ }
+
+ if (pending.result == NULL)
+ {
+ socket_server_pop_pending_cmd(&pending);
+ vim_free(path);
+ return -1;
+ }
+
+ if (result != NULL)
+ *result = pending.result;
+ else
+ vim_free(pending.result);
+
+ if (receiver != NULL)
+ *receiver = path;
+ else
+ vim_free(path);
+
+ socket_server_pop_pending_cmd(&pending);
+
+ return pending.code == 0 ? 0 : -1;
+}
+
+/*
+ * Wait for replies from "client" and place result in "str". Returns OK on
+ * success and FAIL on failure. Timeout is in milliseconds
+ */
+ int
+socket_server_read_reply(char_u *client, char_u **str, int timeout)
+{
+ ss_reply_T *reply = NULL;
+ struct timeval start, now;
+
+ if (!socket_server_name_is_valid(client))
+ return -1;
+
+ if (!socket_server_valid())
+ return -1;
+
+ if (timeout > 0)
+ gettimeofday(&start, NULL);
+
+ // Try seeing if there already is a reply in the queue
+ goto get_reply;
+
+ while (socket_server_dispatch(500) >= 0)
+ {
+ int fd;
+
+ if (timeout > 0)
+ gettimeofday(&now, NULL);
+
+ if (timeout > 0)
+ if ((now.tv_sec * 1000000 + now.tv_usec) -
+ (start.tv_sec * 1000000 + start.tv_usec) >= timeout * 1000)
+ break;
+
+get_reply:
+ reply = socket_server_get_reply(client, NULL);
+
+ if (reply != NULL)
+ break;
+
+ // Check if sender is down by connecting to it as a test. A simple
+ // connect will do.
+ fd = socket_server_connect(client, NULL, TRUE);
+
+ if (fd == -1)
+ return FAIL;
+ else
+ close(fd);
+ }
+
+ if (reply == NULL || reply->strings.ga_data == NULL ||
+ reply->strings.ga_len <= 0)
+ {
+ return FAIL;
+ }
+
+ // Consume the string
+ *str = ((char_u **)reply->strings.ga_data)[0];
+
+ for (int i = 1; i < reply->strings.ga_len; i++)
+ {
+ ((char_u **)reply->strings.ga_data)[i - 1] =
+ ((char_u **)reply->strings.ga_data)[i];
+ }
+ reply->strings.ga_len--;
+
+ if (reply->strings.ga_len < 1)
+ // Last string removed, remove the reply
+ socket_server_remove_reply(client);
+
+
+ return OK;
+}
+
+/*
+ * Check for any replies for "sender". Returns 1 if there is and places the
+ * reply in "str" without consuming it. Returns 0 if otherwise and -1 on
+ * error.
+ */
+ int
+socket_server_peek_reply(char_u *sender, char_u **str)
+{
+ ss_reply_T *reply;
+
+ if (!socket_server_name_is_valid(sender))
+ return -1;
+
+ if (!socket_server_valid())
+ return 0;
+
+ reply = socket_server_get_reply(sender, NULL);
+
+ if (reply != NULL && reply->strings.ga_len > 0)
+ {
+ if (str != NULL)
+ *str = ((char_u **)reply->strings.ga_data)[0];
+ return 1;
+ }
+
+ return 0;
+}
+
+/*
+ * Send a string to "client" as a reply (notification). Returns OK on success
+ * and FAIL on failure.
+ */
+ int
+socket_server_send_reply(char_u *client, char_u *str)
+{
+ int socket_fd;
+ ss_cmd_T cmd;
+ size_t sz;
+ char_u *final;
+
+ if (!socket_server_name_is_valid(client))
+ return FAIL;
+
+ if (!socket_server_valid())
+ {
+ emsg(_(e_socket_server_not_online));
+ return FAIL;
+ }
+
+ socket_fd = socket_server_connect(client, NULL, TRUE);
+
+ if (socket_fd == -1)
+ return FAIL;
+
+ socket_server_init_cmd(&cmd, SS_CMD_TYPE_NOTIFY);
+
+ socket_server_append_msg(&cmd, SS_MSG_TYPE_ENCODING, p_enc, STRLEN(p_enc));
+ socket_server_append_msg(&cmd, SS_MSG_TYPE_STRING, str, STRLEN(str));
+ socket_server_append_msg(&cmd, SS_MSG_TYPE_SENDER,
+ socket_server_path, STRLEN(socket_server_path));
+
+ final = socket_server_encode_cmd(&cmd, &sz);
+
+ if (final == NULL ||
+ socket_server_write(socket_fd, final, sz, 1000) == FAIL)
+ {
+ socket_server_free_cmd(&cmd);
+ close(socket_fd);
+ return FAIL;
+ }
+
+ socket_server_free_cmd(&cmd);
+ vim_free(final);
+ close(socket_fd);
+
+ return OK;
+}
+
+/*
+ * Connect to a socket using "name". "path" is set to the full path of "name"
+ * used to create the socket, only if its not NULL. Returns fd on success and -1
+ * on failure.
+ */
+ static int
+socket_server_connect(char_u *name, char_u **path, int silent)
+{
+ int socket_fd;
+ int res;
+ struct sockaddr_un addr;
+
+ char_u *socket_path = socket_server_get_path_from_name(name);
+
+ if (socket_path == NULL)
+ {
+ if (!silent)
+ semsg(_(e_no_registered_server_named_str), name);
+ return -1;
+ }
+ if (STRLEN(socket_path) >= sizeof(addr.sun_path))
+ {
+ // Path too big
+ vim_free(socket_path);
+ return -1;
+ }
+
+ socket_fd = socket(AF_UNIX, SOCK_STREAM, 0);
+
+ if (socket_fd == -1)
+ goto fail;
+
+ addr.sun_family = AF_UNIX;
+ vim_snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", socket_path);
+
+ res = connect(socket_fd, (struct sockaddr *)&addr, sizeof(addr));
+
+ if (res == -1)
+ {
+ if (!silent)
+ semsg(_(e_socket_server_failed_connecting), socket_path,
+ strerror(errno));
+ goto fail;
+ }
+
+ if (path != NULL)
+ *path = socket_path;
+ else
+ vim_free(socket_path);
+
+ return socket_fd;
+fail:
+ close(socket_fd);
+ vim_free(socket_path);
+ return -1;
+
+}
+
+/*
+ * Add a new pending command to the list of pending commands. Returns OK on
+ * success and FAIL on failure
+ */
+ static void
+socket_server_init_pending_cmd(ss_pending_cmd_T *pending)
+{
+ pending->code = 0;
+ pending->result = NULL;
+ pending->serial = ss_serial;
+ pending->next = ss_pending_cmds;
+ ss_pending_cmds = pending;
+}
+
+/*
+ * Remove pending command from the list, does not free the result string.
+ */
+ static void
+socket_server_pop_pending_cmd(ss_pending_cmd_T *pending)
+{
+ if (ss_pending_cmds == pending)
+ {
+ ss_pending_cmds = pending->next;
+ return;
+ }
+
+ for (ss_pending_cmd_T *cmd = ss_pending_cmds; cmd != NULL; cmd = cmd->next)
+ {
+ if (cmd->next == pending)
+ {
+ cmd->next = pending->next;
+ return;
+ }
+ }
+}
+
+/*
+ * Initialize command structure to empty state
+ */
+ static void
+socket_server_init_cmd(ss_cmd_T *cmd, ss_cmd_type_T type)
+{
+ cmd->cmd_len = 0;
+ cmd->cmd_num = 0;
+ cmd->cmd_type = type;
+}
+
+/*
+ * Append a message to a command. Note that "len" is the length of contents.
+ * Returns OK on sucess and FAIL on failure
+ */
+ static int
+socket_server_append_msg(ss_cmd_T *cmd, char_u type, char_u *contents, int len)
+{
+ ss_msg_T *msg = cmd->cmd_msgs + cmd->cmd_num;
+
+ if (cmd->cmd_num >= SOCKET_SERVER_MAX_MSG)
+ return FAIL;
+
+ // Check if command will be too big.
+ if (SS_CMD_INFO_SIZE + cmd->cmd_len + SS_MSG_INFO_SIZE + len
+ > SOCKET_SERVER_MAX_CMD_SIZE)
+ return FAIL;
+
+ msg->msg_contents = alloc(len);
+
+ if (msg->msg_contents == NULL)
+ return FAIL;
+
+ msg->msg_type = type;
+ msg->msg_len = len;
+ memcpy(msg->msg_contents, contents, len);
+
+ cmd->cmd_len += SS_MSG_INFO_SIZE + len;
+ cmd->cmd_num++;
+
+ return OK;
+}
+
+/*
+ * Free all resources associated with a command object.
+ */
+ static void
+socket_server_free_cmd(ss_cmd_T *cmd)
+{
+ for (uint32_t i = 0; i < cmd->cmd_num; i++)
+ {
+ ss_msg_T *msg = cmd->cmd_msgs + i;
+
+ vim_free(msg->msg_contents);
+ }
+}
+
+/*
+ * Encode command struct and return the final message to send. Returns NULL on
+ * failure.
+ */
+ static char_u *
+socket_server_encode_cmd(ss_cmd_T *cmd, size_t *sz)
+{
+ size_t size;
+ char_u *buf;
+ char_u *start;
+
+ size = SS_CMD_INFO_SIZE + cmd->cmd_len;
+ buf = alloc(size);
+
+ if (buf == NULL)
+ return NULL;
+
+ start = buf;
+ memcpy(start, &cmd->cmd_type, sizeof(cmd->cmd_type));
+ start += sizeof(cmd->cmd_type);
+ memcpy(start, &cmd->cmd_num, sizeof(cmd->cmd_num));
+ start += sizeof(cmd->cmd_num);
+ memcpy(start, &cmd->cmd_len, sizeof(cmd->cmd_len));
+ start += sizeof(cmd->cmd_len);
+
+ // Append messages to buffer
+ for (uint32_t i = 0; i < cmd->cmd_num; i++)
+ {
+ ss_msg_T *msg = cmd->cmd_msgs + i;
+
+ memcpy(start, &msg->msg_type, sizeof(msg->msg_type));
+ start += sizeof(msg->msg_type);
+ memcpy(start, &msg->msg_len, sizeof(msg->msg_len));
+ start += sizeof(msg->msg_len);
+
+ memcpy(start, msg->msg_contents, msg->msg_len);
+ start += msg->msg_len;
+ }
+
+ *sz = size;
+
+ return buf;
+}
+
+/*
+ * Read from "socket_fd" an entire command and return the result in "cmd". The
+ * socket fd should be at the start of the command. Returns OK on success and
+ * FAIL on failure.
+ */
+ static int
+socket_server_decode_cmd(ss_cmd_T *cmd, int socket_fd, int timeout)
+{
+ int got_cmd_info = FALSE; // Consists of type, num, and len
+ size_t total_r = 0;
+ char_u *buf;
+ char_u *cur;
+ struct timeval start, now;
+
+ // We also poll the socket server listening file descriptor to handle
+ // recursive remote calls between Vim instances, such as when one Vim
+ // instance calls remote_expr for an expression that calls remote_expr to
+ // itself again.
+#ifndef HAVE_SELECT
+ struct pollfd pfd;
+
+ pfd.fd = socket_fd;
+ pfd.events = POLLIN;
+#else
+ fd_set rfds;
+ struct timeval tv;
+
+ FD_ZERO(&rfds);
+ FD_SET(socket_fd, &rfds);
+#endif
+
+ buf = alloc(SS_CMD_INFO_SIZE);
+
+ if (buf == NULL)
+ return FAIL;
+
+ // We may exit in the middle of the loop and free the messages, we don't
+ // want to free an uninitialized pointer.
+ memset(cmd, 0, sizeof(*cmd));
+
+ gettimeofday(&start, NULL);
+
+ while (TRUE)
+ {
+ int ret;
+ ssize_t r = 0;
+
+#ifndef HAVE_SELECT
+ ret = poll(&pfd, 1, timeout);
+#else
+ tv.tv_sec = 0;
+ tv.tv_usec = 500 * 1000;
+ ret = select(socket_fd + 1, &rfds, NULL, NULL, &tv);
+#endif
+ if (ret < 0)
+ goto fail;
+ if (ret == 0)
+ goto continue_loop;
+
+ // Get cmd info first so we know the total size of all messages, and
+ // can read it all in one go.
+ if (!got_cmd_info)
+ {
+ r = read(socket_fd, buf + total_r, SS_CMD_INFO_SIZE - total_r);
+
+ if ((size_t)r >= SS_CMD_INFO_SIZE - total_r)
+ {
+ char_u *tmp;
+
+ got_cmd_info = TRUE;
+
+ memcpy(&cmd->cmd_type, buf, sizeof(cmd->cmd_type));
+ memcpy(&cmd->cmd_num, buf + sizeof(cmd->cmd_type),
+ sizeof(cmd->cmd_num));
+ memcpy(&cmd->cmd_len,
+ buf + sizeof(cmd->cmd_type) + sizeof(cmd->cmd_num),
+ sizeof(cmd->cmd_len));
+
+ if (cmd->cmd_num > SOCKET_SERVER_MAX_MSG)
+ // Too many messages to handle or invalid number
+ goto fail;
+
+ if (cmd->cmd_num == 0)
+ // No messages to read
+ goto exit;
+
+ // Now that we now the total size of messages, we can realloc
+ // the buffer to contain all data
+ tmp = vim_realloc(buf, SS_CMD_INFO_SIZE + cmd->cmd_len);
+
+ if (tmp == NULL)
+ goto fail;
+
+ buf = tmp;
+ cur = buf + SS_CMD_INFO_SIZE;
+
+ continue;
+ }
+ }
+ else
+ {
+ // Read message data
+ r = read(socket_fd, cur + total_r, cmd->cmd_len - total_r);
+
+ if ((size_t)r >= cmd->cmd_len - total_r)
+ break;
+ }
+
+ if (r == -1 || r == 0)
+ goto fail;
+
+ total_r += r;
+
+continue_loop:
+ gettimeofday(&now, NULL);
+
+ if ((now.tv_sec * 1000000 + now.tv_usec) -
+ (start.tv_sec * 1000000 + start.tv_usec) >= timeout * 1000)
+ goto fail;
+ }
+
+ // Parse message data
+ for (uint32_t i = 0; i < cmd->cmd_num; i++)
+ {
+ ss_msg_T *msg = cmd->cmd_msgs + i;
+
+ memcpy(&msg->msg_type, cur, sizeof(msg->msg_type));
+ cur += sizeof(msg->msg_type);
+ memcpy(&msg->msg_len, cur, sizeof(msg->msg_len));
+ cur += sizeof(msg->msg_len);
+
+ msg->msg_contents = alloc(msg->msg_len + 1);
+
+ if (msg->msg_contents == NULL)
+ goto fail;
+
+ memcpy(msg->msg_contents, cur, msg->msg_len);
+ msg->msg_contents[msg->msg_len] = 0; // NULL terminate it
+
+ // Move pointer to start of next message
+ cur += msg->msg_len;
+ }
+
+exit:
+ vim_free(buf);
+ return OK;
+fail:
+ socket_server_free_cmd(cmd);
+ vim_free(buf);
+ return FAIL;
+}
+
+/*
+ * Low level function that writes to a socket with a timeout in milliseconds.
+ * Returns OK on success and FAIL on failure.
+ */
+ static int
+socket_server_write(int socket_fd, char_u *data, size_t sz, int timeout)
+{
+ char_u *cur = data;
+ size_t total_w = 0;
+ struct timeval start, now;
+#ifndef HAVE_SELECT
+ struct pollfd pfd;
+
+ pfd.fd = socket_fd;
+ pfd.events = POLLOUT;
+#else
+ fd_set wfds;
+ struct timeval tv;
+
+ FD_ZERO(&wfds);
+ FD_SET(socket_fd, &wfds);
+#endif
+
+ gettimeofday(&start, NULL);
+
+ while (total_w < sz)
+ {
+ int ret;
+ ssize_t written;
+
+ errno = 0;
+#ifndef HAVE_SELECT
+ ret = poll(&pfd, 1, timeout);
+#else
+ tv.tv_sec = 0;
+ tv.tv_usec = 500 * 1000;
+ ret = select(socket_fd + 1, NULL, &wfds, NULL, &tv);
+#endif
+ if (ret < 0)
+ return FAIL;
+ else if (ret == 0)
+ goto continue_loop;
+
+ written = write(socket_fd, cur, sz - total_w);
+
+ if (written == -1)
+ return FAIL;
+
+ total_w += written;
+
+
+continue_loop:
+ gettimeofday(&now, NULL);
+
+ if ((now.tv_sec * 1000000 + now.tv_usec) -
+ (start.tv_sec * 1000000 + start.tv_usec) >= timeout * 1000)
+ return FAIL;
+ }
+
+ return OK;
+}
+
+ static ss_reply_T *
+socket_server_get_reply(char_u *sender, int *index)
+{
+ for (int i = 0; i < ss_replies.ga_len; i++)
+ {
+ ss_reply_T *reply = ((ss_reply_T *)ss_replies.ga_data) + i;
+
+ if (STRCMP(reply->sender, sender) == 0)
+ {
+ if (index != NULL)
+ *index = i;
+ return reply;
+ }
+ }
+ return NULL;
+}
+
+/*
+ * Add reply to list of replies. Returns a pointer to the ss_reply_T that was
+ * initialized or was found.
+ */
+ static ss_reply_T *
+socket_server_add_reply(char_u *sender)
+{
+ ss_reply_T *reply;
+
+ if (ss_replies.ga_growsize == 0)
+ ga_init2(&ss_replies, sizeof(ss_reply_T), 1);
+
+ reply = socket_server_get_reply(sender, NULL);
+
+ if (reply == NULL && ga_grow(&ss_replies, 1) == OK)
+ {
+ reply = ((ss_reply_T *)ss_replies.ga_data) + ss_replies.ga_len++;
+
+ reply->sender = vim_strsave(sender);
+
+ if (reply->sender == NULL)
+ return NULL;
+
+ ga_init2(&reply->strings, sizeof(char_u *), 5);
+ }
+
+ return reply;
+}
+
+ static void
+socket_server_remove_reply(char_u *sender)
+{
+ int index;
+ ss_reply_T *reply = socket_server_get_reply(sender, &index);
+
+ if (reply != NULL)
+ {
+ ss_reply_T *arr = ss_replies.ga_data;
+
+ // Free strings
+ vim_free(reply->sender);
+ ga_clear_strings(&reply->strings);
+
+ // Move all elements after the removed reply forward by one
+ for (int i = index + 1; i < ss_replies.ga_len; i++)
+ arr[i - 1] = arr[i];
+ ss_replies.ga_len--;
+ }
+}
+
+/*
+ * Execute the actions given by command. "fd" is the socket of the client that
+ * sent the command.
+ */
+ static void
+socket_server_exec_cmd(ss_cmd_T *cmd, int fd)
+{
+ char_u *str = NULL;
+ char_u *enc = NULL;
+ char_u *sender = NULL;
+ uint32_t serial = 0;
+ char_u rcode = 0;
+ char_u *to_free;
+ char_u *to_free2;
+
+ for (uint32_t i = 0; i < cmd->cmd_num; i++)
+ {
+ ss_msg_T *msg = cmd->cmd_msgs + i;
+
+ if (msg->msg_type == SS_MSG_TYPE_STRING)
+ str = msg->msg_contents;
+ if (msg->msg_type == SS_MSG_TYPE_ENCODING)
+ enc = msg->msg_contents;
+ if (msg->msg_type == SS_MSG_TYPE_SERIAL)
+ memcpy(&serial, msg->msg_contents, sizeof(serial));
+ if (msg->msg_type == SS_MSG_TYPE_CODE)
+ memcpy(&rcode, msg->msg_contents, sizeof(rcode));
+ else if (msg->msg_type == SS_MSG_TYPE_SENDER)
+ {
+ sender = msg->msg_contents;
+
+ // Save in global
+ vim_free(client_socket);
+ client_socket = vim_strsave(sender);
+ }
+ }
+
+#ifdef FEAT_EVAL
+ ch_log(NULL, "socket_server_exec_cmd(): encoding: %s, result: %s",
+ enc == NULL ? (char_u *)"(null)" : enc,
+ str == NULL ? (char_u *)"(null)" : str);
+#endif
+
+ if (cmd->cmd_type == SS_CMD_TYPE_EXPR ||
+ cmd->cmd_type == SS_CMD_TYPE_KEYSTROKES)
+ {
+ // Either an expression or keystrokes.
+ if (socket_server_valid() && enc != NULL)
+ {
+ str = serverConvert(enc, str, &to_free);
+
+ if (cmd->cmd_type == SS_CMD_TYPE_KEYSTROKES)
+ server_to_input_buf(str);
+ else if (sender != NULL)
+ {
+ // Evaluate expression and send reply containing result
+ char_u *result;
+ size_t sz;
+ char_u *buf;
+ char_u code;
+
+ result = eval_client_expr_to_string(str);
+
+ code = result == NULL ? -1 : 0;
+
+ // Send reply
+ ss_cmd_T rcmd;
+
+ socket_server_init_cmd(&rcmd, SS_CMD_TYPE_REPLY);
+
+ // Don't care about errors, server will just ignore command if
+ // its missing something.
+ if (result != NULL)
+ socket_server_append_msg(&rcmd, SS_MSG_TYPE_STRING, result,
+ STRLEN(result) + 1); // We add +1 in case "result"
+ // is an empty string.
+ else
+ // An error occured, return an error msg instead
+ socket_server_append_msg(&rcmd, SS_MSG_TYPE_STRING,
+ (char_u *)_(e_invalid_expression_received),
+ STRLEN(e_invalid_expression_received));
+
+ socket_server_append_msg(&rcmd, SS_MSG_TYPE_CODE,
+ &code, sizeof(code));
+
+ socket_server_append_msg(&rcmd, SS_MSG_TYPE_ENCODING, p_enc,
+ STRLEN(p_enc));
+
+ socket_server_append_msg(&rcmd, SS_MSG_TYPE_SERIAL,
+ (char_u *)&serial, sizeof(serial));
+
+ buf = socket_server_encode_cmd(&rcmd, &sz);
+
+ if (buf != NULL)
+ {
+ int fd2 = socket_server_connect(sender, NULL, TRUE);
+
+ if (fd2 >= 0)
+ socket_server_write(fd2, buf, sz, 1000);
+ vim_free(buf);
+ close(fd2);
+ }
+
+ socket_server_free_cmd(&rcmd);
+ vim_free(result);
+ }
+ vim_free(to_free);
+ }
+ return;
+ }
+ else if (cmd->cmd_type == SS_CMD_TYPE_REPLY)
+ {
+ // A reply from a previous command we set up, update the corresponding
+ // pending command.
+ if (serial > 0 && str != NULL)
+ {
+ for (ss_pending_cmd_T *pending = ss_pending_cmds; pending != NULL;
+ pending = pending->next)
+ {
+ if (serial == pending->serial && pending->result == NULL)
+ {
+ str = serverConvert(enc, str, &to_free);
+
+ pending->code = rcode;
+
+ if (to_free == NULL)
+ pending->result = vim_strsave(str);
+ else
+ pending->result = str;
+ break;
+ }
+ }
+ }
+ return;
+ }
+ else if (cmd->cmd_type == SS_CMD_TYPE_NOTIFY)
+ {
+ // Notification, execute autocommands and save the reply for later use
+ if (sender != NULL && str != NULL && enc != NULL)
+ {
+ ss_reply_T *reply;
+
+ str = serverConvert(enc, str, &to_free);
+ sender = serverConvert(enc, sender, &to_free2);
+
+ reply = socket_server_add_reply(sender);
+
+ if (reply != NULL)
+ ga_copy_string(&reply->strings, str);
+
+ apply_autocmds(EVENT_REMOTEREPLY, sender, str, TRUE, curbuf);
+
+ vim_free(to_free);
+ vim_free(to_free2);
+ }
+ return;
+ }
+ else if (cmd->cmd_type == SS_CMD_TYPE_ALIVE)
+ {
+ // Client wants to check if we are still responsive, send back a single
+ // byte as a YES.
+ char_u buf[1] = {1};
+#ifndef HAVE_SELECT
+ struct pollfd pfd;
+
+ pfd.fd = fd;
+ pfd.events = POLLIN;
+#else
+ fd_set rfds;
+ struct timeval tv;
+
+ FD_ZERO(&rfds);
+ FD_SET(fd, &rfds);
+#endif
+
+ if (write(fd, buf, 1) == -1)
+ return;
+
+ // Poll until client closes their end
+
+#ifndef HAVE_SELECT
+ poll(&pfd, 1, 1000);
+#else
+ tv.tv_sec = 1;
+ tv.tv_usec = 0;
+ select(fd + 1, &rfds, NULL, NULL, &tv);
+#endif
+ return;
+ }
+
+ // Command type is invalid, do nothing
+ return;
+}
+
+/*
+ * Poll the socket server fd until a new connection is accepted. Returns 0 on
+ * success, 1 if it timed out or if poll returned empty, and -1 on error.
+ */
+ static int
+socket_server_dispatch(int timeout)
+{
+ int ret;
+#ifndef HAVE_SELECT
+ struct pollfd pfd;
+
+ pfd.fd = socket_server_fd;
+ pfd.events = POLLIN;
+#else
+ fd_set rfds;
+ fd_set efds;
+ struct timeval tv;
+
+ FD_ZERO(&rfds);
+ FD_ZERO(&efds);
+ FD_SET(socket_server_fd, &rfds);
+ FD_SET(socket_server_fd, &efds);
+#endif
+
+#ifndef HAVE_SELECT
+ ret = poll(&pfd, 1, timeout);
+#else
+ tv.tv_sec = timeout / 1000;
+ tv.tv_usec = (timeout % 1000) * 1000;
+ ret = select(socket_server_fd + 1, &rfds, NULL, &efds, &tv);
+#endif
+
+ if (ret < 0)
+ return -1;
+ else if (ret == 0)
+ return 1;
+
+#ifndef HAVE_SELECT
+ if (pfd.revents & POLLIN)
+#else
+ if (FD_ISSET(socket_server_fd, &rfds))
+#endif
+ {
+ socket_server_accept_client();
+ return 0;
+ }
+#ifndef HAVE_SELECT
+ else if (pfd.revents & (POLLHUP | POLLERR))
+#else
+ else if (FD_ISSET(socket_server_fd, &efds))
+#endif
+ // Connection was closed
+ return -1;
+ else
+ return 1;
+
+ return -1;
+}
+
+/*
+ * Check if socket "name" is reponsive by sending an ALIVE command. This does
+ * not require the socket server to be active.
+ */
+ static int
+socket_server_check_alive(char_u *name)
+{
+ int socket_fd;
+ int ret;
+ size_t sz;
+ char_u *final;
+ char_u buf[1] = {0};
+#ifndef HAVE_SELECT
+ struct pollfd pfd;
+#else
+ fd_set rfds;
+ struct timeval tv;
+#endif
+
+ socket_fd = socket_server_connect(name, NULL, TRUE);
+
+ if (socket_fd == -1)
+ return FALSE;
+
+#ifndef HAVE_SELECT
+ pfd.fd = socket_fd;
+ pfd.events = POLLIN;
+#else
+ FD_ZERO(&rfds);
+ FD_SET(socket_fd, &rfds);
+#endif
+
+ ss_cmd_T cmd;
+
+ socket_server_init_cmd(&cmd, SS_CMD_TYPE_ALIVE);
+
+ final = socket_server_encode_cmd(&cmd, &sz);
+
+ if (final == NULL ||
+ socket_server_write(socket_fd, final, sz, 1000) == FAIL)
+ {
+ vim_free(final);
+ close(socket_fd);
+ return FALSE;
+ }
+ vim_free(final);
+
+ // Poll for response
+#ifndef HAVE_SELECT
+ ret = poll(&pfd, 1, 1000);
+#else
+ tv.tv_sec = 1;
+ tv.tv_usec = 0;
+ ret = select(socket_fd + 1, &rfds, NULL, NULL, &tv);
+#endif
+
+ if (ret > 0)
+ if (read(socket_fd, buf, 1) == -1)
+ {
+ close(socket_fd);
+ return FALSE;
+ }
+
+ close(socket_fd);
+ return buf[0] == 1;
+}
+
+/*
+ * Get file descriptor of listening socket
+ */
+ int
+socket_server_get_fd(void)
+{
+ return socket_server_fd;
+}
+
+
+/*
+ * Check if socket name is a valid name
+ */
+ static int
+socket_server_name_is_valid(char_u *name)
+{
+ if (STRLEN(name) == 0 || (name[0] != '/' && vim_strchr(name, '/') != NULL))
+ {
+ semsg(_(e_invalid_server_id_used_str), name);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/*
+ * Returns TRUE if there are clients queued in the listening socket waiting to
+ * be accepted
+ */
+ int
+socket_server_waiting_accept(void)
+{
+ int ret;
+#ifndef HAVE_SELECT
+ struct pollfd pfd;
+
+ pfd.fd = socket_server_fd;
+ pfd.events = POLLIN;
+
+ ret = poll(&pfd, 1, 0);
+
+ if (ret > 0 && pfd.revents & POLLIN)
+ return TRUE;
+#else
+ fd_set rfds;
+ struct timeval tv;
+
+ if (socket_server_fd == -1)
+ return FALSE;
+
+ FD_ZERO(&rfds);
+ FD_SET(socket_server_fd, &rfds);
+
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+ ret = select(socket_server_fd + 1, &rfds, NULL, NULL, &tv);
+
+ if (ret > 0 && FD_ISSET(socket_server_fd, &rfds))
+ return TRUE;
+#endif
+
+ return FALSE;
+}
+
+#endif // FEAT_SOCKETSERVER
msgstr ""
"Project-Id-Version: Vim\n"
"Report-Msgid-Bugs-To: vim-dev@vim.org\n"
-"POT-Creation-Date: 2025-08-16 17:57+0200\n"
+"POT-Creation-Date: 2025-08-18 21:30+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
msgid "%d of %d edited"
msgstr ""
+msgid "Socket server not online:Send expression failed"
+msgstr ""
+
msgid "No display: Send expression failed.\n"
msgstr ""
msgid "-Y\t\t\tDo not connect to Wayland compositor"
msgstr ""
+msgid "--clientserver <socket|x11> Backend for clientserver communication"
+msgstr ""
+
msgid "--remote <files>\tEdit <files> in a Vim server if possible"
msgstr ""
msgid "XSMP SmcOpenConnection failed: %s"
msgstr ""
+#, c-format
+msgid "Failed creating socket directory: %s"
+msgstr ""
+
msgid "At line"
msgstr ""
msgid "E1562: Diff anchors cannot be used with hidden diff windows"
msgstr ""
+msgid "E1563: Socket path is too big"
+msgstr ""
+
+msgid "E1564: Socket name cannot have slashes in it without being a path"
+msgstr ""
+
+msgid "E1565: Socket server is not online, call remote_startserver() first"
+msgstr ""
+
+#, c-format
+msgid "E1566: Failed connecting to socket %s: %s"
+msgstr ""
+
+msgid "E1567: Cannot start socket server, socket path is unavailable"
+msgstr ""
+
#. type of cmdline window or 0
#. result of cmdline window or 0
#. buffer of cmdline window or NULL
void gui_mch_start_blink(void);
int gui_mch_early_init_check(int give_message);
int gui_mch_init_check(void);
+void gui_gtk_init_socket_server(void);
+void gui_gtk_uninit_socket_server(void);
void gui_mch_set_dark_theme(int dark);
void gui_mch_show_tabline(int showit);
int gui_mch_showing_tabline(void);
char *did_set_shiftwidth_tabstop(optset_T *args);
char *did_set_showtabline(optset_T *args);
char *did_set_smoothscroll(optset_T *args);
+char *did_set_socktimeoutlen(optset_T *args);
char *did_set_spell(optset_T *args);
char *did_set_swapfile(optset_T *args);
char *did_set_termguicolors(optset_T *args);
int expand_set_clipboard(optexpand_T *args, int *numMatches, char_u ***matches);
char *did_set_clipmethod(optset_T *args);
int expand_set_clipmethod(optexpand_T *args, int *numMatches, char_u ***matches);
+char *did_set_clientserver(optset_T *args UNUSED);
+int expand_set_clientserver(optexpand_T *args, int *numMatches, char_u ***matches);
char *did_set_chars_option(optset_T *args);
int expand_set_chars_option(optexpand_T *args, int *numMatches, char_u ***matches);
char *did_set_cinoptions(optset_T *args);
volatile sig_atomic_t *start_timeout(long msec);
void delete_timer(void);
int mch_create_anon_file(void);
+int socket_server_init(char_u *sock_path);
+void socket_server_uninit(void);
+char_u *socket_server_list_sockets(void);
+void socket_server_accept_client(void);
+int socket_server_valid(void);
+int socket_server_send(char_u *sock_path, char_u *cmd, char_u **result, char_u **receiver, int is_expr, int timeout, int silent);
+int socket_server_read_reply(char_u *sender, char_u **str, int timeout);
+int socket_server_peek_reply(char_u *sender, char_u **str);
+int socket_server_send_reply(char_u *client, char_u *str);
+int socket_server_get_fd(void);
+int socket_server_waiting_accept(void);
/* vim: set ft=c : */
source util/shared.vim
+" Unlike X11, we need the socket server running if we want to send commands to
+" a server via sockets.
+if v:servername == ""
+ call remote_startserver('VIMSOCKETSERVERTEST')
+endif
+
func Check_X11_Connection()
if has('x11')
CheckX11
call assert_fails('call remote_startserver("")', 'E1175:')
call assert_fails('call remote_startserver([])', 'E1174:')
call assert_fails("let x = remote_peek([])", 'E730:')
- call assert_fails("let x = remote_read('vim10')",
- \ has('unix') ? ['E573:.*vim10'] : 'E277:')
- call assert_fails("call server2client('abc', 'xyz')",
- \ has('unix') ? ['E573:.*abc'] : 'E258:')
+
+ " When using socket server, server id is not a number, but the path to the
+ " socket.
+ if has('socketserver') && !has('X11')
+ call assert_fails("let x = remote_read('vim/10')", ['E573:.*vim/10'])
+ call assert_fails("call server2client('a/b/c', 'xyz')", ['E573:.*a/b/c'])
+ else
+ call assert_fails("let x = remote_read('vim10')",
+ \ has('unix') ? ['E573:.*vim10'] : 'E277:')
+ call assert_fails("call server2client('abc', 'xyz')",
+ \ has('unix') ? ['E573:.*abc'] : 'E258:')
+ endif
endfunc
func Test_client_server_stopinsert()
endtry
endfunc
+" Test if socket server and X11 backends can be chosen and work properly.
+func Test_client_server_x11_and_socket_server()
+ CheckNotMSWindows
+ CheckFeature socketserver
+ CheckFeature x11
+
+ let g:test_is_flaky = 1
+ let cmd = GetVimCommand()
+
+ if cmd == ''
+ throw 'GetVimCommand() failed'
+ endif
+ call Check_X11_Connection()
+
+ let types = ['socket', 'x11']
+
+ for type in types
+ let name = 'VIMTEST_' .. toupper(type)
+ let actual_cmd = cmd .. ' --clientserver ' .. type
+ let actual_cmd .= ' --servername ' .. name
+ let job = job_start(actual_cmd, {'stoponexit': 'kill', 'out_io': 'null'})
+
+ call WaitForAssert({-> assert_equal("run", job_status(job))})
+ call WaitForAssert({-> assert_match(name, system(cmd .. ' --clientserver ' .. type .. ' --serverlist'))})
+
+ call assert_match(name, system(actual_cmd .. ' --remote-expr "v:servername"'))
+
+ call system(actual_cmd .. " --remote-expr 'execute(\"qa!\")'")
+ try
+ call WaitForAssert({-> assert_equal("dead", job_status(job))})
+ finally
+ if job_status(job) != 'dead'
+ call assert_report('Server did not exit')
+ call job_stop(job, 'kill')
+ endif
+ endtry
+ endfor
+endfunc
+
+" Test if socket server works in the GUI
+func Test_client_socket_server_server_gui()
+ CheckNotMSWindows
+ CheckFeature socketserver
+ CheckFeature gui_gtk
+
+ let g:test_is_flaky = 1
+ let cmd = GetVimCommand()
+
+ if cmd == ''
+ throw 'GetVimCommand() failed'
+ endif
+ call Check_X11_Connection()
+
+ let name = 'VIMTESTSOCKET'
+ let cmd .= ' --clientserver socket'
+ let cmd .= ' --servername ' .. name
+
+ let job = job_start(cmd, {'stoponexit': 'kill', 'out_io': 'null'})
+
+ call WaitForAssert({-> assert_equal("run", job_status(job))})
+ call WaitForAssert({-> assert_match(name, system(cmd .. ' --serverlist'))})
+
+ call system(cmd .. " --remote-expr 'execute(\"gui\")'")
+
+ call assert_match('1', system(cmd .. " --remote-expr 'has(\"gui_running\")'"))
+ call assert_match(name, system(cmd .. ' --remote-expr "v:servername"'))
+
+ call system(cmd .. " --remote-expr 'execute(\"qa!\")'")
+ try
+ call WaitForAssert({-> assert_equal("dead", job_status(job))})
+ finally
+ if job_status(job) != 'dead'
+ call assert_report('Server did not exit')
+ call job_stop(job, 'kill')
+ endif
+ endtry
+endfunc
+
+" Test if custom paths work for socketserver
+func Test_client_socket_server_custom_path()
+ CheckNotMSWindows
+ CheckFeature socketserver
+ CheckNotFeature x11
+
+ let g:test_is_flaky = 1
+ let cmd = GetVimCommand()
+
+ if cmd == ''
+ throw 'GetVimCommand() failed'
+ endif
+
+ let name = 'VIMTESTSOCKET2'
+
+ let paths = ['./' .. name, '../testdir/' .. name, getcwd(-1) .. '/' .. name]
+
+ for path in paths
+ let actual = cmd .. ' --servername ' .. path
+
+ let job = job_start(actual, {'stoponexit': 'kill', 'out_io': 'null'})
+
+ call WaitForAssert({-> assert_equal("run", job_status(job))})
+ call WaitForAssert({-> assert_equal(path, glob(path))})
+
+ call system(actual .. " --remote-expr 'execute(\"qa!\")'")
+ try
+ call WaitForAssert({-> assert_equal("dead", job_status(job))})
+ finally
+ if job_status(job) != 'dead'
+ call assert_report('Server did not exit')
+ call job_stop(job, 'kill')
+ endif
+ endtry
+ endfor
+endfunc
+
" Uncomment this line to get a debugging log
" call ch_logfile('channellog', 'w')
source util/screendump.vim
import './util/vim9.vim' as v9
+" Socket backend for remote functions require the socket server to be running
+if v:servername == ""
+ call remote_startserver('VIMSOCKETSERVERTEST')
+endif
+
" Test for passing too many or too few arguments to builtin functions
func Test_internalfunc_arg_error()
let l =<< trim END
call s:PreTest()
call s:CheckXConnection()
+ if v:servername == ""
+ call remote_startserver('VIMSOCKETSERVER')
+ endif
+
let l:name = 'WLVIMTEST'
let l:cmd = GetVimCommand() .. ' --servername ' .. l:name
let l:job = job_start(cmd, {'stoponexit': 'kill', 'out_io': 'null'})
call writefile(l:lines, 'Wltester', 'D')
+ if v:servername == ""
+ call remote_startserver('VIMSOCKETSERVER')
+ endif
+
let l:name = 'WLVIMTEST'
let l:cmd = GetVimCommand() .. ' -S Wltester --servername ' .. l:name
let l:job = job_start(cmd, {'stoponexit': 'kill', 'out_io': 'null'})
call s:PreTest()
call s:CheckXConnection()
+ if v:servername == ""
+ call remote_startserver('VIMSOCKETSERVER')
+ endif
+
let l:name = 'WLFLAGVIMTEST'
let l:cmd = GetVimCommand() .. ' -Y --servername ' .. l:name
let l:job = job_start(cmd, {'stoponexit': 'kill', 'out_io': 'null'})
call s:PreTest()
call s:CheckXConnection()
+ if v:servername == ""
+ call remote_startserver('VIMSOCKETSERVER')
+ endif
+
let l:name = 'WLLOSEVIMTEST'
let l:cmd = GetVimCommand() .. ' --servername ' .. l:name
let l:job = job_start(cmd, {
let l:old = $XDG_RUNTIME_DIR
unlet $XDG_RUNTIME_DIR
+ if v:servername == ""
+ call remote_startserver('VIMSOCKETSERVER')
+ endif
+
let l:name = 'WLVIMTEST'
let l:cmd = GetVimCommand() .. ' --servername ' .. l:name
let l:job = job_start(cmd, {
if ((resize_func != NULL && resize_func(TRUE))
#if defined(FEAT_CLIENTSERVER) && defined(UNIX)
- || server_waiting()
+ || (
+# ifdef FEAT_X11
+ (clientserver_method == CLIENTSERVER_METHOD_X11 &&
+ server_waiting())
+# endif
+# if defined(FEAT_X11) && defined(FEAT_SOCKETSERVER)
+ ||
+# endif
+# ifdef FEAT_SOCKETSERVER
+ (clientserver_method == CLIENTSERVER_METHOD_SOCKET &&
+ socket_server_waiting_accept())
+# endif
+ )
#endif
#ifdef MESSAGE_QUEUE
|| interrupted
"-signs",
#endif
"+smartindent",
+#ifdef FEAT_SOCKETSERVER
+ "+socketserver",
+#else
+ "-socketserver",
+#endif
#ifdef FEAT_SODIUM
# ifdef DYNAMIC_SODIUM
"+sodium/dyn",
static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 1651,
/**/
1650,
/**/