aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt5
-rw-r--r--cmake/FindWinpty.cmake10
-rw-r--r--src/nvim/CMakeLists.txt7
-rw-r--r--src/nvim/os/pty_process_win.c189
-rw-r--r--src/nvim/os/pty_process_win.h25
5 files changed, 229 insertions, 7 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 594f631ba0..17e14bcbd0 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -351,6 +351,11 @@ endif()
find_package(LibVterm REQUIRED)
include_directories(SYSTEM ${LIBVTERM_INCLUDE_DIRS})
+if(WIN32)
+ find_package(Winpty REQUIRED)
+ include_directories(SYSTEM ${WINPTY_INCLUDE_DIRS})
+endif()
+
option(CLANG_ASAN_UBSAN "Enable Clang address & undefined behavior sanitizer for nvim binary." OFF)
option(CLANG_MSAN "Enable Clang memory sanitizer for nvim binary." OFF)
option(CLANG_TSAN "Enable Clang thread sanitizer for nvim binary." OFF)
diff --git a/cmake/FindWinpty.cmake b/cmake/FindWinpty.cmake
new file mode 100644
index 0000000000..8feafc58a8
--- /dev/null
+++ b/cmake/FindWinpty.cmake
@@ -0,0 +1,10 @@
+include(LibFindMacros)
+
+find_path(WINPTY_INCLUDE_DIR winpty.h)
+set(WINPTY_INCLUDE_DIRS ${WINPTY_INCLUDE_DIR})
+
+find_library(WINPTY_LIBRARY winpty)
+find_program(WINPTY_AGENT_EXE winpty-agent.exe)
+set(WINPTY_LIBRARIES ${WINPTY_LIBRARY})
+
+find_package_handle_standard_args(Winpty DEFAULT_MSG WINPTY_LIBRARY WINPTY_INCLUDE_DIR)
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt
index c46c0bed6d..5f9d08cfa3 100644
--- a/src/nvim/CMakeLists.txt
+++ b/src/nvim/CMakeLists.txt
@@ -111,6 +111,9 @@ foreach(sfile ${NVIM_SOURCES})
if(WIN32 AND ${f} MATCHES "^(pty_process_unix.c)$")
list(APPEND to_remove ${sfile})
endif()
+ if(NOT WIN32 AND ${f} MATCHES "^(pty_process_win.c)$")
+ list(APPEND to_remove ${sfile})
+ endif()
endforeach()
list(REMOVE_ITEM NVIM_SOURCES ${to_remove})
@@ -350,6 +353,10 @@ if(Iconv_LIBRARIES)
list(APPEND NVIM_LINK_LIBRARIES ${Iconv_LIBRARIES})
endif()
+if(WIN32)
+ list(APPEND NVIM_LINK_LIBRARIES ${WINPTY_LIBRARIES})
+endif()
+
# Put these last on the link line, since multiple things may depend on them.
list(APPEND NVIM_LINK_LIBRARIES
${LIBUV_LIBRARIES}
diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c
new file mode 100644
index 0000000000..e75c92e7fb
--- /dev/null
+++ b/src/nvim/os/pty_process_win.c
@@ -0,0 +1,189 @@
+#include <assert.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include "nvim/memory.h"
+#include "nvim/os/pty_process_win.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "os/pty_process_win.c.generated.h"
+#endif
+
+static void CALLBACK pty_process_finish1(void *context, BOOLEAN unused)
+{
+ uv_async_t *finish_async = (uv_async_t *)context;
+ uv_async_send(finish_async);
+}
+
+bool pty_process_spawn(PtyProcess *ptyproc)
+ FUNC_ATTR_NONNULL_ALL
+{
+ Process *proc = (Process *)ptyproc;
+ bool success = false;
+ winpty_error_ptr_t err = NULL;
+ winpty_config_t *cfg = NULL;
+ winpty_spawn_config_t *spawncfg = NULL;
+ winpty_t *wp = NULL;
+ char *in_name = NULL, *out_name = NULL;
+ HANDLE process_handle = NULL;
+
+ assert(proc->in && proc->out && !proc->err);
+
+ if (!(cfg = winpty_config_new(
+ WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION, &err))) {
+ goto cleanup;
+ }
+ winpty_config_set_initial_size(cfg, ptyproc->width, ptyproc->height);
+
+ if (!(wp = winpty_open(cfg, &err))) {
+ goto cleanup;
+ }
+
+ in_name = utf16_to_utf8(winpty_conin_name(wp));
+ out_name = utf16_to_utf8(winpty_conout_name(wp));
+ uv_pipe_connect(
+ xmalloc(sizeof(uv_connect_t)),
+ &proc->in->uv.pipe,
+ in_name,
+ pty_process_connect_cb);
+ uv_pipe_connect(
+ xmalloc(sizeof(uv_connect_t)),
+ &proc->out->uv.pipe,
+ out_name,
+ pty_process_connect_cb);
+
+ // XXX: Provide the correct ptyprocess parameters (at least, the cmdline...
+ // probably cwd too? what about environ?)
+ if (!(spawncfg = winpty_spawn_config_new(
+ WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN,
+ L"C:\\Windows\\System32\\cmd.exe",
+ L"C:\\Windows\\System32\\cmd.exe",
+ NULL, NULL,
+ &err))) {
+ goto cleanup;
+ }
+ if (!winpty_spawn(wp, spawncfg, &process_handle, NULL, NULL, &err)) {
+ goto cleanup;
+ }
+
+ uv_async_init(&proc->loop->uv, &ptyproc->finish_async, pty_process_finish2);
+ if (!RegisterWaitForSingleObject(&ptyproc->finish_wait, process_handle,
+ pty_process_finish1, &ptyproc->finish_async, INFINITE, 0)) {
+ abort();
+ }
+
+ ptyproc->wp = wp;
+ ptyproc->process_handle = process_handle;
+ wp = NULL;
+ process_handle = NULL;
+ success = true;
+
+cleanup:
+ winpty_error_free(err);
+ winpty_config_free(cfg);
+ winpty_spawn_config_free(spawncfg);
+ winpty_free(wp);
+ xfree(in_name);
+ xfree(out_name);
+ if (process_handle != NULL) {
+ CloseHandle(process_handle);
+ }
+ return success;
+}
+
+void pty_process_resize(PtyProcess *ptyproc, uint16_t width,
+ uint16_t height)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (ptyproc->wp != NULL) {
+ winpty_set_size(ptyproc->wp, width, height, NULL);
+ }
+}
+
+void pty_process_close(PtyProcess *ptyproc)
+ FUNC_ATTR_NONNULL_ALL
+{
+ Process *proc = (Process *)ptyproc;
+
+ ptyproc->is_closing = true;
+ pty_process_close_master(ptyproc);
+
+ uv_handle_t *finish_async_handle = (uv_handle_t *)&ptyproc->finish_async;
+ if (ptyproc->finish_wait != NULL) {
+ // Use INVALID_HANDLE_VALUE to block until either the wait is cancelled
+ // or the callback has signalled the uv_async_t.
+ UnregisterWaitEx(ptyproc->finish_wait, INVALID_HANDLE_VALUE);
+ uv_close(finish_async_handle, pty_process_finish_closing);
+ } else {
+ pty_process_finish_closing(finish_async_handle);
+ }
+}
+
+void pty_process_close_master(PtyProcess *ptyproc)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (ptyproc->wp != NULL) {
+ winpty_free(ptyproc->wp);
+ ptyproc->wp = NULL;
+ }
+}
+
+void pty_process_teardown(Loop *loop)
+ FUNC_ATTR_NONNULL_ALL
+{
+}
+
+// Returns a string freeable with xfree. Never returns NULL (OOM is a fatal
+// error). Windows appears to replace invalid UTF-16 code points (i.e.
+// unpaired surrogates) using U+FFFD (the replacement character).
+static char *utf16_to_utf8(LPCWSTR str)
+ FUNC_ATTR_NONNULL_ALL
+{
+ int len = WideCharToMultiByte(CP_UTF8, 0, str, -1, NULL, 0, NULL, NULL);
+ assert(len >= 1); // Even L"" has a non-zero length due to NUL terminator.
+ char *ret = xmalloc(len);
+ int len2 = WideCharToMultiByte(CP_UTF8, 0, str, -1, ret, len, NULL, NULL);
+ assert(len == len2);
+ return ret;
+}
+
+static void pty_process_connect_cb(uv_connect_t *req, int status)
+{
+ assert(status == 0);
+ xfree(req);
+}
+
+static void pty_process_finish2(uv_async_t *finish_async)
+{
+ PtyProcess *ptyproc =
+ (PtyProcess *)((char *)finish_async - offsetof(PtyProcess, finish_async));
+ Process *proc = (Process *)ptyproc;
+
+ if (!ptyproc->is_closing) {
+ // If pty_process_close has already been called, be consistent and never
+ // call the internal_exit callback.
+
+ DWORD exit_code = 0;
+ GetExitCodeProcess(ptyproc->process_handle, &exit_code);
+ proc->status = exit_code;
+
+ if (proc->internal_exit_cb) {
+ proc->internal_exit_cb(proc);
+ }
+ }
+}
+
+static void pty_process_finish_closing(uv_handle_t *finish_async)
+{
+ PtyProcess *ptyproc =
+ (PtyProcess *)((char *)finish_async - offsetof(PtyProcess, finish_async));
+ Process *proc = (Process *)ptyproc;
+
+ if (ptyproc->process_handle != NULL) {
+ CloseHandle(ptyproc->process_handle);
+ ptyproc->process_handle = NULL;
+ }
+ if (proc->internal_close_cb) {
+ proc->internal_close_cb(proc);
+ }
+}
diff --git a/src/nvim/os/pty_process_win.h b/src/nvim/os/pty_process_win.h
index 8e2b37a1c1..87b2b6545d 100644
--- a/src/nvim/os/pty_process_win.h
+++ b/src/nvim/os/pty_process_win.h
@@ -1,21 +1,23 @@
#ifndef NVIM_OS_PTY_PROCESS_WIN_H
#define NVIM_OS_PTY_PROCESS_WIN_H
+#include <uv.h>
+
+#include <winpty.h>
+
#include "nvim/event/libuv_process.h"
typedef struct pty_process {
Process process;
char *term_name;
uint16_t width, height;
+ winpty_t *wp;
+ uv_async_t finish_async;
+ HANDLE finish_wait;
+ HANDLE process_handle;
+ bool is_closing;
} PtyProcess;
-#define pty_process_spawn(job) libuv_process_spawn((LibuvProcess *)job)
-#define pty_process_close(job) libuv_process_close((LibuvProcess *)job)
-#define pty_process_close_master(job) libuv_process_close((LibuvProcess *)job)
-#define pty_process_resize(job, width, height) ( \
- (void)job, (void)width, (void)height, 0)
-#define pty_process_teardown(loop) ((void)loop, 0)
-
static inline PtyProcess pty_process_init(Loop *loop, void *data)
{
PtyProcess rv;
@@ -23,7 +25,16 @@ static inline PtyProcess pty_process_init(Loop *loop, void *data)
rv.term_name = NULL;
rv.width = 80;
rv.height = 24;
+ rv.wp = NULL;
+ // XXX: Zero rv.finish_async somehow?
+ rv.finish_wait = NULL;
+ rv.process_handle = NULL;
+ rv.is_closing = false;
return rv;
}
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "os/pty_process_win.h.generated.h"
+#endif
+
#endif // NVIM_OS_PTY_PROCESS_WIN_H