systemd-consoled is a very basic terminal-emulator to replace the in-kernel VT layer. It is based on libtsm as emulation layer (which itself has no external dependencies).
systemd-consoled expects to be run in a logind-session. The caller must have already setup the session and prepared it for systemd-consoled. This is usually done by login-managers. --- .gitignore | 1 + Makefile.am | 24 +++ configure.ac | 17 ++ src/console/Makefile | 1 + src/console/consoled-pty.c | 391 ++++++++++++++++++++++++++++++++++++++++ src/console/consoled-screen.c | 170 +++++++++++++++++ src/console/consoled-terminal.c | 371 ++++++++++++++++++++++++++++++++++++++ src/console/consoled.c | 278 ++++++++++++++++++++++++++++ src/console/consoled.h | 128 +++++++++++++ 9 files changed, 1381 insertions(+) create mode 120000 src/console/Makefile create mode 100644 src/console/consoled-pty.c create mode 100644 src/console/consoled-screen.c create mode 100644 src/console/consoled-terminal.c create mode 100644 src/console/consoled.c create mode 100644 src/console/consoled.h diff --git a/.gitignore b/.gitignore index c856412..0f563c3 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,7 @@ /systemd-cgls /systemd-cgroups-agent /systemd-cgtop +/systemd-consoled /systemd-coredump /systemd-coredumpctl /systemd-cryptsetup diff --git a/Makefile.am b/Makefile.am index 1e8aeed..b5f1fd9 100644 --- a/Makefile.am +++ b/Makefile.am @@ -3937,6 +3937,30 @@ update-unifont: src/libsystemd-gfx/unifont.bin src/libsystemd-gfx/unifont.cmp endif # ------------------------------------------------------------------------------ +if ENABLE_CONSOLED +rootlibexec_PROGRAMS += \ + systemd-consoled + +systemd_consoled_SOURCES = \ + src/console/consoled.h \ + src/console/consoled.c \ + src/console/consoled-pty.c \ + src/console/consoled-screen.c \ + src/console/consoled-terminal.c + +systemd_consoled_CFLAGS = \ + $(AM_CFLAGS) \ + $(CONSOLED_CFLAGS) + +systemd_consoled_LDADD = \ + $(CONSOLED_LIBS) \ + libsystemd-bus.la \ + libsystemd-daemon.la \ + libsystemd-gfx.la \ + libsystemd-shared.la +endif + +# ------------------------------------------------------------------------------ if ENABLE_NETWORKD rootlibexec_PROGRAMS += \ systemd-networkd diff --git a/configure.ac b/configure.ac index bf3fce3..924637c 100644 --- a/configure.ac +++ b/configure.ac @@ -306,6 +306,22 @@ AM_CONDITIONAL(HAVE_GFX, [test "$have_gfx" = "yes"]) AM_CONDITIONAL(HAVE_GFX_GL, [test "$have_gfx_gl" = "yes"]) # ------------------------------------------------------------------------------ +have_consoled=no +AC_ARG_ENABLE(consoled, AS_HELP_STRING([--disable-consoled], [disable system console])) +if test "x$enable_consoled" != "xno"; then + if test "x$have_gfx" = xno -a "x$enable_consoled" = xyes; then + AC_MSG_ERROR([*** consoled support requested, but libraries not found]) + elif test "x$have_gfx" = xyes ; then + PKG_CHECK_MODULES(CONSOLED, [ libtsm >= 3 ], + [AC_DEFINE(ENABLE_CONSOLED, 1, [Define if system console is built]) have_consoled=yes], have_consoled=no) + if test "x$have_consoled" = xno -a "x$enable_consoled" = xyes; then + AC_MSG_ERROR([*** consoled support requested, but libraries not found]) + fi + fi +fi +AM_CONDITIONAL(ENABLE_CONSOLED, [test "$have_consoled" = "yes"]) + +# ------------------------------------------------------------------------------ have_blkid=no AC_ARG_ENABLE(blkid, AS_HELP_STRING([--disable-blkid], [disable blkid support])) if test "x$enable_blkid" != "xno"; then @@ -1095,6 +1111,7 @@ AC_MSG_RESULT([ efi: ${have_efi} kmod: ${have_kmod} sd-gfx: ${have_gfx} + consoled: ${have_consoled} blkid: ${have_blkid} nss-myhostname: ${have_myhostname} gudev: ${enable_gudev} diff --git a/src/console/Makefile b/src/console/Makefile new file mode 120000 index 0000000..d0b0e8e --- /dev/null +++ b/src/console/Makefile @@ -0,0 +1 @@ +../Makefile \ No newline at end of file diff --git a/src/console/consoled-pty.c b/src/console/consoled-pty.c new file mode 100644 index 0000000..c3defc1 --- /dev/null +++ b/src/console/consoled-pty.c @@ -0,0 +1,391 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 David Herrmann <dh.herrm...@gmail.com> + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <pty.h> +#include <signal.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/epoll.h> +#include <sys/ioctl.h> +#include <sys/uio.h> +#include <termios.h> +#include <unistd.h> + +#include "consoled.h" +#include "ring.h" +#include "sd-event.h" + +/* + * PTY + * A PTY object represents a single PTY connection between a master and a + * child. The child process is fork()ed so the caller controls what program + * will be run. + * + * Programs like /bin/login tend to perform a vhangup() on their TTY + * before running the login procedure. This also causes the pty master + * to get a EPOLLHUP event as long as no client has the TTY opened. + * This means, we cannot use the TTY connection as reliable way to track + * the client. Instead, we _must_ rely on the PID of the client to track + * them. + * However, this has the side effect that if the client forks and the + * parent exits, we loose them and restart the client. But this seems to + * be the expected behavior so we implement it here. + * + * Unfortunately, epoll always polls for EPOLLHUP so as long as the + * vhangup() is ongoing, we will _always_ get EPOLLHUP and cannot sleep. + * This gets worse if the client closes the TTY but doesn't exit. + * Therefore, the fd must be edge-triggered in the epoll-set so we + * only get the events once they change. + */ + +static void pty_dispatch_write(Pty *pty) { + struct iovec vec[2]; + size_t num; + ssize_t r; + + num = ring_peek(&pty->out_buf, vec); + if (!num) + return; + + /* ignore errors in favor of SIGCHLD; (we're edge-triggered, anyway) */ + r = writev(pty->fd, vec, (int)num); + if (r > 0) + ring_pull(&pty->out_buf, (size_t)r); +} + +static int pty_dispatch_read(Pty *pty) { + ssize_t len, num; + + /* We're edge-triggered, means we need to read the whole queue. This, + * however, might cause us to stall if the writer is faster than we + * are. Therefore, we have some rather arbitrary limit on how fast + * we read. If we reach it, we simply return EAGAIN to the caller and + * let them schedule an idle-event. */ + + num = 4; + do { + len = read(pty->fd, pty->in_buf, sizeof(pty->in_buf)); + if (len > 0) + terminal_from_pty(pty->t, pty->in_buf, len); + } while (len > 0 && --num); + + return !num ? -EAGAIN : 0; +} + +static int pty_dispatch(Pty *pty) { + int r; + + r = pty_dispatch_read(pty); + pty_dispatch_write(pty); + return r; +} + +static int pty_idle_fn(sd_event_source *source, void *data) { + Pty *pty = data; + int r; + + r = pty_dispatch(pty); + if (r == -EAGAIN) + sd_event_source_set_enabled(pty->idle_source, SD_EVENT_ONESHOT); + + return 0; +} + +static int pty_io_fn(sd_event_source *source, int fd, uint32_t ev, void *data) { + Pty *pty = data; + int r; + + r = pty_dispatch(pty); + if (r == -EAGAIN) + sd_event_source_set_enabled(pty->idle_source, SD_EVENT_ONESHOT); + + return 0; +} + +enum { + PTY_FAILED, + PTY_SETUP, +}; + +static char pty_wait(int fd) { + int r; + char d; + + do { + r = read(fd, &d, 1); + } while (r < 0 && (errno == EINTR || errno == EAGAIN)); + + return (r <= 0) ? PTY_FAILED : d; +} + +static int pty_wakeup(int fd, char d) { + int r; + + do { + r = write(fd, &d, 1); + } while (r < 0 && (errno == EINTR || errno == EAGAIN)); + + return (r == 1) ? 0 : -EINVAL; +} + +static int pty_setup_child(int slave, unsigned short term_width, unsigned short term_height) { + struct termios attr; + struct winsize ws; + + if (tcgetattr(slave, &attr) < 0) + return -errno; + + /* erase character should be normal backspace, PLEASEEE! */ + attr.c_cc[VERASE] = 010; + + if (tcsetattr(slave, TCSANOW, &attr) < 0) + return -errno; + + memset(&ws, 0, sizeof(ws)); + ws.ws_col = term_width; + ws.ws_row = term_height; + + if (ioctl(slave, TIOCSWINSZ, &ws) < 0) + return -errno; + + if (dup2(slave, STDIN_FILENO) != STDIN_FILENO || + dup2(slave, STDOUT_FILENO) != STDOUT_FILENO || + dup2(slave, STDERR_FILENO) != STDERR_FILENO) + return -errno; + + return 0; +} + +static int pty_init_child(int fd) { + int r; + sigset_t sigset; + char *slave_name; + int slave, i; + pid_t pid; + + /* unlockpt() requires unset signal-handlers */ + sigemptyset(&sigset); + r = sigprocmask(SIG_SETMASK, &sigset, NULL); + if (r < 0) + return -errno; + + for (i = 1; i < SIGUNUSED; ++i) + signal(i, SIG_DFL); + + r = grantpt(fd); + if (r < 0) + return -errno; + + r = unlockpt(fd); + if (r < 0) + return -errno; + + slave_name = ptsname(fd); + if (!slave_name) + return -errno; + + /* open slave-TTY */ + slave = open(slave_name, O_RDWR | O_CLOEXEC | O_NOCTTY); + if (slave < 0) + return -errno; + + /* open session so we loose our controlling TTY */ + pid = setsid(); + if (pid < 0) { + close(slave); + return -errno; + } + + /* set controlling TTY */ + r = ioctl(slave, TIOCSCTTY, 0); + if (r < 0) { + close(slave); + return -errno; + } + + return slave; +} + +pid_t pty_new(unsigned short term_width, unsigned short term_height, Terminal *t, Pty **out) { + Pty *pty; + pid_t pid; + int fd, comm[2], slave, r; + char d; + + pty = calloc(1, sizeof(*pty)); + if (!pty) + return -ENOMEM; + + fd = posix_openpt(O_RDWR | O_NOCTTY | O_CLOEXEC | O_NONBLOCK); + if (fd < 0) { + free(pty); + return -errno; + } + + r = sd_event_add_io(t->m->event, + fd, + EPOLLHUP | EPOLLERR | EPOLLIN | EPOLLOUT | EPOLLET, + pty_io_fn, + pty, + &pty->fd_source); + if (r < 0) { + close(fd); + free(pty); + return r; + } + + r = sd_event_add_defer(t->m->event, pty_idle_fn, pty, &pty->idle_source); + if (r < 0) { + sd_event_source_set_enabled(pty->fd_source, SD_EVENT_OFF); + sd_event_source_unref(pty->fd_source); + close(fd); + free(pty); + return r; + } + + r = pipe2(comm, O_CLOEXEC); + if (r < 0) { + r = -errno; + sd_event_source_set_enabled(pty->idle_source, SD_EVENT_OFF); + sd_event_source_unref(pty->idle_source); + sd_event_source_set_enabled(pty->fd_source, SD_EVENT_OFF); + sd_event_source_unref(pty->fd_source); + close(fd); + free(pty); + return r; + } + + pid = fork(); + if (pid < 0) { + /* error */ + pid = -errno; + close(comm[0]); + close(comm[1]); + sd_event_source_set_enabled(pty->idle_source, SD_EVENT_OFF); + sd_event_source_unref(pty->idle_source); + sd_event_source_set_enabled(pty->fd_source, SD_EVENT_OFF); + sd_event_source_unref(pty->fd_source); + close(fd); + free(pty); + return pid; + } else if (!pid) { + /* child */ + close(comm[0]); + free(pty); + + slave = pty_init_child(fd); + close(fd); + + if (slave < 0) + _exit(1); + + r = pty_setup_child(slave, term_width, term_height); + if (r < 0) + _exit(1); + + /* close slave if it's not one of the std-fds */ + if (slave > 2) + close(slave); + + /* wake parent */ + pty_wakeup(comm[1], PTY_SETUP); + close(comm[1]); + + *out = NULL; + return pid; + } + + /* parent */ + close(comm[1]); + + pty->fd = fd; + pty->child = pid; + pty->t = t; + + /* Wait for child setup. We need to do that to guarantee that any + * following signals are really delivered. Maybe this is too pedantic, + * but.. it's already implemented. */ + d = pty_wait(comm[0]); + if (d != PTY_SETUP) { + close(comm[0]); + sd_event_source_set_enabled(pty->idle_source, SD_EVENT_OFF); + sd_event_source_unref(pty->idle_source); + sd_event_source_set_enabled(pty->fd_source, SD_EVENT_OFF); + sd_event_source_unref(pty->fd_source); + close(fd); + free(pty); + return -EINVAL; + } + + close(comm[0]); + *out = pty; + return pid; +} + +void pty_free(Pty *pty) { + if (!pty) + return; + + close(pty->fd); + sd_event_source_set_enabled(pty->idle_source, SD_EVENT_OFF); + sd_event_source_unref(pty->idle_source); + sd_event_source_set_enabled(pty->fd_source, SD_EVENT_OFF); + sd_event_source_unref(pty->fd_source); + ring_clear(&pty->out_buf); + free(pty); +} + +int pty_write(Pty *pty, const char *u8, size_t len) { + int r; + + r = ring_push(&pty->out_buf, u8, len); + if (r < 0) + return r; + + return sd_event_source_set_enabled(pty->idle_source, SD_EVENT_ONESHOT); +} + +int pty_resize(Pty *pty, unsigned short term_width, unsigned short term_height) { + struct winsize ws; + int r; + + memset(&ws, 0, sizeof(ws)); + ws.ws_col = term_width; + ws.ws_row = term_height; + + /* This will send SIGWINCH to the pty slave foreground process group. + * We will also get one, but we don't need it. */ + r = ioctl(pty->fd, TIOCSWINSZ, &ws); + return (r < 0) ? -errno : 0; +} + +int pty_signal(Pty *pty, int sig) { + int r; + + r = ioctl(pty->fd, TIOCSIG, sig); + return (r < 0) ? -errno : 0; +} diff --git a/src/console/consoled-screen.c b/src/console/consoled-screen.c new file mode 100644 index 0000000..00c26d3 --- /dev/null +++ b/src/console/consoled-screen.c @@ -0,0 +1,170 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 David Herrmann <dh.herrm...@gmail.com> + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <inttypes.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <unistd.h> + +#include "build.h" +#include "consoled.h" +#include "def.h" +#include "list.h" +#include "log.h" +#include "macro.h" +#include "missing.h" +#include "sd-event.h" +#include "sd-gfx.h" +#include "util.h" + +int screen_new(Terminal *t, sd_gfx_pipe *pipe, Screen **out) { + Screen *s; + + s = calloc(1, sizeof(*s)); + if (!s) + return log_oom(); + + s->t = t; + s->pipe = pipe; + s->plane = sd_gfx_pipe_get_primary_plane(pipe); + + LIST_PREPEND(screen, t->screens, s); + *out = s; + return 0; +} + +void screen_free(Screen *s) { + LIST_REMOVE(screen, s->t->screens, s); + free(s); +} + +struct screen_data { + Screen *s; + sd_gfx_fb *fb; +}; + +static int screen_render_cell_fn(struct tsm_screen *screen, + uint32_t id, + const uint32_t *ch, + size_t len, + unsigned int cwidth, + unsigned int posx, + unsigned int posy, + const struct tsm_screen_attr *attr, + tsm_age_t age, + void *data) { + struct screen_data *d = data; + Screen *s = d->s; + sd_gfx_fb *fb = d->fb; + sd_gfx_buffer *buf; + unsigned int x, y; + uint32_t fc, bc, tc; + + x = posx * s->t->cell_width; + y = posy * s->t->cell_height; + + fc = (0xff << 24) | (attr->fr << 16) | (attr->fg << 8) | attr->fb; + bc = (0xff << 24) | (attr->br << 16) | (attr->bg << 8) | attr->bb; + if (attr->inverse) { + tc = fc; + fc = bc; + bc = tc; + } + + if (!len) { + sd_gfx_fb_fill(fb, bc, x, y, s->t->cell_width, s->t->cell_height); + } else { + sd_gfx_font_render(s->t->font, id, ch, len, &buf); + sd_gfx_fb_blend_bichrome(fb, fc, bc, x, y, buf); + } + + return 0; +} + +static void screen_render(Screen *s, sd_gfx_fb *fb) { + struct screen_data d; + + d.s = s; + d.fb = fb; + tsm_screen_draw(s->t->screen, screen_render_cell_fn, (void*)&d); +} + +void screen_redraw(Screen *s) { + sd_gfx_fb *fb, *front = NULL; + int r; + + if (!s->active) + return; + + if (s->swapping) { + s->need_redraw = 1; + return; + } + + fb = sd_gfx_plane_get_back(s->plane); + if (!fb) { + front = sd_gfx_plane_get_front(s->plane); + fb = front; + } + + s->need_redraw = 0; + screen_render(s, fb); + + if (fb == front) + return; + + sd_gfx_plane_swap_to(s->plane, fb); + r = sd_gfx_pipe_commit(s->pipe); + if (r < 0) { + log_error("screen: cannot swap primary plane: %d", r); + } else { + s->swapping = 1; + } +} + +void screen_wake_up(Screen *s) { + sd_gfx_fb *fb; + + fb = sd_gfx_plane_get_front(s->plane); + s->width = sd_gfx_fb_get_width(fb); + s->height = sd_gfx_fb_get_height(fb); + s->active = 1; +} + +void screen_sleep(Screen *s) { + s->width = 0; + s->height = 0; + s->active = 0; +} + +void screen_swap(Screen *s) { + if (!s->swapping) + return; + + s->swapping = 0; + if (s->need_redraw) + screen_redraw(s); +} diff --git a/src/console/consoled-terminal.c b/src/console/consoled-terminal.c new file mode 100644 index 0000000..963266d --- /dev/null +++ b/src/console/consoled-terminal.c @@ -0,0 +1,371 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 David Herrmann <dh.herrm...@gmail.com> + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <inttypes.h> +#include <libtsm.h> +#include <paths.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <unistd.h> + +#include "build.h" +#include "consoled.h" +#include "def.h" +#include "log.h" +#include "macro.h" +#include "missing.h" +#include "sd-event.h" +#include "sd-gfx.h" +#include "util.h" + +static void terminal_tsm_log_fn(void *data, + const char *file, + int line, + const char *fn, + const char *subs, + unsigned int sev, + const char *format, + va_list args) { + char *msg; + int r; + + r = vasprintf(&msg, format, args); + if (r < 0) + return; + + log_meta(sev, file, line, fn, "%s: %s", subs, msg); + + free(msg); +} + +static void terminal_tsm_write_fn(struct tsm_vte *vte, + const char *u8, + size_t len, + void *data) { + Terminal *t = data; + int r; + + if (!t->pty) + return; + + r = pty_write(t->pty, u8, len); + if (r < 0) + log_error("cannot write message to PTY: %d", r); +} + +int terminal_new(Manager *m, Terminal **out) { + Terminal *t; + int r; + + t = calloc(1, sizeof(*t)); + if (!t) + return log_oom(); + + t->m = m; + + r = sd_gfx_font_new(&t->font, 90); + if (r < 0) + goto error; + + r = tsm_screen_new(&t->screen, terminal_tsm_log_fn, NULL); + if (r < 0) { + log_error("cannot allocate TSM screen: %d", r); + goto error; + } + + r = tsm_vte_new(&t->vte, + t->screen, + terminal_tsm_write_fn, + t, + terminal_tsm_log_fn, + NULL); + if (r < 0) { + log_error("cannot allocate TSM VTE: %d", r); + goto error; + } + + *out = t; + return 0; + +error: + tsm_vte_unref(t->vte); + tsm_screen_unref(t->screen); + sd_gfx_font_free(t->font); + free(t); + return r; +} + +void terminal_free(Terminal *t) { + Screen *s; + + if (!t) + return; + + if (t->pty) { + sd_event_source_set_enabled(t->pty_source, SD_EVENT_OFF); + sd_event_source_unref(t->pty_source); + pty_signal(t->pty, SIGHUP); + pty_free(t->pty); + t->pty = NULL; + } + + while ((s = t->screens)) + screen_free(s); + + tsm_vte_unref(t->vte); + tsm_screen_unref(t->screen); + sd_gfx_font_free(t->font); + free(t); +} + +static void _noreturn_ terminal_run_child(Terminal *t) { + char **argv = (char**)(const char*[]) { + getenv("SHELL") ? : _PATH_BSHELL, + "-il", + NULL + }; + + setenv("TERM", "xterm-256color", 1); + execve(argv[0], argv, environ); + _exit(1); +} + +static int terminal_child_fn(sd_event_source *source, const siginfo_t *s, void *data) { + Terminal *t = data; + + log_warning("child process died"); + + pty_signal(t->pty, SIGHUP); + pty_free(t->pty); + t->pty = NULL; + sd_event_request_quit(t->m->event); + + return 0; +} + +void terminal_start(Terminal *t) { + pid_t pid; + int r; + + pid = pty_new(t->cols, t->rows, t, &t->pty); + if (pid < 0) + goto error; + else if (!pid) + terminal_run_child(t); + + r = sd_event_add_child(t->m->event, + t->pty->child, + WEXITED, + terminal_child_fn, + t, + &t->pty_source); + if (r < 0) + goto error; + + terminal_redraw(t); + return; + +error: + log_error("cannot spawn PTY: %d/%d", (int)pid, r); + if (t->pty) { + pty_signal(t->pty, SIGHUP); + pty_free(t->pty); + t->pty = NULL; + } + sd_event_request_quit(t->m->event); +} + +void terminal_redraw(Terminal *t) { + Screen *s; + + LIST_FOREACH(screen, s, t->screens) + screen_redraw(s); +} + +static void terminal_resize(Terminal *t) { + int r; + + t->cell_width = sd_gfx_font_get_width(t->font) ? : 1; + t->cols = t->min_width / t->cell_width; + if (!t->cols) + t->cols = 1; + + t->cell_height = sd_gfx_font_get_height(t->font) ? : 1; + t->rows = t->min_height / t->cell_height; + if (!t->rows) + t->rows = 1; + + log_debug("terminal: new size is %ux%u / %ux%u", + t->min_width, t->min_height, + t->cols, t->rows); + + r = tsm_screen_resize(t->screen, t->cols, t->rows); + if (r < 0) { + log_error("cannot resize screen: %d", r); + } else if (t->pty) { + pty_resize(t->pty, t->cols, t->rows); + } +} + +void terminal_pipe_add(Terminal *t, sd_gfx_pipe *pipe) { + Screen *s; + int r; + + r = screen_new(t, pipe, &s); + if (r < 0) + return; + + sd_gfx_pipe_set_data(pipe, s); +} + +void terminal_pipe_remove(Terminal *t, sd_gfx_pipe *pipe) { + Screen *s = sd_gfx_pipe_get_data(pipe); + + if (!s) + return; + + screen_free(s); +} + +void terminal_pipe_wake_up(Terminal *t, sd_gfx_pipe *pipe) { + Screen *s = sd_gfx_pipe_get_data(pipe); + bool resize = false; + + if (!s) + return; + + screen_wake_up(s); + + if (s->width < t->min_width || !t->min_width) { + t->min_width = s->width; + resize = true; + } + if (s->height < t->min_height || !t->min_height) { + t->min_height = s->height; + resize = true; + } + + if (resize) { + terminal_resize(t); + terminal_redraw(t); + } else { + screen_redraw(s); + } +} + +void terminal_pipe_sleep(Terminal *t, sd_gfx_pipe *pipe) { + Screen *s = sd_gfx_pipe_get_data(pipe); + unsigned int min_width, min_height; + bool resize = false; + Screen *i; + + if (!s) + return; + + if (s->width <= t->min_width || s->height <= t->min_height) { + min_width = 0; + min_height = 0; + LIST_FOREACH(screen, i, t->screens) { + if (!i->active) + continue; + if (i->width < min_width || !min_width) + min_width = i->width; + if (i->height < min_height || !min_height) + min_height = i->height; + } + + if (min_width != t->min_width || min_height != t->min_height) + resize = true; + } + + screen_sleep(s); + + if (!resize) + return; + + t->min_width = min_width; + t->min_height = min_height; + terminal_resize(t); + terminal_redraw(t); +} + +void terminal_pipe_swap(Terminal *t, sd_gfx_pipe *pipe) { + Screen *s = sd_gfx_pipe_get_data(pipe); + + if (!s) + return; + + screen_swap(s); +} + +static void terminal_kbd_fn(sd_gfx_kbd *kbd, void *fn_data, struct sd_gfx_kbd_event *ev) { + Terminal *t = fn_data; + unsigned int mods; + uint32_t ucs4; + + if (ev->sym_count != 1) + return; + + mods = 0; + if (ev->mods & SD_GFX_SHIFT) + mods |= TSM_SHIFT_MASK; + if (ev->mods & SD_GFX_CAPSL) + mods |= TSM_LOCK_MASK; + if (ev->mods & SD_GFX_CTRL) + mods |= TSM_CONTROL_MASK; + if (ev->mods & SD_GFX_ALT) + mods |= TSM_ALT_MASK; + if (ev->mods & SD_GFX_LOGO) + mods |= TSM_LOGO_MASK; + + ucs4 = ev->codepoints[0]; + if (ev->codepoints[0] == 0xffffffff) + ucs4 = TSM_VTE_INVALID; + + if (tsm_vte_handle_keyboard(t->vte, + ev->keysyms[0], + ev->ascii, + mods, + ucs4)) { + tsm_screen_sb_reset(t->screen); + } +} + +void terminal_kbd_add(Terminal *t, sd_gfx_kbd *kbd) { + sd_gfx_kbd_set_event_fn(kbd, terminal_kbd_fn); + sd_gfx_kbd_set_fn_data(kbd, t); +} + +void terminal_kbd_remove(Terminal *t, sd_gfx_kbd *kbd) { + sd_gfx_kbd_set_event_fn(kbd, NULL); + sd_gfx_kbd_set_fn_data(kbd, NULL); +} + +void terminal_from_pty(Terminal *t, const char *u8, size_t len) { + tsm_vte_input(t->vte, u8, len); + terminal_redraw(t); +} diff --git a/src/console/consoled.c b/src/console/consoled.c new file mode 100644 index 0000000..b42b9aa --- /dev/null +++ b/src/console/consoled.c @@ -0,0 +1,278 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 David Herrmann <dh.herrm...@gmail.com> + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <inttypes.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <unistd.h> + +#include "build.h" +#include "consoled.h" +#include "def.h" +#include "log.h" +#include "macro.h" +#include "missing.h" +#include "sd-bus.h" +#include "sd-daemon.h" +#include "sd-event.h" +#include "sd-gfx.h" +#include "util.h" + +static void manager_card_fn(sd_gfx_card *card, void *fn_data, sd_gfx_card_event *ev) { + Manager *m = fn_data; + Terminal *t; + + switch (ev->type) { + case SD_GFX_CARD_PIPE_CREATE: + /* add all pipes to default terminal */ + t = m->terminal; + terminal_pipe_add(t, ev->pipe); + sd_gfx_pipe_set_fn_data(ev->pipe, t); + break; + case SD_GFX_CARD_PIPE_DESTROY: + t = sd_gfx_pipe_get_fn_data(ev->pipe); + terminal_pipe_remove(t, ev->pipe); + break; + case SD_GFX_CARD_PIPE_WAKE_UP: + t = sd_gfx_pipe_get_fn_data(ev->pipe); + terminal_pipe_wake_up(t, ev->pipe); + break; + case SD_GFX_CARD_PIPE_SLEEP: + t = sd_gfx_pipe_get_fn_data(ev->pipe); + terminal_pipe_sleep(t, ev->pipe); + break; + case SD_GFX_CARD_PIPE_SWAP: + t = sd_gfx_pipe_get_fn_data(ev->pipe); + terminal_pipe_swap(t, ev->pipe); + break; + } +} + +static void manager_add_card(Manager *m, sd_gfx_card *card) { + sd_gfx_card_set_event_fn(card, manager_card_fn); + sd_gfx_card_set_fn_data(card, m); +} + +static void manager_event_fn(sd_gfx_monitor *mon, void *fn_data, sd_gfx_monitor_event *ev) { + Manager *m = fn_data; + + switch (ev->type) { + case SD_GFX_MONITOR_RUN: + terminal_start(m->terminal); + break; + case SD_GFX_MONITOR_CREATE: + switch (ev->devtype) { + case SD_GFX_DEV_CARD: + manager_add_card(m, ev->card); + break; + case SD_GFX_DEV_KBD: + terminal_kbd_add(m->terminal, ev->kbd); + break; + } + break; + case SD_GFX_MONITOR_DESTROY: + switch (ev->devtype) { + case SD_GFX_DEV_CARD: + /* gets destroyed by gfx-monitor */ + break; + case SD_GFX_DEV_KBD: + terminal_kbd_remove(m->terminal, ev->kbd); + break; + } + break; + } +} + +static int manager_signal_fn(sd_event_source *s, const struct signalfd_siginfo *ssi, void *data) { + Manager *m = data; + + log_notice("catched signal %d, exiting..", (int)ssi->ssi_signo); + sd_event_request_quit(m->event); + + return 0; +} + +static int manager_new(Manager **out) { + static const int sigs[] = { + SIGINT, SIGTERM, SIGQUIT, SIGHUP, SIGPIPE, SIGCHLD, 0 + }; + unsigned int i; + sigset_t mask; + Manager *m; + int r; + + m = calloc(1, sizeof(*m)); + if (!m) + return log_oom(); + + r = sd_event_default(&m->event); + if (r < 0) { + log_error("cannot get default event-loop: %d", r); + goto error; + } + + sigemptyset(&mask); + for (i = 0; sigs[i]; ++i) { + sigaddset(&mask, sigs[i]); + r = sd_event_add_signal(m->event, + sigs[i], + manager_signal_fn, + m, + &m->sigs[i]); + if (r < 0) { + log_error("cannot block signal %d: %d", + sigs[i], r); + goto error; + } + } + sigprocmask(SIG_BLOCK, &mask, NULL); + + r = sd_gfx_monitor_new(&m->mon, + SD_GFX_DEV_KBD | SD_GFX_DEV_CARD, + SD_GFX_MONITOR_DEFAULT, + m->event); + if (r < 0) + goto error; + + sd_gfx_monitor_set_fn_data(m->mon, m); + sd_gfx_monitor_set_event_fn(m->mon, manager_event_fn); + sd_gfx_monitor_parse_cmdline(m->mon); + + r = terminal_new(m, &m->terminal); + if (r < 0) + goto error; + + *out = m; + return 0; + +error: + sd_gfx_monitor_free(m->mon); + for (i = 0; m->sigs[i]; ++i) + sd_event_source_unref(m->sigs[i]); + sd_event_unref(m->event); + free(m); + return r; +} + +static void manager_free(Manager *m) { + unsigned int i; + + if (!m) + return; + + sd_gfx_monitor_free(m->mon); + terminal_free(m->terminal); + for (i = 0; m->sigs[i]; ++i) + sd_event_source_unref(m->sigs[i]); + sd_event_unref(m->event); + free(m); +} + +static int manager_run(Manager *m) { + return sd_event_loop(m->event); +} + +static int help(void) { + printf("%s [OPTIONS...] ...\n\n" + "System console with integrated terminal emulator.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + , program_invocation_short_name); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_VERSION = 0x100, + }; + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + {} + }; + int c; + + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) { + switch(c) { + case 'h': + return help(); + case ARG_VERSION: + puts(PACKAGE_STRING); + puts(SYSTEMD_FEATURES); + return 0; + case '?': + return -EINVAL; + default: + assert_not_reached("Unhandled option"); + } + } + + if (optind < argc) { + log_error("This program does not take arguments."); + return -EINVAL; + } + + return 1; +} + +int main(int argc, char *argv[]) { + Manager *m = NULL; + int r; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + r = parse_argv(argc, argv); + if (r < 0) + return EXIT_FAILURE; + if (r == 0) + return EXIT_SUCCESS; + + r = manager_new(&m); + if (r < 0) + goto finish; + + sd_notify(false, "READY=1\nSTATUS=Running..."); + + r = manager_run(m); + +finish: + sd_notify(false, "STATUS=Shutting down..."); + + if (m) + manager_free(m); + + log_debug("exiting.."); + return abs(r); +} diff --git a/src/console/consoled.h b/src/console/consoled.h new file mode 100644 index 0000000..edc327d --- /dev/null +++ b/src/console/consoled.h @@ -0,0 +1,128 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 David Herrmann <dh.herrm...@gmail.com> + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#pragma once + +#include <errno.h> +#include <libtsm.h> +#include <stdlib.h> + +#include "list.h" +#include "ring.h" +#include "sd-event.h" +#include "sd-gfx.h" + +typedef struct Manager Manager; +typedef struct Terminal Terminal; +typedef struct Screen Screen; +typedef struct Pty Pty; + +struct Manager { + sd_event *event; + sd_event_source *sigs[_NSIG]; + sd_gfx_monitor *mon; + + Terminal *terminal; +}; + +struct Terminal { + Manager *m; + sd_gfx_font *font; + struct tsm_screen *screen; + struct tsm_vte *vte; + + Pty *pty; + sd_event_source *pty_source; + + unsigned int min_width; + unsigned int min_height; + unsigned int cols; + unsigned int rows; + unsigned int cell_width; + unsigned int cell_height; + + LIST_HEAD(Screen, screens); +}; + +struct Screen { + Terminal *t; + sd_gfx_pipe *pipe; + sd_gfx_plane *plane; + + unsigned int width; + unsigned int height; + + LIST_FIELDS(Screen, screen); + + unsigned int active : 1; + unsigned int swapping : 1; + unsigned int need_redraw : 1; +}; + +#define PTY_BUFSIZE 16384 + +struct Pty { + Terminal *t; + int fd; + sd_event_source *fd_source; + sd_event_source *idle_source; + pid_t child; + char in_buf[PTY_BUFSIZE]; + Ring out_buf; +}; + +/* terminal */ + +int terminal_new(Manager *m, Terminal **out); +void terminal_free(Terminal *t); + +void terminal_start(Terminal *t); +void terminal_redraw(Terminal *t); + +void terminal_pipe_add(Terminal *t, sd_gfx_pipe *pipe); +void terminal_pipe_remove(Terminal *t, sd_gfx_pipe *pipe); +void terminal_pipe_wake_up(Terminal *t, sd_gfx_pipe *pipe); +void terminal_pipe_sleep(Terminal *t, sd_gfx_pipe *pipe); +void terminal_pipe_swap(Terminal *t, sd_gfx_pipe *pipe); + +void terminal_kbd_add(Terminal *t, sd_gfx_kbd *kbd); +void terminal_kbd_remove(Terminal *t, sd_gfx_kbd *kbd); + +void terminal_from_pty(Terminal *t, const char *u8, size_t len); + +/* screen */ + +int screen_new(Terminal *t, sd_gfx_pipe *pipe, Screen **out); +void screen_free(Screen *s); + +void screen_redraw(Screen *s); +void screen_wake_up(Screen *s); +void screen_sleep(Screen *s); +void screen_swap(Screen *s); + +/* pty */ + +pid_t pty_new(unsigned short term_width, unsigned short term_height, Terminal *t, Pty **out); +void pty_free(Pty *pty); + +int pty_write(Pty *pty, const char *u8, size_t len); +int pty_resize(Pty *pty, unsigned short term_width, unsigned short term_height); +int pty_signal(Pty *pty, int sig); -- 1.8.4.2 _______________________________________________ systemd-devel mailing list systemd-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/systemd-devel