From 9e99726ca776aa6da6c00215c22a8e5b4774f3db Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 9 Jan 2012 00:47:25 +0200 Subject: [PATCH 1/7] eloop: allow reporting error conditions Add KMSCON_ERR, the equivalent of EPOLLERR. Signed-off-by: Ran Benita --- src/eloop.c | 2 ++ src/eloop.h | 1 + 2 files changed, 3 insertions(+) diff --git a/src/eloop.c b/src/eloop.c index 8b05ca7..20d417e 100644 --- a/src/eloop.c +++ b/src/eloop.c @@ -566,6 +566,8 @@ int kmscon_eloop_dispatch(struct kmscon_eloop *loop, int timeout) mask |= KMSCON_WRITEABLE; if (ep[i].events & EPOLLHUP) mask |= KMSCON_HUP; + if (ep[i].events & EPOLLERR) + mask |= KMSCON_ERR; fd->cb(fd, mask, fd->data); } diff --git a/src/eloop.h b/src/eloop.h index 8e560f2..e12e0ca 100644 --- a/src/eloop.h +++ b/src/eloop.h @@ -50,6 +50,7 @@ enum kmscon_eloop_flags { KMSCON_READABLE = 0x01, KMSCON_WRITEABLE = 0x02, KMSCON_HUP = 0x04, + KMSCON_ERR = 0x08, }; int kmscon_eloop_new(struct kmscon_eloop **out); From 9a51e10e32ddff13eeff300aacb8c465badd5c81 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 9 Jan 2012 00:48:39 +0200 Subject: [PATCH 2/7] log: make sure not to override errno This makes things easier in the common case of -> -> - Signed-off-by: Ran Benita --- src/log.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/log.c b/src/log.c index 40aa1d2..5810a4b 100644 --- a/src/log.c +++ b/src/log.c @@ -30,6 +30,7 @@ * prefixes. */ +#include #include #include #include @@ -49,5 +50,9 @@ void log_printf(const char *format, ...) __attribute__ ((format (printf, 1, 0))) void log_vprintf(const char *format, va_list list) { + int saved_errno = errno; + vfprintf(stderr, format, list); + + errno = saved_errno; } From 64c018bfd28a1ca372ffc25ca40d443cd7a29c54 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 9 Jan 2012 00:57:15 +0200 Subject: [PATCH 3/7] input: make "us" default layout, override by env vars The other layouts can be confusing. Signed-off-by: Ran Benita --- src/input.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/input.c b/src/input.c index e0f2f37..9d239ee 100644 --- a/src/input.c +++ b/src/input.c @@ -240,11 +240,7 @@ int kmscon_input_new(struct kmscon_input **out) { int ret; struct kmscon_input *input; - - /* TODO: Make configurable */ - static const char *layout = "us,ru,il"; - static const char *variant = "intl,,biblical"; - static const char *options = "grp:menu_toggle,grp:ctrl_shift_toggle"; + static const char *layout, *variant, *options; if (!out) return -EINVAL; @@ -259,6 +255,11 @@ int kmscon_input_new(struct kmscon_input **out) input->ref = 1; input->state = INPUT_ASLEEP; + /* TODO: Make properly configurable */ + layout = getenv("KMSCON_XKB_LAYOUT") ?: "us"; + variant = getenv("KMSCON_XKB_VARIANT") ?: ""; + options = getenv("KMSCON_XKB_OPTIONS") ?: ""; + ret = kmscon_xkb_new_desc(layout, variant, options, &input->xkb_desc); if (ret) { log_warning("input: cannot create xkb description\n"); From 969da54ee30e375c36f582b4407e2e84ac3cb8cb Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 9 Jan 2012 01:28:36 +0200 Subject: [PATCH 4/7] vt: add a few CLOEXEC's These are the only open() calls missing the flag. Signed-off-by: Ran Benita --- src/vt.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/vt.c b/src/vt.c index 5f704d9..aeec7e8 100644 --- a/src/vt.c +++ b/src/vt.c @@ -234,9 +234,10 @@ static int open_tty(int id, int *tty_fd, int *tty_num) return -EINVAL; if (id == KMSCON_VT_NEW) { - fd = open("/dev/tty0", O_NONBLOCK | O_NOCTTY); + fd = open("/dev/tty0", O_NONBLOCK | O_NOCTTY | O_CLOEXEC); if (fd < 0) { - fd = open("/dev/tty1", O_NONBLOCK | O_NOCTTY); + fd = open("/dev/tty1", O_NONBLOCK | O_NOCTTY | + O_CLOEXEC); if (fd < 0) { log_err("vt: cannot find unused tty\n"); return -errno; @@ -255,7 +256,7 @@ static int open_tty(int id, int *tty_fd, int *tty_num) filename[sizeof(filename) - 1] = 0; log_debug("vt: using tty %s\n", filename); - fd = open(filename, O_RDWR | O_NOCTTY); + fd = open(filename, O_RDWR | O_NOCTTY | O_CLOEXEC); if (fd < 0) { log_err("vt: cannot open vt\n"); return -errno; From 0a5bf7bb2840bb493f0f297c4dce86e2bda04cc9 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 9 Jan 2012 01:55:55 +0200 Subject: [PATCH 5/7] vte, terminal: add pseudo terminal support This commit adds some needed terminal emulator infrastructure. We allow to "open" (and close) a vte object (resp. terminal object). We then create a pty pair, fork and exec a shell. We route input to the shell and draw its output to the console. We add callbacks for when - The buffer changes (through the vte object). We can then schedule a screen redraw. - The shell (child process) exits. We can then exit ourselves, start up a new shell, etc. There is not yet any real VTE processing, so we display raw escape codes and so on. However, this should provide immediate feedback for any further vte development, as we start to act like a real terminal emulator. Signed-off-by: Ran Benita --- src/console.h | 3 + src/console_cell.c | 8 +- src/terminal.c | 92 ++++++++--- src/terminal.h | 13 +- src/vte.c | 364 +++++++++++++++++++++++++++++++++++++++++- src/vte.h | 16 +- tests/test_terminal.c | 20 ++- 7 files changed, 473 insertions(+), 43 deletions(-) diff --git a/src/console.h b/src/console.h index b91c625..d5d0d56 100644 --- a/src/console.h +++ b/src/console.h @@ -43,6 +43,9 @@ struct kmscon_buffer; struct kmscon_console; +#define KMSCON_DEFAULT_WIDTH 80 +#define KMSCON_DEFAULT_HEIGHT 24 + /* console buffer with cell objects */ int kmscon_buffer_new(struct kmscon_buffer **out, unsigned int x, diff --git a/src/console_cell.c b/src/console_cell.c index e3608e0..485e7f8 100644 --- a/src/console_cell.c +++ b/src/console_cell.c @@ -86,8 +86,6 @@ #include "log.h" #include "unicode.h" -#define DEFAULT_WIDTH 80 -#define DEFAULT_HEIGHT 24 #define DEFAULT_SCROLLBACK 128 struct cell { @@ -172,7 +170,7 @@ static int resize_line(struct line *line, unsigned int width) return -EINVAL; if (!width) - width = DEFAULT_WIDTH; + width = KMSCON_DEFAULT_WIDTH; if (line->size < width) { tmp = realloc(line->cells, width * sizeof(struct cell)); @@ -337,9 +335,9 @@ int kmscon_buffer_resize(struct kmscon_buffer *buf, unsigned int x, return -EINVAL; if (!x) - x = DEFAULT_WIDTH; + x = KMSCON_DEFAULT_WIDTH; if (!y) - y = DEFAULT_HEIGHT; + y = KMSCON_DEFAULT_HEIGHT; if (buf->size_x == x && buf->size_y == y) return 0; diff --git a/src/terminal.c b/src/terminal.c index ec61bfd..c02b802 100644 --- a/src/terminal.c +++ b/src/terminal.c @@ -39,6 +39,7 @@ #include "console.h" #include "eloop.h" #include "font.h" +#include "input.h" #include "log.h" #include "terminal.h" #include "unicode.h" @@ -59,6 +60,9 @@ struct kmscon_terminal { struct kmscon_console *console; struct kmscon_idle *redraw; struct kmscon_vte *vte; + + kmscon_terminal_closed_cb closed_cb; + void *closed_data; }; static void draw_all(struct kmscon_idle *idle, void *data) @@ -101,20 +105,10 @@ static void schedule_redraw(struct kmscon_terminal *term) log_warning("terminal: cannot schedule redraw\n"); } -static const char help_text[] = -"terminal subsystem - KMS based console test\n" -"This is some default text to test the drawing operations.\n\n"; - -static void print_help(struct kmscon_terminal *term) +void vte_changed(struct kmscon_vte *vte, void *data) { - unsigned int i, len; - kmscon_symbol_t ch; - - len = sizeof(help_text) - 1; - for (i = 0; i < len; ++i) { - ch = kmscon_symbol_make(help_text[i]); - kmscon_terminal_input(term, ch); - } + struct kmscon_terminal *term = data; + schedule_redraw(term); } int kmscon_terminal_new(struct kmscon_terminal **out, @@ -143,11 +137,10 @@ int kmscon_terminal_new(struct kmscon_terminal **out, if (ret) goto err_idle; - ret = kmscon_vte_new(&term->vte); + ret = kmscon_vte_new(&term->vte, vte_changed, term); if (ret) goto err_con; kmscon_vte_bind(term->vte, term->console); - print_help(term); *out = term; return 0; @@ -177,16 +170,16 @@ void kmscon_terminal_unref(struct kmscon_terminal *term) if (--term->ref) return; + term->closed_cb = NULL; + kmscon_terminal_close(term); kmscon_terminal_rm_all_outputs(term); kmscon_vte_unref(term->vte); kmscon_console_unref(term->console); - kmscon_terminal_disconnect_eloop(term); free(term); log_debug("terminal: destroying terminal object\n"); } -int kmscon_terminal_connect_eloop(struct kmscon_terminal *term, - struct kmscon_eloop *eloop) +int connect_eloop(struct kmscon_terminal *term, struct kmscon_eloop *eloop) { if (!term || !eloop) return -EINVAL; @@ -200,7 +193,7 @@ int kmscon_terminal_connect_eloop(struct kmscon_terminal *term, return 0; } -void kmscon_terminal_disconnect_eloop(struct kmscon_terminal *term) +void disconnect_eloop(struct kmscon_terminal *term) { if (!term) return; @@ -209,6 +202,60 @@ void kmscon_terminal_disconnect_eloop(struct kmscon_terminal *term) term->eloop = NULL; } +static void vte_closed(struct kmscon_vte *vte, void *data) +{ + struct kmscon_terminal *term = data; + kmscon_terminal_close(term); +} + +int kmscon_terminal_open(struct kmscon_terminal *term, + struct kmscon_eloop *eloop, + kmscon_terminal_closed_cb closed_cb, void *data) +{ + int ret; + + if (!term) + return -EINVAL; + + ret = connect_eloop(term, eloop); + if (ret == -EALREADY) { + disconnect_eloop(term); + ret = connect_eloop(term, eloop); + } + if (ret) + return ret; + + ret = kmscon_vte_open(term->vte, eloop, vte_closed, term); + if (ret) { + disconnect_eloop(term); + return ret; + } + + term->closed_cb = closed_cb; + term->closed_data = data; + return 0; +} + +void kmscon_terminal_close(struct kmscon_terminal *term) +{ + kmscon_terminal_closed_cb cb; + void *data; + + if (!term) + return; + + cb = term->closed_cb; + data = term->closed_data; + term->closed_data = NULL; + term->closed_cb = NULL; + + disconnect_eloop(term); + kmscon_vte_close(term->vte); + + if (cb) + cb(term, data); +} + int kmscon_terminal_add_output(struct kmscon_terminal *term, struct kmscon_output *output) { @@ -239,6 +286,7 @@ int kmscon_terminal_add_output(struct kmscon_terminal *term, if (term->max_height < height) { term->max_height = height; kmscon_console_resize(term->console, 0, 0, term->max_height); + kmscon_vte_resize(term->vte); } schedule_redraw(term); @@ -261,8 +309,8 @@ void kmscon_terminal_rm_all_outputs(struct kmscon_terminal *term) } } -void kmscon_terminal_input(struct kmscon_terminal *term, kmscon_symbol_t ch) +void kmscon_terminal_input(struct kmscon_terminal *term, + struct kmscon_input_event *ev) { - kmscon_vte_input(term->vte, ch); - schedule_redraw(term); + kmscon_vte_input(term->vte, ev); } diff --git a/src/terminal.h b/src/terminal.h index 0168b5f..3ce04d6 100644 --- a/src/terminal.h +++ b/src/terminal.h @@ -41,19 +41,24 @@ struct kmscon_terminal; +typedef void (*kmscon_terminal_closed_cb) (struct kmscon_terminal *term, + void *data); + int kmscon_terminal_new(struct kmscon_terminal **out, struct kmscon_font_factory *ff); void kmscon_terminal_ref(struct kmscon_terminal *term); void kmscon_terminal_unref(struct kmscon_terminal *term); -int kmscon_terminal_connect_eloop(struct kmscon_terminal *term, - struct kmscon_eloop *eloop); -void kmscon_terminal_disconnect_eloop(struct kmscon_terminal *term); +int kmscon_terminal_open(struct kmscon_terminal *term, + struct kmscon_eloop *eloop, + kmscon_terminal_closed_cb closed_cb, void *data); +void kmscon_terminal_close(struct kmscon_terminal *term); int kmscon_terminal_add_output(struct kmscon_terminal *term, struct kmscon_output *output); void kmscon_terminal_rm_all_outputs(struct kmscon_terminal *term); -void kmscon_terminal_input(struct kmscon_terminal *term, kmscon_symbol_t ch); +void kmscon_terminal_input(struct kmscon_terminal *term, + struct kmscon_input_event *ev); #endif /* KMSCON_TERMINAL_H */ diff --git a/src/vte.c b/src/vte.c index 105e501..4c4578f 100644 --- a/src/vte.c +++ b/src/vte.c @@ -30,11 +30,22 @@ * console subsystem as output and is tightly bound to it. */ +/* for pty functions */ +#define _XOPEN_SOURCE 700 + +#include #include +#include +#include +#include #include #include +#include +#include #include "console.h" +#include "eloop.h" +#include "input.h" #include "log.h" #include "unicode.h" #include "vte.h" @@ -42,9 +53,20 @@ struct kmscon_vte { unsigned long ref; struct kmscon_console *con; + struct kmscon_eloop *eloop; + + int pty; + struct kmscon_fd *pty_fd; + + kmscon_vte_changed_cb changed_cb; + void *changed_data; + + kmscon_vte_closed_cb closed_cb; + void *closed_data; }; -int kmscon_vte_new(struct kmscon_vte **out) +int kmscon_vte_new(struct kmscon_vte **out, + kmscon_vte_changed_cb changed_cb, void *data) { struct kmscon_vte *vte; @@ -58,8 +80,11 @@ int kmscon_vte_new(struct kmscon_vte **out) return -ENOMEM; memset(vte, 0, sizeof(*vte)); + vte->pty = -1; vte->ref = 1; + vte->changed_cb = changed_cb; + vte->changed_data = data; *out = vte; return 0; } @@ -81,6 +106,7 @@ void kmscon_vte_unref(struct kmscon_vte *vte) return; kmscon_console_unref(vte->con); + kmscon_vte_close(vte); free(vte); log_debug("vte: destroying vte object\n"); } @@ -95,7 +121,37 @@ void kmscon_vte_bind(struct kmscon_vte *vte, struct kmscon_console *con) kmscon_console_ref(vte->con); } -void kmscon_vte_input(struct kmscon_vte *vte, kmscon_symbol_t ch) +/* FIXME: this is just temporary. */ +void kmscon_vte_input(struct kmscon_vte *vte, struct kmscon_input_event *ev) +{ + kmscon_symbol_t ch; + ssize_t len; + + if (!vte || !vte->con || vte->pty < 0) + return; + + if (ev->keysym == XK_Return) + ch = '\n'; + else if (ev->unicode == KMSCON_INPUT_INVALID) + return; + else + ch = kmscon_symbol_make(ev->unicode); + + if (ch > 127) + return; + + if (ev->mods & KMSCON_CONTROL_MASK) + if (iscntrl(toupper(ch) ^ 64)) + ch = toupper(ch) ^ 64; + + len = write(vte->pty, (char *)&ch, 1); + if (len <= 0) { + kmscon_vte_close(vte); + return; + } +} + +void kmscon_vte_putc(struct kmscon_vte *vte, kmscon_symbol_t ch) { if (!vte || !vte->con) return; @@ -105,3 +161,307 @@ void kmscon_vte_input(struct kmscon_vte *vte, kmscon_symbol_t ch) else kmscon_console_write(vte->con, ch); } + +/* + * TODO: + * - Decide which terminal we're emulating and set TERM accordingly. + * - Decide what to exec here: login, some getty equivalent, a shell... + * - Might also need to update some details in utmp wtmp and friends. + */ +static void __attribute__((noreturn)) +exec_child(int pty_master) +{ + const char *sh; + + setenv("TERM", "linux", 1); + + sh = getenv("SHELL") ?: _PATH_BSHELL; + execlp(sh, sh, "-i", NULL); + + _exit(EXIT_FAILURE); +} + +static int fork_pty_child(int master, struct winsize *ws) +{ + int ret, saved_errno; + pid_t pid; + const char *slave_name; + int slave = -1; + + /* This doesn't actually do anything on linux. */ + ret = grantpt(master); + if (ret < 0) { + log_err("vte: grantpt failed: %m"); + goto err_out; + } + + ret = unlockpt(master); + if (ret < 0) { + log_err("vte: cannot unlock pty: %m"); + goto err_out; + } + + slave_name = ptsname(master); + if (!slave_name) { + log_err("vte: cannot find pty slave name: %m"); + goto err_out; + } + + /* This also loses our controlling tty. */ + pid = setsid(); + if (pid < 0) { + log_err("vte: cannot start a new session: %m"); + goto err_out; + } + + /* And the slave pty becomes our controlling tty. */ + slave = open(slave_name, O_RDWR | O_CLOEXEC); + if (slave < 0) { + log_err("vte: cannot open pty slave: %m"); + goto err_out; + } + + ret = ioctl(slave, TIOCSWINSZ, ws); + if (ret) + log_warning("vte: cannot set slave pty window size: %m"); + + if (dup2(slave, STDIN_FILENO) != STDIN_FILENO || + dup2(slave, STDOUT_FILENO) != STDOUT_FILENO || + dup2(slave, STDERR_FILENO) != STDERR_FILENO) { + log_err("vte: cannot duplicate slave pty: %m"); + goto err_out; + } + + close(master); + close(slave); + return 0; + +err_out: + saved_errno = errno; + if (slave > 0) + close(slave); + close(master); + return -saved_errno; +} + +/* + * This is functionally equivalent to forkpty(3). We do it manually to obtain + * a little bit more control of the process, and as a bonus avoid linking to + * the libutil library in glibc. + */ +static pid_t fork_pty(int *pty_out, struct winsize *ws) +{ + int ret; + pid_t pid; + int master; + + master = posix_openpt(O_RDWR | O_NOCTTY | O_CLOEXEC | O_NONBLOCK); + if (master < 0) { + ret = -errno; + log_err("vte: cannot open pty master: %m"); + goto err_out; + } + + pid = fork(); + switch (pid) { + case -1: + log_err("vte: failed to fork pty slave: %m"); + ret = -errno; + goto err_master; + case 0: + ret = fork_pty_child(master, ws); + if (ret) + goto err_master; + *pty_out = -1; + return 0; + default: + *pty_out = master; + return pid; + } + +err_master: + close(master); +err_out: + *pty_out = -1; + errno = -ret; + return -1; +} + +static int pty_spawn(struct kmscon_vte *vte) +{ + pid_t pid; + + if (vte->pty >= 0) + return -EALREADY; + + struct winsize ws; + memset(&ws, 0, sizeof(ws)); + ws.ws_col = kmscon_console_get_width(vte->con) ?: + KMSCON_DEFAULT_WIDTH; + ws.ws_row = kmscon_console_get_height(vte->con) ?: + KMSCON_DEFAULT_HEIGHT; + + pid = fork_pty(&vte->pty, &ws); + switch (pid) { + case -1: + log_err("vte: cannot fork or open pty pair: %m"); + return -errno; + case 0: + exec_child(vte->pty); + default: + break; + } + + return 0; +} + +void pty_input(struct kmscon_fd *fd, int mask, void *data) +{ + int ret, nread; + ssize_t len, i; + struct kmscon_vte *vte = data; + + if (!vte || vte->pty < 0) + return; + + /* + * If we get a hangup or an error, but the pty is still readable, we + * read what's left and deal with the rest on the next dispatch. + */ + if (!(mask & KMSCON_READABLE)) { + if (mask & KMSCON_ERR) + log_warning("vte: error condition happened on pty\n"); + kmscon_vte_close(vte); + return; + } + + ret = ioctl(vte->pty, FIONREAD, &nread); + if (ret) { + log_warning("vte: cannot peek into pty input buffer: %m"); + return; + } else if (nread <= 0) { + return; + } + + char buf[nread]; + len = read(vte->pty, buf, nread); + if (len == -1) { + /* EIO is hangup, although we should have caught it above. */ + if (errno != EIO) + log_err("vte: cannot read from pty: %m"); + kmscon_vte_close(vte); + return; + } else if (len == 0) { + kmscon_vte_close(vte); + return; + } + + for (i=0; i < len; i++) + kmscon_vte_putc(vte, buf[i]); + + if (vte->changed_cb) + vte->changed_cb(vte, vte->changed_data); +} + +static int connect_eloop(struct kmscon_vte *vte, struct kmscon_eloop *eloop) +{ + int ret; + + if (vte->eloop) + return -EALREADY; + + ret = kmscon_eloop_new_fd(eloop, &vte->pty_fd, vte->pty, + KMSCON_READABLE, pty_input, vte); + if (ret) + return ret; + + kmscon_eloop_ref(eloop); + vte->eloop = eloop; + return 0; +} + +static void disconnect_eloop(struct kmscon_vte *vte) +{ + kmscon_eloop_rm_fd(vte->pty_fd); + kmscon_eloop_unref(vte->eloop); + vte->pty_fd = NULL; + vte->eloop = NULL; +} + +int kmscon_vte_open(struct kmscon_vte *vte, struct kmscon_eloop *eloop, + kmscon_vte_closed_cb closed_cb, void *data) +{ + int ret; + + if (!vte || !eloop) + return -EINVAL; + + if (vte->pty >= 0) + return -EALREADY; + + ret = pty_spawn(vte); + if (ret) + return ret; + + ret = connect_eloop(vte, eloop); + if (ret == -EALREADY) { + disconnect_eloop(vte); + ret = connect_eloop(vte, eloop); + } + if (ret) { + close(vte->pty); + vte->pty = -1; + return ret; + } + + vte->closed_cb = closed_cb; + vte->closed_data = data; + return 0; +} + +void kmscon_vte_close(struct kmscon_vte *vte) +{ + kmscon_vte_closed_cb cb; + void *data; + + if (!vte || vte->pty < 0) + return; + + disconnect_eloop(vte); + + close(vte->pty); + vte->pty = -1; + + cb = vte->closed_cb; + data = vte->closed_data; + vte->closed_cb = NULL; + vte->closed_data = NULL; + + if (cb) + cb(vte, data); +} + +void kmscon_vte_resize(struct kmscon_vte *vte) +{ + int ret; + struct winsize ws; + + if (!vte || !vte->con || vte->pty < 0) + return; + + memset(&ws, 0, sizeof(ws)); + ws.ws_col = kmscon_console_get_width(vte->con); + ws.ws_row = kmscon_console_get_height(vte->con); + + /* + * This will send SIGWINCH to the pty slave foreground process group. + * We will also get one, but we don't need it. + */ + ret = ioctl(vte->pty, TIOCSWINSZ, &ws); + if (ret) { + log_warning("vte: cannot set window size\n"); + return; + } + + log_debug("vte: window size set to %hdx%hd\n", ws.ws_col, ws.ws_row); +} diff --git a/src/vte.h b/src/vte.h index 1d82752..30296c9 100644 --- a/src/vte.h +++ b/src/vte.h @@ -36,14 +36,26 @@ #include #include "console.h" #include "unicode.h" +#include "eloop.h" struct kmscon_vte; -int kmscon_vte_new(struct kmscon_vte **out); +typedef void (*kmscon_vte_changed_cb) (struct kmscon_vte *vte, void *data); +typedef void (*kmscon_vte_closed_cb) (struct kmscon_vte *vte, void *data); + +int kmscon_vte_new(struct kmscon_vte **out, + kmscon_vte_changed_cb changed_cb, void *data); void kmscon_vte_ref(struct kmscon_vte *vte); void kmscon_vte_unref(struct kmscon_vte *vte); +int kmscon_vte_open(struct kmscon_vte *vte, struct kmscon_eloop *eloop, + kmscon_vte_closed_cb closed_cb, void *data); +void kmscon_vte_close(struct kmscon_vte *vte); + void kmscon_vte_bind(struct kmscon_vte *vte, struct kmscon_console *con); -void kmscon_vte_input(struct kmscon_vte *vte, kmscon_symbol_t ch); +void kmscon_vte_resize(struct kmscon_vte *vte); + +void kmscon_vte_input(struct kmscon_vte *vte, struct kmscon_input_event *ev); +void kmscon_vte_putc(struct kmscon_vte *vte, kmscon_symbol_t ch); #endif /* KMSCON_VTE_H */ diff --git a/tests/test_terminal.c b/tests/test_terminal.c index 48d30ba..19c20f0 100644 --- a/tests/test_terminal.c +++ b/tests/test_terminal.c @@ -63,17 +63,20 @@ static void sig_term(struct kmscon_signal *sig, int signum, void *data) terminate = 1; } +static void terminal_closed(struct kmscon_terminal *term, void *data) +{ + /* + * Alternativly, we could spwan a new login/shell here, like what + * happens when the user exits the shell in a linux console. + */ + terminate = 1; +} + static void read_input(struct kmscon_input *input, struct kmscon_input_event *ev, void *data) { struct app *app = data; - kmscon_symbol_t ch; - - if (ev->unicode == KMSCON_INPUT_INVALID) - return; - - ch = kmscon_symbol_make(ev->unicode); - kmscon_terminal_input(app->term, ch); + kmscon_terminal_input(app->term, ev); } static void activate_outputs(struct app *app) @@ -186,7 +189,8 @@ static int setup_app(struct app *app) if (ret) goto err_loop; - ret = kmscon_terminal_connect_eloop(app->term, app->eloop); + ret = kmscon_terminal_open(app->term, app->eloop, + terminal_closed, NULL); if (ret) goto err_loop; From d00714f7ce62f019d66957966566b4d147723b25 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Tue, 10 Jan 2012 02:52:42 +0200 Subject: [PATCH 6/7] test_terminal: wait on children to avoid zombies Unfortunately, there is no clean way I see to hook this up from the vte object. We can (and will) have more than one vte object opened at a time, but the semantics of signalfd make it impossible to deliver each SIGCHLD to its rightful owner without complicating things. [ From what I tested: - If you have two signalfd's listening to the same signal, they will be dispatched in some round-robin manner. - Also, if more than one child exits before we read signalfd (possibly beloging to different terminals), they will be compressed to one event. ] We therefore need to do the reaping from a central location, and need to remember to copy this snippet over to main.c in the future. Signed-off-by: Ran Benita --- tests/test_terminal.c | 68 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 62 insertions(+), 6 deletions(-) diff --git a/tests/test_terminal.c b/tests/test_terminal.c index 19c20f0..49b9467 100644 --- a/tests/test_terminal.c +++ b/tests/test_terminal.c @@ -36,6 +36,7 @@ #include #include #include +#include #include "eloop.h" #include "input.h" @@ -48,6 +49,7 @@ struct app { struct kmscon_eloop *eloop; struct kmscon_signal *sig_term; struct kmscon_signal *sig_int; + struct kmscon_signal *sig_chld; struct kmscon_symbol_table *st; struct kmscon_font_factory *ff; struct kmscon_compositor *comp; @@ -63,13 +65,61 @@ static void sig_term(struct kmscon_signal *sig, int signum, void *data) terminate = 1; } +static void sig_chld(struct kmscon_signal *sig, int signum, void *data) +{ + pid_t pid; + int status; + + /* + * If multiple children exit at the same time, signalfd would put them + * all in one event. So we reap in a loop. + */ + while (1) { + pid = waitpid(-1, &status, WNOHANG); + if (pid == -1) { + if (errno != ECHILD) + log_warning("vte: cannot wait on child: %m\n"); + break; + } else if (pid == 0) { + break; + } else if (WIFEXITED(status)) { + if (WEXITSTATUS(status) != 0) + log_info("vte: child %d exited with status " + "%hd\n", pid, WEXITSTATUS(status)); + else + log_debug("vte: child %d exited " + "successfully\n", pid); + } else if (WIFSIGNALED(status)) { + log_debug("vte: child %d exited by signal %d\n", pid, + WTERMSIG(status)); + } + } +} + static void terminal_closed(struct kmscon_terminal *term, void *data) { - /* - * Alternativly, we could spwan a new login/shell here, like what - * happens when the user exits the shell in a linux console. - */ - terminate = 1; +#if 0 + /* + * Alternativly, we could spwan a new login/shell here, like what + * happens when the user exits the shell in a linux console: + */ + + int ret; + struct app *app = data; + + if (!app) + goto err_out; + + ret = kmscon_terminal_open(app->term, app->eloop, + terminal_closed, app); + if (ret) + goto err_out; + + return; + +err_out: +#endif + terminate = 1; } static void read_input(struct kmscon_input *input, @@ -134,6 +184,7 @@ static void destroy_app(struct app *app) kmscon_compositor_unref(app->comp); kmscon_font_factory_unref(app->ff); kmscon_symbol_table_unref(app->st); + kmscon_eloop_rm_signal(app->sig_chld); kmscon_eloop_rm_signal(app->sig_int); kmscon_eloop_rm_signal(app->sig_term); kmscon_eloop_unref(app->eloop); @@ -157,6 +208,11 @@ static int setup_app(struct app *app) if (ret) goto err_loop; + ret = kmscon_eloop_new_signal(app->eloop, &app->sig_chld, SIGCHLD, + sig_chld, NULL); + if (ret) + goto err_loop; + ret = kmscon_symbol_table_new(&app->st); if (ret) goto err_loop; @@ -190,7 +246,7 @@ static int setup_app(struct app *app) goto err_loop; ret = kmscon_terminal_open(app->term, app->eloop, - terminal_closed, NULL); + terminal_closed, app); if (ret) goto err_loop; From 685202bbc4f512e207015944dd8511624ee86a31 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Tue, 10 Jan 2012 03:18:45 +0200 Subject: [PATCH 7/7] vte: handle NULL winsize in fork_pty We do actually set it, but we don't really have to. Protect it just in case. Signed-off-by: Ran Benita --- src/vte.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/vte.c b/src/vte.c index 4c4578f..1e26f15 100644 --- a/src/vte.c +++ b/src/vte.c @@ -221,9 +221,12 @@ static int fork_pty_child(int master, struct winsize *ws) goto err_out; } - ret = ioctl(slave, TIOCSWINSZ, ws); - if (ret) - log_warning("vte: cannot set slave pty window size: %m"); + if (ws) { + ret = ioctl(slave, TIOCSWINSZ, ws); + if (ret) + log_warning("vte: cannot set slave pty " + "window size: %m"); + } if (dup2(slave, STDIN_FILENO) != STDIN_FILENO || dup2(slave, STDOUT_FILENO) != STDOUT_FILENO ||