diff options
| author | Thiago de Arruda <tpadilha84@gmail.com> | 2015-02-23 12:34:20 -0300 |
|---|---|---|
| committer | Thiago de Arruda <tpadilha84@gmail.com> | 2015-02-23 21:43:33 -0300 |
| commit | d7e560e5b309578f142c23467d566877cb54ee9a (patch) | |
| tree | 9de6cc3996a01d133cd883d61a7c24a564a772ba /src/nvim/os | |
| parent | 1ec7db70ecac75736b5042203e57aae57b65abe6 (diff) | |
| download | rneovim-d7e560e5b309578f142c23467d566877cb54ee9a.tar.gz rneovim-d7e560e5b309578f142c23467d566877cb54ee9a.tar.bz2 rneovim-d7e560e5b309578f142c23467d566877cb54ee9a.zip | |
job: Allow spawning jobs connected to pseudo terminals
Diffstat (limited to 'src/nvim/os')
| -rw-r--r-- | src/nvim/os/job.c | 11 | ||||
| -rw-r--r-- | src/nvim/os/job_defs.h | 13 | ||||
| -rw-r--r-- | src/nvim/os/job_private.h | 21 | ||||
| -rw-r--r-- | src/nvim/os/pty_process.c | 225 | ||||
| -rw-r--r-- | src/nvim/os/pty_process.h | 7 |
5 files changed, 272 insertions, 5 deletions
diff --git a/src/nvim/os/job.c b/src/nvim/os/job.c index 94bb9067ed..9855a19269 100644 --- a/src/nvim/os/job.c +++ b/src/nvim/os/job.c @@ -7,6 +7,7 @@ #include "nvim/os/job.h" #include "nvim/os/job_defs.h" #include "nvim/os/job_private.h" +#include "nvim/os/pty_process.h" #include "nvim/os/rstream.h" #include "nvim/os/rstream_defs.h" #include "nvim/os/wstream.h" @@ -320,6 +321,16 @@ void *job_data(Job *job) return job->opts.data; } +/// Resize the window for a pty job +bool job_resize(Job *job, uint16_t width, uint16_t height) +{ + if (!job->opts.pty) { + return false; + } + pty_process_resize(job, width, height); + return true; +} + /// Iterates the table, sending SIGTERM to stopped jobs and SIGKILL to those /// that didn't die from SIGTERM after a while(exit_timeout is 0). static void job_stop_timer_cb(uv_timer_t *handle) diff --git a/src/nvim/os/job_defs.h b/src/nvim/os/job_defs.h index 340ef551be..ac9a37b366 100644 --- a/src/nvim/os/job_defs.h +++ b/src/nvim/os/job_defs.h @@ -37,6 +37,13 @@ typedef struct { job_exit_cb exit_cb; // Maximum memory used by the job's WStream size_t maxmem; + // Connect the job to a pseudo terminal + bool pty; + // Initial window dimensions if the job is connected to a pseudo terminal + uint16_t width, height; + // Value for the $TERM environment variable. A default value of "ansi" is + // assumed if NULL + char *term_name; } JobOptions; #define JOB_OPTIONS_INIT ((JobOptions) { \ @@ -46,6 +53,10 @@ typedef struct { .stdout_cb = NULL, \ .stderr_cb = NULL, \ .exit_cb = NULL, \ - .maxmem = 0 \ + .maxmem = 0, \ + .pty = false, \ + .width = 80, \ + .height = 24, \ + .term_name = NULL \ }) #endif // NVIM_OS_JOB_DEFS_H diff --git a/src/nvim/os/job_private.h b/src/nvim/os/job_private.h index 1beaa1bd70..b1d5e13feb 100644 --- a/src/nvim/os/job_private.h +++ b/src/nvim/os/job_private.h @@ -8,6 +8,7 @@ #include "nvim/os/rstream_defs.h" #include "nvim/os/wstream_defs.h" #include "nvim/os/pipe_process.h" +#include "nvim/os/pty_process.h" #include "nvim/os/shell.h" #include "nvim/log.h" @@ -45,12 +46,16 @@ extern uv_timer_t job_stop_timer; static inline bool process_spawn(Job *job) { - return pipe_process_spawn(job); + return job->opts.pty ? pty_process_spawn(job) : pipe_process_spawn(job); } static inline void process_init(Job *job) { - pipe_process_init(job); + if (job->opts.pty) { + pty_process_init(job); + } else { + pipe_process_init(job); + } } static inline void process_close(Job *job) @@ -59,12 +64,20 @@ static inline void process_close(Job *job) return; } job->closed = true; - pipe_process_close(job); + if (job->opts.pty) { + pty_process_close(job); + } else { + pipe_process_close(job); + } } static inline void process_destroy(Job *job) { - pipe_process_destroy(job); + if (job->opts.pty) { + pty_process_destroy(job); + } else { + pipe_process_destroy(job); + } } static inline void job_exit_callback(Job *job) diff --git a/src/nvim/os/pty_process.c b/src/nvim/os/pty_process.c new file mode 100644 index 0000000000..bd7247c741 --- /dev/null +++ b/src/nvim/os/pty_process.c @@ -0,0 +1,225 @@ +// Some of the code came from pangoterm and libuv +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> + +#include <unistd.h> +#include <termios.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/ioctl.h> + +// forkpty is not in POSIX, so headers are platform-specific +#if defined(__FreeBSD__) +# include <libutil.h> +#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) +# include <util.h> +#else +# include <pty.h> +#endif + +#include <uv.h> + +#include "nvim/os/job.h" +#include "nvim/os/job_defs.h" +#include "nvim/os/job_private.h" +#include "nvim/os/pty_process.h" +#include "nvim/memory.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "os/pty_process.c.generated.h" +#endif + +typedef struct { + struct winsize winsize; + uv_pipe_t proc_stdin, proc_stdout, proc_stderr; + uv_signal_t schld; + int tty_fd; +} PtyProcess; + +void pty_process_init(Job *job) +{ + PtyProcess *ptyproc = xmalloc(sizeof(PtyProcess)); + + if (job->opts.writable) { + uv_pipe_init(uv_default_loop(), &ptyproc->proc_stdin, 0); + ptyproc->proc_stdin.data = NULL; + } + + if (job->opts.stdout_cb) { + uv_pipe_init(uv_default_loop(), &ptyproc->proc_stdout, 0); + ptyproc->proc_stdout.data = NULL; + } + + if (job->opts.stderr_cb) { + uv_pipe_init(uv_default_loop(), &ptyproc->proc_stderr, 0); + ptyproc->proc_stderr.data = NULL; + } + + job->proc_stdin = (uv_stream_t *)&ptyproc->proc_stdin; + job->proc_stdout = (uv_stream_t *)&ptyproc->proc_stdout; + job->proc_stderr = (uv_stream_t *)&ptyproc->proc_stderr; + job->process = ptyproc; +} + +void pty_process_destroy(Job *job) +{ + free(job->opts.term_name); + free(job->process); + job->process = NULL; +} + +bool pty_process_spawn(Job *job) +{ + int master; + PtyProcess *ptyproc = job->process; + ptyproc->winsize = (struct winsize){job->opts.height, job->opts.width, 0, 0}; + struct termios termios; + init_termios(&termios); + uv_disable_stdio_inheritance(); + + int pid = forkpty(&master, NULL, &termios, &ptyproc->winsize); + + if (pid < 0) { + return false; + } else if (pid == 0) { + init_child(job); + abort(); + } + + // make sure the master file descriptor is non blocking + fcntl(master, F_SETFL, fcntl(master, F_GETFL) | O_NONBLOCK); + + if (job->opts.writable) { + uv_pipe_open(&ptyproc->proc_stdin, dup(master)); + } + + if (job->opts.stdout_cb) { + uv_pipe_open(&ptyproc->proc_stdout, dup(master)); + } + + if (job->opts.stderr_cb) { + uv_pipe_open(&ptyproc->proc_stderr, dup(master)); + } + + uv_signal_init(uv_default_loop(), &ptyproc->schld); + uv_signal_start(&ptyproc->schld, chld_handler, SIGCHLD); + ptyproc->schld.data = job; + ptyproc->tty_fd = master; + job->pid = pid; + return true; +} + +void pty_process_close(Job *job) +{ + PtyProcess *ptyproc = job->process; + uv_signal_stop(&ptyproc->schld); + uv_close((uv_handle_t *)&ptyproc->schld, NULL); + job_close_streams(job); + job_decref(job); +} + +void pty_process_resize(Job *job, uint16_t width, uint16_t height) +{ + PtyProcess *ptyproc = job->process; + ptyproc->winsize = (struct winsize){height, width, 0, 0}; + ioctl(ptyproc->tty_fd, TIOCSWINSZ, &ptyproc->winsize); +} + +static void init_child(Job *job) +{ + unsetenv("COLUMNS"); + unsetenv("LINES"); + unsetenv("TERMCAP"); + unsetenv("COLORTERM"); + unsetenv("COLORFGBG"); + + signal(SIGCHLD, SIG_DFL); + signal(SIGHUP, SIG_DFL); + signal(SIGINT, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + signal(SIGTERM, SIG_DFL); + signal(SIGALRM, SIG_DFL); + + setenv("TERM", job->opts.term_name ? job->opts.term_name : "ansi", 1); + execvp(job->opts.argv[0], job->opts.argv); + fprintf(stderr, "execvp failed: %s\n", strerror(errno)); +} + +static void chld_handler(uv_signal_t *handle, int signum) +{ + Job *job = handle->data; + int stat = 0; + + if (waitpid(job->pid, &stat, 0) < 0) { + fprintf(stderr, "Waiting for pid %d failed: %s\n", job->pid, + strerror(errno)); + return; + } + + if (WIFSTOPPED(stat) || WIFCONTINUED(stat)) { + // Did not exit + return; + } + + if (WIFEXITED(stat)) { + job->status = WEXITSTATUS(stat); + } else if (WIFSIGNALED(stat)) { + job->status = WTERMSIG(stat); + } + + pty_process_close(job); +} + +static void init_termios(struct termios *termios) +{ + memset(termios, 0, sizeof(struct termios)); + // Taken from pangoterm + termios->c_iflag = ICRNL|IXON; + termios->c_oflag = OPOST|ONLCR|TAB0; + termios->c_cflag = CS8|CREAD; + termios->c_lflag = ISIG|ICANON|IEXTEN|ECHO|ECHOE|ECHOK; + + cfsetspeed(termios, 38400); + +#ifdef IUTF8 + termios->c_iflag |= IUTF8; +#endif +#ifdef NL0 + termios->c_oflag |= NL0; +#endif +#ifdef CR0 + termios->c_oflag |= CR0; +#endif +#ifdef BS0 + termios->c_oflag |= BS0; +#endif +#ifdef VT0 + termios->c_oflag |= VT0; +#endif +#ifdef FF0 + termios->c_oflag |= FF0; +#endif +#ifdef ECHOCTL + termios->c_lflag |= ECHOCTL; +#endif +#ifdef ECHOKE + termios->c_lflag |= ECHOKE; +#endif + + termios->c_cc[VINTR] = 0x1f & 'C'; + termios->c_cc[VQUIT] = 0x1f & '\\'; + termios->c_cc[VERASE] = 0x7f; + termios->c_cc[VKILL] = 0x1f & 'U'; + termios->c_cc[VEOF] = 0x1f & 'D'; + termios->c_cc[VEOL] = _POSIX_VDISABLE; + termios->c_cc[VEOL2] = _POSIX_VDISABLE; + termios->c_cc[VSTART] = 0x1f & 'Q'; + termios->c_cc[VSTOP] = 0x1f & 'S'; + termios->c_cc[VSUSP] = 0x1f & 'Z'; + termios->c_cc[VREPRINT] = 0x1f & 'R'; + termios->c_cc[VWERASE] = 0x1f & 'W'; + termios->c_cc[VLNEXT] = 0x1f & 'V'; + termios->c_cc[VMIN] = 1; + termios->c_cc[VTIME] = 0; +} diff --git a/src/nvim/os/pty_process.h b/src/nvim/os/pty_process.h new file mode 100644 index 0000000000..62fcd1671f --- /dev/null +++ b/src/nvim/os/pty_process.h @@ -0,0 +1,7 @@ +#ifndef NVIM_OS_PTY_PROCESS_H +#define NVIM_OS_PTY_PROCESS_H + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "os/pty_process.h.generated.h" +#endif +#endif // NVIM_OS_PTY_PROCESS_H |