Merge 685202bbc4f512e207015944dd8511624ee86a31 into 0fc69a4ca5b359504bd017acecda7843a387aa78

This commit is contained in:
GitHub Merge Button 2012-01-11 04:54:59 -08:00
commit b519c87ded
7 changed files with 532 additions and 43 deletions

View File

@ -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,

View File

@ -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;

View File

@ -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);
}

View File

@ -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 */

367
src/vte.c
View File

@ -30,11 +30,22 @@
* console subsystem as output and is tightly bound to it.
*/
/* for pty functions */
#define _XOPEN_SOURCE 700
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <paths.h>
#include <pty.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <X11/keysym.h>
#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,310 @@ 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;
}
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 ||
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);
}

View File

@ -36,14 +36,26 @@
#include <stdlib.h>
#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 */

View File

@ -36,6 +36,7 @@
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#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,17 +65,68 @@ 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)
{
#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,
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)
@ -131,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);
@ -154,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;
@ -186,7 +245,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, app);
if (ret)
goto err_loop;