diff options
author | Ryan Prichard <ryan.prichard@gmail.com> | 2016-02-24 02:14:19 -0600 |
---|---|---|
committer | Justin M. Keyes <justinkz@gmail.com> | 2017-08-16 09:13:43 +0200 |
commit | 7f22a27a10655dc40528f35034cbdf8c9543241c (patch) | |
tree | a42277c939dbb715cf1d6505e7abc44f30833612 /src/nvim/os/pty_process_win.c | |
parent | 30cb66e8ba520d4564189ff763b5859e3d80581f (diff) | |
download | rneovim-7f22a27a10655dc40528f35034cbdf8c9543241c.tar.gz rneovim-7f22a27a10655dc40528f35034cbdf8c9543241c.tar.bz2 rneovim-7f22a27a10655dc40528f35034cbdf8c9543241c.zip |
win: integrate winpty (WIP)
Handling of process exit is still broken. It detects the moment when the
child process exits, then quickly stops polling for process output. It
should continue polling for output until the agent has scraped all of the
process' output. This problem is easy to notice by running a command like
"dir && exit", but even typing "exit<ENTER>" can manifest the problem --
the "t" might not appear.
winpty's Cygwin adapter handles shutdown by waiting for the agent to close
the CONOUT pipe, which it does after it has scraped the child's last
output. AFAIK, neovim doesn't do anything interesting when winpty closes
the CONOUT pipe.
Diffstat (limited to 'src/nvim/os/pty_process_win.c')
-rw-r--r-- | src/nvim/os/pty_process_win.c | 189 |
1 files changed, 189 insertions, 0 deletions
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); + } +} |