[systemd-commits] 7 commits - autogen.sh .gitignore Makefile.am src/journal src/libsystemd-terminal

David Herrmann dvdhrm at kemper.freedesktop.org
Fri Jul 18 04:02:39 PDT 2014


 .gitignore                                 |    2 
 Makefile.am                                |   25 
 autogen.sh                                 |    3 
 src/journal/test-journal-send.c            |    2 
 src/libsystemd-terminal/subterm.c          |  991 +++++++
 src/libsystemd-terminal/term-charset.c     |  491 +++
 src/libsystemd-terminal/term-internal.h    |  539 +++-
 src/libsystemd-terminal/term-page.c        |  954 +++++++
 src/libsystemd-terminal/term-parser.c      | 1626 ++++++++++++
 src/libsystemd-terminal/term-screen.c      | 3882 +++++++++++++++++++++++++++++
 src/libsystemd-terminal/test-term-parser.c |  143 +
 11 files changed, 8651 insertions(+), 7 deletions(-)

New commits:
commit a72e03749f445a85010e4518ff94aa0d109043a0
Author: David Herrmann <dh.herrmann at gmail.com>
Date:   Fri Jul 18 13:00:30 2014 +0200

    autogen: add "t" switch with --enable-terminal
    
    Just temporarily add a "t" switch to "./autogen t" runs with
    --enable-terminal. Once it's compile-tested enough, we can add it to the
    default flags.

diff --git a/autogen.sh b/autogen.sh
index 4823fe7..744d13b 100755
--- a/autogen.sh
+++ b/autogen.sh
@@ -62,6 +62,9 @@ cd $oldpwd
 if [ "x$1" = "xc" ]; then
         $topdir/configure CFLAGS='-g -O0 -ftrapv' --enable-compat-libs --enable-kdbus $args
         make clean
+elif [ "x$1" = "xt" ]; then
+        $topdir/configure CFLAGS='-g -O0 -ftrapv' --enable-compat-libs --enable-kdbus --enable-terminal $args
+        make clean
 elif [ "x$1" = "xg" ]; then
         $topdir/configure CFLAGS='-g -Og -ftrapv' --enable-compat-libs --enable-kdbus $args
         make clean

commit 037ee337f0f64bd35ced765f2e2d97f496d4e7c7
Author: David Herrmann <dh.herrmann at gmail.com>
Date:   Fri Jul 18 12:58:00 2014 +0200

    journal: reduce test-journal-send timeout from 10s to 1s
    
    The sleep(10) in test-journal-send is quite aggressive. We need it only
    for the journal to get our cgroup information. But even that information
    is not vital to the test, so a sleep(1) should be just fine.

diff --git a/src/journal/test-journal-send.c b/src/journal/test-journal-send.c
index 3e986ed..45eb327 100644
--- a/src/journal/test-journal-send.c
+++ b/src/journal/test-journal-send.c
@@ -72,7 +72,7 @@ int main(int argc, char *argv[]) {
                         "N_CPUS=%li", sysconf(_SC_NPROCESSORS_ONLN),
                         NULL);
 
-        sleep(10);
+        sleep(1);
 
         return 0;
 }

commit 5ab887e98d80ffaf05a97abe4cdc1553a85f0637
Author: David Herrmann <dh.herrmann at gmail.com>
Date:   Thu Jul 17 11:10:53 2014 +0200

    terminal: add systemd-subterm example
    
    The systemd-subterm example is a stacked terminal that shows how to
    use sd-term. Instead of rendering images and displaying it via X11/etc.,
    it uses its parent terminal to display the page (terminal-emulator inside
    a terminal-emulator) (like GNU-screen and friends do).
    
    This is only for testing and not installed system-wide!

diff --git a/.gitignore b/.gitignore
index 288cde6..c29bb3a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -107,6 +107,7 @@
 /systemd-shutdownd
 /systemd-sleep
 /systemd-socket-proxyd
+/systemd-subterm
 /systemd-sysctl
 /systemd-system-update-generator
 /systemd-sysusers
diff --git a/Makefile.am b/Makefile.am
index 0de6014..0615f66 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2849,6 +2849,17 @@ libsystemd_terminal_la_LIBADD = \
 	libsystemd-internal.la \
 	libsystemd-shared.la
 
+noinst_PROGRAMS += \
+	systemd-subterm
+
+systemd_subterm_SOURCES = \
+	src/libsystemd-terminal/subterm.c
+
+systemd_subterm_LDADD = \
+	libsystemd-terminal.la \
+	libsystemd-internal.la \
+	libsystemd-shared.la
+
 test_term_page_SOURCES = \
 	src/libsystemd-terminal/test-term-page.c
 
diff --git a/src/libsystemd-terminal/subterm.c b/src/libsystemd-terminal/subterm.c
new file mode 100644
index 0000000..72ce2f6
--- /dev/null
+++ b/src/libsystemd-terminal/subterm.c
@@ -0,0 +1,991 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+/***
+  This file is part of systemd.
+
+  Copyright (C) 2014 David Herrmann <dh.herrmann at 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/>.
+***/
+
+/*
+ * Stacked Terminal-Emulator
+ * This is an interactive test of the term_screen implementation. It runs a
+ * fully-fletched terminal-emulator inside of a parent TTY. That is, instead of
+ * rendering the terminal as X11-window, it renders it as sub-window in the
+ * parent TTY. Think of this like what "GNU-screen" does.
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <termios.h>
+#include "macro.h"
+#include "pty.h"
+#include "ring.h"
+#include "sd-event.h"
+#include "term-internal.h"
+#include "util.h"
+
+typedef struct Output Output;
+typedef struct Terminal Terminal;
+
+struct Output {
+        int fd;
+        unsigned int width;
+        unsigned int height;
+        unsigned int in_width;
+        unsigned int in_height;
+        unsigned int cursor_x;
+        unsigned int cursor_y;
+
+        char obuf[4096];
+        size_t n_obuf;
+
+        bool resized : 1;
+        bool in_menu : 1;
+};
+
+struct Terminal {
+        sd_event *event;
+        sd_event_source *frame_timer;
+        Output *output;
+        term_utf8 utf8;
+        term_parser *parser;
+        term_screen *screen;
+
+        int in_fd;
+        int out_fd;
+        struct termios saved_in_attr;
+        struct termios saved_out_attr;
+
+        Pty *pty;
+        Ring out_ring;
+
+        bool is_scheduled : 1;
+        bool is_dirty : 1;
+        bool is_menu : 1;
+};
+
+/*
+ * Output Handling
+ */
+
+#define BORDER_HORIZ            "\xe2\x94\x81"
+#define BORDER_VERT             "\xe2\x94\x83"
+#define BORDER_VERT_RIGHT       "\xe2\x94\xa3"
+#define BORDER_VERT_LEFT        "\xe2\x94\xab"
+#define BORDER_DOWN_RIGHT       "\xe2\x94\x8f"
+#define BORDER_DOWN_LEFT        "\xe2\x94\x93"
+#define BORDER_UP_RIGHT         "\xe2\x94\x97"
+#define BORDER_UP_LEFT          "\xe2\x94\x9b"
+
+static int output_winch(Output *o) {
+        struct winsize wsz = { };
+        int r;
+
+        assert_return(o, -EINVAL);
+
+        r = ioctl(o->fd, TIOCGWINSZ, &wsz);
+        if (r < 0) {
+                log_error("error: cannot read window-size: %m");
+                return -errno;
+        }
+
+        if (wsz.ws_col != o->width || wsz.ws_row != o->height) {
+                o->width = wsz.ws_col;
+                o->height = wsz.ws_row;
+                o->in_width = MAX(o->width, 2U) - 2;
+                o->in_height = MAX(o->height, 6U) - 6;
+                o->resized = true;
+        }
+
+        return 0;
+}
+
+static int output_flush(Output *o) {
+        ssize_t len;
+
+        if (o->n_obuf < 1)
+                return 0;
+
+        len = loop_write(o->fd, o->obuf, o->n_obuf, false);
+        if (len < 0) {
+                log_error("error: cannot write to TTY (%zd): %s", len, strerror(-len));
+                return len;
+        }
+
+        o->n_obuf = 0;
+
+        return 0;
+}
+
+static int output_write(Output *o, const void *buf, size_t size) {
+        ssize_t len;
+        int r;
+
+        assert_return(o, -EINVAL);
+        assert_return(buf || size < 1, -EINVAL);
+
+        if (size < 1)
+                return 0;
+
+        if (o->n_obuf + size > o->n_obuf && o->n_obuf + size <= sizeof(o->obuf)) {
+                memcpy(o->obuf + o->n_obuf, buf, size);
+                o->n_obuf += size;
+                return 0;
+        }
+
+        r = output_flush(o);
+        if (r < 0)
+                return r;
+
+        len = loop_write(o->fd, buf, size, false);
+        if (len < 0) {
+                log_error("error: cannot write to TTY (%zd): %s", len, strerror(-len));
+                return len;
+        }
+
+        return 0;
+}
+
+static int output_vnprintf(Output *o, size_t max, const char *format, va_list args) {
+        char buf[4096];
+        int r;
+
+        assert_return(o, -EINVAL);
+        assert_return(format, -EINVAL);
+        assert_return(max <= sizeof(buf), -EINVAL);
+
+        r = vsnprintf(buf, max, format, args);
+        if (r > (ssize_t)max)
+                r = max;
+
+        return output_write(o, buf, r);
+}
+
+static int output_nprintf(Output *o, size_t max, const char *format, ...) {
+        va_list args;
+        int r;
+
+        va_start(args, format);
+        r = output_vnprintf(o, max, format, args);
+        va_end(args);
+
+        return r;
+}
+
+static int output_vprintf(Output *o, const char *format, va_list args) {
+        char buf[4096];
+        int r;
+
+        assert_return(o, -EINVAL);
+        assert_return(format, -EINVAL);
+
+        r = vsnprintf(buf, sizeof(buf), format, args);
+
+        assert_return(r < (ssize_t)sizeof(buf), -ENOBUFS);
+
+        return output_write(o, buf, r);
+}
+
+static int output_printf(Output *o, const char *format, ...) {
+        va_list args;
+        int r;
+
+        va_start(args, format);
+        r = output_vprintf(o, format, args);
+        va_end(args);
+
+        return r;
+}
+
+static int output_move_to(Output *o, unsigned int x, unsigned int y) {
+        int r;
+
+        assert_return(o, -EINVAL);
+
+        /* force the \e[H code as o->cursor_x/y might be out-of-date */
+
+        r = output_printf(o, "\e[%u;%uH", y + 1, x + 1);
+        if (r < 0)
+                return r;
+
+        o->cursor_x = x;
+        o->cursor_y = y;
+        return 0;
+}
+
+static int output_print_line(Output *o, size_t len) {
+        const char line[] =
+                BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ
+                BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ
+                BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ
+                BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ
+                BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ
+                BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ
+                BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ;
+        const size_t max = (sizeof(line) - 1) / (sizeof(BORDER_HORIZ) - 1);
+        size_t i;
+        int r = 0;
+
+        assert_return(o, -EINVAL);
+
+        for ( ; len > 0; len -= i) {
+                i = (len > max) ? max : len;
+                r = output_write(o, line, i * (sizeof(BORDER_HORIZ) - 1));
+                if (r < 0)
+                        break;
+        }
+
+        return r;
+}
+
+static int output_frame_printl(Output *o, const char *format, ...) {
+        va_list args;
+        int r;
+
+        assert(o);
+        assert(format);
+
+        /* out of frame? */
+        if (o->cursor_y < 3 || o->cursor_y >= o->height - 3)
+                return 0;
+
+        va_start(args, format);
+        r = output_vnprintf(o, o->width - 2, format, args);
+        va_end(args);
+
+        if (r < 0)
+                return r;
+
+        return output_move_to(o, 1, o->cursor_y + 1);
+}
+
+static Output *output_free(Output *o) {
+        if (!o)
+                return NULL;
+
+        /* disable alternate screen buffer */
+        output_printf(o, "\e[?1049l");
+        output_flush(o);
+
+        /* o->fd is owned by the caller */
+        free(o);
+
+        return NULL;
+}
+
+static int output_new(Output **out, int fd) {
+        Output *o;
+        int r;
+
+        assert_return(out, -EINVAL);
+
+        o = new0(Output, 1);
+        if (!o)
+                return log_oom();
+
+        o->fd = fd;
+
+        r = output_winch(o);
+        if (r < 0)
+                goto error;
+
+        /* enable alternate screen buffer */
+        r = output_printf(o, "\e[?1049h");
+        if (r < 0)
+                goto error;
+
+        r = output_flush(o);
+        if (r < 0)
+                goto error;
+
+        *out = o;
+        return 0;
+
+error:
+        output_free(o);
+        return r;
+}
+
+static void output_draw_frame(Output *o) {
+        unsigned int i;
+
+        assert(o);
+
+        /* print header-frame */
+
+        output_printf(o, BORDER_DOWN_RIGHT);
+        output_print_line(o, o->width - 2);
+        output_printf(o, BORDER_DOWN_LEFT
+                         "\r\n"
+                         BORDER_VERT
+                         "\e[2;%uH"    /* cursor-position: 2/x */
+                         BORDER_VERT
+                         "\r\n"
+                         BORDER_VERT_RIGHT,
+                      o->width);
+        output_print_line(o, o->width - 2);
+        output_printf(o, BORDER_VERT_LEFT
+                         "\r\n");
+
+        /* print body-frame */
+
+        for (i = 0; i < o->in_height; ++i) {
+                output_printf(o, BORDER_VERT
+                                 "\e[%u;%uH"    /* cursor-position: 2/x */
+                                 BORDER_VERT
+                                 "\r\n",
+                              i + 4, o->width);
+        }
+
+        /* print footer-frame */
+
+        output_printf(o, BORDER_VERT_RIGHT);
+        output_print_line(o, o->width - 2);
+        output_printf(o, BORDER_VERT_LEFT
+                         "\r\n"
+                         BORDER_VERT
+                         "\e[%u;%uH"    /* cursor-position: 2/x */
+                         BORDER_VERT
+                         "\r\n"
+                         BORDER_UP_RIGHT,
+                      o->height - 1, o->width);
+        output_print_line(o, o->width - 2);
+        output_printf(o, BORDER_UP_LEFT);
+
+        /* print header/footer text */
+
+        output_printf(o, "\e[2;3H");
+        output_nprintf(o, o->width - 4, "systemd - embedded terminal emulator");
+        output_printf(o, "\e[%u;3H", o->height - 1);
+        output_nprintf(o, o->width - 4, "press ^C to enter menu");
+}
+
+static void output_draw_menu(Output *o) {
+        assert(o);
+
+        output_frame_printl(o, "");
+        output_frame_printl(o, "    Menu: (the following keys are recognized)");
+        output_frame_printl(o, "      q: quit");
+        output_frame_printl(o, "     ^C: send ^C to the PTY");
+}
+
+static void output_draw_screen(Output *o, term_screen *s) {
+        unsigned int i, j;
+        bool first = true;
+
+        assert(o);
+        assert(s);
+
+        for (j = 0; j < s->page->height && j < o->in_height; ++j) {
+                if (!first)
+                        output_printf(o, "\e[m\r\n" BORDER_VERT);
+                first = false;
+
+                for (i = 0; i < s->page->width && i < o->in_width; ++i) {
+                        term_charbuf_t buf;
+                        term_cell *cell = &s->page->lines[j]->cells[i];
+                        size_t k, len, ulen;
+                        const uint32_t *str;
+                        char utf8[4];
+
+                        switch (cell->attr.fg.ccode) {
+                        case TERM_CCODE_DEFAULT:
+                                output_printf(o, "\e[39m");
+                                break;
+                        case TERM_CCODE_256:
+                                output_printf(o, "\e[38;5;%um", cell->attr.fg.c256);
+                                break;
+                        case TERM_CCODE_RGB:
+                                output_printf(o, "\e[38;2;%u;%u;%um", cell->attr.fg.red, cell->attr.fg.green, cell->attr.fg.blue);
+                                break;
+                        case TERM_CCODE_BLACK ... TERM_CCODE_WHITE:
+                                if (cell->attr.bold)
+                                        output_printf(o, "\e[%um", cell->attr.fg.ccode - TERM_CCODE_BLACK + 90);
+                                else
+                                        output_printf(o, "\e[%um", cell->attr.fg.ccode - TERM_CCODE_BLACK + 30);
+                                break;
+                        case TERM_CCODE_LIGHT_BLACK ... TERM_CCODE_LIGHT_WHITE:
+                                output_printf(o, "\e[%um", cell->attr.fg.ccode - TERM_CCODE_LIGHT_BLACK + 90);
+                                break;
+                        }
+
+                        switch (cell->attr.bg.ccode) {
+                        case TERM_CCODE_DEFAULT:
+                                output_printf(o, "\e[49m");
+                                break;
+                        case TERM_CCODE_256:
+                                output_printf(o, "\e[48;5;%um", cell->attr.bg.c256);
+                                break;
+                        case TERM_CCODE_RGB:
+                                output_printf(o, "\e[48;2;%u;%u;%um", cell->attr.bg.red, cell->attr.bg.green, cell->attr.bg.blue);
+                                break;
+                        case TERM_CCODE_BLACK ... TERM_CCODE_WHITE:
+                                output_printf(o, "\e[%um", cell->attr.bg.ccode - TERM_CCODE_BLACK + 40);
+                                break;
+                        case TERM_CCODE_LIGHT_BLACK ... TERM_CCODE_LIGHT_WHITE:
+                                output_printf(o, "\e[%um", cell->attr.bg.ccode - TERM_CCODE_LIGHT_BLACK + 100);
+                                break;
+                        }
+
+                        output_printf(o, "\e[%u;%u;%u;%u;%u;%um",
+                                      cell->attr.bold ? 1 : 22,
+                                      cell->attr.italic ? 3 : 23,
+                                      cell->attr.underline ? 4 : 24,
+                                      cell->attr.inverse ? 7 : 27,
+                                      cell->attr.blink ? 5 : 25,
+                                      cell->attr.hidden ? 8 : 28);
+
+                        str = term_char_resolve(cell->ch, &len, &buf);
+
+                        if (len < 1) {
+                                output_printf(o, " ");
+                        } else {
+                                for (k = 0; k < len; ++k) {
+                                        ulen = term_utf8_encode(utf8, str[k]);
+                                        output_write(o, utf8, ulen);
+                                }
+                        }
+                }
+        }
+
+        output_move_to(o, s->cursor_x + 1, s->cursor_y + 3);
+        output_printf(o, "\e[m");
+}
+
+static void output_draw(Output *o, bool menu, term_screen *screen) {
+        assert(o);
+
+        /*
+         * This renders the contenst of the terminal. The layout contains a
+         * header, the main body and a footer. Around all areas we draw a
+         * border. It looks something like this:
+         *
+         *     +----------------------------------------------------+
+         *     |                      Header                        |
+         *     +----------------------------------------------------+
+         *     |                                                    |
+         *     |                                                    |
+         *     |                                                    |
+         *     |                       Body                         |
+         *     |                                                    |
+         *     |                                                    |
+         *     ~                                                    ~
+         *     ~                                                    ~
+         *     +----------------------------------------------------+
+         *     |                      Footer                        |
+         *     +----------------------------------------------------+
+         *
+         * The body is the part that grows vertically.
+         *
+         * We need at least 6 vertical lines to render the screen. This would
+         * leave 0 lines for the body. Therefore, we require 7 lines so there's
+         * at least one body line. Similarly, we need 2 horizontal cells for the
+         * frame, so we require 3.
+         * If the window is too small, we print an error message instead.
+         */
+
+        if (o->in_width < 1 || o->in_height < 1) {
+                output_printf(o, "\e[2J"         /* erase-in-display: whole screen */
+                                 "\e[H");        /* cursor-position: home */
+                output_printf(o, "error: screen too small, need at least 3x7 cells");
+                output_flush(o);
+                return;
+        }
+
+        /* hide cursor */
+        output_printf(o, "\e[?25l");
+
+        /* frame-content is contant; only resizes can change it */
+        if (o->resized || o->in_menu != menu) {
+                output_printf(o, "\e[2J"         /* erase-in-display: whole screen */
+                                 "\e[H");        /* cursor-position: home */
+                output_draw_frame(o);
+                o->resized = false;
+                o->in_menu = menu;
+        }
+
+        /* move cursor to child's position */
+        output_move_to(o, 1, 3);
+
+        if (menu)
+                output_draw_menu(o);
+        else
+                output_draw_screen(o, screen);
+
+        /* show cursor */
+        if (!(screen->flags & TERM_FLAG_HIDE_CURSOR))
+                output_printf(o, "\e[?25h");
+
+        /*
+         * Hack: sd-term was not written to support TTY as output-objects, thus
+         * expects callers to use term_screen_feed_keyboard(). However, we
+         * forward TTY input directly. Hence, we're not notified about keypad
+         * changes. Update the related modes djring redraw to keep them at least
+         * in sync.
+         */
+        if (screen->flags & TERM_FLAG_CURSOR_KEYS)
+                output_printf(o, "\e[?1h");
+        else
+                output_printf(o, "\e[?1l");
+
+        if (screen->flags & TERM_FLAG_KEYPAD_MODE)
+                output_printf(o, "\e=");
+        else
+                output_printf(o, "\e>");
+
+        output_flush(o);
+}
+
+/*
+ * Terminal Handling
+ */
+
+static void terminal_dirty(Terminal *t) {
+        uint64_t usec;
+        int r;
+
+        assert(t);
+
+        if (t->is_scheduled) {
+                t->is_dirty = true;
+                return;
+        }
+
+        /* 16ms timer */
+        r = sd_event_now(t->event, CLOCK_MONOTONIC, &usec);
+        assert(r >= 0);
+
+        usec += 16 * USEC_PER_MSEC;
+        r = sd_event_source_set_time(t->frame_timer, usec);
+        if (r >= 0) {
+                r = sd_event_source_set_enabled(t->frame_timer, SD_EVENT_ONESHOT);
+                if (r >= 0)
+                        t->is_scheduled = true;
+        }
+
+        t->is_dirty = false;
+        output_draw(t->output, t->is_menu, t->screen);
+}
+
+static int terminal_frame_timer_fn(sd_event_source *source, uint64_t usec, void *userdata) {
+        Terminal *t = userdata;
+
+        t->is_scheduled = false;
+        if (t->is_dirty)
+                terminal_dirty(t);
+
+        return 0;
+}
+
+static int terminal_winch_fn(sd_event_source *source, const struct signalfd_siginfo *ssi, void *userdata) {
+        Terminal *t = userdata;
+        int r;
+
+        output_winch(t->output);
+
+        if (t->pty) {
+                r = pty_resize(t->pty, t->output->in_width, t->output->in_height);
+                if (r < 0)
+                        log_error("error: pty_resize() (%d): %s", r, strerror(-r));
+        }
+
+        r = term_screen_resize(t->screen, t->output->in_width, t->output->in_height);
+        if (r < 0)
+                log_error("error: term_screen_resize() (%d): %s", r, strerror(-r));
+
+        terminal_dirty(t);
+
+        return 0;
+}
+
+static int terminal_push_tmp(Terminal *t, uint32_t ucs4) {
+        char buf[4];
+        size_t len;
+        int r;
+
+        assert(t);
+
+        len = term_utf8_encode(buf, ucs4);
+        if (len < 1)
+                return 0;
+
+        r = ring_push(&t->out_ring, buf, len);
+        if (r < 0)
+                log_oom();
+
+        return r;
+}
+
+static int terminal_write_tmp(Terminal *t) {
+        struct iovec vec[2];
+        size_t num, i;
+        int r;
+
+        assert(t);
+
+        num = ring_peek(&t->out_ring, vec);
+        if (num < 1)
+                return 0;
+
+        if (t->pty) {
+                for (i = 0; i < num; ++i) {
+                        r = pty_write(t->pty, vec[i].iov_base, vec[i].iov_len);
+                        if (r < 0) {
+                                log_error("error: cannot write to PTY (%d): %s", r, strerror(-r));
+                                return r;
+                        }
+                }
+        }
+
+        ring_flush(&t->out_ring);
+        return 0;
+}
+
+static void terminal_discard_tmp(Terminal *t) {
+        assert(t);
+
+        ring_flush(&t->out_ring);
+}
+
+static int terminal_menu(Terminal *t, const term_seq *seq) {
+        switch (seq->type) {
+        case TERM_SEQ_IGNORE:
+                break;
+        case TERM_SEQ_GRAPHIC:
+                switch (seq->terminator) {
+                case 'q':
+                        sd_event_exit(t->event, 0);
+                        return 0;
+                }
+
+                break;
+        case TERM_SEQ_CONTROL:
+                switch (seq->terminator) {
+                case 0x03:
+                        terminal_push_tmp(t, 0x03);
+                        terminal_write_tmp(t);
+                        break;
+                }
+
+                break;
+        }
+
+        t->is_menu = false;
+        terminal_dirty(t);
+
+        return 0;
+}
+
+static int terminal_io_fn(sd_event_source *source, int fd, uint32_t revents, void *userdata) {
+        Terminal *t = userdata;
+        char buf[4096];
+        ssize_t len, i;
+        int r, type;
+
+        len = read(fd, buf, sizeof(buf));
+        if (len < 0) {
+                if (errno == EAGAIN || errno == EINTR)
+                        return 0;
+
+                log_error("error: cannot read from TTY (%d): %m", -errno);
+                return -errno;
+        }
+
+        for (i = 0; i < len; ++i) {
+                const term_seq *seq;
+                const uint32_t *str;
+                size_t n_str, j;
+
+                str = term_utf8_decode(&t->utf8, &n_str, buf[i]);
+                for (j = 0; j < n_str; ++j) {
+                        type = term_parser_feed(t->parser, &seq, str[j]);
+                        if (type < 0) {
+                                log_error("error: term_parser_feed() (%d): %s", type, strerror(-type));
+                                return type;
+                        }
+
+                        if (!t->is_menu) {
+                                r = terminal_push_tmp(t, str[j]);
+                                if (r < 0)
+                                        return r;
+                        }
+
+                        if (type == TERM_SEQ_NONE) {
+                                /* We only intercept one-char sequences, so in
+                                 * case term_parser_feed() couldn't parse a
+                                 * sequence, it is waiting for more data. We
+                                 * know it can never be a one-char sequence
+                                 * then, so we can safely forward the data.
+                                 * This avoids withholding ESC or other values
+                                 * that may be one-shot depending on the
+                                 * application. */
+                                r = terminal_write_tmp(t);
+                                if (r < 0)
+                                        return r;
+                        } else if (t->is_menu) {
+                                r = terminal_menu(t, seq);
+                                if (r < 0)
+                                        return r;
+                        } else if (seq->type == TERM_SEQ_CONTROL && seq->terminator == 0x03) { /* ^C opens the menu */
+                                terminal_discard_tmp(t);
+                                t->is_menu = true;
+                                terminal_dirty(t);
+                        } else {
+                                r = terminal_write_tmp(t);
+                                if (r < 0)
+                                        return r;
+                        }
+                }
+        }
+
+        return 0;
+}
+
+static int terminal_pty_fn(Pty *pty, void *userdata, unsigned int event, const void *ptr, size_t size) {
+        Terminal *t = userdata;
+        int r;
+
+        switch (event) {
+        case PTY_CHILD:
+                sd_event_exit(t->event, 0);
+                break;
+        case PTY_DATA:
+                r = term_screen_feed_text(t->screen, ptr, size);
+                if (r < 0) {
+                        log_error("error: term_screen_feed_text() (%d): %s", r, strerror(-r));
+                        return r;
+                }
+
+                terminal_dirty(t);
+                break;
+        }
+
+        return 0;
+}
+
+static int terminal_write_fn(term_screen *screen, void *userdata, const void *buf, size_t size) {
+        Terminal *t = userdata;
+        int r;
+
+        if (!t->pty)
+                return 0;
+
+        r = ring_push(&t->out_ring, buf, size);
+        if (r < 0)
+                log_oom();
+
+        return r;
+}
+
+static int terminal_cmd_fn(term_screen *screen, void *userdata, unsigned int cmd, const term_seq *seq) {
+        return 0;
+}
+
+static Terminal *terminal_free(Terminal *t) {
+        if (!t)
+                return NULL;
+
+        ring_clear(&t->out_ring);
+        term_screen_unref(t->screen);
+        term_parser_free(t->parser);
+        output_free(t->output);
+        sd_event_source_unref(t->frame_timer);
+        sd_event_unref(t->event);
+        tcsetattr(t->in_fd, TCSANOW, &t->saved_in_attr);
+        tcsetattr(t->out_fd, TCSANOW, &t->saved_out_attr);
+        free(t);
+
+        return NULL;
+}
+
+static int terminal_new(Terminal **out, int in_fd, int out_fd) {
+        struct termios in_attr, out_attr;
+        Terminal *t;
+        int r;
+
+        assert_return(out, -EINVAL);
+
+        r = tcgetattr(in_fd, &in_attr);
+        if (r < 0) {
+                log_error("error: tcgetattr() (%d): %m", -errno);
+                return -errno;
+        }
+
+        r = tcgetattr(out_fd, &out_attr);
+        if (r < 0) {
+                log_error("error: tcgetattr() (%d): %m", -errno);
+                return -errno;
+        }
+
+        t = new0(Terminal, 1);
+        if (!t)
+                return log_oom();
+
+        t->in_fd = in_fd;
+        t->out_fd = out_fd;
+        memcpy(&t->saved_in_attr, &in_attr, sizeof(in_attr));
+        memcpy(&t->saved_out_attr, &out_attr, sizeof(out_attr));
+
+        cfmakeraw(&in_attr);
+        cfmakeraw(&out_attr);
+
+        r = tcsetattr(t->in_fd, TCSANOW, &in_attr);
+        if (r < 0) {
+                log_error("error: tcsetattr() (%d): %s", r, strerror(-r));
+                goto error;
+        }
+
+        r = tcsetattr(t->out_fd, TCSANOW, &out_attr);
+        if (r < 0) {
+                log_error("error: tcsetattr() (%d): %s", r, strerror(-r));
+                goto error;
+        }
+
+        r = sd_event_default(&t->event);
+        if (r < 0) {
+                log_error("error: sd_event_default() (%d): %s", r, strerror(-r));
+                goto error;
+        }
+
+        r = sigprocmask_many(SIG_BLOCK, SIGINT, SIGQUIT, SIGTERM, SIGWINCH, SIGCHLD, -1);
+        if (r < 0) {
+                log_error("error: sigprocmask_many() (%d): %s", r, strerror(-r));
+                goto error;
+        }
+
+        r = sd_event_add_signal(t->event, NULL, SIGINT, NULL, NULL);
+        if (r < 0) {
+                log_error("error: sd_event_add_signal() (%d): %s", r, strerror(-r));
+                goto error;
+        }
+
+        r = sd_event_add_signal(t->event, NULL, SIGQUIT, NULL, NULL);
+        if (r < 0) {
+                log_error("error: sd_event_add_signal() (%d): %s", r, strerror(-r));
+                goto error;
+        }
+
+        r = sd_event_add_signal(t->event, NULL, SIGTERM, NULL, NULL);
+        if (r < 0) {
+                log_error("error: sd_event_add_signal() (%d): %s", r, strerror(-r));
+                goto error;
+        }
+
+        r = sd_event_add_signal(t->event, NULL, SIGWINCH, terminal_winch_fn, t);
+        if (r < 0) {
+                log_error("error: sd_event_add_signal() (%d): %s", r, strerror(-r));
+                goto error;
+        }
+
+        /* force initial redraw on event-loop enter */
+        t->is_dirty = true;
+        r = sd_event_add_time(t->event, &t->frame_timer, CLOCK_MONOTONIC, 0, 0, terminal_frame_timer_fn, t);
+        if (r < 0) {
+                log_error("error: sd_event_add_time() (%d): %s", r, strerror(-r));
+                goto error;
+        }
+
+        r = output_new(&t->output, out_fd);
+        if (r < 0)
+                goto error;
+
+        r = term_parser_new(&t->parser, true);
+        if (r < 0)
+                goto error;
+
+        r = term_screen_new(&t->screen, terminal_write_fn, t, terminal_cmd_fn, t);
+        if (r < 0)
+                goto error;
+
+        r = term_screen_set_answerback(t->screen, "systemd-subterm");
+        if (r < 0)
+                goto error;
+
+        r = term_screen_resize(t->screen, t->output->in_width, t->output->in_height);
+        if (r < 0) {
+                log_error("error: term_screen_resize() (%d): %s", r, strerror(-r));
+                goto error;
+        }
+
+        r = sd_event_add_io(t->event, NULL, in_fd, EPOLLIN, terminal_io_fn, t);
+        if (r < 0)
+                goto error;
+
+        *out = t;
+        return 0;
+
+error:
+        terminal_free(t);
+        return r;
+}
+
+static int terminal_run(Terminal *t) {
+        pid_t pid;
+
+        assert_return(t, -EINVAL);
+
+        pid = pty_fork(&t->pty, t->event, terminal_pty_fn, t, t->output->in_width, t->output->in_height);
+        if (pid < 0) {
+                log_error("error: cannot fork PTY (%d): %s", pid, strerror(-pid));
+                return pid;
+        } else if (pid == 0) {
+                /* child */
+
+                char **argv = (char*[]){
+                        (char*)getenv("SHELL") ? : (char*)_PATH_BSHELL,
+                        NULL
+                };
+
+                setenv("TERM", "xterm-256color", 1);
+                setenv("COLORTERM", "systemd-subterm", 1);
+
+                execve(argv[0], argv, environ);
+                log_error("error: cannot exec %s (%d): %m", argv[0], -errno);
+                _exit(1);
+        }
+
+        /* parent */
+
+        return sd_event_loop(t->event);
+}
+
+/*
+ * Context Handling
+ */
+
+int main(int argc, char *argv[]) {
+        Terminal *t = NULL;
+        int r;
+
+        r = terminal_new(&t, 0, 1);
+        if (r < 0)
+                goto out;
+
+        r = terminal_run(t);
+        if (r < 0)
+                goto out;
+
+out:
+        if (r < 0)
+                log_error("error: terminal failed (%d): %s", r, strerror(-r));
+        terminal_free(t);
+        return -r;
+}

commit e432f9e8f999fe75d79a2499035c8e84b04a8b1a
Author: David Herrmann <dh.herrmann at gmail.com>
Date:   Tue Jul 8 15:11:29 2014 +0200

    terminal: add screen-handling
    
    The screen-layer represents the terminal-side (compared to the host-side).
    It connects term_parser with term_page and implements all the required
    control sequences.
    
    We do not implement all available control sequences. Even though our
    parser recognizes them, there is no need to handle them. Most of them are
    legacy or unused. We try to be as compatible to xterm, so if we missed
    something, we can implement it later. However, all the VT510 / VT440 stuff
    can safely be skipped (who needs terminal macros? WTF?).
    
    The keyboard-handling is still missing. It will be added once
    systemd-console is available and we pulled in the key-definitions.

diff --git a/Makefile.am b/Makefile.am
index 73f1252..0de6014 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2842,6 +2842,7 @@ libsystemd_terminal_la_SOURCES = \
 	src/libsystemd-terminal/term-charset.c \
 	src/libsystemd-terminal/term-page.c \
 	src/libsystemd-terminal/term-parser.c \
+	src/libsystemd-terminal/term-screen.c \
 	src/libsystemd-terminal/term-wcwidth.c
 
 libsystemd_terminal_la_LIBADD = \
diff --git a/src/libsystemd-terminal/term-internal.h b/src/libsystemd-terminal/term-internal.h
index a3d1f54..345996b 100644
--- a/src/libsystemd-terminal/term-internal.h
+++ b/src/libsystemd-terminal/term-internal.h
@@ -42,6 +42,8 @@ typedef struct term_seq term_seq;
 typedef struct term_parser term_parser;
 typedef uint32_t term_charset[96];
 
+typedef struct term_screen term_screen;
+
 /*
  * Miscellaneous
  * Sundry things and external helpers.
@@ -433,8 +435,8 @@ enum {
         TERM_CMD_DA1,                           /* primary-device-attributes */
         TERM_CMD_DA2,                           /* secondary-device-attributes */
         TERM_CMD_DA3,                           /* tertiary-device-attributes */
-        TERM_CMD_DC1,                           /* device-control-1 */
-        TERM_CMD_DC3,                           /* device-control-3 */
+        TERM_CMD_DC1,                           /* device-control-1 or XON */
+        TERM_CMD_DC3,                           /* device-control-3 or XOFF */
         TERM_CMD_DCH,                           /* delete-character */
         TERM_CMD_DECALN,                        /* screen-alignment-pattern */
         TERM_CMD_DECANM,                        /* ansi-mode */
@@ -445,135 +447,135 @@ enum {
         TERM_CMD_DECDHL_BH,                     /* double-width-double-height-line: bottom half */
         TERM_CMD_DECDHL_TH,                     /* double-width-double-height-line: top half */
         TERM_CMD_DECDWL,                        /* double-width-single-height-line */
-        TERM_CMD_DECEFR,
-        TERM_CMD_DECELF,
-        TERM_CMD_DECELR,
-        TERM_CMD_DECERA,
-        TERM_CMD_DECFI,
-        TERM_CMD_DECFRA,
-        TERM_CMD_DECIC,
-        TERM_CMD_DECID,
-        TERM_CMD_DECINVM,
-        TERM_CMD_DECKBD,
-        TERM_CMD_DECKPAM,
-        TERM_CMD_DECKPNM,
-        TERM_CMD_DECLFKC,
-        TERM_CMD_DECLL,
-        TERM_CMD_DECLTOD,
-        TERM_CMD_DECPCTERM,
-        TERM_CMD_DECPKA,
-        TERM_CMD_DECPKFMR,
-        TERM_CMD_DECRARA,
-        TERM_CMD_DECRC,
-        TERM_CMD_DECREQTPARM,
-        TERM_CMD_DECRPKT,
-        TERM_CMD_DECRQCRA,
-        TERM_CMD_DECRQDE,
-        TERM_CMD_DECRQKT,
-        TERM_CMD_DECRQLP,
-        TERM_CMD_DECRQM_ANSI,
-        TERM_CMD_DECRQM_DEC,
-        TERM_CMD_DECRQPKFM,
-        TERM_CMD_DECRQPSR,
-        TERM_CMD_DECRQTSR,
-        TERM_CMD_DECRQUPSS,
-        TERM_CMD_DECSACE,
-        TERM_CMD_DECSASD,
-        TERM_CMD_DECSC,
-        TERM_CMD_DECSCA,
-        TERM_CMD_DECSCL,
-        TERM_CMD_DECSCP,
-        TERM_CMD_DECSCPP,
-        TERM_CMD_DECSCS,
-        TERM_CMD_DECSCUSR,
-        TERM_CMD_DECSDDT,
-        TERM_CMD_DECSDPT,
-        TERM_CMD_DECSED,
-        TERM_CMD_DECSEL,
-        TERM_CMD_DECSERA,
-        TERM_CMD_DECSFC,
-        TERM_CMD_DECSKCV,
-        TERM_CMD_DECSLCK,
-        TERM_CMD_DECSLE,
-        TERM_CMD_DECSLPP,
-        TERM_CMD_DECSLRM_OR_SC,
-        TERM_CMD_DECSMBV,
-        TERM_CMD_DECSMKR,
-        TERM_CMD_DECSNLS,
-        TERM_CMD_DECSPP,
-        TERM_CMD_DECSPPCS,
-        TERM_CMD_DECSPRTT,
-        TERM_CMD_DECSR,
-        TERM_CMD_DECSRFR,
-        TERM_CMD_DECSSCLS,
-        TERM_CMD_DECSSDT,
-        TERM_CMD_DECSSL,
-        TERM_CMD_DECST8C,
-        TERM_CMD_DECSTBM,
-        TERM_CMD_DECSTR,
-        TERM_CMD_DECSTRL,
-        TERM_CMD_DECSWBV,
-        TERM_CMD_DECSWL,
-        TERM_CMD_DECTID,
-        TERM_CMD_DECTME,
-        TERM_CMD_DECTST,
-        TERM_CMD_DL,
-        TERM_CMD_DSR_ANSI,
-        TERM_CMD_DSR_DEC,
-        TERM_CMD_ECH,
-        TERM_CMD_ED,
-        TERM_CMD_EL,
-        TERM_CMD_ENQ,
-        TERM_CMD_EPA,
-        TERM_CMD_FF,
-        TERM_CMD_HPA,
-        TERM_CMD_HPR,
-        TERM_CMD_HT,
-        TERM_CMD_HTS,
-        TERM_CMD_HVP,
-        TERM_CMD_ICH,
-        TERM_CMD_IL,
-        TERM_CMD_IND,
-        TERM_CMD_LF,
-        TERM_CMD_LS1R,
-        TERM_CMD_LS2,
-        TERM_CMD_LS2R,
-        TERM_CMD_LS3,
-        TERM_CMD_LS3R,
-        TERM_CMD_MC_ANSI,
-        TERM_CMD_MC_DEC,
-        TERM_CMD_NEL,
-        TERM_CMD_NP,
-        TERM_CMD_NULL,
-        TERM_CMD_PP,
-        TERM_CMD_PPA,
-        TERM_CMD_PPB,
-        TERM_CMD_PPR,
-        TERM_CMD_RC,
-        TERM_CMD_REP,
-        TERM_CMD_RI,
-        TERM_CMD_RIS,
-        TERM_CMD_RM_ANSI,
-        TERM_CMD_RM_DEC,
-        TERM_CMD_S7C1T,
-        TERM_CMD_S8C1T,
-        TERM_CMD_SCS,
-        TERM_CMD_SD,
-        TERM_CMD_SGR,
-        TERM_CMD_SI,
-        TERM_CMD_SM_ANSI,
-        TERM_CMD_SM_DEC,
-        TERM_CMD_SO,
-        TERM_CMD_SPA,
-        TERM_CMD_SS2,
-        TERM_CMD_SS3,
-        TERM_CMD_ST,
-        TERM_CMD_SU,
-        TERM_CMD_SUB,
-        TERM_CMD_TBC,
-        TERM_CMD_VPA,
-        TERM_CMD_VPR,
-        TERM_CMD_VT,
+        TERM_CMD_DECEFR,                        /* enable-filter-rectangle */
+        TERM_CMD_DECELF,                        /* enable-local-functions */
+        TERM_CMD_DECELR,                        /* enable-locator-reporting */
+        TERM_CMD_DECERA,                        /* erase-rectangular-area */
+        TERM_CMD_DECFI,                         /* forward-index */
+        TERM_CMD_DECFRA,                        /* fill-rectangular-area */
+        TERM_CMD_DECIC,                         /* insert-column */
+        TERM_CMD_DECID,                         /* return-terminal-id */
+        TERM_CMD_DECINVM,                       /* invoke-macro */
+        TERM_CMD_DECKBD,                        /* keyboard-language-selection */
+        TERM_CMD_DECKPAM,                       /* keypad-application-mode */
+        TERM_CMD_DECKPNM,                       /* keypad-numeric-mode */
+        TERM_CMD_DECLFKC,                       /* local-function-key-control */
+        TERM_CMD_DECLL,                         /* load-leds */
+        TERM_CMD_DECLTOD,                       /* load-time-of-day */
+        TERM_CMD_DECPCTERM,                     /* pcterm-mode */
+        TERM_CMD_DECPKA,                        /* program-key-action */
+        TERM_CMD_DECPKFMR,                      /* program-key-free-memory-report */
+        TERM_CMD_DECRARA,                       /* reverse-attributes-in-rectangular-area */
+        TERM_CMD_DECRC,                         /* restore-cursor */
+        TERM_CMD_DECREQTPARM,                   /* request-terminal-parameters */
+        TERM_CMD_DECRPKT,                       /* report-key-type */
+        TERM_CMD_DECRQCRA,                      /* request-checksum-of-rectangular-area */
+        TERM_CMD_DECRQDE,                       /* request-display-extent */
+        TERM_CMD_DECRQKT,                       /* request-key-type */
+        TERM_CMD_DECRQLP,                       /* request-locator-position */
+        TERM_CMD_DECRQM_ANSI,                   /* request-mode-ansi */
+        TERM_CMD_DECRQM_DEC,                    /* request-mode-dec */
+        TERM_CMD_DECRQPKFM,                     /* request-program-key-free-memory */
+        TERM_CMD_DECRQPSR,                      /* request-presentation-state-report */
+        TERM_CMD_DECRQTSR,                      /* request-terminal-state-report */
+        TERM_CMD_DECRQUPSS,                     /* request-user-preferred-supplemental-set */
+        TERM_CMD_DECSACE,                       /* select-attribute-change-extent */
+        TERM_CMD_DECSASD,                       /* select-active-status-display */
+        TERM_CMD_DECSC,                         /* save-cursor */
+        TERM_CMD_DECSCA,                        /* select-character-protection-attribute */
+        TERM_CMD_DECSCL,                        /* select-conformance-level */
+        TERM_CMD_DECSCP,                        /* select-communication-port */
+        TERM_CMD_DECSCPP,                       /* select-columns-per-page */
+        TERM_CMD_DECSCS,                        /* select-communication-speed */
+        TERM_CMD_DECSCUSR,                      /* set-cursor-style */
+        TERM_CMD_DECSDDT,                       /* select-disconnect-delay-time */
+        TERM_CMD_DECSDPT,                       /* select-digital-printed-data-type */
+        TERM_CMD_DECSED,                        /* selective-erase-in-display */
+        TERM_CMD_DECSEL,                        /* selective-erase-in-line */
+        TERM_CMD_DECSERA,                       /* selective-erase-rectangular-area */
+        TERM_CMD_DECSFC,                        /* select-flow-control */
+        TERM_CMD_DECSKCV,                       /* set-key-click-volume */
+        TERM_CMD_DECSLCK,                       /* set-lock-key-style */
+        TERM_CMD_DECSLE,                        /* select-locator-events */
+        TERM_CMD_DECSLPP,                       /* set-lines-per-page */
+        TERM_CMD_DECSLRM_OR_SC,                 /* set-left-and-right-margins or save-cursor */
+        TERM_CMD_DECSMBV,                       /* set-margin-bell-volume */
+        TERM_CMD_DECSMKR,                       /* select-modifier-key-reporting */
+        TERM_CMD_DECSNLS,                       /* set-lines-per-screen */
+        TERM_CMD_DECSPP,                        /* set-port-parameter */
+        TERM_CMD_DECSPPCS,                      /* select-pro-printer-character-set */
+        TERM_CMD_DECSPRTT,                      /* select-printer-type */
+        TERM_CMD_DECSR,                         /* secure-reset */
+        TERM_CMD_DECSRFR,                       /* select-refresh-rate */
+        TERM_CMD_DECSSCLS,                      /* set-scroll-speed */
+        TERM_CMD_DECSSDT,                       /* select-status-display-line-type */
+        TERM_CMD_DECSSL,                        /* select-setup-language */
+        TERM_CMD_DECST8C,                       /* set-tab-at-every-8-columns */
+        TERM_CMD_DECSTBM,                       /* set-top-and-bottom-margins */
+        TERM_CMD_DECSTR,                        /* soft-terminal-reset */
+        TERM_CMD_DECSTRL,                       /* set-transmit-rate-limit */
+        TERM_CMD_DECSWBV,                       /* set-warning-bell-volume */
+        TERM_CMD_DECSWL,                        /* single-width-single-height-line */
+        TERM_CMD_DECTID,                        /* select-terminal-id */
+        TERM_CMD_DECTME,                        /* terminal-mode-emulation */
+        TERM_CMD_DECTST,                        /* invoke-confidence-test */
+        TERM_CMD_DL,                            /* delete-line */
+        TERM_CMD_DSR_ANSI,                      /* device-status-report-ansi */
+        TERM_CMD_DSR_DEC,                       /* device-status-report-dec */
+        TERM_CMD_ECH,                           /* erase-character */
+        TERM_CMD_ED,                            /* erase-in-display */
+        TERM_CMD_EL,                            /* erase-in-line */
+        TERM_CMD_ENQ,                           /* enquiry */
+        TERM_CMD_EPA,                           /* end-of-guarded-area */
+        TERM_CMD_FF,                            /* form-feed */
+        TERM_CMD_HPA,                           /* horizontal-position-absolute */
+        TERM_CMD_HPR,                           /* horizontal-position-relative */
+        TERM_CMD_HT,                            /* horizontal-tab */
+        TERM_CMD_HTS,                           /* horizontal-tab-set */
+        TERM_CMD_HVP,                           /* horizontal-and-vertical-position */
+        TERM_CMD_ICH,                           /* insert-character */
+        TERM_CMD_IL,                            /* insert-line */
+        TERM_CMD_IND,                           /* index */
+        TERM_CMD_LF,                            /* line-feed */
+        TERM_CMD_LS1R,                          /* locking-shift-1-right */
+        TERM_CMD_LS2,                           /* locking-shift-2 */
+        TERM_CMD_LS2R,                          /* locking-shift-2-right */
+        TERM_CMD_LS3,                           /* locking-shift-3 */
+        TERM_CMD_LS3R,                          /* locking-shift-3-right */
+        TERM_CMD_MC_ANSI,                       /* media-copy-ansi */
+        TERM_CMD_MC_DEC,                        /* media-copy-dec */
+        TERM_CMD_NEL,                           /* next-line */
+        TERM_CMD_NP,                            /* next-page */
+        TERM_CMD_NULL,                          /* null */
+        TERM_CMD_PP,                            /* preceding-page */
+        TERM_CMD_PPA,                           /* page-position-absolute */
+        TERM_CMD_PPB,                           /* page-position-backward */
+        TERM_CMD_PPR,                           /* page-position-relative */
+        TERM_CMD_RC,                            /* restore-cursor */
+        TERM_CMD_REP,                           /* repeat */
+        TERM_CMD_RI,                            /* reverse-index */
+        TERM_CMD_RIS,                           /* reset-to-initial-state */
+        TERM_CMD_RM_ANSI,                       /* reset-mode-ansi */
+        TERM_CMD_RM_DEC,                        /* reset-mode-dec */
+        TERM_CMD_S7C1T,                         /* set-7bit-c1-terminal */
+        TERM_CMD_S8C1T,                         /* set-8bit-c1-terminal */
+        TERM_CMD_SCS,                           /* select-character-set */
+        TERM_CMD_SD,                            /* scroll-down */
+        TERM_CMD_SGR,                           /* select-graphics-rendition */
+        TERM_CMD_SI,                            /* shift-in */
+        TERM_CMD_SM_ANSI,                       /* set-mode-ansi */
+        TERM_CMD_SM_DEC,                        /* set-mode-dec */
+        TERM_CMD_SO,                            /* shift-out */
+        TERM_CMD_SPA,                           /* start-of-protected-area */
+        TERM_CMD_SS2,                           /* single-shift-2 */
+        TERM_CMD_SS3,                           /* single-shift-3 */
+        TERM_CMD_ST,                            /* string-terminator */
+        TERM_CMD_SU,                            /* scroll-up */
+        TERM_CMD_SUB,                           /* substitute */
+        TERM_CMD_TBC,                           /* tab-clear */
+        TERM_CMD_VPA,                           /* vertical-line-position-absolute */
+        TERM_CMD_VPR,                           /* vertical-line-position-relative */
+        TERM_CMD_VT,                            /* vertical-tab */
         TERM_CMD_XTERM_CLLHP,                   /* xterm-cursor-lower-left-hp-bugfix */
         TERM_CMD_XTERM_IHMT,                    /* xterm-initiate-highlight-mouse-tracking*/
         TERM_CMD_XTERM_MLHP,                    /* xterm-memory-lock-hp-bugfix */
@@ -684,3 +686,97 @@ int term_parser_feed(term_parser *parser, const term_seq **seq_out, uint32_t raw
 
 #define _term_parser_free_ _cleanup_(term_parser_freep)
 DEFINE_TRIVIAL_CLEANUP_FUNC(term_parser*, term_parser_free);
+
+/*
+ * Screens
+ * A term_screen object represents the terminal-side of the communication. It
+ * connects the term-parser and term-pages and handles all required commands.
+ * All state is managed by it.
+ */
+
+enum {
+        TERM_FLAG_7BIT_MODE                     = (1U << 0),    /* 7bit mode (default: off) */
+        TERM_FLAG_HIDE_CURSOR                   = (1U << 1),    /* hide cursor caret (default: off) */
+        TERM_FLAG_INHIBIT_TPARM                 = (1U << 2),    /* do not send TPARM unrequested (default: off) */
+        TERM_FLAG_NEWLINE_MODE                  = (1U << 3),    /* perform carriage-return on line-feeds (default: off) */
+        TERM_FLAG_ORIGIN_MODE                   = (1U << 4),    /* in origin mode, the cursor is bound by the margins (default: off) */
+        TERM_FLAG_PENDING_WRAP                  = (1U << 5),    /* wrap-around is pending */
+        TERM_FLAG_AUTO_WRAP                     = (1U << 6),    /* auto-wrap mode causes line-wraps at line-ends (default: off) */
+        TERM_FLAG_KEYPAD_MODE                   = (1U << 7),    /* application-keypad mode (default: off) */
+        TERM_FLAG_CURSOR_KEYS                   = (1U << 8),    /* enable application cursor-keys (default: off) */
+};
+
+enum {
+        TERM_CONFORMANCE_LEVEL_VT52,
+        TERM_CONFORMANCE_LEVEL_VT100,
+        TERM_CONFORMANCE_LEVEL_VT400,
+        TERM_CONFORMANCE_LEVEL_CNT,
+};
+
+typedef int (*term_screen_write_fn) (term_screen *screen, void *userdata, const void *buf, size_t size);
+typedef int (*term_screen_cmd_fn) (term_screen *screen, void *userdata, unsigned int cmd, const term_seq *seq);
+
+struct term_screen {
+        unsigned long ref;
+        term_age_t age;
+
+        term_page *page;
+        term_page *page_main;
+        term_page *page_alt;
+        term_history *history;
+        term_history *history_main;
+
+        unsigned int n_tabs;
+        uint8_t *tabs;
+
+        term_utf8 utf8;
+        term_parser *parser;
+
+        term_screen_write_fn write_fn;
+        void *write_fn_data;
+        term_screen_cmd_fn cmd_fn;
+        void *cmd_fn_data;
+
+        unsigned int flags;
+        unsigned int conformance_level;
+        unsigned int cursor_x;
+        unsigned int cursor_y;
+        term_attr attr;
+        term_attr default_attr;
+
+        term_charset **gl;
+        term_charset **gr;
+        term_charset **glt;
+        term_charset **grt;
+        term_charset *g0;
+        term_charset *g1;
+        term_charset *g2;
+        term_charset *g3;
+
+        char *answerback;
+
+        struct {
+                unsigned int cursor_x;
+                unsigned int cursor_y;
+                term_attr attr;
+                term_charset **gl;
+                term_charset **gr;
+                term_charset **glt;
+                term_charset **grt;
+                unsigned int flags;
+        } saved;
+};
+
+int term_screen_new(term_screen **out, term_screen_write_fn write_fn, void *write_fn_data, term_screen_cmd_fn cmd_fn, void *cmd_fn_data);
+term_screen *term_screen_ref(term_screen *screen);
+term_screen *term_screen_unref(term_screen *screen);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(term_screen*, term_screen_unref);
+
+int term_screen_feed_text(term_screen *screen, const uint8_t *in, size_t size);
+int term_screen_feed_keyboard(term_screen *screen, uint32_t keysym, uint32_t ascii, uint32_t ucs4, unsigned int mods);
+int term_screen_resize(term_screen *screen, unsigned int width, unsigned int height);
+void term_screen_soft_reset(term_screen *screen);
+void term_screen_hard_reset(term_screen *screen);
+
+int term_screen_set_answerback(term_screen *screen, const char *answerback);
diff --git a/src/libsystemd-terminal/term-screen.c b/src/libsystemd-terminal/term-screen.c
new file mode 100644
index 0000000..a19c684
--- /dev/null
+++ b/src/libsystemd-terminal/term-screen.c
@@ -0,0 +1,3882 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright (C) 2014 David Herrmann <dh.herrmann at 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/>.
+***/
+
+/*
+ * Terminal Screens
+ * The term_screen layer implements the terminal-side. It handles all commands
+ * returned by the seq-parser and applies them to its own pages.
+ *
+ * While there are a lot of legacy control-sequences, we only support a small
+ * subset. There is no reason to implement unused codes like horizontal
+ * scrolling.
+ * If you implement new commands, make sure to document them properly.
+ *
+ * Standards:
+ *   ECMA-48
+ *   ANSI X3.64
+ *   ISO/IEC 6429
+ * References:
+ *   http://www.vt100.net/emu/ctrlseq_dec.html
+ *   http://www.vt100.net/docs/vt100-ug/chapter3.html
+ *   http://www.vt100.net/docs/vt510-rm/chapter4
+ *   http://www.vt100.net/docs/vt510-rm/contents
+ *   http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
+ *   ASCII
+ *   http://en.wikipedia.org/wiki/C0_and_C1_control_codes
+ *   https://en.wikipedia.org/wiki/ANSI_color
+ */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include "macro.h"
+#include "term-internal.h"
+#include "util.h"
+
+int term_screen_new(term_screen **out, term_screen_write_fn write_fn, void *write_fn_data, term_screen_cmd_fn cmd_fn, void *cmd_fn_data) {
+        _cleanup_(term_screen_unrefp) term_screen *screen = NULL;
+        int r;
+
+        assert_return(out, -EINVAL);
+
+        screen = new0(term_screen, 1);
+        if (!screen)
+                return -ENOMEM;
+
+        screen->ref = 1;
+        screen->age = 1;
+        screen->write_fn = write_fn;
+        screen->write_fn_data = write_fn_data;
+        screen->cmd_fn = cmd_fn;
+        screen->cmd_fn_data = cmd_fn_data;
+        screen->flags = TERM_FLAG_7BIT_MODE;
+        screen->conformance_level = TERM_CONFORMANCE_LEVEL_VT400;
+        screen->gl = &screen->g0;
+        screen->gr = &screen->g1;
+        screen->g0 = &term_unicode_lower;
+        screen->g1 = &term_unicode_upper;
+        screen->g2 = &term_unicode_lower;
+        screen->g3 = &term_unicode_upper;
+
+        screen->saved.cursor_x = 0;
+        screen->saved.cursor_y = 0;
+        screen->saved.attr = screen->attr;
+        screen->saved.gl = screen->gl;
+        screen->saved.gr = screen->gr;
+
+        r = term_page_new(&screen->page_main);
+        if (r < 0)
+                return r;
+
+        r = term_page_new(&screen->page_alt);
+        if (r < 0)
+                return r;
+
+        r = term_parser_new(&screen->parser, false);
+        if (r < 0)
+                return r;
+
+        r = term_history_new(&screen->history_main);
+        if (r < 0)
+                return r;
+
+        screen->page = screen->page_main;
+        screen->history = screen->history_main;
+
+        *out = screen;
+        screen = NULL;
+        return 0;
+}
+
+term_screen *term_screen_ref(term_screen *screen) {
+        if (!screen)
+                return NULL;
+
+        assert_return(screen->ref > 0, NULL);
+
+        ++screen->ref;
+        return screen;
+}
+
+term_screen *term_screen_unref(term_screen *screen) {
+        if (!screen)
+                return NULL;
+
+        assert_return(screen->ref > 0, NULL);
+
+        if (--screen->ref)
+                return NULL;
+
+        free(screen->answerback);
+        free(screen->tabs);
+        term_history_free(screen->history_main);
+        term_page_free(screen->page_alt);
+        term_page_free(screen->page_main);
+        term_parser_free(screen->parser);
+        free(screen);
+
+        return NULL;
+}
+
+/*
+ * Write-Helpers
+ * Unfortunately, 7bit/8bit compat mode requires us to send C1 controls encoded
+ * as 7bit if asked by the application. This is really used in the wild, so we
+ * cannot fall back to "always 7bit".
+ * screen_write() is the underlying backend which forwards any writes to the
+ * users's callback. It's the users responsibility to buffer these and write
+ * them out once their call to term_screen_feed_*() returns.
+ * The SEQ_WRITE() and SEQ_WRITE_KEY() macros allow constructing C0/C1 sequences
+ * directly in the code-base without requiring any intermediate buffer during
+ * runtime.
+ */
+
+#define C0_CSI "\e["
+#define C1_CSI "\x9b"
+
+#define SEQ(_screen, _prefix_esc, _c0, _c1, _seq) \
+                (((_screen)->flags & TERM_FLAG_7BIT_MODE) ? \
+                        ((_prefix_esc) ? ("\e" _c0 _seq) : (_c0 _seq)) : \
+                        ((_prefix_esc) ? ("\e" _c1 _seq) : (_c1 _seq)))
+
+#define SEQ_SIZE(_screen, _prefix_esc, _c0, _c1, _seq) \
+                (((_screen)->flags & TERM_FLAG_7BIT_MODE) ? \
+                        ((_prefix_esc) ? sizeof("\e" _c0 _seq) : sizeof(_c0 _seq)) : \
+                        ((_prefix_esc) ? sizeof("\e" _c1 _seq) : sizeof(_c1 _seq)))
+
+#define SEQ_WRITE_KEY(_screen, _prefix_esc, _c0, _c1, _seq) \
+                screen_write((_screen), \
+                             SEQ((_screen), (_prefix_esc), \
+                                 _c0, _c1, _seq), \
+                             SEQ_SIZE((_screen), (_prefix_esc), \
+                                     _c0, _c1, _seq) - 1)
+
+#define SEQ_WRITE(_screen, _c0, _c1, _seq) \
+                SEQ_WRITE_KEY((_screen), false, _c0, _c1, _seq)
+
+static int screen_write(term_screen *screen, const void *buf, size_t len) {
+        if (len < 1 || !screen->write_fn)
+                return 0;
+
+        return screen->write_fn(screen, screen->write_fn_data, buf, len);
+}
+
+/*
+ * Command Forwarding
+ * Some commands cannot be handled by the screen-layer directly. Those are
+ * forwarded to the command-handler of the caller. This is rarely used and can
+ * safely be set to NULL.
+ */
+
+static int screen_forward(term_screen *screen, unsigned int cmd, const term_seq *seq) {
+        if (!screen->cmd_fn)
+                return 0;
+
+        return screen->cmd_fn(screen, screen->cmd_fn_data, cmd, seq);
+}
+
+/*
+ * Screen Helpers
+ * These helpers implement common-operations like cursor-handler and more, which
+ * are used by several command dispatchers.
+ */
+
+static unsigned int screen_clamp_x(term_screen *screen, unsigned int x) {
+        if (x >= screen->page->width)
+                return (screen->page->width > 0) ? screen->page->width - 1 : 0;
+
+        return x;
+}
+
+static unsigned int screen_clamp_y(term_screen *screen, unsigned int y) {
+        if (y >= screen->page->height)
+                return (screen->page->height > 0) ? screen->page->height - 1 : 0;
+
+        return y;
+}
+
+static bool screen_tab_is_set(term_screen *screen, unsigned int pos) {
+        if (pos >= screen->page->width)
+                return false;
+
+        return screen->tabs[pos / 8] & (1 << (pos % 8));
+}
+
+static inline void screen_age_cursor(term_screen *screen) {
+        term_cell *cell;
+
+        cell = term_page_get_cell(screen->page, screen->cursor_x, screen->cursor_y);
+        if (cell)
+                cell->age = screen->age;
+}
+
+static void screen_cursor_clear_wrap(term_screen *screen) {
+        screen->flags &= ~TERM_FLAG_PENDING_WRAP;
+}
+
+static void screen_cursor_set(term_screen *screen, unsigned int x, unsigned int y) {
+        x = screen_clamp_x(screen, x);
+        y = screen_clamp_y(screen, y);
+
+        if (x == screen->cursor_x && y == screen->cursor_y)
+                return;
+
+        if (!(screen->flags & TERM_FLAG_HIDE_CURSOR))
+                screen_age_cursor(screen);
+
+        screen->cursor_x = x;
+        screen->cursor_y = y;
+
+        if (!(screen->flags & TERM_FLAG_HIDE_CURSOR))
+                screen_age_cursor(screen);
+}
+
+static void screen_cursor_set_rel(term_screen *screen, unsigned int x, unsigned int y) {
+        if (screen->flags & TERM_FLAG_ORIGIN_MODE) {
+                x = screen_clamp_x(screen, x);
+                y = screen_clamp_x(screen, y) + screen->page->scroll_idx;
+
+                if (y >= screen->page->scroll_idx + screen->page->scroll_num) {
+                        y = screen->page->scroll_idx + screen->page->scroll_num;
+                        if (screen->page->scroll_num > 0)
+                                y -= 1;
+                }
+        }
+
+        screen_cursor_set(screen, x, y);
+}
+
+static void screen_cursor_left(term_screen *screen, unsigned int num) {
+        if (num > screen->cursor_x)
+                num = screen->cursor_x;
+
+        screen_cursor_set(screen, screen->cursor_x - num, screen->cursor_y);
+}
+
+static void screen_cursor_left_tab(term_screen *screen, unsigned int num) {
+        unsigned int i;
+
+        i = screen->cursor_x;
+        while (i > 0 && num > 0) {
+                if (screen_tab_is_set(screen, --i))
+                        --num;
+        }
+
+        screen_cursor_set(screen, i, screen->cursor_y);
+}
+
+static void screen_cursor_right(term_screen *screen, unsigned int num) {
+        if (num > screen->page->width)
+                num = screen->page->width;
+
+        screen_cursor_set(screen, screen->cursor_x + num, screen->cursor_y);
+}
+
+static void screen_cursor_right_tab(term_screen *screen, unsigned int num) {
+        unsigned int i;
+
+        i = screen->cursor_x;
+        while (i + 1 < screen->page->width && num > 0) {
+                if (screen_tab_is_set(screen, ++i))
+                        --num;
+        }
+
+        screen_cursor_set(screen, i, screen->cursor_y);
+}
+
+static void screen_cursor_up(term_screen *screen, unsigned int num, bool scroll) {
+        unsigned int max;
+
+        if (screen->cursor_y < screen->page->scroll_idx) {
+                if (num > screen->cursor_y)
+                        num = screen->cursor_y;
+
+                screen_cursor_set(screen, screen->cursor_x, screen->cursor_y - num);
+        } else {
+                max = screen->cursor_y - screen->page->scroll_idx;
+                if (num > max) {
+                        if (num < 1)
+                                return;
+
+                        if (!(screen->flags & TERM_FLAG_HIDE_CURSOR))
+                                screen_age_cursor(screen);
+
+                        if (scroll)
+                                term_page_scroll_down(screen->page, num - max, &screen->attr, screen->age, NULL);
+
+                        screen->cursor_y = screen->page->scroll_idx;
+
+                        if (!(screen->flags & TERM_FLAG_HIDE_CURSOR))
+                                screen_age_cursor(screen);
+                } else {
+                        screen_cursor_set(screen, screen->cursor_x, screen->cursor_y - num);
+                }
+        }
+}
+
+static void screen_cursor_down(term_screen *screen, unsigned int num, bool scroll) {
+        unsigned int max;
+
+        if (screen->cursor_y >= screen->page->scroll_idx + screen->page->scroll_num) {
+                if (num > screen->page->height)
+                        num = screen->page->height;
+
+                screen_cursor_set(screen, screen->cursor_x, screen->cursor_y - num);
+        } else {
+                max = screen->page->scroll_idx + screen->page->scroll_num - 1 - screen->cursor_y;
+                if (num > max) {
+                        if (num < 1)
+                                return;
+
+                        if (!(screen->flags & TERM_FLAG_HIDE_CURSOR))
+                                screen_age_cursor(screen);
+
+                        if (scroll)
+                                term_page_scroll_up(screen->page, num - max, &screen->attr, screen->age, screen->history);
+
+                        screen->cursor_y = screen->page->scroll_idx + screen->page->scroll_num - 1;
+
+                        if (!(screen->flags & TERM_FLAG_HIDE_CURSOR))
+                                screen_age_cursor(screen);
+                } else {
+                        screen_cursor_set(screen, screen->cursor_x, screen->cursor_y + num);
+                }
+        }
+}
+
+static inline void set_reset(term_screen *screen, unsigned int flag, bool set) {
+        if (set)
+                screen->flags |= flag;
+        else
+                screen->flags &= ~flag;
+}
+
+static void screen_mode_change(term_screen *screen, unsigned int mode, bool dec, bool set) {
+        switch (mode) {
+        case 1:
+                if (dec) {
+                        /*
+                         * DECCKM: cursor-keys
+                         * TODO
+                         */
+                        set_reset(screen, TERM_FLAG_CURSOR_KEYS, set);
+                }
+
+                break;
+        case 6:
+                if (dec) {
+                        /*
+                         * DECOM: origin-mode
+                         * TODO
+                         */
+                        set_reset(screen, TERM_FLAG_ORIGIN_MODE, set);
+                }
+
+                break;
+        case 7:
+                if (dec) {
+                        /*
+                         * DECAWN: auto-wrap mode
+                         * TODO
+                         */
+                        set_reset(screen, TERM_FLAG_AUTO_WRAP, set);
+                }
+
+                break;
+        case 20:
+                if (!dec) {
+                        /*
+                         * LNM: line-feed/new-line mode
+                         * TODO
+                         */
+                        set_reset(screen, TERM_FLAG_NEWLINE_MODE, set);
+                }
+
+                break;
+        case 25:
+                if (dec) {
+                        /*
+                         * DECTCEM: text-cursor-enable
+                         * TODO
+                         */
+                        set_reset(screen, TERM_FLAG_HIDE_CURSOR, !set);
+                }
+
+                break;
+        }
+}
+
+/* map a character according to current GL and GR maps */
+static uint32_t screen_map(term_screen *screen, uint32_t val) {
+        uint32_t nval = -1U;
+
+        /* 32 and 127 always map to identity. 160 and 255 map to identity iff a
+         * 96 character set is loaded into GR. Values above 255 always map to
+         * identity. */
+        switch (val) {
+        case 33 ... 126:
+                if (screen->glt) {
+                        nval = (**screen->glt)[val - 32];
+                        screen->glt = NULL;
+                } else {
+                        nval = (**screen->gl)[val - 32];
+                }
+                break;
+        case 160 ... 255:
+                if (screen->grt) {
+                        nval = (**screen->grt)[val - 160];
+                        screen->grt = NULL;
+                } else {
+                        nval = (**screen->gr)[val - 160];
+                }
+                break;
+        }
+
+        return (nval == -1U) ? val : nval;
+}
+
+/*
+ * Command Handlers
+ * This is the inofficial documentation of all the TERM_CMD_* definitions. Each
+ * handled command has a separate function with an extensive comment on the
+ * semantics of the command.
+ * Note that many semantics are unknown and need to be verified. This is mostly
+ * about error-handling, though. Applications rarely rely on those features.
+ */
+
+static int screen_DA1(term_screen *screen, const term_seq *seq);
+static int screen_LF(term_screen *screen, const term_seq *seq);
+
+static int screen_GRAPHIC(term_screen *screen, const term_seq *seq) {
+        term_char_t ch = TERM_CHAR_NULL;
+        uint32_t c;
+
+        if (screen->cursor_x + 1 == screen->page->width
+            && screen->flags & TERM_FLAG_PENDING_WRAP
+            && screen->flags & TERM_FLAG_AUTO_WRAP) {
+                screen_cursor_down(screen, 1, true);
+                screen_cursor_set(screen, 0, screen->cursor_y);
+        }
+
+        screen_cursor_clear_wrap(screen);
+
+        c = screen_map(screen, seq->terminator);
+        ch = term_char_merge(ch, screen_map(screen, c));
+        term_page_write(screen->page, screen->cursor_x, screen->cursor_y, ch, 1, &screen->attr, screen->age, false);
+
+        if (screen->cursor_x + 1 == screen->page->width)
+                screen->flags |= TERM_FLAG_PENDING_WRAP;
+        else
+                screen_cursor_right(screen, 1);
+
+        return 0;
+}
+
+static int screen_BEL(term_screen *screen, const term_seq *seq) {
+        /*
+         * BEL - sound bell tone
+         * This command should trigger an acoustic bell. Usually, this is
+         * forwarded directly to the pcspkr. However, bells have become quite
+         * uncommon and annoying, so we're not implementing them here. Instead,
+         * it's one of the commands we forward to the caller.
+         */
+
+        return screen_forward(screen, TERM_CMD_BEL, seq);
+}
+
+static int screen_BS(term_screen *screen, const term_seq *seq) {
+        /*
+         * BS - backspace
+         * Move cursor one cell to the left. If already at the left margin,
+         * nothing happens.
+         */
+
+        screen_cursor_clear_wrap(screen);
+        screen_cursor_left(screen, 1);
+        return 0;
+}
+
+static int screen_CBT(term_screen *screen, const term_seq *seq) {
+        /*
+         * CBT - cursor-backward-tabulation
+         * Move the cursor @args[0] tabs backwards (to the left). The
+         * current cursor cell, in case it's a tab, is not counted.
+         * Furthermore, the cursor cannot be moved beyond position 0 and
+         * it will stop there.
+         *
+         * Defaults:
+         *   args[0]: 1
+         */
+
+        unsigned int num = 1;
+
+        if (seq->args[0] > 0)
+                num = seq->args[0];
+
+        screen_cursor_clear_wrap(screen);
+        screen_cursor_left_tab(screen, num);
+
+        return 0;
+}
+
+static int screen_CHA(term_screen *screen, const term_seq *seq) {
+        /*
+         * CHA - cursor-horizontal-absolute
+         * Move the cursor to position @args[0] in the current line. The
+         * cursor cannot be moved beyond the rightmost cell and will stop
+         * there.
+         *
+         * Defaults:
+         *   args[0]: 1
+         */
+
+        unsigned int pos = 1;
+
+        if (seq->args[0] > 0)
+                pos = seq->args[0];
+
+        screen_cursor_clear_wrap(screen);
+        screen_cursor_set(screen, pos - 1, screen->cursor_y);
+
+        return 0;
+}
+
+static int screen_CHT(term_screen *screen, const term_seq *seq) {
+        /*
+         * CHT - cursor-horizontal-forward-tabulation
+         * Move the cursor @args[0] tabs forward (to the right). The
+         * current cursor cell, in case it's a tab, is not counted.
+         * Furthermore, the cursor cannot be moved beyond the rightmost cell
+         * and will stop there.
+         *
+         * Defaults:
+         *   args[0]: 1
+         */
+
+        unsigned int num = 1;
+
+        if (seq->args[0] > 0)
+                num = seq->args[0];
+
+        screen_cursor_clear_wrap(screen);
+        screen_cursor_right_tab(screen, num);
+
+        return 0;
+}
+
+static int screen_CNL(term_screen *screen, const term_seq *seq) {
+        /*
+         * CNL - cursor-next-line
+         * Move the cursor @args[0] lines down.
+         *
+         * TODO: Does this stop at the bottom or cause a scroll-up?
+         *
+         * Defaults:
+         *   args[0]: 1
+         */
+
+        unsigned int num = 1;
+
+        if (seq->args[0] > 0)
+                num = seq->args[0];
+
+        screen_cursor_clear_wrap(screen);
+        screen_cursor_down(screen, num, false);
+
+        return 0;
+}
+
+static int screen_CPL(term_screen *screen, const term_seq *seq) {
+        /*
+         * CPL - cursor-preceding-line
+         * Move the cursor @args[0] lines up.
+         *
+         * TODO: Does this stop at the top or cause a scroll-up?
+         *
+         * Defaults:
+         *   args[0]: 1
+         */
+
+        unsigned int num = 1;
+
+        if (seq->args[0] > 0)
+                num = seq->args[0];
+
+        screen_cursor_clear_wrap(screen);
+        screen_cursor_up(screen, num, false);
+
+        return 0;
+}
+
+static int screen_CR(term_screen *screen, const term_seq *seq) {
+        /*
+         * CR - carriage-return
+         * Move the cursor to the left margin on the current line.
+         */
+
+        screen_cursor_clear_wrap(screen);
+        screen_cursor_set(screen, 0, screen->cursor_y);
+
+        return 0;
+}
+
+static int screen_CUB(term_screen *screen, const term_seq *seq) {
+        /*
+         * CUB - cursor-backward
+         * Move the cursor @args[0] positions to the left. The cursor stops
+         * at the left-most position.
+         *
+         * Defaults:
+         *   args[0]: 1
+         */
+
+        unsigned int num = 1;
+
+        if (seq->args[0] > 0)
+                num = seq->args[0];
+
+        screen_cursor_clear_wrap(screen);
+        screen_cursor_left(screen, num);
+
+        return 0;
+}
+
+static int screen_CUD(term_screen *screen, const term_seq *seq) {
+        /*
+         * CUD - cursor-down
+         * Move the cursor @args[0] positions down. The cursor stops at the
+         * bottom margin. If it was already moved further, it stops at the
+         * bottom line.
+         *
+         * Defaults:
+         *   args[0]: 1
+         */
+
+        unsigned int num = 1;
+
+        if (seq->args[0] > 0)
+                num = seq->args[0];
+
+        screen_cursor_clear_wrap(screen);
+        screen_cursor_down(screen, num, false);
+
+        return 0;
+}
+
+static int screen_CUF(term_screen *screen, const term_seq *seq) {
+        /*
+         * CUF -cursor-forward
+         * Move the cursor @args[0] positions to the right. The cursor stops
+         * at the right-most position.
+         *
+         * Defaults:
+         *   args[0]: 1
+         */
+
+        unsigned int num = 1;
+
+        if (seq->args[0] > 0)
+                num = seq->args[0];
+
+        screen_cursor_clear_wrap(screen);
+        screen_cursor_right(screen, num);
+
+        return 0;
+}
+
+static int screen_CUP(term_screen *screen, const term_seq *seq) {
+        /*
+         * CUP - cursor-position
+         * Moves the cursor to position @args[1] x @args[0]. If either is 0, it
+         * is treated as 1. The positions are subject to the origin-mode and
+         * clamped to the addressable with/height.
+         *
+         * Defaults:
+         *   args[0]: 1
+         *   args[1]: 1
+         */
+
+        unsigned int x = 1, y = 1;
+
+        if (seq->args[0] > 0)
+                y = seq->args[0];
+        if (seq->args[1] > 0)
+                x = seq->args[1];
+
+        screen_cursor_clear_wrap(screen);
+        screen_cursor_set_rel(screen, x - 1, y - 1);
+
+        return 0;
+}
+
+static int screen_CUU(term_screen *screen, const term_seq *seq) {
+        /*
+         * CUU - cursor-up
+         * Move the cursor @args[0] positions up. The cursor stops at the
+         * top margin. If it was already moved further, it stops at the
+         * top line.
+         *
+         * Defaults:
+         *   args[0]: 1
+         *
+         */
+
+        unsigned int num = 1;
+
+        if (seq->args[0] > 0)
+                num = seq->args[0];
+
+        screen_cursor_clear_wrap(screen);
+        screen_cursor_up(screen, num, false);
+
+        return 0;
+}
+
+static int screen_DA1(term_screen *screen, const term_seq *seq) {
+        /*
+         * DA1 - primary-device-attributes
+         * The primary DA asks for basic terminal features. We simply return
+         * a hard-coded list of features we implement.
+         * Note that the primary DA asks for supported features, not currently
+         * enabled features.
+         *
+         * The terminal's answer is:
+         *   ^[ ? 64 ; ARGS c
+         * The first argument, 64, is fixed and denotes a VT420, the last
+         * DEC-term that extended this number.
+         * All following arguments denote supported features. Note
+         * that at most 15 features can be sent (max CSI args). It is safe to
+         * send more, but clients might not be able to parse them. This is a
+         * client's problem and we shouldn't care. There is no other way to
+         * send those feature lists, so we have to extend them beyond 15 in
+         * those cases.
+         *
+         * Known modes:
+         *    1: 132 column mode
+         *       The 132 column mode is supported by the terminal.
+         *    2: printer port
+         *       A priner-port is supported and can be addressed via
+         *       control-codes.
+         *    3: ReGIS graphics
+         *       Support for ReGIS graphics is available. The ReGIS routines
+         *       provide the "remote graphics instruction set" and allow basic
+         *       vector-rendering.
+         *    4: sixel
+         *       Support of Sixel graphics is available. This provides access
+         *       to the sixel bitmap routines.
+         *    6: selective erase
+         *       The terminal supports DECSCA and related selective-erase
+         *       functions. This allows to protect specific cells from being
+         *       erased, if specified.
+         *    7: soft character set (DRCS)
+         *       TODO: ?
+         *    8: user-defined keys (UDKs)
+         *       TODO: ?
+         *    9: national-replacement character sets (NRCS)
+         *       National-replacement character-sets are available.
+         *   12: Yugoslavian (SCS)
+         *       TODO: ?
+         *   15: technical character set
+         *       The DEC technical-character-set is available.
+         *   18: windowing capability
+         *       TODO: ?
+         *   21: horizontal scrolling
+         *       TODO: ?
+         *   22: ANSII color
+         *       TODO: ?
+         *   23: Greek
+         *       TODO: ?
+         *   24: Turkish
+         *       TODO: ?
+         *   29: ANSI text locator
+         *       TODO: ?
+         *   42: ISO Latin-2 character set
+         *       TODO: ?
+         *   44: PCTerm
+         *       TODO: ?
+         *   45: soft keymap
+         *       TODO: ?
+         *   46: ASCII emulation
+         *       TODO: ?
+         */
+
+        return SEQ_WRITE(screen, C0_CSI, C1_CSI, "?64;1;6;9;15c");
+}
+
+static int screen_DA2(term_screen *screen, const term_seq *seq) {
+        /*
+         * DA2 - secondary-device-attributes
+         * The secondary DA asks for the terminal-ID, firmware versions and
+         * other non-primary attributes. All these values are
+         * informational-only and should not be used by the host to detect
+         * terminal features.
+         *
+         * The terminal's response is:
+         *   ^[ > 61 ; FIRMWARE ; KEYBOARD c
+         * whereas 65 is fixed for VT525 terminals, the last terminal-line that
+         * increased this number. FIRMWARE is the firmware
+         * version encoded as major/minor (20 == 2.0) and KEYBOARD is 0 for STD
+         * keyboard and 1 for PC keyboards.
+         *
+         * We replace the firmware-version with the systemd-version so clients
+         * can decode it again.
+         */
+
+        return SEQ_WRITE(screen, C0_CSI, C1_CSI, ">65;" PACKAGE_VERSION ";1c");
+}
+
+static int screen_DA3(term_screen *screen, const term_seq *seq) {
+        /*
+         * DA3 - tertiary-device-attributes
+         * The tertiary DA is used to query the terminal-ID.
+         *
+         * The terminal's response is:
+         *   ^P ! | XX AA BB CC ^\
+         * whereas all four parameters are hexadecimal-encoded pairs. XX
+         * denotes the manufacturing site, AA BB CC is the terminal's ID.
+         */
+
+        /* we do not support tertiary DAs */
+        return 0;
+}
+
+static int screen_DC1(term_screen *screen, const term_seq *seq) {
+        /*
+         * DC1 - device-control-1 or XON
+         * This clears any previous XOFF and resumes terminal-transmission.
+         */
+
+        /* we do not support XON */
+        return 0;
+}
+
+static int screen_DC3(term_screen *screen, const term_seq *seq) {
+        /*
+         * DC3 - device-control-3 or XOFF
+         * Stops terminal transmission. No further characters are sent until
+         * an XON is received.
+         */
+
+        /* we do not support XOFF */
+        return 0;
+}
+
+static int screen_DCH(term_screen *screen, const term_seq *seq) {
+        /*
+         * DCH - delete-character
+         * This deletes @argv[0] characters at the current cursor position. As
+         * characters are deleted, the remaining characters between the cursor
+         * and right margin move to the left. Character attributes move with the
+         * characters. The terminal adds blank spaces with no visual character
+         * attributes at the right margin. DCH has no effect outside the
+         * scrolling margins.
+         *
+         * Defaults:
+         *   args[0]: 1
+         */
+
+        unsigned int num = 1;
+
+        if (seq->args[0] > 0)
+                num = seq->args[0];
+
+        screen_cursor_clear_wrap(screen);
+        term_page_delete_cells(screen->page, screen->cursor_x, screen->cursor_y, num, &screen->attr, screen->age);
+
+        return 0;
+}
+
+static int screen_DECALN(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECALN - screen-alignment-pattern
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECANM(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECANM - ansi-mode
+         * Set the terminal into VT52 compatibility mode. Control sequences
+         * overlap with regular sequences so we have to detect them early before
+         * dispatching them.
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECBI(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECBI - back-index
+         * This control function moves the cursor backward one column. If the
+         * cursor is at the left margin, then all screen data within the margin
+         * moves one column to the right. The column that shifted past the right
+         * margin is lost.
+         * DECBI adds a new column at the left margin with no visual attributes.
+         * DECBI does not affect the margins. If the cursor is beyond the
+         * left-margin at the left border, then the terminal ignores DECBI.
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECCARA(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECCARA - change-attributes-in-rectangular-area
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECCRA(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECCRA - copy-rectangular-area
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECDC(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECDC - delete-column
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECDHL_BH(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECDHL_BH - double-width-double-height-line: bottom half
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECDHL_TH(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECDHL_TH - double-width-double-height-line: top half
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECDWL(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECDWL - double-width-single-height-line
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECEFR(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECEFR - enable-filter-rectangle
+         * Defines the coordinates of a filter rectangle (top, left, bottom,
+         * right as @args[0] to @args[3]) and activates it.
+         * Anytime the locator is detected outside of the filter rectangle, an
+         * outside rectangle event is generated and the rectangle is disabled.
+         * Filter rectangles are always treated as "one-shot" events. Any
+         * parameters that are omitted default to the current locator position.
+         * If all parameters are omitted, any locator motion will be reported.
+         * DECELR always cancels any prevous rectangle definition.
+         *
+         * The locator is usually associated with the mouse-cursor, but based
+         * on cells instead of pixels. See DECELR how to initialize and enable
+         * it. DECELR can also enable pixel-mode instead of cell-mode.
+         *
+         * TODO: implement
+         */
+
+        return 0;
+}
+
+static int screen_DECELF(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECELF - enable-local-functions
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECELR(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECELR - enable-locator-reporting
+         * This changes the locator-reporting mode. @args[0] specifies the mode
+         * to set, 0 disables locator-reporting, 1 enables it continously, 2
+         * enables it for a single report. @args[1] specifies the
+         * precision-mode. 0 and 2 set the reporting to cell-precision, 1 sets
+         * pixel-precision.
+         *
+         * Defaults:
+         *   args[0]: 0
+         *   args[1]: 0
+         *
+         * TODO: implement
+         */
+
+        return 0;
+}
+
+static int screen_DECERA(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECERA - erase-rectangular-area
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECFI(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECFI - forward-index
+         * This control function moves the cursor forward one column. If the
+         * cursor is at the right margin, then all screen data within the
+         * margins moves one column to the left. The column shifted past the
+         * left margin is lost.
+         * DECFI adds a new column at the right margin, with no visual
+         * attributes. DECFI does not affect margins. If the cursor is beyond
+         * the right margin at the border of the page when the terminal
+         * receives DECFI, then the terminal ignores DECFI.
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECFRA(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECFRA - fill-rectangular-area
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECIC(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECIC - insert-column
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECID(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECID - return-terminal-id
+         * This is an obsolete form of TERM_CMD_DA1.
+         */
+
+        return screen_DA1(screen, seq);
+}
+
+static int screen_DECINVM(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECINVM - invoke-macro
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECKBD(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECKBD - keyboard-language-selection
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECKPAM(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECKPAM - keypad-application-mode
+         * Enables the keypad-application mode. If enabled, the keypad sends
+         * special characters instead of the printed characters. This way,
+         * applications can detect whether a numeric key was pressed on the
+         * top-row or on the keypad.
+         * Default is keypad-numeric-mode.
+         */
+
+        screen->flags |= TERM_FLAG_KEYPAD_MODE;
+
+        return 0;
+}
+
+static int screen_DECKPNM(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECKPNM - keypad-numeric-mode
+         * This disables the keypad-application-mode (DECKPAM) and returns to
+         * the keypad-numeric-mode. Keypresses on the keypad generate the same
+         * sequences as corresponding keypresses on the main keyboard.
+         * Default is keypad-numeric-mode.
+         */
+
+        screen->flags &= ~TERM_FLAG_KEYPAD_MODE;
+
+        return 0;
+}
+
+static int screen_DECLFKC(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECLFKC - local-function-key-control
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECLL(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECLL - load-leds
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECLTOD(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECLTOD - load-time-of-day
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECPCTERM(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECPCTERM - pcterm-mode
+         * This enters/exits the PCTerm mode. Default mode is VT-mode. It can
+         * also select parameters for scancode/keycode mappings in SCO mode.
+         *
+         * Definitely not worth implementing. Lets kill PCTerm/SCO modes!
+         */
+
+        return 0;
+}
+
+static int screen_DECPKA(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECPKA - program-key-action
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECPKFMR(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECPKFMR - program-key-free-memory-report
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECRARA(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECRARA - reverse-attributes-in-rectangular-area
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECRC(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECRC - restore-cursor
+         * Restores the terminal to the state saved by the save cursor (DECSC)
+         * function. This includes more than just the cursor-position.
+         *
+         * If nothing was saved by DECSC, then DECRC performs the following
+         * actions:
+         *   * Moves the cursor to the home position (upper left of screen).
+         *   * Resets origin mode (DECOM).
+         *   * Turns all character attributes off (normal setting).
+         *   * Maps the ASCII character set into GL, and the DEC Supplemental
+         *     Graphic set into GR.
+         *
+         * The terminal maintains a separate DECSC buffer for the main display
+         * and the status line. This feature lets you save a separate operating
+         * state for the main display and the status line.
+         */
+
+        screen->attr = screen->saved.attr;
+        screen->gl = screen->saved.gl;
+        screen->gr = screen->saved.gr;
+        screen->glt = screen->saved.glt;
+        screen->grt = screen->saved.grt;
+        set_reset(screen, TERM_FLAG_AUTO_WRAP, screen->flags & TERM_FLAG_AUTO_WRAP);
+        set_reset(screen, TERM_FLAG_ORIGIN_MODE, screen->flags & TERM_FLAG_ORIGIN_MODE);
+        screen_cursor_set(screen, screen->saved.cursor_x, screen->saved.cursor_y);
+
+        return 0;
+}
+
+static int screen_DECREQTPARM(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECREQTPARM - request-terminal-parameters
+         * The sequence DECREPTPARM is sent by the terminal controller to notify
+         * the host of the status of selected terminal parameters. The status
+         * sequence may be sent when requested by the host or at the terminal's
+         * discretion. DECREPTPARM is sent upon receipt of a DECREQTPARM.
+         *
+         * If @args[0] is 0, this marks a request and the terminal is allowed
+         * to send DECREPTPARM messages without request. If it is 1, the same
+         * applies but the terminal should no longer send DECREPTPARM
+         * unrequested.
+         * 2 and 3 mark a report, but 3 is only used if the terminal answers as
+         * an explicit request with @args[0] == 1.
+         *
+         * The other arguments are ignored in requests, but have the following
+         * meaning in responses:
+         *   args[1]: 1=no-parity-set 4=parity-set-and-odd 5=parity-set-and-even
+         *   args[2]: 1=8bits-per-char 2=7bits-per-char
+         *   args[3]: transmission-speed
+         *   args[4]: receive-speed
+         *   args[5]: 1=bit-rate-multiplier-is-16
+         *   args[6]: This value communicates the four switch values in block 5
+         *            of SETUP B, which are only visible to the user when an STP
+         *            option is installed. These bits may be assigned for an STP
+         *            device. The four bits are a decimal-encoded binary number.
+         *            Value between 0-15.
+         *
+         * The transmission/receive speeds have mappings for number => bits/s
+         * which are quite weird. Examples are: 96->3600, 112->9600, 120->19200
+         *
+         * Defaults:
+         *   args[0]: 0
+         */
+
+        if (seq->n_args < 1 || seq->args[0] == 0) {
+                screen->flags &= ~TERM_FLAG_INHIBIT_TPARM;
+                return SEQ_WRITE(screen, C0_CSI, C1_CSI, "2;1;1;120;120;1;0x");
+        } else if (seq->args[0] == 1) {
+                screen->flags |= TERM_FLAG_INHIBIT_TPARM;
+                return SEQ_WRITE(screen, C0_CSI, C1_CSI, "3;1;1;120;120;1;0x");
+        } else {
+                return 0;
+        }
+}
+
+static int screen_DECRPKT(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECRPKT - report-key-type
+         * Response to DECRQKT, we can safely ignore it as we're the one sending
+         * it to the host.
+         */
+
+        return 0;
+}
+
+static int screen_DECRQCRA(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECRQCRA - request-checksum-of-rectangular-area
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECRQDE(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECRQDE - request-display-extent
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECRQKT(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECRQKT - request-key-type
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECRQLP(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECRQLP - request-locator-position
+         * See DECELR for locator-information.
+         *
+         * TODO: document and implement
+         */
+
+        return 0;
+}
+
+static int screen_DECRQM_ANSI(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECRQM_ANSI - request-mode-ansi
+         * The host sends this control function to find out if a particular mode
+         * is set or reset. The terminal responds with a report mode function.
+         * @args[0] contains the mode to query.
+         *
+         * Response is DECRPM with the first argument set to the mode that was
+         * queried, second argument is 0 if mode is invalid, 1 if mode is set,
+         * 2 if mode is not set (reset), 3 if mode is permanently set and 4 if
+         * mode is permanently not set (reset):
+         *   ANSI: ^[ MODE ; VALUE $ y
+         *   DEC:  ^[ ? MODE ; VALUE $ y
+         *
+         * TODO: implement
+         */
+
+        return 0;
+}
+
+static int screen_DECRQM_DEC(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECRQM_DEC - request-mode-dec
+         * Same as DECRQM_ANSI but for DEC modes.
+         *
+         * TODO: implement
+         */
+
+        return 0;
+}
+
+static int screen_DECRQPKFM(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECRQPKFM - request-program-key-free-memory
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECRQPSR(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECRQPSR - request-presentation-state-report
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECRQTSR(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECRQTSR - request-terminal-state-report
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECRQUPSS(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECRQUPSS - request-user-preferred-supplemental-set
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECSACE(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECSACE - select-attribute-change-extent
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECSASD(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECSASD - select-active-status-display
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECSC(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECSC - save-cursor
+         * Save cursor and terminal state so it can be restored later on.
+         * Saves the following items in the terminal's memory:
+         *   * Cursor position
+         *   * Character attributes set by the SGR command
+         *   * Character sets (G0, G1, G2, or G3) currently in GL and GR
+         *   * Wrap flag (autowrap or no autowrap)
+         *   * State of origin mode (DECOM)
+         *   * Selective erase attribute
+         *   * Any single shift 2 (SS2) or single shift 3 (SS3) functions sent
+         */
+
+        screen->saved.cursor_x = screen->cursor_x;
+        screen->saved.cursor_y = screen->cursor_y;
+        screen->saved.attr = screen->attr;
+        screen->saved.gl = screen->gl;
+        screen->saved.gr = screen->gr;
+        screen->saved.glt = screen->glt;
+        screen->saved.grt = screen->grt;
+        screen->saved.flags = screen->flags & (TERM_FLAG_AUTO_WRAP
+                                               | TERM_FLAG_ORIGIN_MODE);
+
+        return 0;
+}
+
+static int screen_DECSCA(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECSCA - select-character-protection-attribute
+         * Defines the characters that come after it as erasable or not erasable
+         * from the screen. The selective erase control functions (DECSED and
+         * DECSEL) can only erase characters defined as erasable.
+         *
+         * @args[0] specifies the new mode. 0 and 2 mark any following character
+         * as erasable, 1 marks it as not erasable.
+         *
+         * Defaults:
+         *   args[0]: 0
+         */
+
+        unsigned int mode = 0;
+
+        if (seq->args[0] > 0)
+                mode = seq->args[0];
+
+        switch (mode) {
+        case 0:
+        case 2:
+                screen->attr.protect = 0;
+                break;
+        case 1:
+                screen->attr.protect = 1;
+                break;
+        }
+
+        return 0;
+}
+
+static int screen_DECSCL(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECSCL - select-conformance-level
+         * Select the terminal's operating level. The factory default is
+         * level 4 (VT Level 4 mode, 7-bit controls).
+         * When you change the conformance level, the terminal performs a hard
+         * reset (RIS).
+         *
+         * @args[0] defines the conformance-level, valid values are:
+         *   61: Level 1 (VT100)
+         *   62: Level 2 (VT200)
+         *   63: Level 3 (VT300)
+         *   64: Level 4 (VT400)
+         * @args[1] defines the 8bit-mode, valid values are:
+         *    0: 8-bit controls
+         *    1: 7-bit controls
+         *    2: 8-bit controls (same as 0)
+         *
+         * If @args[0] is 61, then @args[1] is ignored and 7bit controls are
+         * enforced.
+         *
+         * Defaults:
+         *   args[0]: 64
+         *   args[1]: 0
+         */
+
+        unsigned int level = 64, bit = 0;
+
+        if (seq->n_args > 0) {
+                level = seq->args[0];
+                if (seq->n_args > 1)
+                        bit = seq->args[1];
+        }
+
+        term_screen_hard_reset(screen);
+
+        switch (level) {
+        case 61:
+                screen->conformance_level = TERM_CONFORMANCE_LEVEL_VT100;
+                screen->flags |= TERM_FLAG_7BIT_MODE;
+                break;
+        case 62 ... 69:
+                screen->conformance_level = TERM_CONFORMANCE_LEVEL_VT400;
+                if (bit == 1)
+                        screen->flags |= TERM_FLAG_7BIT_MODE;
+                else
+                        screen->flags &= ~TERM_FLAG_7BIT_MODE;
+                break;
+        }
+
+        return 0;
+}
+
+static int screen_DECSCP(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECSCP - select-communication-port
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECSCPP(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECSCPP - select-columns-per-page
+         * Select columns per page. The number of rows is unaffected by this.
+         * @args[0] selectes the number of columns (width), DEC only defines 80
+         * and 132, but we allow any integer here. 0 is equivalent to 80.
+         * Page content is *not* cleared and the cursor is left untouched.
+         * However, if the page is reduced in width and the cursor would be
+         * outside the visible region, it's set to the right border. Newly added
+         * cells are cleared. No data is retained outside the visible region.
+         *
+         * Defaults:
+         *   args[0]: 0
+         *
+         * TODO: implement
+         */
+
+        return 0;
+}
+
+static int screen_DECSCS(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECSCS - select-communication-speed
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECSCUSR(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECSCUSR - set-cursor-style
+         * This changes the style of the cursor. @args[0] can be one of:
+         *   0, 1: blinking block
+         *      2: steady block
+         *      3: blinking underline
+         *      4: steady underline
+         * Changing this setting does _not_ affect the cursor visibility itself.
+         * Use DECTCEM for that.
+         *
+         * Defaults:
+         *   args[0]: 0
+         *
+         * TODO: implement
+         */
+
+        return 0;
+}
+
+static int screen_DECSDDT(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECSDDT - select-disconnect-delay-time
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECSDPT(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECSDPT - select-digital-printed-data-type
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECSED(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECSED - selective-erase-in-display
+         * This control function erases some or all of the erasable characters
+         * in the display. DECSED can only erase characters defined as erasable
+         * by the DECSCA control function. DECSED works inside or outside the
+         * scrolling margins.
+         *
+         * @args[0] defines which regions are erased. If it is 0, all cells from
+         * the cursor (inclusive) till the end of the display are erase. If it
+         * is 1, all cells from the start of the display till the cursor
+         * (inclusive) are erased. If it is 2, all cells are erased.
+         *
+         * Defaults:
+         *   args[0]: 0
+         */
+
+        unsigned int mode = 0;
+
+        if (seq->args[0] > 0)
+                mode = seq->args[0];
+
+        switch (mode) {
+        case 0:
+                term_page_erase(screen->page,
+                                screen->cursor_x, screen->cursor_y,
+                                screen->page->width, screen->page->height,
+                                &screen->attr, screen->age, true);
+                break;
+        case 1:
+                term_page_erase(screen->page,
+                                0, 0,
+                                screen->cursor_x, screen->cursor_y,
+                                &screen->attr, screen->age, true);
+                break;
+        case 2:
+                term_page_erase(screen->page,
+                                0, 0,
+                                screen->page->width, screen->page->height,
+                                &screen->attr, screen->age, true);
+                break;
+        }
+
+        return 0;
+}
+
+static int screen_DECSEL(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECSEL - selective-erase-in-line
+         * This control function erases some or all of the erasable characters
+         * in a single line of text. DECSEL erases only those characters defined
+         * as erasable by the DECSCA control function. DECSEL works inside or
+         * outside the scrolling margins.
+         *
+         * @args[0] defines the region to be erased. If it is 0, all cells from
+         * the cursor (inclusive) till the end of the line are erase. If it is
+         * 1, all cells from the start of the line till the cursor (inclusive)
+         * are erased. If it is 2, the whole line of the cursor is erased.
+         *
+         * Defaults:
+         *   args[0]: 0
+         */
+
+        unsigned int mode = 0;
+
+        if (seq->args[0] > 0)
+                mode = seq->args[0];
+
+        switch (mode) {
+        case 0:
+                term_page_erase(screen->page,
+                                screen->cursor_x, screen->cursor_y,
+                                screen->page->width, screen->cursor_y,
+                                &screen->attr, screen->age, true);
+                break;
+        case 1:
+                term_page_erase(screen->page,
+                                0, screen->cursor_y,
+                                screen->cursor_x, screen->cursor_y,
+                                &screen->attr, screen->age, true);
+                break;
+        case 2:
+                term_page_erase(screen->page,
+                                0, screen->cursor_y,
+                                screen->page->width, screen->cursor_y,
+                                &screen->attr, screen->age, true);
+                break;
+        }
+
+        return 0;
+}
+
+static int screen_DECSERA(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECSERA - selective-erase-rectangular-area
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECSFC(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECSFC - select-flow-control
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECSKCV(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECSKCV - set-key-click-volume
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECSLCK(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECSLCK - set-lock-key-style
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECSLE(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECSLE - select-locator-events
+         *
+         * TODO: implement
+         */
+
+        return 0;
+}
+
+static int screen_DECSLPP(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECSLPP - set-lines-per-page
+         * Set the number of lines used for the page. @args[0] specifies the
+         * number of lines to be used. DEC only allows a limited number of
+         * choices, however, we allow all integers. 0 is equivalent to 24.
+         *
+         * Defaults:
+         *   args[0]: 0
+         *
+         * TODO: implement
+         */
+
+        return 0;
+}
+
+static int screen_DECSLRM_OR_SC(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECSLRM_OR_SC - set-left-and-right-margins or save-cursor
+         *
+         * TODO: Detect save-cursor and run it. DECSLRM is not worth
+         *       implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECSMBV(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECSMBV - set-margin-bell-volume
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECSMKR(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECSMKR - select-modifier-key-reporting
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECSNLS(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECSNLS - set-lines-per-screen
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECSPP(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECSPP - set-port-parameter
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECSPPCS(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECSPPCS - select-pro-printer-character-set
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECSPRTT(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECSPRTT - select-printer-type
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECSR(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECSR - secure-reset
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECSRFR(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECSRFR - select-refresh-rate
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECSSCLS(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECSSCLS - set-scroll-speed
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECSSDT(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECSSDT - select-status-display-line-type
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECSSL(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECSSL - select-setup-language
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECST8C(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECST8C - set-tab-at-every-8-columns
+         * Clear the tab-ruler and reset it to a tab at every 8th column,
+         * starting at 9 (though, setting a tab at 1 is fine as it has no
+         * effect).
+         */
+
+        unsigned int i;
+
+        for (i = 0; i < screen->page->width; i += 8)
+                screen->tabs[i / 8] = 0x1;
+
+        return 0;
+}
+
+static int screen_DECSTBM(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECSTBM - set-top-and-bottom-margins
+         * This control function sets the top and bottom margins for the current
+         * page. You cannot perform scrolling outside the margins.
+         *
+         * @args[0] defines the top margin, @args[1] defines the bottom margin.
+         * The bottom margin must be lower than the top-margin.
+         *
+         * This call resets the cursor position to 0/0 of the page.
+         *
+         * Defaults:
+         *   args[0]: 1
+         *   args[1]: last page-line
+         */
+
+        unsigned int top, bottom;
+
+        top = 1;
+        bottom = screen->page->height;
+
+        if (seq->args[0] > 0)
+                top = seq->args[0];
+        if (seq->args[1] > 0)
+                bottom = seq->args[1];
+
+        if (top > screen->page->height)
+                top = screen->page->height;
+        if (bottom > screen->page->height)
+                bottom = screen->page->height;
+
+        if (top >= bottom || top > screen->page->height || bottom > screen->page->height) {
+                top = 1;
+                bottom = screen->page->height;
+        }
+
+        term_page_set_scroll_region(screen->page_main, top - 1, bottom - top + 1);
+        term_page_set_scroll_region(screen->page_alt, top - 1, bottom - top + 1);
+        screen_cursor_clear_wrap(screen);
+        screen_cursor_set(screen, 0, 0);
+
+        return 0;
+}
+
+static int screen_DECSTR(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECSTR - soft-terminal-reset
+         * Perform a soft reset to the default values.
+         */
+
+        term_screen_soft_reset(screen);
+
+        return 0;
+}
+
+static int screen_DECSTRL(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECSTRL - set-transmit-rate-limit
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECSWBV(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECSWBV - set-warning-bell-volume
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECSWL(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECSWL - single-width-single-height-line
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECTID(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECTID - select-terminal-id
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECTME(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECTME - terminal-mode-emulation
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DECTST(term_screen *screen, const term_seq *seq) {
+        /*
+         * DECTST - invoke-confidence-test
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_DL(term_screen *screen, const term_seq *seq) {
+        /*
+         * DL - delete-line
+         * This control function deletes one or more lines in the scrolling
+         * region, starting with the line that has the cursor. @args[0] defines
+         * the number of lines to delete. 0 is treated the same as 1.
+         * As lines are deleted, lines below the cursor and in the scrolling
+         * region move up. The terminal adds blank lines with no visual
+         * character attributes at the bottom of the scrolling region. If it is
+         * greater than the number of lines remaining on the page, DL deletes
+         * only the remaining lines. DL has no effect outside the scrolling
+         * margins.
+         *
+         * Defaults:
+         *   args[0]: 1
+         */
+
+        unsigned int num = 1;
+
+        if (seq->args[0] > 0)
+                num = seq->args[0];
+
+        term_page_delete_lines(screen->page, screen->cursor_y, num, &screen->attr, screen->age);
+
+        return 0;
+}
+
+static int screen_DSR_ANSI(term_screen *screen, const term_seq *seq) {
+        /*
+         * DSR_ANSI - device-status-report-ansi
+         *
+         * TODO: implement
+         */
+
+        return 0;
+}
+
+static int screen_DSR_DEC(term_screen *screen, const term_seq *seq) {
+        /*
+         * DSR_DEC - device-status-report-dec
+         *
+         * TODO: implement
+         */
+
+        return 0;
+}
+
+static int screen_ECH(term_screen *screen, const term_seq *seq) {
+        /*
+         * ECH - erase-character
+         * This control function erases one or more characters, from the cursor
+         * position to the right. ECH clears character attributes from erased
+         * character positions. ECH works inside or outside the scrolling
+         * margins.
+         * @args[0] defines the number of characters to erase. 0 is treated the
+         * same as 1.
+         *
+         * Defaults:
+         *   args[0]: 1
+         */
+
+        unsigned int num = 1;
+
+        if (seq->args[0] > 0)
+                num = seq->args[0];
+
+        term_page_erase(screen->page,
+                        screen->cursor_x, screen->cursor_y,
+                        screen->cursor_x + num, screen->cursor_y,
+                        &screen->attr, screen->age, false);
+
+        return 0;
+}
+
+static int screen_ED(term_screen *screen, const term_seq *seq) {
+        /*
+         * ED - erase-in-display
+         * This control function erases characters from part or all of the
+         * display. When you erase complete lines, they become single-height,
+         * single-width lines, with all visual character attributes cleared. ED
+         * works inside or outside the scrolling margins.
+         *
+         * @args[0] defines the region to erase. 0 means from cursor (inclusive)
+         * till the end of the screen. 1 means from the start of the screen till
+         * the cursor (inclusive) and 2 means the whole screen.
+         *
+         * Defaults:
+         *   args[0]: 0
+         */
+
+        unsigned int mode = 0;
+
+        if (seq->args[0] > 0)
+                mode = seq->args[0];
+
+        switch (mode) {
+        case 0:
+                term_page_erase(screen->page,
+                                screen->cursor_x, screen->cursor_y,
+                                screen->page->width, screen->page->height,
+                                &screen->attr, screen->age, false);
+                break;
+        case 1:
+                term_page_erase(screen->page,
+                                0, 0,
+                                screen->cursor_x, screen->cursor_y,
+                                &screen->attr, screen->age, false);
+                break;
+        case 2:
+                term_page_erase(screen->page,
+                                0, 0,
+                                screen->page->width, screen->page->height,
+                                &screen->attr, screen->age, false);
+                break;
+        }
+
+        return 0;
+}
+
+static int screen_EL(term_screen *screen, const term_seq *seq) {
+        /*
+         * EL - erase-in-line
+         * This control function erases characters on the line that has the
+         * cursor. EL clears all character attributes from erased character
+         * positions. EL works inside or outside the scrolling margins.
+         *
+         * @args[0] defines the region to erase. 0 means from cursor (inclusive)
+         * till the end of the line. 1 means from the start of the line till the
+         * cursor (inclusive) and 2 means the whole line.
+         *
+         * Defaults:
+         *   args[0]: 0
+         */
+
+        unsigned int mode = 0;
+
+        if (seq->args[0] > 0)
+                mode = seq->args[0];
+
+        switch (mode) {
+        case 0:
+                term_page_erase(screen->page,
+                                screen->cursor_x, screen->cursor_y,
+                                screen->page->width, screen->cursor_y,
+                                &screen->attr, screen->age, false);
+                break;
+        case 1:
+                term_page_erase(screen->page,
+                                0, screen->cursor_y,
+                                screen->cursor_x, screen->cursor_y,
+                                &screen->attr, screen->age, false);
+                break;
+        case 2:
+                term_page_erase(screen->page,
+                                0, screen->cursor_y,
+                                screen->page->width, screen->cursor_y,
+                                &screen->attr, screen->age, false);
+                break;
+        }
+
+        return 0;
+}
+
+static int screen_ENQ(term_screen *screen, const term_seq *seq) {
+        /*
+         * ENQ - enquiry
+         * Transmit the answerback-string. If none is set, do nothing.
+         */
+
+        if (screen->answerback)
+                return screen_write(screen, screen->answerback, strlen(screen->answerback));
+
+        return 0;
+}
+
+static int screen_EPA(term_screen *screen, const term_seq *seq) {
+        /*
+         * EPA - end-of-guarded-area
+         *
+         * TODO: What is this?
+         */
+
+        return 0;
+}
+
+static int screen_FF(term_screen *screen, const term_seq *seq) {
+        /*
+         * FF - form-feed
+         * This causes the cursor to jump to the next line. It is treated the
+         * same as LF.
+         */
+
+        return screen_LF(screen, seq);
+}
+
+static int screen_HPA(term_screen *screen, const term_seq *seq) {
+        /*
+         * HPA - horizontal-position-absolute
+         * HPA causes the active position to be moved to the n-th horizontal
+         * position of the active line. If an attempt is made to move the active
+         * position past the last position on the line, then the active position
+         * stops at the last position on the line.
+         *
+         * @args[0] defines the horizontal position. 0 is treated as 1.
+         *
+         * Defaults:
+         *   args[0]: 1
+         */
+
+        unsigned int num = 1;
+
+        if (seq->args[0] > 0)
+                num = seq->args[0];
+
+        screen_cursor_clear_wrap(screen);
+        screen_cursor_set(screen, num - 1, screen->cursor_y);
+
+        return 0;
+}
+
+static int screen_HPR(term_screen *screen, const term_seq *seq) {
+        /*
+         * HPR - horizontal-position-relative
+         * HPR causes the active position to be moved to the n-th following
+         * horizontal position of the active line. If an attempt is made to move
+         * the active position past the last position on the line, then the
+         * active position stops at the last position on the line.
+         *
+         * @args[0] defines the horizontal position. 0 is treated as 1.
+         *
+         * Defaults:
+         *   args[0]: 1
+         */
+
+        unsigned int num = 1;
+
+        if (seq->args[0] > 0)
+                num = seq->args[0];
+
+        screen_cursor_clear_wrap(screen);
+        screen_cursor_right(screen, num);
+
+        return 0;
+}
+
+static int screen_HT(term_screen *screen, const term_seq *seq) {
+        /*
+         * HT - horizontal-tab
+         * Moves the cursor to the next tab stop. If there are no more tab
+         * stops, the cursor moves to the right margin. HT does not cause text
+         * to auto wrap.
+         */
+
+        screen_cursor_clear_wrap(screen);
+        screen_cursor_right_tab(screen, 1);
+
+        return 0;
+}
+
+static int screen_HTS(term_screen *screen, const term_seq *seq) {
+        /*
+         * HTS - horizontal-tab-set
+         * HTS sets a horizontal tab stop at the column position indicated by
+         * the value of the active column when the terminal receives an HTS.
+         *
+         * Executing an HTS does not effect the other horizontal tab stop
+         * settings.
+         */
+
+        unsigned int pos;
+
+        pos = screen->cursor_x;
+        if (screen->page->width > 0)
+                screen->tabs[pos / 8] |= 1U << (pos % 8);
+
+        return 0;
+}
+
+static int screen_HVP(term_screen *screen, const term_seq *seq) {
+        /*
+         * HVP - horizontal-and-vertical-position
+         * This control function works the same as the cursor position (CUP)
+         * function. Origin mode (DECOM) selects line numbering and the ability
+         * to move the cursor into margins.
+         *
+         * Defaults:
+         *   args[0]: 1
+         *   args[1]: 1
+         */
+
+        return screen_CUP(screen, seq);
+}
+
+static int screen_ICH(term_screen *screen, const term_seq *seq) {
+        /*
+         * ICH - insert-character
+         * This control function inserts one or more space (SP) characters
+         * starting at the cursor position. @args[0] is the number of characters
+         * to insert. 0 is treated as 1.
+         *
+         * The ICH sequence inserts blank characters with the normal
+         * character attribute. The cursor remains at the beginning of the blank
+         * characters. Text between the cursor and right margin moves to the
+         * right. Characters scrolled past the right margin are lost. ICH has no
+         * effect outside the scrolling margins.
+         *
+         * Defaults:
+         *   args[0]: 1
+         */
+
+        unsigned int num = 1;
+
+        if (seq->args[0] > 0)
+                num = seq->args[0];
+
+        screen_cursor_clear_wrap(screen);
+        term_page_insert_cells(screen->page, screen->cursor_x, screen->cursor_y, num, &screen->attr, screen->age);
+
+        return 0;
+}
+
+static int screen_IL(term_screen *screen, const term_seq *seq) {
+        /*
+         * IL - insert-line
+         * This control function inserts one or more blank lines, starting at
+         * the cursor. @args[0] is the number of lines to insert. 0 is treated
+         * as 1.
+         *
+         * As lines are inserted, lines below the cursor and in the scrolling
+         * region move down. Lines scrolled off the page are lost. IL has no
+         * effect outside the page margins.
+         *
+         * Defaults:
+         *   args[0]: 1
+         */
+
+        unsigned int num = 1;
+
+        if (seq->args[0] > 0)
+                num = seq->args[0];
+
+        screen_cursor_clear_wrap(screen);
+        term_page_insert_lines(screen->page, screen->cursor_y, num, &screen->attr, screen->age);
+
+        return 0;
+}
+
+static int screen_IND(term_screen *screen, const term_seq *seq) {
+        /*
+         * IND - index
+         * IND moves the cursor down one line in the same column. If the cursor
+         * is at the bottom margin, then the screen performs a scroll-up.
+         */
+
+        screen_cursor_down(screen, 1, true);
+
+        return 0;
+}
+
+static int screen_LF(term_screen *screen, const term_seq *seq) {
+        /*
+         * LF - line-feed
+         * Causes a line feed or a new line operation, depending on the setting
+         * of line feed/new line mode.
+         */
+
+        screen_cursor_down(screen, 1, true);
+        if (screen->flags & TERM_FLAG_NEWLINE_MODE)
+                screen_cursor_left(screen, screen->cursor_x);
+
+        return 0;
+}
+
+static int screen_LS1R(term_screen *screen, const term_seq *seq) {
+        /*
+         * LS1R - locking-shift-1-right
+         * Map G1 into GR.
+         */
+
+        screen->gr = &screen->g1;
+
+        return 0;
+}
+
+static int screen_LS2(term_screen *screen, const term_seq *seq) {
+        /*
+         * LS2 - locking-shift-2
+         * Map G2 into GL.
+         */
+
+        screen->gl = &screen->g2;
+
+        return 0;
+}
+
+static int screen_LS2R(term_screen *screen, const term_seq *seq) {
+        /*
+         * LS2R - locking-shift-2-right
+         * Map G2 into GR.
+         */
+
+        screen->gr = &screen->g2;
+
+        return 0;
+}
+
+static int screen_LS3(term_screen *screen, const term_seq *seq) {
+        /*
+         * LS3 - locking-shift-3
+         * Map G3 into GL.
+         */
+
+        screen->gl = &screen->g3;
+
+        return 0;
+}
+
+static int screen_LS3R(term_screen *screen, const term_seq *seq) {
+        /*
+         * LS3R - locking-shift-3-right
+         * Map G3 into GR.
+         */
+
+        screen->gr = &screen->g3;
+
+        return 0;
+}
+
+static int screen_MC_ANSI(term_screen *screen, const term_seq *seq) {
+        /*
+         * MC_ANSI - media-copy-ansi
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_MC_DEC(term_screen *screen, const term_seq *seq) {
+        /*
+         * MC_DEC - media-copy-dec
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_NEL(term_screen *screen, const term_seq *seq) {
+        /*
+         * NEL - next-line
+         * Moves cursor to first position on next line. If cursor is at bottom
+         * margin, then screen performs a scroll-up.
+         */
+
+        screen_cursor_clear_wrap(screen);
+        screen_cursor_down(screen, 1, true);
+        screen_cursor_set(screen, 0, screen->cursor_y);
+
+        return 0;
+}
+
+static int screen_NP(term_screen *screen, const term_seq *seq) {
+        /*
+         * NP - next-page
+         * This control function moves the cursor forward to the home position
+         * on one of the following pages in page memory. If there is only one
+         * page, then the terminal ignores NP.
+         * If NP tries to move the cursor past the last page in memory, then the
+         * cursor stops at the last page.
+         *
+         * @args[0] defines the number of pages to forward. 0 is treated as 1.
+         *
+         * Defaults:
+         *   args[0]: 1
+         *
+         * Probably not worth implementing. We only support a single page.
+         */
+
+        return 0;
+}
+
+static int screen_NULL(term_screen *screen, const term_seq *seq) {
+        /*
+         * NULL - null
+         * The NULL operation does nothing. ASCII NULL is always ignored.
+         */
+
+        return 0;
+}
+
+static int screen_PP(term_screen *screen, const term_seq *seq) {
+        /*
+         * PP - preceding-page
+         * This control function moves the cursor backward to the home position
+         * on one of the preceding pages in page memory. If there is only one
+         * page, then the terminal ignores PP.
+         * If PP tries to move the cursor back farther than the first page in
+         * memory, then the cursor stops at the first page.
+         *
+         * @args[0] defines the number of pages to go backwards. 0 is treated
+         * as 1.
+         *
+         * Defaults:
+         *   args[0]: 1
+         *
+         * Probably not worth implementing. We only support a single page.
+         */
+
+        return 0;
+}
+
+static int screen_PPA(term_screen *screen, const term_seq *seq) {
+        /*
+         * PPA - page-position-absolute
+         * This control function can move the cursor to the corresponding row
+         * and column on any page in page memory. You select the page by its
+         * number. If there is only one page, then the terminal ignores PPA.
+         *
+         * @args[0] is the number of the page to move the cursor to. If it is
+         * greater than the number of the last page in memory, then the cursor
+         * stops at the last page. If it is less than the number of the first
+         * page, then the cursor stops at the first page.
+         *
+         * Defaults:
+         *   args[0]: 1
+         *
+         * Probably not worth implementing. We only support a single page.
+         */
+
+        return 0;
+}
+
+static int screen_PPB(term_screen *screen, const term_seq *seq) {
+        /*
+         * PPB - page-position-backward
+         * This control function moves the cursor backward to the corresponding
+         * row and column on one of the preceding pages in page memory. If there
+         * is only one page, then the terminal ignores PPB.
+         *
+         * @args[0] indicates the number of pages to move the cursor backward.
+         * If it tries to move the cursor back farther than the first page in
+         * memory, then the cursor stops at the first page. 0 is treated as 1.
+         *
+         * Defaults:
+         *   args[0]: 1
+         *
+         * Probably not worth implementing. We only support a single page.
+         */
+
+        return 0;
+}
+
+static int screen_PPR(term_screen *screen, const term_seq *seq) {
+        /*
+         * PPR - page-position-relative
+         * This control function moves the cursor forward to the corresponding
+         * row and column on one of the following pages in page memory. If there
+         * is only one page, then the terminal ignores PPR.
+         *
+         * @args[0] indicates how many pages to move the cursor forward. If it
+         * tries to move the cursor beyond the last page in memory, then the
+         * cursor stops at the last page. 0 is treated as 1.
+         *
+         * Defaults:
+         *   args[0]: 1
+         *
+         * Probably not worth implementing. We only support a single page.
+         */
+
+        return 0;
+}
+
+static int screen_RC(term_screen *screen, const term_seq *seq) {
+        /*
+         * RC - restore-cursor
+         */
+
+        return screen_DECRC(screen, seq);
+}
+
+static int screen_REP(term_screen *screen, const term_seq *seq) {
+        /*
+         * REP - repeat
+         * Repeat the preceding graphics-character the given number of times.
+         * @args[0] specifies how often it shall be repeated. 0 is treated as 1.
+         *
+         * Defaults:
+         *   args[0]: 1
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_RI(term_screen *screen, const term_seq *seq) {
+        /*
+         * RI - reverse-index
+         * Moves the cursor up one line in the same column. If the cursor is at
+         * the top margin, the page scrolls down.
+         */
+
+        screen_cursor_up(screen, 1, true);
+
+        return 0;
+}
+
+static int screen_RIS(term_screen *screen, const term_seq *seq) {
+        /*
+         * RIS - reset-to-initial-state
+         * This control function causes a nonvolatile memory (NVR) recall to
+         * occur. RIS replaces all set-up features with their saved settings.
+         *
+         * The terminal stores these saved settings in NVR memory. The saved
+         * setting for a feature is the same as the factory-default setting,
+         * unless you saved a new setting.
+         */
+
+        term_screen_hard_reset(screen);
+
+        return 0;
+}
+
+static int screen_RM_ANSI(term_screen *screen, const term_seq *seq) {
+        /*
+         * RM_ANSI - reset-mode-ansi
+         *
+         * TODO: implement (see VT510rm manual)
+         */
+
+        unsigned int i;
+
+        for (i = 0; i < seq->n_args; ++i)
+                screen_mode_change(screen, seq->args[i], false, false);
+
+        return 0;
+}
+
+static int screen_RM_DEC(term_screen *screen, const term_seq *seq) {
+        /*
+         * RM_DEC - reset-mode-dec
+         * This is the same as RM_ANSI but for DEC modes.
+         */
+
+        unsigned int i;
+
+        for (i = 0; i < seq->n_args; ++i)
+                screen_mode_change(screen, seq->args[i], true, false);
+
+        return 0;
+}
+
+static int screen_S7C1T(term_screen *screen, const term_seq *seq) {
+        /*
+         * S7C1T - set-7bit-c1-terminal
+         * This causes the terminal to start sending C1 controls as 7bit
+         * sequences instead of 8bit C1 controls.
+         * This is ignored if the terminal is below level-2 emulation mode
+         * (VT100 and below), the terminal already sends 7bit controls then.
+         */
+
+        if (screen->conformance_level > TERM_CONFORMANCE_LEVEL_VT100)
+                screen->flags |= TERM_FLAG_7BIT_MODE;
+
+        return 0;
+}
+
+static int screen_S8C1T(term_screen *screen, const term_seq *seq) {
+        /*
+         * S8C1T - set-8bit-c1-terminal
+         * This causes the terminal to start sending C1 controls as 8bit C1
+         * control instead of 7bit sequences.
+         * This is ignored if the terminal is below level-2 emulation mode
+         * (VT100 and below). The terminal always sends 7bit controls in those
+         * modes.
+         */
+
+        if (screen->conformance_level > TERM_CONFORMANCE_LEVEL_VT100)
+                screen->flags &= ~TERM_FLAG_7BIT_MODE;
+
+        return 0;
+}
+
+static int screen_SCS(term_screen *screen, const term_seq *seq) {
+        /*
+         * SCS - select-character-set
+         * Designate character sets to G-sets. The mapping from intermediates
+         * and terminal characters in the escape sequence to G-sets and
+         * character-sets is non-trivial and implemented separately. See there
+         * for more information.
+         * This call simply sets the selected G-set to the desired
+         * character-set.
+         */
+
+        term_charset *cs = NULL;
+
+        /* TODO: support more of them? */
+        switch (seq->charset) {
+        case TERM_CHARSET_ISO_LATIN1_SUPPLEMENTAL:
+        case TERM_CHARSET_ISO_LATIN2_SUPPLEMENTAL:
+        case TERM_CHARSET_ISO_LATIN5_SUPPLEMENTAL:
+        case TERM_CHARSET_ISO_GREEK_SUPPLEMENTAL:
+        case TERM_CHARSET_ISO_HEBREW_SUPPLEMENTAL:
+        case TERM_CHARSET_ISO_LATIN_CYRILLIC:
+                break;
+
+        case TERM_CHARSET_DEC_SPECIAL_GRAPHIC:
+                cs = &term_dec_special_graphics;
+                break;
+        case TERM_CHARSET_DEC_SUPPLEMENTAL:
+                cs = &term_dec_supplemental_graphics;
+                break;
+        case TERM_CHARSET_DEC_TECHNICAL:
+        case TERM_CHARSET_CYRILLIC_DEC:
+        case TERM_CHARSET_DUTCH_NRCS:
+        case TERM_CHARSET_FINNISH_NRCS:
+        case TERM_CHARSET_FRENCH_NRCS:
+        case TERM_CHARSET_FRENCH_CANADIAN_NRCS:
+        case TERM_CHARSET_GERMAN_NRCS:
+        case TERM_CHARSET_GREEK_DEC:
+        case TERM_CHARSET_GREEK_NRCS:
+        case TERM_CHARSET_HEBREW_DEC:
+        case TERM_CHARSET_HEBREW_NRCS:
+        case TERM_CHARSET_ITALIAN_NRCS:
+        case TERM_CHARSET_NORWEGIAN_DANISH_NRCS:
+        case TERM_CHARSET_PORTUGUESE_NRCS:
+        case TERM_CHARSET_RUSSIAN_NRCS:
+        case TERM_CHARSET_SCS_NRCS:
+        case TERM_CHARSET_SPANISH_NRCS:
+        case TERM_CHARSET_SWEDISH_NRCS:
+        case TERM_CHARSET_SWISS_NRCS:
+        case TERM_CHARSET_TURKISH_DEC:
+        case TERM_CHARSET_TURKISH_NRCS:
+                break;
+
+        case TERM_CHARSET_USERPREF_SUPPLEMENTAL:
+                break;
+        }
+
+        if (seq->intermediates & TERM_SEQ_FLAG_POPEN)
+                screen->g0 = cs ? : &term_unicode_lower;
+        else if (seq->intermediates & TERM_SEQ_FLAG_PCLOSE)
+                screen->g1 = cs ? : &term_unicode_upper;
+        else if (seq->intermediates & TERM_SEQ_FLAG_MULT)
+                screen->g2 = cs ? : &term_unicode_lower;
+        else if (seq->intermediates & TERM_SEQ_FLAG_PLUS)
+                screen->g3 = cs ? : &term_unicode_upper;
+        else if (seq->intermediates & TERM_SEQ_FLAG_MINUS)
+                screen->g1 = cs ? : &term_unicode_upper;
+        else if (seq->intermediates & TERM_SEQ_FLAG_DOT)
+                screen->g2 = cs ? : &term_unicode_lower;
+        else if (seq->intermediates & TERM_SEQ_FLAG_SLASH)
+                screen->g3 = cs ? : &term_unicode_upper;
+
+        return 0;
+}
+
+static int screen_SD(term_screen *screen, const term_seq *seq) {
+        /*
+         * SD - scroll-down
+         * This control function moves the user window down a specified number
+         * of lines in page memory.
+         * @args[0] is the number of lines to move the
+         * user window up in page memory. New lines appear at the top of the
+         * display. Old lines disappear at the bottom of the display. You
+         * cannot pan past the top margin of the current page. 0 is treated
+         * as 1.
+         *
+         * Defaults:
+         *   args[0]: 1
+         */
+
+        unsigned int num = 1;
+
+        if (seq->args[0] > 0)
+                num = seq->args[0];
+
+        term_page_scroll_down(screen->page, num, &screen->attr, screen->age, NULL);
+
+        return 0;
+}
+
+static int screen_SGR(term_screen *screen, const term_seq *seq) {
+        /*
+         * SGR - select-graphics-rendition
+         */
+
+        term_color *dst;
+        unsigned int i, code;
+        int v;
+
+        if (seq->n_args < 1) {
+                zero(screen->attr);
+                return 0;
+        }
+
+        for (i = 0; i < seq->n_args; ++i) {
+                v = seq->args[i];
+                switch (v) {
+                case 1:
+                        screen->attr.bold = 1;
+                        break;
+                case 3:
+                        screen->attr.italic = 1;
+                        break;
+                case 4:
+                        screen->attr.underline = 1;
+                        break;
+                case 5:
+                        screen->attr.blink = 1;
+                        break;
+                case 7:
+                        screen->attr.inverse = 1;
+                        break;
+                case 8:
+                        screen->attr.hidden = 1;
+                        break;
+                case 22:
+                        screen->attr.bold = 0;
+                        break;
+                case 23:
+                        screen->attr.italic = 0;
+                        break;
+                case 24:
+                        screen->attr.underline = 0;
+                        break;
+                case 25:
+                        screen->attr.blink = 0;
+                        break;
+                case 27:
+                        screen->attr.inverse = 0;
+                        break;
+                case 28:
+                        screen->attr.hidden = 0;
+                        break;
+                case 30 ... 37:
+                        screen->attr.fg.ccode = v - 30 + TERM_CCODE_BLACK;
+                        break;
+                case 39:
+                        screen->attr.fg.ccode = 0;
+                        break;
+                case 40 ... 47:
+                        screen->attr.bg.ccode = v - 40 + TERM_CCODE_BLACK;
+                        break;
+                case 49:
+                        screen->attr.bg.ccode = 0;
+                        break;
+                case 90 ... 97:
+                        screen->attr.fg.ccode = v - 90 + TERM_CCODE_LIGHT_BLACK;
+                        break;
+                case 100 ... 107:
+                        screen->attr.bg.ccode = v - 100 + TERM_CCODE_LIGHT_BLACK;
+                        break;
+                case 38:
+                        /* fallthrough */
+                case 48:
+
+                        if (v == 38)
+                                dst = &screen->attr.fg;
+                        else
+                                dst = &screen->attr.bg;
+
+                        ++i;
+                        if (i >= seq->n_args)
+                                break;
+
+                        switch (seq->args[i]) {
+                        case 2:
+                                /* 24bit-color support */
+
+                                i += 3;
+                                if (i >= seq->n_args)
+                                        break;
+
+                                dst->ccode = TERM_CCODE_RGB;
+                                dst->red = (seq->args[i - 2] >= 0) ? seq->args[i - 2] : 0;
+                                dst->green = (seq->args[i - 1] >= 0) ? seq->args[i - 1] : 0;
+                                dst->blue = (seq->args[i] >= 0) ? seq->args[i] : 0;
+
+                                break;
+                        case 5:
+                                /* 256-color support */
+
+                                ++i;
+                                if (i >= seq->n_args || seq->args[i] < 0)
+                                        break;
+
+                                code = seq->args[i];
+                                if (code < 16) {
+                                        dst->ccode = code;
+                                } else if (code < 232) {
+                                        static const uint8_t bval[] = {
+                                                0x00, 0x5f, 0x87,
+                                                0xaf, 0xd7, 0xff,
+                                        };
+
+                                        dst->ccode = TERM_CCODE_256;
+                                        dst->c256 = code;
+                                        code -= 16;
+                                        dst->blue = bval[code % 6];
+                                        code /= 6;
+                                        dst->green = bval[code % 6];
+                                        code /= 6;
+                                        dst->red = bval[code % 6];
+                                } else if (code < 256) {
+                                        dst->ccode = TERM_CCODE_256;
+                                        dst->c256 = code;
+                                        code = (code - 232) * 10 + 8;
+                                        dst->red = code;
+                                        dst->green = code;
+                                        dst->blue = code;
+                                }
+
+                                break;
+                        }
+
+                        break;
+                case -1:
+                        /* fallthrough */
+                case 0:
+                        zero(screen->attr);
+                        break;
+                }
+        }
+
+        return 0;
+}
+
+static int screen_SI(term_screen *screen, const term_seq *seq) {
+        /*
+         * SI - shift-in
+         * Map G0 into GL.
+         */
+
+        screen->gl = &screen->g0;
+
+        return 0;
+}
+
+static int screen_SM_ANSI(term_screen *screen, const term_seq *seq) {
+        /*
+         * SM_ANSI - set-mode-ansi
+         *
+         * TODO: implement
+         */
+
+        unsigned int i;
+
+        for (i = 0; i < seq->n_args; ++i)
+                screen_mode_change(screen, seq->args[i], false, true);
+
+        return 0;
+}
+
+static int screen_SM_DEC(term_screen *screen, const term_seq *seq) {
+        /*
+         * SM_DEC - set-mode-dec
+         * This is the same as SM_ANSI but for DEC modes.
+         */
+
+        unsigned int i;
+
+        for (i = 0; i < seq->n_args; ++i)
+                screen_mode_change(screen, seq->args[i], true, true);
+
+        return 0;
+}
+
+static int screen_SO(term_screen *screen, const term_seq *seq) {
+        /*
+         * SO - shift-out
+         * Map G1 into GL.
+         */
+
+        screen->gl = &screen->g1;
+
+        return 0;
+}
+
+static int screen_SPA(term_screen *screen, const term_seq *seq) {
+        /*
+         * SPA - start-of-protected-area
+         *
+         * TODO: What is this?
+         */
+
+        return 0;
+}
+
+static int screen_SS2(term_screen *screen, const term_seq *seq) {
+        /*
+         * SS2 - single-shift-2
+         * Temporarily map G2 into GL for the next graphics character.
+         */
+
+        screen->glt = &screen->g2;
+
+        return 0;
+}
+
+static int screen_SS3(term_screen *screen, const term_seq *seq) {
+        /*
+         * SS3 - single-shift-3
+         * Temporarily map G3 into GL for the next graphics character
+         */
+
+        screen->glt = &screen->g3;
+
+        return 0;
+}
+
+static int screen_ST(term_screen *screen, const term_seq *seq) {
+        /*
+         * ST - string-terminator
+         * The string-terminator is usually part of control-sequences and
+         * handled by the parser. In all other situations it is silently
+         * ignored.
+         */
+
+        return 0;
+}
+
+static int screen_SU(term_screen *screen, const term_seq *seq) {
+        /*
+         * SU - scroll-up
+         * This control function moves the user window up a specified number of
+         * lines in page memory.
+         * @args[0] is the number of lines to move the
+         * user window down in page memory. New lines appear at the bottom of
+         * the display. Old lines disappear at the top of the display. You
+         * cannot pan past the bottom margin of the current page. 0 is treated
+         * as 1.
+         *
+         * Defaults:
+         *   args[0]: 1
+         */
+
+        unsigned int num = 1;
+
+        if (seq->args[0] > 0)
+                num = seq->args[0];
+
+        term_page_scroll_up(screen->page, num, &screen->attr, screen->age, screen->history);
+
+        return 0;
+}
+
+static int screen_SUB(term_screen *screen, const term_seq *seq) {
+        /*
+         * SUB - substitute
+         * Cancel the current control-sequence and print a replacement
+         * character. Our parser already handles this so all we have to do is
+         * print the replacement character.
+         */
+
+        static const term_seq rep = {
+                .type = TERM_SEQ_GRAPHIC,
+                .command = TERM_CMD_GRAPHIC,
+                .terminator = 0xfffd,
+        };
+
+        return screen_GRAPHIC(screen, &rep);
+}
+
+static int screen_TBC(term_screen *screen, const term_seq *seq) {
+        /*
+         * TBC - tab-clear
+         * This clears tab-stops. If @args[0] is 0, the tab-stop at the current
+         * cursor position is cleared. If it is 3, all tab stops are cleared.
+         *
+         * Defaults:
+         *   args[0]: 0
+         */
+
+        unsigned int mode = 0, pos;
+
+        if (seq->args[0] > 0)
+                mode = seq->args[0];
+
+        switch (mode) {
+        case 0:
+                pos = screen->cursor_x;
+                if (screen->page->width > 0)
+                        screen->tabs[pos / 8] &= ~(1U << (pos % 8));
+                break;
+        case 3:
+                if (screen->page->width > 0)
+                        memset(screen->tabs, 0, (screen->page->width + 7) / 8);
+                break;
+        }
+
+        return 0;
+}
+
+static int screen_VPA(term_screen *screen, const term_seq *seq) {
+        /*
+         * VPA - vertical-line-position-absolute
+         * VPA causes the active position to be moved to the corresponding
+         * horizontal position. @args[0] specifies the line to jump to. If an
+         * attempt is made to move the active position below the last line, then
+         * the active position stops on the last line. 0 is treated as 1.
+         *
+         * Defaults:
+         *   args[0]: 1
+         */
+
+        unsigned int pos = 1;
+
+        if (seq->args[0] > 0)
+                pos = seq->args[0];
+
+        screen_cursor_clear_wrap(screen);
+        screen_cursor_set_rel(screen, screen->cursor_x, pos - 1);
+
+        return 0;
+}
+
+static int screen_VPR(term_screen *screen, const term_seq *seq) {
+        /*
+         * VPR - vertical-line-position-relative
+         * VPR causes the active position to be moved to the corresponding
+         * horizontal position. @args[0] specifies the number of lines to jump
+         * down relative to the current cursor position. If an attempt is made
+         * to move the active position below the last line, the active position
+         * stops at the last line. 0 is treated as 1.
+         *
+         * Defaults:
+         *   args[0]: 1
+         */
+
+        unsigned int num = 1;
+
+        if (seq->args[0] > 0)
+                num = seq->args[0];
+
+        screen_cursor_clear_wrap(screen);
+        screen_cursor_down(screen, num, false);
+
+        return 0;
+}
+
+static int screen_VT(term_screen *screen, const term_seq *seq) {
+        /*
+         * VT - vertical-tab
+         * This causes a vertical jump by one line. Terminals treat it exactly
+         * the same as LF.
+         */
+
+        return screen_LF(screen, seq);
+}
+
+static int screen_XTERM_CLLHP(term_screen *screen, const term_seq *seq) {
+        /*
+         * XTERM_CLLHP - xterm-cursor-lower-left-hp-bugfix
+         * Move the cursor to the lower-left corner of the page. This is an HP
+         * bugfix by xterm.
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_XTERM_IHMT(term_screen *screen, const term_seq *seq) {
+        /*
+         * XTERM_IHMT - xterm-initiate-highlight-mouse-tracking
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_XTERM_MLHP(term_screen *screen, const term_seq *seq) {
+        /*
+         * XTERM_MLHP - xterm-memory-lock-hp-bugfix
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_XTERM_MUHP(term_screen *screen, const term_seq *seq) {
+        /*
+         * XTERM_MUHP - xterm-memory-unlock-hp-bugfix
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_XTERM_RPM(term_screen *screen, const term_seq *seq) {
+        /*
+         * XTERM_RPM - xterm-restore-private-mode
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_XTERM_RRV(term_screen *screen, const term_seq *seq) {
+        /*
+         * XTERM_RRV - xterm-reset-resource-value
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_XTERM_RTM(term_screen *screen, const term_seq *seq) {
+        /*
+         * XTERM_RTM - xterm-reset-title-mode
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_XTERM_SACL1(term_screen *screen, const term_seq *seq) {
+        /*
+         * XTERM_SACL1 - xterm-set-ansi-conformance-level-1
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_XTERM_SACL2(term_screen *screen, const term_seq *seq) {
+        /*
+         * XTERM_SACL2 - xterm-set-ansi-conformance-level-2
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_XTERM_SACL3(term_screen *screen, const term_seq *seq) {
+        /*
+         * XTERM_SACL3 - xterm-set-ansi-conformance-level-3
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_XTERM_SDCS(term_screen *screen, const term_seq *seq) {
+        /*
+         * XTERM_SDCS - xterm-set-default-character-set
+         * Select the default character set. We treat this the same as UTF-8 as
+         * this is our default character set. As we always use UTF-8, this
+         * becomes as no-op.
+         */
+
+        return 0;
+}
+
+static int screen_XTERM_SGFX(term_screen *screen, const term_seq *seq) {
+        /*
+         * XTERM_SGFX - xterm-sixel-graphics
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_XTERM_SPM(term_screen *screen, const term_seq *seq) {
+        /*
+         * XTERM_SPM - xterm-set-private-mode
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_XTERM_SRV(term_screen *screen, const term_seq *seq) {
+        /*
+         * XTERM_SRV - xterm-set-resource-value
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_XTERM_STM(term_screen *screen, const term_seq *seq) {
+        /*
+         * XTERM_STM - xterm-set-title-mode
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+static int screen_XTERM_SUCS(term_screen *screen, const term_seq *seq) {
+        /*
+         * XTERM_SUCS - xterm-select-utf8-character-set
+         * Select UTF-8 as character set. This is our default on only character
+         * set. Hence, this is a no-op.
+         */
+
+        return 0;
+}
+
+static int screen_XTERM_WM(term_screen *screen, const term_seq *seq) {
+        /*
+         * XTERM_WM - xterm-window-management
+         *
+         * Probably not worth implementing.
+         */
+
+        return 0;
+}
+
+/*
+ * Feeding data
+ * The screen_feed_*() handlers take data from the user and feed it into the
+ * screen. Once the parser has detected a sequence, we parse the command-type
+ * and forward it to the command-dispatchers.
+ */
+
+static int screen_feed_cmd(term_screen *screen, const term_seq *seq) {
+        switch (seq->command) {
+        case TERM_CMD_GRAPHIC:
+                return screen_GRAPHIC(screen, seq);
+        case TERM_CMD_BEL:
+                return screen_BEL(screen, seq);
+        case TERM_CMD_BS:
+                return screen_BS(screen, seq);
+        case TERM_CMD_CBT:
+                return screen_CBT(screen, seq);
+        case TERM_CMD_CHA:
+                return screen_CHA(screen, seq);
+        case TERM_CMD_CHT:
+                return screen_CHT(screen, seq);
+        case TERM_CMD_CNL:
+                return screen_CNL(screen, seq);
+        case TERM_CMD_CPL:
+                return screen_CPL(screen, seq);
+        case TERM_CMD_CR:
+                return screen_CR(screen, seq);
+        case TERM_CMD_CUB:
+                return screen_CUB(screen, seq);
+        case TERM_CMD_CUD:
+                return screen_CUD(screen, seq);
+        case TERM_CMD_CUF:
+                return screen_CUF(screen, seq);
+        case TERM_CMD_CUP:
+                return screen_CUP(screen, seq);
+        case TERM_CMD_CUU:
+                return screen_CUU(screen, seq);
+        case TERM_CMD_DA1:
+                return screen_DA1(screen, seq);
+        case TERM_CMD_DA2:
+                return screen_DA2(screen, seq);
+        case TERM_CMD_DA3:
+                return screen_DA3(screen, seq);
+        case TERM_CMD_DC1:
+                return screen_DC1(screen, seq);
+        case TERM_CMD_DC3:
+                return screen_DC3(screen, seq);
+        case TERM_CMD_DCH:
+                return screen_DCH(screen, seq);
+        case TERM_CMD_DECALN:
+                return screen_DECALN(screen, seq);
+        case TERM_CMD_DECANM:
+                return screen_DECANM(screen, seq);
+        case TERM_CMD_DECBI:
+                return screen_DECBI(screen, seq);
+        case TERM_CMD_DECCARA:
+                return screen_DECCARA(screen, seq);
+        case TERM_CMD_DECCRA:
+                return screen_DECCRA(screen, seq);
+        case TERM_CMD_DECDC:
+                return screen_DECDC(screen, seq);
+        case TERM_CMD_DECDHL_BH:
+                return screen_DECDHL_BH(screen, seq);
+        case TERM_CMD_DECDHL_TH:
+                return screen_DECDHL_TH(screen, seq);
+        case TERM_CMD_DECDWL:
+                return screen_DECDWL(screen, seq);
+        case TERM_CMD_DECEFR:
+                return screen_DECEFR(screen, seq);
+        case TERM_CMD_DECELF:
+                return screen_DECELF(screen, seq);
+        case TERM_CMD_DECELR:
+                return screen_DECELR(screen, seq);
+        case TERM_CMD_DECERA:
+                return screen_DECERA(screen, seq);
+        case TERM_CMD_DECFI:
+                return screen_DECFI(screen, seq);
+        case TERM_CMD_DECFRA:
+                return screen_DECFRA(screen, seq);
+        case TERM_CMD_DECIC:
+                return screen_DECIC(screen, seq);
+        case TERM_CMD_DECID:
+                return screen_DECID(screen, seq);
+        case TERM_CMD_DECINVM:
+                return screen_DECINVM(screen, seq);
+        case TERM_CMD_DECKBD:
+                return screen_DECKBD(screen, seq);
+        case TERM_CMD_DECKPAM:
+                return screen_DECKPAM(screen, seq);
+        case TERM_CMD_DECKPNM:
+                return screen_DECKPNM(screen, seq);
+        case TERM_CMD_DECLFKC:
+                return screen_DECLFKC(screen, seq);
+        case TERM_CMD_DECLL:
+                return screen_DECLL(screen, seq);
+        case TERM_CMD_DECLTOD:
+                return screen_DECLTOD(screen, seq);
+        case TERM_CMD_DECPCTERM:
+                return screen_DECPCTERM(screen, seq);
+        case TERM_CMD_DECPKA:
+                return screen_DECPKA(screen, seq);
+        case TERM_CMD_DECPKFMR:
+                return screen_DECPKFMR(screen, seq);
+        case TERM_CMD_DECRARA:
+                return screen_DECRARA(screen, seq);
+        case TERM_CMD_DECRC:
+                return screen_DECRC(screen, seq);
+        case TERM_CMD_DECREQTPARM:
+                return screen_DECREQTPARM(screen, seq);
+        case TERM_CMD_DECRPKT:
+                return screen_DECRPKT(screen, seq);
+        case TERM_CMD_DECRQCRA:
+                return screen_DECRQCRA(screen, seq);
+        case TERM_CMD_DECRQDE:
+                return screen_DECRQDE(screen, seq);
+        case TERM_CMD_DECRQKT:
+                return screen_DECRQKT(screen, seq);
+        case TERM_CMD_DECRQLP:
+                return screen_DECRQLP(screen, seq);
+        case TERM_CMD_DECRQM_ANSI:
+                return screen_DECRQM_ANSI(screen, seq);
+        case TERM_CMD_DECRQM_DEC:
+                return screen_DECRQM_DEC(screen, seq);
+        case TERM_CMD_DECRQPKFM:
+                return screen_DECRQPKFM(screen, seq);
+        case TERM_CMD_DECRQPSR:
+                return screen_DECRQPSR(screen, seq);
+        case TERM_CMD_DECRQTSR:
+                return screen_DECRQTSR(screen, seq);
+        case TERM_CMD_DECRQUPSS:
+                return screen_DECRQUPSS(screen, seq);
+        case TERM_CMD_DECSACE:
+                return screen_DECSACE(screen, seq);
+        case TERM_CMD_DECSASD:
+                return screen_DECSASD(screen, seq);
+        case TERM_CMD_DECSC:
+                return screen_DECSC(screen, seq);
+        case TERM_CMD_DECSCA:
+                return screen_DECSCA(screen, seq);
+        case TERM_CMD_DECSCL:
+                return screen_DECSCL(screen, seq);
+        case TERM_CMD_DECSCP:
+                return screen_DECSCP(screen, seq);
+        case TERM_CMD_DECSCPP:
+                return screen_DECSCPP(screen, seq);
+        case TERM_CMD_DECSCS:
+                return screen_DECSCS(screen, seq);
+        case TERM_CMD_DECSCUSR:
+                return screen_DECSCUSR(screen, seq);
+        case TERM_CMD_DECSDDT:
+                return screen_DECSDDT(screen, seq);
+        case TERM_CMD_DECSDPT:
+                return screen_DECSDPT(screen, seq);
+        case TERM_CMD_DECSED:
+                return screen_DECSED(screen, seq);
+        case TERM_CMD_DECSEL:
+                return screen_DECSEL(screen, seq);
+        case TERM_CMD_DECSERA:
+                return screen_DECSERA(screen, seq);
+        case TERM_CMD_DECSFC:
+                return screen_DECSFC(screen, seq);
+        case TERM_CMD_DECSKCV:
+                return screen_DECSKCV(screen, seq);
+        case TERM_CMD_DECSLCK:
+                return screen_DECSLCK(screen, seq);
+        case TERM_CMD_DECSLE:
+                return screen_DECSLE(screen, seq);
+        case TERM_CMD_DECSLPP:
+                return screen_DECSLPP(screen, seq);
+        case TERM_CMD_DECSLRM_OR_SC:
+                return screen_DECSLRM_OR_SC(screen, seq);
+        case TERM_CMD_DECSMBV:
+                return screen_DECSMBV(screen, seq);
+        case TERM_CMD_DECSMKR:
+                return screen_DECSMKR(screen, seq);
+        case TERM_CMD_DECSNLS:
+                return screen_DECSNLS(screen, seq);
+        case TERM_CMD_DECSPP:
+                return screen_DECSPP(screen, seq);
+        case TERM_CMD_DECSPPCS:
+                return screen_DECSPPCS(screen, seq);
+        case TERM_CMD_DECSPRTT:
+                return screen_DECSPRTT(screen, seq);
+        case TERM_CMD_DECSR:
+                return screen_DECSR(screen, seq);
+        case TERM_CMD_DECSRFR:
+                return screen_DECSRFR(screen, seq);
+        case TERM_CMD_DECSSCLS:
+                return screen_DECSSCLS(screen, seq);
+        case TERM_CMD_DECSSDT:
+                return screen_DECSSDT(screen, seq);
+        case TERM_CMD_DECSSL:
+                return screen_DECSSL(screen, seq);
+        case TERM_CMD_DECST8C:
+                return screen_DECST8C(screen, seq);
+        case TERM_CMD_DECSTBM:
+                return screen_DECSTBM(screen, seq);
+        case TERM_CMD_DECSTR:
+                return screen_DECSTR(screen, seq);
+        case TERM_CMD_DECSTRL:
+                return screen_DECSTRL(screen, seq);
+        case TERM_CMD_DECSWBV:
+                return screen_DECSWBV(screen, seq);
+        case TERM_CMD_DECSWL:
+                return screen_DECSWL(screen, seq);
+        case TERM_CMD_DECTID:
+                return screen_DECTID(screen, seq);
+        case TERM_CMD_DECTME:
+                return screen_DECTME(screen, seq);
+        case TERM_CMD_DECTST:
+                return screen_DECTST(screen, seq);
+        case TERM_CMD_DL:
+                return screen_DL(screen, seq);
+        case TERM_CMD_DSR_ANSI:
+                return screen_DSR_ANSI(screen, seq);
+        case TERM_CMD_DSR_DEC:
+                return screen_DSR_DEC(screen, seq);
+        case TERM_CMD_ECH:
+                return screen_ECH(screen, seq);
+        case TERM_CMD_ED:
+                return screen_ED(screen, seq);
+        case TERM_CMD_EL:
+                return screen_EL(screen, seq);
+        case TERM_CMD_ENQ:
+                return screen_ENQ(screen, seq);
+        case TERM_CMD_EPA:
+                return screen_EPA(screen, seq);
+        case TERM_CMD_FF:
+                return screen_FF(screen, seq);
+        case TERM_CMD_HPA:
+                return screen_HPA(screen, seq);
+        case TERM_CMD_HPR:
+                return screen_HPR(screen, seq);
+        case TERM_CMD_HT:
+                return screen_HT(screen, seq);
+        case TERM_CMD_HTS:
+                return screen_HTS(screen, seq);
+        case TERM_CMD_HVP:
+                return screen_HVP(screen, seq);
+        case TERM_CMD_ICH:
+                return screen_ICH(screen, seq);
+        case TERM_CMD_IL:
+                return screen_IL(screen, seq);
+        case TERM_CMD_IND:
+                return screen_IND(screen, seq);
+        case TERM_CMD_LF:
+                return screen_LF(screen, seq);
+        case TERM_CMD_LS1R:
+                return screen_LS1R(screen, seq);
+        case TERM_CMD_LS2:
+                return screen_LS2(screen, seq);
+        case TERM_CMD_LS2R:
+                return screen_LS2R(screen, seq);
+        case TERM_CMD_LS3:
+                return screen_LS3(screen, seq);
+        case TERM_CMD_LS3R:
+                return screen_LS3R(screen, seq);
+        case TERM_CMD_MC_ANSI:
+                return screen_MC_ANSI(screen, seq);
+        case TERM_CMD_MC_DEC:
+                return screen_MC_DEC(screen, seq);
+        case TERM_CMD_NEL:
+                return screen_NEL(screen, seq);
+        case TERM_CMD_NP:
+                return screen_NP(screen, seq);
+        case TERM_CMD_NULL:
+                return screen_NULL(screen, seq);
+        case TERM_CMD_PP:
+                return screen_PP(screen, seq);
+        case TERM_CMD_PPA:
+                return screen_PPA(screen, seq);
+        case TERM_CMD_PPB:
+                return screen_PPB(screen, seq);
+        case TERM_CMD_PPR:
+                return screen_PPR(screen, seq);
+        case TERM_CMD_RC:
+                return screen_RC(screen, seq);
+        case TERM_CMD_REP:
+                return screen_REP(screen, seq);
+        case TERM_CMD_RI:
+                return screen_RI(screen, seq);
+        case TERM_CMD_RIS:
+                return screen_RIS(screen, seq);
+        case TERM_CMD_RM_ANSI:
+                return screen_RM_ANSI(screen, seq);
+        case TERM_CMD_RM_DEC:
+                return screen_RM_DEC(screen, seq);
+        case TERM_CMD_S7C1T:
+                return screen_S7C1T(screen, seq);
+        case TERM_CMD_S8C1T:
+                return screen_S8C1T(screen, seq);
+        case TERM_CMD_SCS:
+                return screen_SCS(screen, seq);
+        case TERM_CMD_SD:
+                return screen_SD(screen, seq);
+        case TERM_CMD_SGR:
+                return screen_SGR(screen, seq);
+        case TERM_CMD_SI:
+                return screen_SI(screen, seq);
+        case TERM_CMD_SM_ANSI:
+                return screen_SM_ANSI(screen, seq);
+        case TERM_CMD_SM_DEC:
+                return screen_SM_DEC(screen, seq);
+        case TERM_CMD_SO:
+                return screen_SO(screen, seq);
+        case TERM_CMD_SPA:
+                return screen_SPA(screen, seq);
+        case TERM_CMD_SS2:
+                return screen_SS2(screen, seq);
+        case TERM_CMD_SS3:
+                return screen_SS3(screen, seq);
+        case TERM_CMD_ST:
+                return screen_ST(screen, seq);
+        case TERM_CMD_SU:
+                return screen_SU(screen, seq);
+        case TERM_CMD_SUB:
+                return screen_SUB(screen, seq);
+        case TERM_CMD_TBC:
+                return screen_TBC(screen, seq);
+        case TERM_CMD_VPA:
+                return screen_VPA(screen, seq);
+        case TERM_CMD_VPR:
+                return screen_VPR(screen, seq);
+        case TERM_CMD_VT:
+                return screen_VT(screen, seq);
+        case TERM_CMD_XTERM_CLLHP:
+                return screen_XTERM_CLLHP(screen, seq);
+        case TERM_CMD_XTERM_IHMT:
+                return screen_XTERM_IHMT(screen, seq);
+        case TERM_CMD_XTERM_MLHP:
+                return screen_XTERM_MLHP(screen, seq);
+        case TERM_CMD_XTERM_MUHP:
+                return screen_XTERM_MUHP(screen, seq);
+        case TERM_CMD_XTERM_RPM:
+                return screen_XTERM_RPM(screen, seq);
+        case TERM_CMD_XTERM_RRV:
+                return screen_XTERM_RRV(screen, seq);
+        case TERM_CMD_XTERM_RTM:
+                return screen_XTERM_RTM(screen, seq);
+        case TERM_CMD_XTERM_SACL1:
+                return screen_XTERM_SACL1(screen, seq);
+        case TERM_CMD_XTERM_SACL2:
+                return screen_XTERM_SACL2(screen, seq);
+        case TERM_CMD_XTERM_SACL3:
+                return screen_XTERM_SACL3(screen, seq);
+        case TERM_CMD_XTERM_SDCS:
+                return screen_XTERM_SDCS(screen, seq);
+        case TERM_CMD_XTERM_SGFX:
+                return screen_XTERM_SGFX(screen, seq);
+        case TERM_CMD_XTERM_SPM:
+                return screen_XTERM_SPM(screen, seq);
+        case TERM_CMD_XTERM_SRV:
+                return screen_XTERM_SRV(screen, seq);
+        case TERM_CMD_XTERM_STM:
+                return screen_XTERM_STM(screen, seq);
+        case TERM_CMD_XTERM_SUCS:
+                return screen_XTERM_SUCS(screen, seq);
+        case TERM_CMD_XTERM_WM:
+                return screen_XTERM_WM(screen, seq);
+        }
+
+        return 0;
+}
+
+int term_screen_feed_text(term_screen *screen, const uint8_t *in, size_t size) {
+        const uint32_t *ucs4_str;
+        size_t i, j, ucs4_len;
+        const term_seq *seq;
+        int r;
+
+        assert_return(screen, -EINVAL);
+
+        /* Feed bytes into utf8 decoder and handle parsed ucs4 chars. We always
+         * treat data as UTF-8, but the parser makes sure to fall back to raw
+         * 8bit mode if the stream is not valid UTF-8. This should be more than
+         * enough to support old 7bit/8bit modes. */
+        for (i = 0; i < size; ++i) {
+                ucs4_str = term_utf8_decode(&screen->utf8, &ucs4_len, in[i]);
+                for (j = 0; j < ucs4_len; ++j) {
+                        r = term_parser_feed(screen->parser, &seq, ucs4_str[j]);
+                        if (r < 0) {
+                                return r;
+                        } else if (r != TERM_SEQ_NONE) {
+                                r = screen_feed_cmd(screen, seq);
+                                if (r < 0)
+                                        return r;
+                        }
+                }
+        }
+
+        return 0;
+}
+
+int term_screen_feed_keyboard(term_screen *screen, uint32_t keysym, uint32_t ascii, uint32_t ucs4, unsigned int mods) {
+        assert_return(screen, -EINVAL);
+
+        /* TODO */
+
+        return 0;
+}
+
+int term_screen_resize(term_screen *screen, unsigned int x, unsigned int y) {
+        unsigned int i;
+        uint8_t *t;
+        int r;
+
+        assert_return(screen, -EINVAL);
+
+        r = term_page_reserve(screen->page_main, x, y, &screen->attr, screen->age);
+        if (r < 0)
+                return r;
+
+        r = term_page_reserve(screen->page_alt, x, y, &screen->attr, screen->age);
+        if (r < 0)
+                return r;
+
+        if (x > screen->n_tabs) {
+                t = realloc(screen->tabs, (x + 7) / 8);
+                if (!t)
+                        return -ENOMEM;
+
+                screen->tabs = t;
+                screen->n_tabs = x;
+        }
+
+        for (i = (screen->page->width + 7) / 8 * 8; i < x; i += 8)
+                screen->tabs[i / 8] = 0x1;
+
+        term_page_resize(screen->page_main, x, y, &screen->attr, screen->age, screen->history);
+        term_page_resize(screen->page_alt, x, y, &screen->attr, screen->age, NULL);
+
+        screen->cursor_x = screen_clamp_x(screen, screen->cursor_x);
+        screen->cursor_y = screen_clamp_x(screen, screen->cursor_y);
+        screen_cursor_clear_wrap(screen);
+
+        return 0;
+}
+
+void term_screen_soft_reset(term_screen *screen) {
+        unsigned int i;
+
+        assert(screen);
+
+        screen->gl = &screen->g0;
+        screen->gr = &screen->g1;
+        screen->glt = NULL;
+        screen->grt = NULL;
+        screen->g0 = &term_unicode_lower;
+        screen->g1 = &term_unicode_upper;
+        screen->g2 = &term_unicode_lower;
+        screen->g3 = &term_unicode_upper;
+
+        screen->page = screen->page_main;
+        screen->history = screen->history_main;
+        screen->flags = TERM_FLAG_7BIT_MODE;
+        screen->conformance_level = TERM_CONFORMANCE_LEVEL_VT400;
+        screen->attr = screen->default_attr;
+
+        screen->saved.cursor_x = 0;
+        screen->saved.cursor_y = 0;
+        screen->saved.attr = screen->attr;
+        screen->saved.gl = screen->gl;
+        screen->saved.gr = screen->gr;
+        screen->saved.glt = NULL;
+        screen->saved.grt = NULL;
+        screen->flags = 0;
+
+        for (i = 0; i < screen->page->width; i += 8)
+                screen->tabs[i / 8] = 0x1;
+
+        term_page_set_scroll_region(screen->page_main, 0, screen->page->height);
+        term_page_set_scroll_region(screen->page_alt, 0, screen->page->height);
+}
+
+void term_screen_hard_reset(term_screen *screen) {
+        assert(screen);
+
+        term_screen_soft_reset(screen);
+        zero(screen->utf8);
+        screen->cursor_x = 0;
+        screen->cursor_y = 0;
+        term_page_erase(screen->page_main, 0, 0, screen->page->width, screen->page->height, &screen->attr, screen->age, false);
+        term_page_erase(screen->page_alt, 0, 0, screen->page->width, screen->page->height, &screen->attr, screen->age, false);
+}
+
+int term_screen_set_answerback(term_screen *screen, const char *answerback) {
+        char *t = NULL;
+
+        assert_return(screen, -EINVAL);
+
+        if (answerback) {
+                t = strdup(answerback);
+                if (!t)
+                        return -ENOMEM;
+        }
+
+        free(screen->answerback);
+        screen->answerback = t;
+
+        return 0;
+}

commit 1c9633d669948155455e29b0c6e770995a8b1ca3
Author: David Herrmann <dh.herrmann at gmail.com>
Date:   Sun Jun 15 14:50:00 2014 +0200

    terminal: add parser state-machine
    
    The term-parser is used to parse any input from TTY-clients. It reads CSI,
    DCS, OSC and ST control sequences and normal escape sequences. It doesn't
    do anything with the parsed data besides detecting the sequence and
    returning it. The caller has to react to them.
    
    The parser also comes with its own UTF-8 helpers. The reason for that is
    that we don't want to assert() or hard-fail on parsing errors. Instead,
    we treat any invalid UTF-8 sequences as ISO-8859-1. This allows pasting
    invalid data into a terminal (which cannot be controlled through the TTY,
    anyway) and we still deal with it in a proper manner.
    This is _required_ for 8-bit and 7-bit DEC modes (including the g0-g3
    mappings), so it's not just an ugly fallback because we can (it's still
    horribly ugly but at least we have an excuse).

diff --git a/.gitignore b/.gitignore
index 0ba01da..288cde6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -220,6 +220,7 @@
 /test-strxcpyx
 /test-tables
 /test-term-page
+/test-term-parser
 /test-time
 /test-tmpfiles
 /test-udev
diff --git a/Makefile.am b/Makefile.am
index db846ad..73f1252 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2839,7 +2839,9 @@ libsystemd_terminal_la_CFLAGS = \
 
 libsystemd_terminal_la_SOURCES = \
 	src/libsystemd-terminal/term-internal.h \
+	src/libsystemd-terminal/term-charset.c \
 	src/libsystemd-terminal/term-page.c \
+	src/libsystemd-terminal/term-parser.c \
 	src/libsystemd-terminal/term-wcwidth.c
 
 libsystemd_terminal_la_LIBADD = \
@@ -2854,8 +2856,17 @@ test_term_page_LDADD = \
 	libsystemd-internal.la \
 	libsystemd-shared.la
 
+test_term_parser_SOURCES = \
+	src/libsystemd-terminal/test-term-parser.c
+
+test_term_parser_LDADD = \
+	libsystemd-terminal.la \
+	libsystemd-internal.la \
+	libsystemd-shared.la
+
 tests += \
-	test-term-page
+	test-term-page \
+	test-term-parser
 
 # ------------------------------------------------------------------------------
 if ENABLE_GTK_DOC
diff --git a/src/libsystemd-terminal/term-charset.c b/src/libsystemd-terminal/term-charset.c
new file mode 100644
index 0000000..a00a191
--- /dev/null
+++ b/src/libsystemd-terminal/term-charset.c
@@ -0,0 +1,491 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright (C) 2014 David Herrmann <dh.herrmann at 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/>.
+***/
+
+/*
+ * VTE Character Sets
+ * These are predefined charactersets that can be loaded into GL and GR. By
+ * default we use unicode_lower and unicode_upper, that is, both sets have the
+ * exact unicode mapping. unicode_lower is effectively ASCII and unicode_upper
+ * as defined by the unicode standard (I guess, ISO 8859-1).
+ * Several other character sets are defined here. However, all of them are
+ * limited to the 96 character space of GL or GR. Everything beyond GR (which
+ * was not supported by the classic VTs by DEC but is available in VT emulators
+ * that support unicode/UTF8) is always mapped to unicode and cannot be changed
+ * by these character sets. Even mapping GL and GR is only available for
+ * backwards compatibility as new applications can use the Unicode functionality
+ * of the VTE.
+ *
+ * Moreover, mapping GR is almost unnecessary to support. In fact, Unicode UTF-8
+ * support in VTE works by reading every incoming data as UTF-8 stream. This
+ * maps GL/ASCII to ASCII, as UTF-8 is backwards compatible to ASCII, however,
+ * everything that has the 8th bit set is a >=2-byte haracter in UTF-8. That is,
+ * this is in no way backwards compatible to >=VT220 8bit support. Therefore, if
+ * someone maps a character set into GR and wants to use them with this VTE,
+ * then they must already send UTF-8 characters to use GR (all GR characters are
+ * 8-bits). Hence, they can easily also send the correct UTF-8 character for the
+ * unicode mapping.
+ * The only advantage is that most characters in many sets are 3-byte UTF-8
+ * characters and by mapping the set into GR/GL you can use 2 or 1 byte UTF-8
+ * characters which saves bandwidth.
+ * Another reason is, if you have older applications that use the VT220 8-bit
+ * support and you put a ASCII/8bit-extension to UTF-8 converter in between, you
+ * need these mappings to have the application behave correctly if it uses GL/GR
+ * mappings extensively.
+ *
+ * Anyway, we support GL/GR mappings so here are the most commonly used maps as
+ * defined by Unicode-standard, DEC-private maps and other famous charmaps.
+ *
+ * Characters 1-32 are always the control characters (part of CL) and cannot be
+ * mapped. Characters 34-127 (94 characters) are part of GL and can be mapped.
+ * Characters 33 and 128 are not part of GL and always mapped by the VTE.
+ * However, for GR they can be mapped differently (96 chars) so we have to
+ * include them. The mapper has to take care not to use them in GL.
+ */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include "term-internal.h"
+
+/*
+ * Lower Unicode character set. This maps the characters to the basic ASCII
+ * characters 33-126. These are all graphics characters defined in ASCII.
+ */
+term_charset term_unicode_lower = {
+        [0] = 32,
+        [1] = 33,
+        [2] = 34,
+        [3] = 35,
+        [4] = 36,
+        [5] = 37,
+        [6] = 38,
+        [7] = 39,
+        [8] = 40,
+        [9] = 41,
+        [10] = 42,
+        [11] = 43,
+        [12] = 44,
+        [13] = 45,
+        [14] = 46,
+        [15] = 47,
+        [16] = 48,
+        [17] = 49,
+        [18] = 50,
+        [19] = 51,
+        [20] = 52,
+        [21] = 53,
+        [22] = 54,
+        [23] = 55,
+        [24] = 56,
+        [25] = 57,
+        [26] = 58,
+        [27] = 59,
+        [28] = 60,
+        [29] = 61,
+        [30] = 62,
+        [31] = 63,
+        [32] = 64,
+        [33] = 65,
+        [34] = 66,
+        [35] = 67,
+        [36] = 68,
+        [37] = 69,
+        [38] = 70,
+        [39] = 71,
+        [40] = 72,
+        [41] = 73,
+        [42] = 74,
+        [43] = 75,
+        [44] = 76,
+        [45] = 77,
+        [46] = 78,
+        [47] = 79,
+        [48] = 80,
+        [49] = 81,
+        [50] = 82,
+        [51] = 83,
+        [52] = 84,
+        [53] = 85,
+        [54] = 86,
+        [55] = 87,
+        [56] = 88,
+        [57] = 89,
+        [58] = 90,
+        [59] = 91,
+        [60] = 92,
+        [61] = 93,
+        [62] = 94,
+        [63] = 95,
+        [64] = 96,
+        [65] = 97,
+        [66] = 98,
+        [67] = 99,
+        [68] = 100,
+        [69] = 101,
+        [70] = 102,
+        [71] = 103,
+        [72] = 104,
+        [73] = 105,
+        [74] = 106,
+        [75] = 107,
+        [76] = 108,
+        [77] = 109,
+        [78] = 110,
+        [79] = 111,
+        [80] = 112,
+        [81] = 113,
+        [82] = 114,
+        [83] = 115,
+        [84] = 116,
+        [85] = 117,
+        [86] = 118,
+        [87] = 119,
+        [88] = 120,
+        [89] = 121,
+        [90] = 122,
+        [91] = 123,
+        [92] = 124,
+        [93] = 125,
+        [94] = 126,
+        [95] = 127,
+};
+
+/*
+ * Upper Unicode Table
+ * This maps all characters to the upper unicode characters 161-254. These are
+ * not compatible to any older 8 bit character sets. See the Unicode standard
+ * for the definitions of each symbol.
+ */
+term_charset term_unicode_upper = {
+        [0] = 160,
+        [1] = 161,
+        [2] = 162,
+        [3] = 163,
+        [4] = 164,
+        [5] = 165,
+        [6] = 166,
+        [7] = 167,
+        [8] = 168,
+        [9] = 169,
+        [10] = 170,
+        [11] = 171,
+        [12] = 172,
+        [13] = 173,
+        [14] = 174,
+        [15] = 175,
+        [16] = 176,
+        [17] = 177,
+        [18] = 178,
+        [19] = 179,
+        [20] = 180,
+        [21] = 181,
+        [22] = 182,
+        [23] = 183,
+        [24] = 184,
+        [25] = 185,
+        [26] = 186,
+        [27] = 187,
+        [28] = 188,
+        [29] = 189,
+        [30] = 190,
+        [31] = 191,
+        [32] = 192,
+        [33] = 193,
+        [34] = 194,
+        [35] = 195,
+        [36] = 196,
+        [37] = 197,
+        [38] = 198,
+        [39] = 199,
+        [40] = 200,
+        [41] = 201,
+        [42] = 202,
+        [43] = 203,
+        [44] = 204,
+        [45] = 205,
+        [46] = 206,
+        [47] = 207,
+        [48] = 208,
+        [49] = 209,
+        [50] = 210,
+        [51] = 211,
+        [52] = 212,
+        [53] = 213,
+        [54] = 214,
+        [55] = 215,
+        [56] = 216,
+        [57] = 217,
+        [58] = 218,
+        [59] = 219,
+        [60] = 220,
+        [61] = 221,
+        [62] = 222,
+        [63] = 223,
+        [64] = 224,
+        [65] = 225,
+        [66] = 226,
+        [67] = 227,
+        [68] = 228,
+        [69] = 229,
+        [70] = 230,
+        [71] = 231,
+        [72] = 232,
+        [73] = 233,
+        [74] = 234,
+        [75] = 235,
+        [76] = 236,
+        [77] = 237,
+        [78] = 238,
+        [79] = 239,
+        [80] = 240,
+        [81] = 241,
+        [82] = 242,
+        [83] = 243,
+        [84] = 244,
+        [85] = 245,
+        [86] = 246,
+        [87] = 247,
+        [88] = 248,
+        [89] = 249,
+        [90] = 250,
+        [91] = 251,
+        [92] = 252,
+        [93] = 253,
+        [94] = 254,
+        [95] = 255,
+};
+
+/*
+ * The DEC supplemental graphics set. For its definition see here:
+ *  http://vt100.net/docs/vt220-rm/table2-3b.html
+ * Its basically a mixture of common European symbols that are not part of
+ * ASCII. Most often, this is mapped into GR to extend the basci ASCII part.
+ *
+ * This is very similar to unicode_upper, however, few symbols differ so do not
+ * mix them up!
+ */
+term_charset term_dec_supplemental_graphics = {
+        [0] = -1,       /* undefined */
+        [1] = 161,
+        [2] = 162,
+        [3] = 163,
+        [4] = 0,
+        [5] = 165,
+        [6] = 0,
+        [7] = 167,
+        [8] = 164,
+        [9] = 169,
+        [10] = 170,
+        [11] = 171,
+        [12] = 0,
+        [13] = 0,
+        [14] = 0,
+        [15] = 0,
+        [16] = 176,
+        [17] = 177,
+        [18] = 178,
+        [19] = 179,
+        [20] = 0,
+        [21] = 181,
+        [22] = 182,
+        [23] = 183,
+        [24] = 0,
+        [25] = 185,
+        [26] = 186,
+        [27] = 187,
+        [28] = 188,
+        [29] = 189,
+        [30] = 0,
+        [31] = 191,
+        [32] = 192,
+        [33] = 193,
+        [34] = 194,
+        [35] = 195,
+        [36] = 196,
+        [37] = 197,
+        [38] = 198,
+        [39] = 199,
+        [40] = 200,
+        [41] = 201,
+        [42] = 202,
+        [43] = 203,
+        [44] = 204,
+        [45] = 205,
+        [46] = 206,
+        [47] = 207,
+        [48] = 0,
+        [49] = 209,
+        [50] = 210,
+        [51] = 211,
+        [52] = 212,
+        [53] = 213,
+        [54] = 214,
+        [55] = 338,
+        [56] = 216,
+        [57] = 217,
+        [58] = 218,
+        [59] = 219,
+        [60] = 220,
+        [61] = 376,
+        [62] = 0,
+        [63] = 223,
+        [64] = 224,
+        [65] = 225,
+        [66] = 226,
+        [67] = 227,
+        [68] = 228,
+        [69] = 229,
+        [70] = 230,
+        [71] = 231,
+        [72] = 232,
+        [73] = 233,
+        [74] = 234,
+        [75] = 235,
+        [76] = 236,
+        [77] = 237,
+        [78] = 238,
+        [79] = 239,
+        [80] = 0,
+        [81] = 241,
+        [82] = 242,
+        [83] = 243,
+        [84] = 244,
+        [85] = 245,
+        [86] = 246,
+        [87] = 339,
+        [88] = 248,
+        [89] = 249,
+        [90] = 250,
+        [91] = 251,
+        [92] = 252,
+        [93] = 255,
+        [94] = 0,
+        [95] = -1,       /* undefined */
+};
+
+/*
+ * DEC special graphics character set. See here for its definition:
+ *  http://vt100.net/docs/vt220-rm/table2-4.html
+ * This contains several characters to create ASCII drawings and similar. Its
+ * commonly mapped into GR to extend the basic ASCII characters.
+ *
+ * Lower 62 characters map to ASCII 33-64, everything beyond is special and
+ * commonly used for ASCII drawings. It depends on the Unicode Standard 3.2 for
+ * the extended horizontal scan-line characters 3, 5, 7, and 9.
+ */
+term_charset term_dec_special_graphics = {
+        [0] = -1,       /* undefined */
+        [1] = 33,
+        [2] = 34,
+        [3] = 35,
+        [4] = 36,
+        [5] = 37,
+        [6] = 38,
+        [7] = 39,
+        [8] = 40,
+        [9] = 41,
+        [10] = 42,
+        [11] = 43,
+        [12] = 44,
+        [13] = 45,
+        [14] = 46,
+        [15] = 47,
+        [16] = 48,
+        [17] = 49,
+        [18] = 50,
+        [19] = 51,
+        [20] = 52,
+        [21] = 53,
+        [22] = 54,
+        [23] = 55,
+        [24] = 56,
+        [25] = 57,
+        [26] = 58,
+        [27] = 59,
+        [28] = 60,
+        [29] = 61,
+        [30] = 62,
+        [31] = 63,
+        [32] = 64,
+        [33] = 65,
+        [34] = 66,
+        [35] = 67,
+        [36] = 68,
+        [37] = 69,
+        [38] = 70,
+        [39] = 71,
+        [40] = 72,
+        [41] = 73,
+        [42] = 74,
+        [43] = 75,
+        [44] = 76,
+        [45] = 77,
+        [46] = 78,
+        [47] = 79,
+        [48] = 80,
+        [49] = 81,
+        [50] = 82,
+        [51] = 83,
+        [52] = 84,
+        [53] = 85,
+        [54] = 86,
+        [55] = 87,
+        [56] = 88,
+        [57] = 89,
+        [58] = 90,
+        [59] = 91,
+        [60] = 92,
+        [61] = 93,
+        [62] = 94,
+        [63] = 0,
+        [64] = 9830,
+        [65] = 9618,
+        [66] = 9225,
+        [67] = 9228,
+        [68] = 9229,
+        [69] = 9226,
+        [70] = 176,
+        [71] = 177,
+        [72] = 9252,
+        [73] = 9227,
+        [74] = 9496,
+        [75] = 9488,
+        [76] = 9484,
+        [77] = 9492,
+        [78] = 9532,
+        [79] = 9146,
+        [80] = 9147,
+        [81] = 9472,
+        [82] = 9148,
+        [83] = 9149,
+        [84] = 9500,
+        [85] = 9508,
+        [86] = 9524,
+        [87] = 9516,
+        [88] = 9474,
+        [89] = 8804,
+        [90] = 8805,
+        [91] = 960,
+        [92] = 8800,
+        [93] = 163,
+        [94] = 8901,
+        [95] = -1,      /* undefined */
+};
diff --git a/src/libsystemd-terminal/term-internal.h b/src/libsystemd-terminal/term-internal.h
index d7d2f98..a3d1f54 100644
--- a/src/libsystemd-terminal/term-internal.h
+++ b/src/libsystemd-terminal/term-internal.h
@@ -37,6 +37,11 @@ typedef struct term_line term_line;
 typedef struct term_page term_page;
 typedef struct term_history term_history;
 
+typedef struct term_utf8 term_utf8;
+typedef struct term_seq term_seq;
+typedef struct term_parser term_parser;
+typedef uint32_t term_charset[96];
+
 /*
  * Miscellaneous
  * Sundry things and external helpers.
@@ -335,3 +340,347 @@ void term_history_trim(term_history *history, unsigned int max);
 void term_history_push(term_history *history, term_line *line);
 term_line *term_history_pop(term_history *history, unsigned int reserve_width, const term_attr *attr, term_age_t age);
 unsigned int term_history_peek(term_history *history, unsigned int max, unsigned int reserve_width, const term_attr *attr, term_age_t age);
+
+/*
+ * UTF-8
+ * The UTF-decoder and encoder are adjusted for terminals and provide proper
+ * fallbacks for invalid UTF-8. In terminals it's quite usual to use fallbacks
+ * instead of rejecting invalid input. This way, old legacy applications still
+ * work (this is especially important for 7bit/ASCII DEC modes).
+ */
+
+struct term_utf8 {
+        uint32_t chars[5];
+        uint32_t ucs4;
+
+        unsigned int i_bytes : 3;
+        unsigned int n_bytes : 3;
+        unsigned int valid : 1;
+};
+
+size_t term_utf8_encode(char *out_utf8, uint32_t g);
+const uint32_t *term_utf8_decode(term_utf8 *p, size_t *out_len, char c);
+
+/*
+ * Parsers
+ * The term_parser object parses control-sequences for both host and terminal
+ * side. Based on this parser, there is a set of command-parsers that take a
+ * term_seq sequence and returns the command it represents. This is different
+ * for host and terminal side so a different set of parsers is provided.
+ */
+
+enum {
+        TERM_SEQ_NONE,                  /* placeholder, no sequence parsed */
+
+        TERM_SEQ_IGNORE,                /* no-op character */
+        TERM_SEQ_GRAPHIC,               /* graphic character */
+        TERM_SEQ_CONTROL,               /* control character */
+        TERM_SEQ_ESCAPE,                /* escape sequence */
+        TERM_SEQ_CSI,                   /* control sequence function */
+        TERM_SEQ_DCS,                   /* device control string */
+        TERM_SEQ_OSC,                   /* operating system control */
+
+        TERM_SEQ_CNT
+};
+
+enum {
+        /* these must be kept compatible to (1U << (ch - 0x20)) */
+
+        TERM_SEQ_FLAG_SPACE             = (1U <<  0),   /* char:   */
+        TERM_SEQ_FLAG_BANG              = (1U <<  1),   /* char: ! */
+        TERM_SEQ_FLAG_DQUOTE            = (1U <<  2),   /* char: " */
+        TERM_SEQ_FLAG_HASH              = (1U <<  3),   /* char: # */
+        TERM_SEQ_FLAG_CASH              = (1U <<  4),   /* char: $ */
+        TERM_SEQ_FLAG_PERCENT           = (1U <<  5),   /* char: % */
+        TERM_SEQ_FLAG_AND               = (1U <<  6),   /* char: & */
+        TERM_SEQ_FLAG_SQUOTE            = (1U <<  7),   /* char: ' */
+        TERM_SEQ_FLAG_POPEN             = (1U <<  8),   /* char: ( */
+        TERM_SEQ_FLAG_PCLOSE            = (1U <<  9),   /* char: ) */
+        TERM_SEQ_FLAG_MULT              = (1U << 10),   /* char: * */
+        TERM_SEQ_FLAG_PLUS              = (1U << 11),   /* char: + */
+        TERM_SEQ_FLAG_COMMA             = (1U << 12),   /* char: , */
+        TERM_SEQ_FLAG_MINUS             = (1U << 13),   /* char: - */
+        TERM_SEQ_FLAG_DOT               = (1U << 14),   /* char: . */
+        TERM_SEQ_FLAG_SLASH             = (1U << 15),   /* char: / */
+
+        /* 16-35 is reserved for numbers; unused */
+
+        /* COLON is reserved            = (1U << 26),      char: : */
+        /* SEMICOLON is reserved        = (1U << 27),      char: ; */
+        TERM_SEQ_FLAG_LT                = (1U << 28),   /* char: < */
+        TERM_SEQ_FLAG_EQUAL             = (1U << 29),   /* char: = */
+        TERM_SEQ_FLAG_GT                = (1U << 30),   /* char: > */
+        TERM_SEQ_FLAG_WHAT              = (1U << 31),   /* char: ? */
+};
+
+enum {
+        TERM_CMD_NONE,                          /* placeholder */
+        TERM_CMD_GRAPHIC,                       /* graphics character */
+
+        TERM_CMD_BEL,                           /* bell */
+        TERM_CMD_BS,                            /* backspace */
+        TERM_CMD_CBT,                           /* cursor-backward-tabulation */
+        TERM_CMD_CHA,                           /* cursor-horizontal-absolute */
+        TERM_CMD_CHT,                           /* cursor-horizontal-forward-tabulation */
+        TERM_CMD_CNL,                           /* cursor-next-line */
+        TERM_CMD_CPL,                           /* cursor-previous-line */
+        TERM_CMD_CR,                            /* carriage-return */
+        TERM_CMD_CUB,                           /* cursor-backward */
+        TERM_CMD_CUD,                           /* cursor-down */
+        TERM_CMD_CUF,                           /* cursor-forward */
+        TERM_CMD_CUP,                           /* cursor-position */
+        TERM_CMD_CUU,                           /* cursor-up */
+        TERM_CMD_DA1,                           /* primary-device-attributes */
+        TERM_CMD_DA2,                           /* secondary-device-attributes */
+        TERM_CMD_DA3,                           /* tertiary-device-attributes */
+        TERM_CMD_DC1,                           /* device-control-1 */
+        TERM_CMD_DC3,                           /* device-control-3 */
+        TERM_CMD_DCH,                           /* delete-character */
+        TERM_CMD_DECALN,                        /* screen-alignment-pattern */
+        TERM_CMD_DECANM,                        /* ansi-mode */
+        TERM_CMD_DECBI,                         /* back-index */
+        TERM_CMD_DECCARA,                       /* change-attributes-in-rectangular-area */
+        TERM_CMD_DECCRA,                        /* copy-rectangular-area */
+        TERM_CMD_DECDC,                         /* delete-column */
+        TERM_CMD_DECDHL_BH,                     /* double-width-double-height-line: bottom half */
+        TERM_CMD_DECDHL_TH,                     /* double-width-double-height-line: top half */
+        TERM_CMD_DECDWL,                        /* double-width-single-height-line */
+        TERM_CMD_DECEFR,
+        TERM_CMD_DECELF,
+        TERM_CMD_DECELR,
+        TERM_CMD_DECERA,
+        TERM_CMD_DECFI,
+        TERM_CMD_DECFRA,
+        TERM_CMD_DECIC,
+        TERM_CMD_DECID,
+        TERM_CMD_DECINVM,
+        TERM_CMD_DECKBD,
+        TERM_CMD_DECKPAM,
+        TERM_CMD_DECKPNM,
+        TERM_CMD_DECLFKC,
+        TERM_CMD_DECLL,
+        TERM_CMD_DECLTOD,
+        TERM_CMD_DECPCTERM,
+        TERM_CMD_DECPKA,
+        TERM_CMD_DECPKFMR,
+        TERM_CMD_DECRARA,
+        TERM_CMD_DECRC,
+        TERM_CMD_DECREQTPARM,
+        TERM_CMD_DECRPKT,
+        TERM_CMD_DECRQCRA,
+        TERM_CMD_DECRQDE,
+        TERM_CMD_DECRQKT,
+        TERM_CMD_DECRQLP,
+        TERM_CMD_DECRQM_ANSI,
+        TERM_CMD_DECRQM_DEC,
+        TERM_CMD_DECRQPKFM,
+        TERM_CMD_DECRQPSR,
+        TERM_CMD_DECRQTSR,
+        TERM_CMD_DECRQUPSS,
+        TERM_CMD_DECSACE,
+        TERM_CMD_DECSASD,
+        TERM_CMD_DECSC,
+        TERM_CMD_DECSCA,
+        TERM_CMD_DECSCL,
+        TERM_CMD_DECSCP,
+        TERM_CMD_DECSCPP,
+        TERM_CMD_DECSCS,
+        TERM_CMD_DECSCUSR,
+        TERM_CMD_DECSDDT,
+        TERM_CMD_DECSDPT,
+        TERM_CMD_DECSED,
+        TERM_CMD_DECSEL,
+        TERM_CMD_DECSERA,
+        TERM_CMD_DECSFC,
+        TERM_CMD_DECSKCV,
+        TERM_CMD_DECSLCK,
+        TERM_CMD_DECSLE,
+        TERM_CMD_DECSLPP,
+        TERM_CMD_DECSLRM_OR_SC,
+        TERM_CMD_DECSMBV,
+        TERM_CMD_DECSMKR,
+        TERM_CMD_DECSNLS,
+        TERM_CMD_DECSPP,
+        TERM_CMD_DECSPPCS,
+        TERM_CMD_DECSPRTT,
+        TERM_CMD_DECSR,
+        TERM_CMD_DECSRFR,
+        TERM_CMD_DECSSCLS,
+        TERM_CMD_DECSSDT,
+        TERM_CMD_DECSSL,
+        TERM_CMD_DECST8C,
+        TERM_CMD_DECSTBM,
+        TERM_CMD_DECSTR,
+        TERM_CMD_DECSTRL,
+        TERM_CMD_DECSWBV,
+        TERM_CMD_DECSWL,
+        TERM_CMD_DECTID,
+        TERM_CMD_DECTME,
+        TERM_CMD_DECTST,
+        TERM_CMD_DL,
+        TERM_CMD_DSR_ANSI,
+        TERM_CMD_DSR_DEC,
+        TERM_CMD_ECH,
+        TERM_CMD_ED,
+        TERM_CMD_EL,
+        TERM_CMD_ENQ,
+        TERM_CMD_EPA,
+        TERM_CMD_FF,
+        TERM_CMD_HPA,
+        TERM_CMD_HPR,
+        TERM_CMD_HT,
+        TERM_CMD_HTS,
+        TERM_CMD_HVP,
+        TERM_CMD_ICH,
+        TERM_CMD_IL,
+        TERM_CMD_IND,
+        TERM_CMD_LF,
+        TERM_CMD_LS1R,
+        TERM_CMD_LS2,
+        TERM_CMD_LS2R,
+        TERM_CMD_LS3,
+        TERM_CMD_LS3R,
+        TERM_CMD_MC_ANSI,
+        TERM_CMD_MC_DEC,
+        TERM_CMD_NEL,
+        TERM_CMD_NP,
+        TERM_CMD_NULL,
+        TERM_CMD_PP,
+        TERM_CMD_PPA,
+        TERM_CMD_PPB,
+        TERM_CMD_PPR,
+        TERM_CMD_RC,
+        TERM_CMD_REP,
+        TERM_CMD_RI,
+        TERM_CMD_RIS,
+        TERM_CMD_RM_ANSI,
+        TERM_CMD_RM_DEC,
+        TERM_CMD_S7C1T,
+        TERM_CMD_S8C1T,
+        TERM_CMD_SCS,
+        TERM_CMD_SD,
+        TERM_CMD_SGR,
+        TERM_CMD_SI,
+        TERM_CMD_SM_ANSI,
+        TERM_CMD_SM_DEC,
+        TERM_CMD_SO,
+        TERM_CMD_SPA,
+        TERM_CMD_SS2,
+        TERM_CMD_SS3,
+        TERM_CMD_ST,
+        TERM_CMD_SU,
+        TERM_CMD_SUB,
+        TERM_CMD_TBC,
+        TERM_CMD_VPA,
+        TERM_CMD_VPR,
+        TERM_CMD_VT,
+        TERM_CMD_XTERM_CLLHP,                   /* xterm-cursor-lower-left-hp-bugfix */
+        TERM_CMD_XTERM_IHMT,                    /* xterm-initiate-highlight-mouse-tracking*/
+        TERM_CMD_XTERM_MLHP,                    /* xterm-memory-lock-hp-bugfix */
+        TERM_CMD_XTERM_MUHP,                    /* xterm-memory-unlock-hp-bugfix */
+        TERM_CMD_XTERM_RPM,                     /* xterm-restore-private-mode */
+        TERM_CMD_XTERM_RRV,                     /* xterm-reset-resource-value */
+        TERM_CMD_XTERM_RTM,                     /* xterm-reset-title-mode */
+        TERM_CMD_XTERM_SACL1,                   /* xterm-set-ansi-conformance-level-1 */
+        TERM_CMD_XTERM_SACL2,                   /* xterm-set-ansi-conformance-level-2 */
+        TERM_CMD_XTERM_SACL3,                   /* xterm-set-ansi-conformance-level-3 */
+        TERM_CMD_XTERM_SDCS,                    /* xterm-set-default-character-set */
+        TERM_CMD_XTERM_SGFX,                    /* xterm-sixel-graphics */
+        TERM_CMD_XTERM_SPM,                     /* xterm-set-private-mode */
+        TERM_CMD_XTERM_SRV,                     /* xterm-set-resource-value */
+        TERM_CMD_XTERM_STM,                     /* xterm-set-title-mode */
+        TERM_CMD_XTERM_SUCS,                    /* xterm-set-utf8-character-set */
+        TERM_CMD_XTERM_WM,                      /* xterm-window-management */
+
+        TERM_CMD_CNT
+};
+
+enum {
+        /*
+         * Charsets: DEC marks charsets according to "Digital Equ. Corp.".
+         *           NRCS marks charsets according to the "National Replacement
+         *           Character Sets". ISO marks charsets according to ISO-8859.
+         * The USERDEF charset is special and can be modified by the host.
+         */
+
+        TERM_CHARSET_NONE,
+
+        /* 96-compat charsets */
+        TERM_CHARSET_ISO_LATIN1_SUPPLEMENTAL,
+        TERM_CHARSET_BRITISH_NRCS = TERM_CHARSET_ISO_LATIN1_SUPPLEMENTAL,
+        TERM_CHARSET_ISO_LATIN2_SUPPLEMENTAL,
+        TERM_CHARSET_AMERICAN_NRCS = TERM_CHARSET_ISO_LATIN2_SUPPLEMENTAL,
+        TERM_CHARSET_ISO_LATIN5_SUPPLEMENTAL,
+        TERM_CHARSET_ISO_GREEK_SUPPLEMENTAL,
+        TERM_CHARSET_ISO_HEBREW_SUPPLEMENTAL,
+        TERM_CHARSET_ISO_LATIN_CYRILLIC,
+
+        TERM_CHARSET_96_CNT,
+
+        /* 94-compat charsets */
+        TERM_CHARSET_DEC_SPECIAL_GRAPHIC = TERM_CHARSET_96_CNT,
+        TERM_CHARSET_DEC_SUPPLEMENTAL,
+        TERM_CHARSET_DEC_TECHNICAL,
+        TERM_CHARSET_CYRILLIC_DEC,
+        TERM_CHARSET_DUTCH_NRCS,
+        TERM_CHARSET_FINNISH_NRCS,
+        TERM_CHARSET_FRENCH_NRCS,
+        TERM_CHARSET_FRENCH_CANADIAN_NRCS,
+        TERM_CHARSET_GERMAN_NRCS,
+        TERM_CHARSET_GREEK_DEC,
+        TERM_CHARSET_GREEK_NRCS,
+        TERM_CHARSET_HEBREW_DEC,
+        TERM_CHARSET_HEBREW_NRCS,
+        TERM_CHARSET_ITALIAN_NRCS,
+        TERM_CHARSET_NORWEGIAN_DANISH_NRCS,
+        TERM_CHARSET_PORTUGUESE_NRCS,
+        TERM_CHARSET_RUSSIAN_NRCS,
+        TERM_CHARSET_SCS_NRCS,
+        TERM_CHARSET_SPANISH_NRCS,
+        TERM_CHARSET_SWEDISH_NRCS,
+        TERM_CHARSET_SWISS_NRCS,
+        TERM_CHARSET_TURKISH_DEC,
+        TERM_CHARSET_TURKISH_NRCS,
+
+        TERM_CHARSET_94_CNT,
+
+        /* special charsets */
+        TERM_CHARSET_USERPREF_SUPPLEMENTAL = TERM_CHARSET_94_CNT,
+
+        TERM_CHARSET_CNT,
+};
+
+extern term_charset term_unicode_lower;
+extern term_charset term_unicode_upper;
+extern term_charset term_dec_supplemental_graphics;
+extern term_charset term_dec_special_graphics;
+
+#define TERM_PARSER_ARG_MAX (16)
+#define TERM_PARSER_ST_MAX (4096)
+
+struct term_seq {
+        unsigned int type;
+        unsigned int command;
+        uint32_t terminator;
+        unsigned int intermediates;
+        unsigned int charset;
+        unsigned int n_args;
+        int args[TERM_PARSER_ARG_MAX];
+        unsigned int n_st;
+        char *st;
+};
+
+struct term_parser {
+        term_seq seq;
+        size_t st_alloc;
+        unsigned int state;
+
+        bool is_host : 1;
+};
+
+int term_parser_new(term_parser **out, bool host);
+term_parser *term_parser_free(term_parser *parser);
+int term_parser_feed(term_parser *parser, const term_seq **seq_out, uint32_t raw);
+
+#define _term_parser_free_ _cleanup_(term_parser_freep)
+DEFINE_TRIVIAL_CLEANUP_FUNC(term_parser*, term_parser_free);
diff --git a/src/libsystemd-terminal/term-parser.c b/src/libsystemd-terminal/term-parser.c
new file mode 100644
index 0000000..1c96852
--- /dev/null
+++ b/src/libsystemd-terminal/term-parser.c
@@ -0,0 +1,1626 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright (C) 2014 David Herrmann <dh.herrmann at 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/>.
+***/
+
+/*
+ * Terminal Parser
+ * This file contains a bunch of UTF-8 helpers and the main ctlseq-parser. The
+ * parser is a simple state-machine that correctly parses all CSI, DCS, OSC, ST
+ * control sequences and generic escape sequences.
+ * The parser itself does not perform any actions but lets the caller react to
+ * detected sequences.
+ */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include "macro.h"
+#include "term-internal.h"
+#include "util.h"
+
+/**
+ * term_utf8_encode() - Encode single UCS-4 character as UTF-8
+ * @out_utf8: output buffer of at least 4 bytes or NULL
+ * @g: UCS-4 character to encode
+ *
+ * This encodes a single UCS-4 character as UTF-8 and writes it into @out_utf8.
+ * The length of the character is returned. It is not zero-terminated! If the
+ * output buffer is NULL, only the length is returned.
+ *
+ * Returns: The length in bytes that the UTF-8 representation does or would
+ *          occupy.
+ */
+size_t term_utf8_encode(char *out_utf8, uint32_t g) {
+        if (g < (1 << 7)) {
+                if (out_utf8)
+                        out_utf8[0] = g & 0x7f;
+                return 1;
+        } else if (g < (1 << 11)) {
+                if (out_utf8) {
+                        out_utf8[0] = 0xc0 | ((g >> 6) & 0x1f);
+                        out_utf8[1] = 0x80 | (g & 0x3f);
+                }
+                return 2;
+        } else if (g < (1 << 16)) {
+                if (out_utf8) {
+                        out_utf8[0] = 0xe0 | ((g >> 12) & 0x0f);
+                        out_utf8[1] = 0x80 | ((g >> 6) & 0x3f);
+                        out_utf8[2] = 0x80 | (g & 0x3f);
+                }
+                return 3;
+        } else if (g < (1 << 21)) {
+                if (out_utf8) {
+                        out_utf8[0] = 0xf0 | ((g >> 18) & 0x07);
+                        out_utf8[1] = 0x80 | ((g >> 12) & 0x3f);
+                        out_utf8[2] = 0x80 | ((g >> 6) & 0x3f);
+                        out_utf8[3] = 0x80 | (g & 0x3f);
+                }
+                return 4;
+        } else {
+                return 0;
+        }
+}
+
+/**
+ * term_utf8_decode() - Try decoding the next UCS-4 character
+ * @p: decoder object to operate on or NULL
+ * @out_len: output buffer for length of decoded UCS-4 string or NULL
+ * @c: next char to push into decoder
+ *
+ * This decodes a UTF-8 stream. It must be called for each input-byte of the
+ * UTF-8 stream and returns a UCS-4 stream. The length of the returned UCS-4
+ * string (number of parsed characters) is stored in @out_len if non-NULL. A
+ * pointer to the string is returned (or NULL if none was parsed). The string
+ * is not zero-terminated! Furthermore, the string is only valid until the next
+ * invokation of this function. It is also bound to the parser-state @p.
+ *
+ * This function is highly optimized to work with terminal-emulators. Instead
+ * of being strict about UTF-8 validity, this tries to perform a fallback to
+ * ISO-8859-1 in case a wrong series was detected. Therefore, this function
+ * might return multiple UCS-4 characters by parsing just a single UTF-8 byte.
+ *
+ * The parser state @p should be allocated and managed by the caller. There're
+ * no helpers to do that for you. To initialize it, simply reset it to all
+ * zero. You can reset or free the object at any point in time.
+ *
+ * Returns: Pointer to the UCS-4 string or NULL.
+ */
+const uint32_t *term_utf8_decode(term_utf8 *p, size_t *out_len, char c) {
+        uint32_t t, *res = NULL;
+        uint8_t byte;
+        size_t len = 0;
+
+        if (!p)
+                goto out;
+
+        byte = c;
+
+        if (!p->valid || p->i_bytes >= p->n_bytes) {
+                /*
+                 * If the previous sequence was invalid or fully parsed, start
+                 * parsing a fresh new sequence.
+                 */
+
+                if ((byte & 0xE0) == 0xC0) {
+                        /* start of two byte sequence */
+                        t = byte & 0x1F;
+                        p->n_bytes = 2;
+                        p->i_bytes = 1;
+                        p->valid = 1;
+                } else if ((byte & 0xF0) == 0xE0) {
+                        /* start of three byte sequence */
+                        t = byte & 0x0F;
+                        p->n_bytes = 3;
+                        p->i_bytes = 1;
+                        p->valid = 1;
+                } else if ((byte & 0xF8) == 0xF0) {
+                        /* start of four byte sequence */
+                        t = byte & 0x07;
+                        p->n_bytes = 4;
+                        p->i_bytes = 1;
+                        p->valid = 1;
+                } else {
+                        /* Either of:
+                         *  - single ASCII 7-bit char
+                         *  - out-of-sync continuation byte
+                         *  - overlong encoding
+                         * All of them are treated as single byte ISO-8859-1 */
+                        t = byte;
+                        p->n_bytes = 1;
+                        p->i_bytes = 1;
+                        p->valid = 0;
+                }
+
+                p->chars[0] = byte;
+                p->ucs4 = t << (6 * (p->n_bytes - p->i_bytes));
+        } else {
+                /*
+                 * ..otherwise, try to continue the previous sequence..
+                 */
+
+                if ((byte & 0xC0) == 0x80) {
+                        /*
+                         * Valid continuation byte. Append to sequence and
+                         * update the ucs4 cache accordingly.
+                         */
+
+                        t = byte & 0x3F;
+                        p->chars[p->i_bytes++] = byte;
+                        p->ucs4 |= t << (6 * (p->n_bytes - p->i_bytes));
+                } else {
+                        /*
+                         * Invalid continuation? Treat cached sequence as
+                         * ISO-8859-1, but parse the new char as valid new
+                         * starting character. If it's a new single-byte UTF-8
+                         * sequence, we immediately return it in the same run,
+                         * otherwise, we might suffer from starvation.
+                         */
+
+                        if ((byte & 0xE0) == 0xC0 ||
+                            (byte & 0xF0) == 0xE0 ||
+                            (byte & 0xF8) == 0xF0) {
+                                /*
+                                 * New multi-byte sequence. Move to-be-returned
+                                 * data at the end and start new sequence. Only
+                                 * return the old sequence.
+                                 */
+
+                                memmove(p->chars + 1,
+                                        p->chars,
+                                        sizeof(*p->chars) * p->i_bytes);
+                                res = p->chars + 1;
+                                len = p->i_bytes;
+
+                                if ((byte & 0xE0) == 0xC0) {
+                                        /* start of two byte sequence */
+                                        t = byte & 0x1F;
+                                        p->n_bytes = 2;
+                                        p->i_bytes = 1;
+                                        p->valid = 1;
+                                } else if ((byte & 0xF0) == 0xE0) {
+                                        /* start of three byte sequence */
+                                        t = byte & 0x0F;
+                                        p->n_bytes = 3;
+                                        p->i_bytes = 1;
+                                        p->valid = 1;
+                                } else if ((byte & 0xF8) == 0xF0) {
+                                        /* start of four byte sequence */
+                                        t = byte & 0x07;
+                                        p->n_bytes = 4;
+                                        p->i_bytes = 1;
+                                        p->valid = 1;
+                                }
+
+                                p->chars[0] = byte;
+                                p->ucs4 = t << (6 * (p->n_bytes - p->i_bytes));
+
+                                goto out;
+                        } else {
+                                /*
+                                 * New single byte sequence, append to output
+                                 * and return combined sequence.
+                                 */
+
+                                p->chars[p->i_bytes++] = byte;
+                                p->valid = 0;
+                        }
+                }
+        }
+
+        /*
+         * Check whether a full sequence (valid or invalid) has been parsed and
+         * then return it. Otherwise, return nothing.
+         */
+        if (p->valid) {
+                /* still parsing? then bail out */
+                if (p->i_bytes < p->n_bytes)
+                        goto out;
+
+                res = &p->ucs4;
+                len = 1;
+        } else {
+                res = p->chars;
+                len = p->i_bytes;
+        }
+
+        p->valid = 0;
+        p->i_bytes = 0;
+        p->n_bytes = 0;
+
+out:
+        if (out_len)
+                *out_len = len;
+        return len > 0 ? res : NULL;
+}
+
+/*
+ * Command Parser
+ * The ctl-seq parser "term_parser" only detects whole sequences, it does not
+ * detect the specific command. Once a sequence is parsed, the command-parsers
+ * are used to figure out their meaning. Note that this depends on whether we
+ * run on the host or terminal side.
+ */
+
+static unsigned int term_parse_host_control(const term_seq *seq) {
+        assert_return(seq, TERM_CMD_NONE);
+
+        switch (seq->terminator) {
+        case 0x00: /* NUL */
+                return TERM_CMD_NULL;
+        case 0x05: /* ENQ */
+                return TERM_CMD_ENQ;
+        case 0x07: /* BEL */
+                return TERM_CMD_BEL;
+        case 0x08: /* BS */
+                return TERM_CMD_BS;
+        case 0x09: /* HT */
+                return TERM_CMD_HT;
+        case 0x0a: /* LF */
+                return TERM_CMD_LF;
+        case 0x0b: /* VT */
+                return TERM_CMD_VT;
+        case 0x0c: /* FF */
+                return TERM_CMD_FF;
+        case 0x0d: /* CR */
+                return TERM_CMD_CR;
+        case 0x0e: /* SO */
+                return TERM_CMD_SO;
+        case 0x0f: /* SI */
+                return TERM_CMD_SI;
+        case 0x11: /* DC1 */
+                return TERM_CMD_DC1;
+        case 0x13: /* DC3 */
+                return TERM_CMD_DC3;
+        case 0x18: /* CAN */
+                /* this is already handled by the state-machine */
+                break;
+        case 0x1a: /* SUB */
+                return TERM_CMD_SUB;
+        case 0x1b: /* ESC */
+                /* this is already handled by the state-machine */
+                break;
+        case 0x1f: /* DEL */
+                /* this is already handled by the state-machine */
+                break;
+        case 0x84: /* IND */
+                return TERM_CMD_IND;
+        case 0x85: /* NEL */
+                return TERM_CMD_NEL;
+        case 0x88: /* HTS */
+                return TERM_CMD_HTS;
+        case 0x8d: /* RI */
+                return TERM_CMD_RI;
+        case 0x8e: /* SS2 */
+                return TERM_CMD_SS2;
+        case 0x8f: /* SS3 */
+                return TERM_CMD_SS3;
+        case 0x90: /* DCS */
+                /* this is already handled by the state-machine */
+                break;
+        case 0x96: /* SPA */
+                return TERM_CMD_SPA;
+        case 0x97: /* EPA */
+                return TERM_CMD_EPA;
+        case 0x98: /* SOS */
+                /* this is already handled by the state-machine */
+                break;
+        case 0x9a: /* DECID */
+                return TERM_CMD_DECID;
+        case 0x9b: /* CSI */
+                /* this is already handled by the state-machine */
+                break;
+        case 0x9c: /* ST */
+                return TERM_CMD_ST;
+        case 0x9d: /* OSC */
+                /* this is already handled by the state-machine */
+                break;
+        case 0x9e: /* PM */
+                /* this is already handled by the state-machine */
+                break;
+        case 0x9f: /* APC */
+                /* this is already handled by the state-machine */
+                break;
+        }
+
+        return TERM_CMD_NONE;
+}
+
+static inline int charset_from_cmd(uint32_t raw, unsigned int flags, bool require_96) {
+        static const struct {
+                uint32_t raw;
+                unsigned int flags;
+        } charset_cmds[] = {
+                /* 96-compat charsets */
+                [TERM_CHARSET_ISO_LATIN1_SUPPLEMENTAL]   = { .raw = 'A', .flags = 0 },
+                [TERM_CHARSET_ISO_LATIN2_SUPPLEMENTAL]   = { .raw = 'B', .flags = 0 },
+                [TERM_CHARSET_ISO_LATIN5_SUPPLEMENTAL]   = { .raw = 'M', .flags = 0 },
+                [TERM_CHARSET_ISO_GREEK_SUPPLEMENTAL]    = { .raw = 'F', .flags = 0 },
+                [TERM_CHARSET_ISO_HEBREW_SUPPLEMENTAL]   = { .raw = 'H', .flags = 0 },
+                [TERM_CHARSET_ISO_LATIN_CYRILLIC]        = { .raw = 'L', .flags = 0 },
+
+                /* 94-compat charsets */
+                [TERM_CHARSET_DEC_SPECIAL_GRAPHIC]       = { .raw = '0', .flags = 0 },
+                [TERM_CHARSET_DEC_SUPPLEMENTAL]          = { .raw = '5', .flags = TERM_SEQ_FLAG_PERCENT },
+                [TERM_CHARSET_DEC_TECHNICAL]             = { .raw = '>', .flags = 0 },
+                [TERM_CHARSET_CYRILLIC_DEC]              = { .raw = '4', .flags = TERM_SEQ_FLAG_AND },
+                [TERM_CHARSET_DUTCH_NRCS]                = { .raw = '4', .flags = 0 },
+                [TERM_CHARSET_FINNISH_NRCS]              = { .raw = '5', .flags = 0 },
+                [TERM_CHARSET_FRENCH_NRCS]               = { .raw = 'R', .flags = 0 },
+                [TERM_CHARSET_FRENCH_CANADIAN_NRCS]      = { .raw = '9', .flags = 0 },
+                [TERM_CHARSET_GERMAN_NRCS]               = { .raw = 'K', .flags = 0 },
+                [TERM_CHARSET_GREEK_DEC]                 = { .raw = '?', .flags = TERM_SEQ_FLAG_DQUOTE },
+                [TERM_CHARSET_GREEK_NRCS]                = { .raw = '>', .flags = TERM_SEQ_FLAG_DQUOTE },
+                [TERM_CHARSET_HEBREW_DEC]                = { .raw = '4', .flags = TERM_SEQ_FLAG_DQUOTE },
+                [TERM_CHARSET_HEBREW_NRCS]               = { .raw = '=', .flags = TERM_SEQ_FLAG_PERCENT },
+                [TERM_CHARSET_ITALIAN_NRCS]              = { .raw = 'Y', .flags = 0 },
+                [TERM_CHARSET_NORWEGIAN_DANISH_NRCS]     = { .raw = '`', .flags = 0 },
+                [TERM_CHARSET_PORTUGUESE_NRCS]           = { .raw = '6', .flags = TERM_SEQ_FLAG_PERCENT },
+                [TERM_CHARSET_RUSSIAN_NRCS]              = { .raw = '5', .flags = TERM_SEQ_FLAG_AND },
+                [TERM_CHARSET_SCS_NRCS]                  = { .raw = '3', .flags = TERM_SEQ_FLAG_PERCENT },
+                [TERM_CHARSET_SPANISH_NRCS]              = { .raw = 'Z', .flags = 0 },
+                [TERM_CHARSET_SWEDISH_NRCS]              = { .raw = '7', .flags = 0 },
+                [TERM_CHARSET_SWISS_NRCS]                = { .raw = '=', .flags = 0 },
+                [TERM_CHARSET_TURKISH_DEC]               = { .raw = '0', .flags = TERM_SEQ_FLAG_PERCENT },
+                [TERM_CHARSET_TURKISH_NRCS]              = { .raw = '2', .flags = TERM_SEQ_FLAG_PERCENT },
+
+                /* special charsets */
+                [TERM_CHARSET_USERPREF_SUPPLEMENTAL]     = { .raw = '<', .flags = 0 },
+
+                /* secondary choices */
+                [TERM_CHARSET_CNT + TERM_CHARSET_FINNISH_NRCS]            = { .raw = 'C', .flags = 0 },
+                [TERM_CHARSET_CNT + TERM_CHARSET_FRENCH_NRCS]             = { .raw = 'f', .flags = 0 },
+                [TERM_CHARSET_CNT + TERM_CHARSET_FRENCH_CANADIAN_NRCS]    = { .raw = 'Q', .flags = 0 },
+                [TERM_CHARSET_CNT + TERM_CHARSET_NORWEGIAN_DANISH_NRCS]   = { .raw = 'E', .flags = 0 },
+                [TERM_CHARSET_CNT + TERM_CHARSET_SWEDISH_NRCS]            = { .raw = 'H', .flags = 0 }, /* unused; conflicts with ISO_HEBREW */
+
+                /* tertiary choices */
+                [TERM_CHARSET_CNT + TERM_CHARSET_CNT + TERM_CHARSET_NORWEGIAN_DANISH_NRCS] = { .raw = '6', .flags = 0 },
+        };
+        size_t i, cs;
+
+        /*
+         * Secondary choice on SWEDISH_NRCS and primary choice on
+         * ISO_HEBREW_SUPPLEMENTAL have a conflict: raw=="H", flags==0.
+         * We always choose the ISO 96-compat set, which is what VT510 does.
+         */
+
+        for (i = 0; i < ELEMENTSOF(charset_cmds); ++i) {
+                if (charset_cmds[i].raw == raw && charset_cmds[i].flags == flags) {
+                        cs = i;
+                        while (cs >= TERM_CHARSET_CNT)
+                                cs -= TERM_CHARSET_CNT;
+
+                        if (!require_96 || cs < TERM_CHARSET_96_CNT || cs >= TERM_CHARSET_94_CNT)
+                                return cs;
+                }
+        }
+
+        return -ENOENT;
+}
+
+/* true if exactly one bit in @value is set */
+static inline bool exactly_one_bit_set(unsigned int value) {
+        return __builtin_popcount(value) == 1;
+}
+
+static unsigned int term_parse_host_escape(const term_seq *seq, unsigned int *cs_out) {
+        unsigned int t, flags;
+        int cs;
+
+        assert_return(seq, TERM_CMD_NONE);
+
+        flags = seq->intermediates;
+        t = TERM_SEQ_FLAG_POPEN | TERM_SEQ_FLAG_PCLOSE | TERM_SEQ_FLAG_MULT |
+            TERM_SEQ_FLAG_PLUS  | TERM_SEQ_FLAG_MINUS  | TERM_SEQ_FLAG_DOT  |
+            TERM_SEQ_FLAG_SLASH;
+
+        if (exactly_one_bit_set(flags & t)) {
+                switch (flags & t) {
+                case TERM_SEQ_FLAG_POPEN:
+                case TERM_SEQ_FLAG_PCLOSE:
+                case TERM_SEQ_FLAG_MULT:
+                case TERM_SEQ_FLAG_PLUS:
+                        cs = charset_from_cmd(seq->terminator, flags & ~t, false);
+                        break;
+                case TERM_SEQ_FLAG_MINUS:
+                case TERM_SEQ_FLAG_DOT:
+                case TERM_SEQ_FLAG_SLASH:
+                        cs = charset_from_cmd(seq->terminator, flags & ~t, true);
+                        break;
+                default:
+                        cs = -ENOENT;
+                        break;
+                }
+
+                if (cs >= 0) {
+                        if (cs_out)
+                                *cs_out = cs;
+                        return TERM_CMD_SCS;
+                }
+
+                /* looked like a charset-cmd but wasn't; continue */
+        }
+
+        switch (seq->terminator) {
+        case '3':
+                if (flags == TERM_SEQ_FLAG_HASH) /* DECDHL top-half */
+                        return TERM_CMD_DECDHL_TH;
+                break;
+        case '4':
+                if (flags == TERM_SEQ_FLAG_HASH) /* DECDHL bottom-half */
+                        return TERM_CMD_DECDHL_BH;
+                break;
+        case '5':
+                if (flags == TERM_SEQ_FLAG_HASH) /* DECSWL */
+                        return TERM_CMD_DECSWL;
+                break;
+        case '6':
+                if (flags == 0) /* DECBI */
+                        return TERM_CMD_DECBI;
+                else if (flags == TERM_SEQ_FLAG_HASH) /* DECDWL */
+                        return TERM_CMD_DECDWL;
+                break;
+        case '7':
+                if (flags == 0) /* DECSC */
+                        return TERM_CMD_DECSC;
+                break;
+        case '8':
+                if (flags == 0) /* DECRC */
+                        return TERM_CMD_DECRC;
+                else if (flags == TERM_SEQ_FLAG_HASH) /* DECALN */
+                        return TERM_CMD_DECALN;
+                break;
+        case '9':
+                if (flags == 0) /* DECFI */
+                        return TERM_CMD_DECFI;
+                break;
+        case '<':
+                if (flags == 0) /* DECANM */
+                        return TERM_CMD_DECANM;
+                break;
+        case '=':
+                if (flags == 0) /* DECKPAM */
+                        return TERM_CMD_DECKPAM;
+                break;
+        case '>':
+                if (flags == 0) /* DECKPNM */
+                        return TERM_CMD_DECKPNM;
+                break;
+        case '@':
+                if (flags == TERM_SEQ_FLAG_PERCENT) {
+                        /* Select default character set */
+                        return TERM_CMD_XTERM_SDCS;
+                }
+                break;
+        case 'D':
+                if (flags == 0) /* IND */
+                        return TERM_CMD_IND;
+                break;
+        case 'E':
+                if (flags == 0) /* NEL */
+                        return TERM_CMD_NEL;
+                break;
+        case 'F':
+                if (flags == 0) /* Cursor to lower-left corner of screen */
+                        return TERM_CMD_XTERM_CLLHP;
+                else if (flags == TERM_SEQ_FLAG_SPACE) /* S7C1T */
+                        return TERM_CMD_S7C1T;
+                break;
+        case 'G':
+                if (flags == TERM_SEQ_FLAG_SPACE) { /* S8C1T */
+                        return TERM_CMD_S8C1T;
+                } else if (flags == TERM_SEQ_FLAG_PERCENT) {
+                        /* Select UTF-8 character set */
+                        return TERM_CMD_XTERM_SUCS;
+                }
+                break;
+        case 'H':
+                if (flags == 0) /* HTS */
+                        return TERM_CMD_HTS;
+                break;
+        case 'L':
+                if (flags == TERM_SEQ_FLAG_SPACE) {
+                        /* Set ANSI conformance level 1 */
+                        return TERM_CMD_XTERM_SACL1;
+                }
+                break;
+        case 'M':
+                if (flags == 0) { /* RI */
+                        return TERM_CMD_RI;
+                } else if (flags == TERM_SEQ_FLAG_SPACE) {
+                        /* Set ANSI conformance level 2 */
+                        return TERM_CMD_XTERM_SACL2;
+                }
+                break;
+        case 'N':
+                if (flags == 0) { /* SS2 */
+                        return TERM_CMD_SS2;
+                } else if (flags == TERM_SEQ_FLAG_SPACE) {
+                        /* Set ANSI conformance level 3 */
+                        return TERM_CMD_XTERM_SACL3;
+                }
+                break;
+        case 'O':
+                if (flags == 0) /* SS3 */
+                        return TERM_CMD_SS3;
+                break;
+        case 'P':
+                if (flags == 0) /* DCS: this is already handled by the state-machine */
+                        return 0;
+                break;
+        case 'V':
+                if (flags == 0) /* SPA */
+                        return TERM_CMD_SPA;
+                break;
+        case 'W':
+                if (flags == 0) /* EPA */
+                        return TERM_CMD_EPA;
+                break;
+        case 'X':
+                if (flags == 0) { /* SOS */
+                        /* this is already handled by the state-machine */
+                        break;
+                }
+                break;
+        case 'Z':
+                if (flags == 0) /* DECID */
+                        return TERM_CMD_DECID;
+                break;
+        case '[':
+                if (flags == 0) { /* CSI */
+                        /* this is already handled by the state-machine */
+                        break;
+                }
+                break;
+        case '\\':
+                if (flags == 0) /* ST */
+                        return TERM_CMD_ST;
+                break;
+        case ']':
+                if (flags == 0) { /* OSC */
+                        /* this is already handled by the state-machine */
+                        break;
+                }
+                break;
+        case '^':
+                if (flags == 0) { /* PM */
+                        /* this is already handled by the state-machine */
+                        break;
+                }
+                break;
+        case '_':
+                if (flags == 0) { /* APC */
+                        /* this is already handled by the state-machine */
+                        break;
+                }
+                break;
+        case 'c':
+                if (flags == 0) /* RIS */
+                        return TERM_CMD_RIS;
+                break;
+        case 'l':
+                if (flags == 0) /* Memory lock */
+                        return TERM_CMD_XTERM_MLHP;
+                break;
+        case 'm':
+                if (flags == 0) /* Memory unlock */
+                        return TERM_CMD_XTERM_MUHP;
+                break;
+        case 'n':
+                if (flags == 0) /* LS2 */
+                        return TERM_CMD_LS2;
+                break;
+        case 'o':
+                if (flags == 0) /* LS3 */
+                        return TERM_CMD_LS3;
+                break;
+        case '|':
+                if (flags == 0) /* LS3R */
+                        return TERM_CMD_LS3R;
+                break;
+        case '}':
+                if (flags == 0) /* LS2R */
+                        return TERM_CMD_LS2R;
+                break;
+        case '~':
+                if (flags == 0) /* LS1R */
+                        return TERM_CMD_LS1R;
+                break;
+        }
+
+        return TERM_CMD_NONE;
+}
+
+static unsigned int term_parse_host_csi(const term_seq *seq) {
+        unsigned int flags;
+
+        assert_return(seq, TERM_CMD_NONE);
+
+        flags = seq->intermediates;
+
+        switch (seq->terminator) {
+        case 'A':
+                if (flags == 0) /* CUU */
+                        return TERM_CMD_CUU;
+                break;
+        case 'a':
+                if (flags == 0) /* HPR */
+                        return TERM_CMD_HPR;
+                break;
+        case 'B':
+                if (flags == 0) /* CUD */
+                        return TERM_CMD_CUD;
+                break;
+        case 'b':
+                if (flags == 0) /* REP */
+                        return TERM_CMD_REP;
+                break;
+        case 'C':
+                if (flags == 0) /* CUF */
+                        return TERM_CMD_CUF;
+                break;
+        case 'c':
+                if (flags == 0) /* DA1 */
+                        return TERM_CMD_DA1;
+                else if (flags == TERM_SEQ_FLAG_GT) /* DA2 */
+                        return TERM_CMD_DA2;
+                else if (flags == TERM_SEQ_FLAG_EQUAL) /* DA3 */
+                        return TERM_CMD_DA3;
+                break;
+        case 'D':
+                if (flags == 0) /* CUB */
+                        return TERM_CMD_CUB;
+                break;
+        case 'd':
+                if (flags == 0) /* VPA */
+                        return TERM_CMD_VPA;
+                break;
+        case 'E':
+                if (flags == 0) /* CNL */
+                        return TERM_CMD_CNL;
+                break;
+        case 'e':
+                if (flags == 0) /* VPR */
+                        return TERM_CMD_VPR;
+                break;
+        case 'F':
+                if (flags == 0) /* CPL */
+                        return TERM_CMD_CPL;
+                break;
+        case 'f':
+                if (flags == 0) /* HVP */
+                        return TERM_CMD_HVP;
+                break;
+        case 'G':
+                if (flags == 0) /* CHA */
+                        return TERM_CMD_CHA;
+                break;
+        case 'g':
+                if (flags == 0) /* TBC */
+                        return TERM_CMD_TBC;
+                else if (flags == TERM_SEQ_FLAG_MULT) /* DECLFKC */
+                        return TERM_CMD_DECLFKC;
+                break;
+        case 'H':
+                if (flags == 0) /* CUP */
+                        return TERM_CMD_CUP;
+                break;
+        case 'h':
+                if (flags == 0) /* SM ANSI */
+                        return TERM_CMD_SM_ANSI;
+                else if (flags == TERM_SEQ_FLAG_WHAT) /* SM DEC */
+                        return TERM_CMD_SM_DEC;
+                break;
+        case 'I':
+                if (flags == 0) /* CHT */
+                        return TERM_CMD_CHT;
+                break;
+        case 'i':
+                if (flags == 0) /* MC ANSI */
+                        return TERM_CMD_MC_ANSI;
+                else if (flags == TERM_SEQ_FLAG_WHAT) /* MC DEC */
+                        return TERM_CMD_MC_DEC;
+                break;
+        case 'J':
+                if (flags == 0) /* ED */
+                        return TERM_CMD_ED;
+                else if (flags == TERM_SEQ_FLAG_WHAT) /* DECSED */
+                        return TERM_CMD_DECSED;
+                break;
+        case 'K':
+                if (flags == 0) /* EL */
+                        return TERM_CMD_EL;
+                else if (flags == TERM_SEQ_FLAG_WHAT) /* DECSEL */
+                        return TERM_CMD_DECSEL;
+                break;
+        case 'L':
+                if (flags == 0) /* IL */
+                        return TERM_CMD_IL;
+                break;
+        case 'l':
+                if (flags == 0) /* RM ANSI */
+                        return TERM_CMD_RM_ANSI;
+                else if (flags == TERM_SEQ_FLAG_WHAT) /* RM DEC */
+                        return TERM_CMD_RM_DEC;
+                break;
+        case 'M':
+                if (flags == 0) /* DL */
+                        return TERM_CMD_DL;
+                break;
+        case 'm':
+                if (flags == 0) /* SGR */
+                        return TERM_CMD_SGR;
+                else if (flags == TERM_SEQ_FLAG_GT) /* XTERM SMR */
+                        return TERM_CMD_XTERM_SRV;
+                break;
+        case 'n':
+                if (flags == 0) /* DSR ANSI */
+                        return TERM_CMD_DSR_ANSI;
+                else if (flags == TERM_SEQ_FLAG_GT) /* XTERM RMR */
+                        return TERM_CMD_XTERM_RRV;
+                else if (flags == TERM_SEQ_FLAG_WHAT) /* DSR DEC */
+                        return TERM_CMD_DSR_DEC;
+                break;
+        case 'P':
+                if (flags == 0) /* DCH */
+                        return TERM_CMD_DCH;
+                else if (flags == TERM_SEQ_FLAG_SPACE) /* PPA */
+                        return TERM_CMD_PPA;
+                break;
+        case 'p':
+                if (flags == 0) /* DECSSL */
+                        return TERM_CMD_DECSSL;
+                else if (flags == TERM_SEQ_FLAG_SPACE) /* DECSSCLS */
+                        return TERM_CMD_DECSSCLS;
+                else if (flags == TERM_SEQ_FLAG_BANG) /* DECSTR */
+                        return TERM_CMD_DECSTR;
+                else if (flags == TERM_SEQ_FLAG_DQUOTE) /* DECSCL */
+                        return TERM_CMD_DECSCL;
+                else if (flags == TERM_SEQ_FLAG_CASH) /* DECRQM-ANSI */
+                        return TERM_CMD_DECRQM_ANSI;
+                else if (flags == (TERM_SEQ_FLAG_CASH | TERM_SEQ_FLAG_WHAT)) /* DECRQM-DEC */
+                        return TERM_CMD_DECRQM_DEC;
+                else if (flags == TERM_SEQ_FLAG_PCLOSE) /* DECSDPT */
+                        return TERM_CMD_DECSDPT;
+                else if (flags == TERM_SEQ_FLAG_MULT) /* DECSPPCS */
+                        return TERM_CMD_DECSPPCS;
+                else if (flags == TERM_SEQ_FLAG_PLUS) /* DECSR */
+                        return TERM_CMD_DECSR;
+                else if (flags == TERM_SEQ_FLAG_COMMA) /* DECLTOD */
+                        return TERM_CMD_DECLTOD;
+                else if (flags == TERM_SEQ_FLAG_GT) /* XTERM SPM */
+                        return TERM_CMD_XTERM_SPM;
+                break;
+        case 'Q':
+                if (flags == TERM_SEQ_FLAG_SPACE) /* PPR */
+                        return TERM_CMD_PPR;
+                break;
+        case 'q':
+                if (flags == 0) /* DECLL */
+                        return TERM_CMD_DECLL;
+                else if (flags == TERM_SEQ_FLAG_SPACE) /* DECSCUSR */
+                        return TERM_CMD_DECSCUSR;
+                else if (flags == TERM_SEQ_FLAG_DQUOTE) /* DECSCA */
+                        return TERM_CMD_DECSCA;
+                else if (flags == TERM_SEQ_FLAG_CASH) /* DECSDDT */
+                        return TERM_CMD_DECSDDT;
+                else if (flags == TERM_SEQ_FLAG_MULT) /* DECSRC */
+                        return TERM_CMD_DECSR;
+                else if (flags == TERM_SEQ_FLAG_PLUS) /* DECELF */
+                        return TERM_CMD_DECELF;
+                else if (flags == TERM_SEQ_FLAG_COMMA) /* DECTID */
+                        return TERM_CMD_DECTID;
+                break;
+        case 'R':
+                if (flags == TERM_SEQ_FLAG_SPACE) /* PPB */
+                        return TERM_CMD_PPB;
+                break;
+        case 'r':
+                if (flags == 0) {
+                        /* DECSTBM */
+                        return TERM_CMD_DECSTBM;
+                } else if (flags == TERM_SEQ_FLAG_SPACE) {
+                        /* DECSKCV */
+                        return TERM_CMD_DECSKCV;
+                } else if (flags == TERM_SEQ_FLAG_CASH) {
+                        /* DECCARA */
+                        return TERM_CMD_DECCARA;
+                } else if (flags == TERM_SEQ_FLAG_MULT) {
+                        /* DECSCS */
+                        return TERM_CMD_DECSCS;
+                } else if (flags == TERM_SEQ_FLAG_PLUS) {
+                        /* DECSMKR */
+                        return TERM_CMD_DECSMKR;
+                } else if (flags == TERM_SEQ_FLAG_WHAT) {
+                        /*
+                         * There's a conflict between DECPCTERM and XTERM-RPM.
+                         * XTERM-RPM takes a single argument, DECPCTERM takes 2.
+                         * Split both up and forward the call to the closer
+                         * match.
+                         */
+                        if (seq->n_args <= 1) /* XTERM RPM */
+                                return TERM_CMD_XTERM_RPM;
+                        else if (seq->n_args >= 2) /* DECPCTERM */
+                                return TERM_CMD_DECPCTERM;
+                }
+                break;
+        case 'S':
+                if (flags == 0) /* SU */
+                        return TERM_CMD_SU;
+                else if (flags == TERM_SEQ_FLAG_WHAT) /* XTERM SGFX */
+                        return TERM_CMD_XTERM_SGFX;
+                break;
+        case 's':
+                if (flags == 0) {
+                        /*
+                         * There's a conflict between DECSLRM and SC-ANSI which
+                         * cannot be resolved without knowing the state of
+                         * DECLRMM. We leave that decision up to the caller.
+                         */
+                        return TERM_CMD_DECSLRM_OR_SC;
+                } else if (flags == TERM_SEQ_FLAG_CASH) {
+                        /* DECSPRTT */
+                        return TERM_CMD_DECSPRTT;
+                } else if (flags == TERM_SEQ_FLAG_MULT) {
+                        /* DECSFC */
+                        return TERM_CMD_DECSFC;
+                } else if (flags == TERM_SEQ_FLAG_WHAT) {
+                        /* XTERM SPM */
+                        return TERM_CMD_XTERM_SPM;
+                }
+                break;
+        case 'T':
+                if (flags == 0) {
+                        /*
+                         * Awesome: There's a conflict between SD and XTERM IHMT
+                         * that we have to resolve by checking the parameter
+                         * count.. XTERM_IHMT needs exactly 5 arguments, SD
+                         * takes 0 or 1. We're conservative here and give both
+                         * a wider range to allow unused arguments (compat...).
+                         */
+                        if (seq->n_args >= 5) {
+                                /* XTERM IHMT */
+                                return TERM_CMD_XTERM_IHMT;
+                        } else if (seq->n_args < 5) {
+                                /* SD */
+                                return TERM_CMD_SD;
+                        }
+                } else if (flags == TERM_SEQ_FLAG_GT) {
+                        /* XTERM RTM */
+                        return TERM_CMD_XTERM_RTM;
+                }
+                break;
+        case 't':
+                if (flags == 0) {
+                        if (seq->n_args > 0 && seq->args[0] < 24) {
+                                /* XTERM WM */
+                                return TERM_CMD_XTERM_WM;
+                        } else {
+                                /* DECSLPP */
+                                return TERM_CMD_DECSLPP;
+                        }
+                } else if (flags == TERM_SEQ_FLAG_SPACE) {
+                        /* DECSWBV */
+                        return TERM_CMD_DECSWBV;
+                } else if (flags == TERM_SEQ_FLAG_DQUOTE) {
+                        /* DECSRFR */
+                        return TERM_CMD_DECSRFR;
+                } else if (flags == TERM_SEQ_FLAG_CASH) {
+                        /* DECRARA */
+                        return TERM_CMD_DECRARA;
+                } else if (flags == TERM_SEQ_FLAG_GT) {
+                        /* XTERM STM */
+                        return TERM_CMD_XTERM_STM;
+                }
+                break;
+        case 'U':
+                if (flags == 0) /* NP */
+                        return TERM_CMD_NP;
+                break;
+        case 'u':
+                if (flags == 0) {
+                        /* RC */
+                        return TERM_CMD_RC;
+                } else if (flags == TERM_SEQ_FLAG_SPACE) {
+                        /* DECSMBV */
+                        return TERM_CMD_DECSMBV;
+                } else if (flags == TERM_SEQ_FLAG_DQUOTE) {
+                        /* DECSTRL */
+                        return TERM_CMD_DECSTRL;
+                } else if (flags == TERM_SEQ_FLAG_WHAT) {
+                        /* DECRQUPSS */
+                        return TERM_CMD_DECRQUPSS;
+                } else if (seq->args[0] == 1 && flags == TERM_SEQ_FLAG_CASH) {
+                        /* DECRQTSR */
+                        return TERM_CMD_DECRQTSR;
+                } else if (flags == TERM_SEQ_FLAG_MULT) {
+                        /* DECSCP */
+                        return TERM_CMD_DECSCP;
+                } else if (flags == TERM_SEQ_FLAG_COMMA) {
+                        /* DECRQKT */
+                        return TERM_CMD_DECRQKT;
+                }
+                break;
+        case 'V':
+                if (flags == 0) /* PP */
+                        return TERM_CMD_PP;
+                break;
+        case 'v':
+                if (flags == TERM_SEQ_FLAG_SPACE) /* DECSLCK */
+                        return TERM_CMD_DECSLCK;
+                else if (flags == TERM_SEQ_FLAG_DQUOTE) /* DECRQDE */
+                        return TERM_CMD_DECRQDE;
+                else if (flags == TERM_SEQ_FLAG_CASH) /* DECCRA */
+                        return TERM_CMD_DECCRA;
+                else if (flags == TERM_SEQ_FLAG_COMMA) /* DECRPKT */
+                        return TERM_CMD_DECRPKT;
+                break;
+        case 'W':
+                if (seq->args[0] == 5 && flags == TERM_SEQ_FLAG_WHAT) {
+                        /* DECST8C */
+                        return TERM_CMD_DECST8C;
+                }
+                break;
+        case 'w':
+                if (flags == TERM_SEQ_FLAG_CASH) /* DECRQPSR */
+                        return TERM_CMD_DECRQPSR;
+                else if (flags == TERM_SEQ_FLAG_SQUOTE) /* DECEFR */
+                        return TERM_CMD_DECEFR;
+                else if (flags == TERM_SEQ_FLAG_PLUS) /* DECSPP */
+                        return TERM_CMD_DECSPP;
+                break;
+        case 'X':
+                if (flags == 0) /* ECH */
+                        return TERM_CMD_ECH;
+                break;
+        case 'x':
+                if (flags == 0) /* DECREQTPARM */
+                        return TERM_CMD_DECREQTPARM;
+                else if (flags == TERM_SEQ_FLAG_CASH) /* DECFRA */
+                        return TERM_CMD_DECFRA;
+                else if (flags == TERM_SEQ_FLAG_MULT) /* DECSACE */
+                        return TERM_CMD_DECSACE;
+                else if (flags == TERM_SEQ_FLAG_PLUS) /* DECRQPKFM */
+                        return TERM_CMD_DECRQPKFM;
+                break;
+        case 'y':
+                if (flags == 0) /* DECTST */
+                        return TERM_CMD_DECTST;
+                else if (flags == TERM_SEQ_FLAG_MULT) /* DECRQCRA */
+                        return TERM_CMD_DECRQCRA;
+                else if (flags == TERM_SEQ_FLAG_PLUS) /* DECPKFMR */
+                        return TERM_CMD_DECPKFMR;
+                break;
+        case 'Z':
+                if (flags == 0) /* CBT */
+                        return TERM_CMD_CBT;
+                break;
+        case 'z':
+                if (flags == TERM_SEQ_FLAG_CASH) /* DECERA */
+                        return TERM_CMD_DECERA;
+                else if (flags == TERM_SEQ_FLAG_SQUOTE) /* DECELR */
+                        return TERM_CMD_DECELR;
+                else if (flags == TERM_SEQ_FLAG_MULT) /* DECINVM */
+                        return TERM_CMD_DECINVM;
+                else if (flags == TERM_SEQ_FLAG_PLUS) /* DECPKA */
+                        return TERM_CMD_DECPKA;
+                break;
+        case '@':
+                if (flags == 0) /* ICH */
+                        return TERM_CMD_ICH;
+                break;
+        case '`':
+                if (flags == 0) /* HPA */
+                        return TERM_CMD_HPA;
+                break;
+        case '{':
+                if (flags == TERM_SEQ_FLAG_CASH) /* DECSERA */
+                        return TERM_CMD_DECSERA;
+                else if (flags == TERM_SEQ_FLAG_SQUOTE) /* DECSLE */
+                        return TERM_CMD_DECSLE;
+                break;
+        case '|':
+                if (flags == TERM_SEQ_FLAG_CASH) /* DECSCPP */
+                        return TERM_CMD_DECSCPP;
+                else if (flags == TERM_SEQ_FLAG_SQUOTE) /* DECRQLP */
+                        return TERM_CMD_DECRQLP;
+                else if (flags == TERM_SEQ_FLAG_MULT) /* DECSNLS */
+                        return TERM_CMD_DECSNLS;
+                break;
+        case '}':
+                if (flags == TERM_SEQ_FLAG_SPACE) /* DECKBD */
+                        return TERM_CMD_DECKBD;
+                else if (flags == TERM_SEQ_FLAG_CASH) /* DECSASD */
+                        return TERM_CMD_DECSASD;
+                else if (flags == TERM_SEQ_FLAG_SQUOTE) /* DECIC */
+                        return TERM_CMD_DECIC;
+                break;
+        case '~':
+                if (flags == TERM_SEQ_FLAG_SPACE) /* DECTME */
+                        return TERM_CMD_DECTME;
+                else if (flags == TERM_SEQ_FLAG_CASH) /* DECSSDT */
+                        return TERM_CMD_DECSSDT;
+                else if (flags == TERM_SEQ_FLAG_SQUOTE) /* DECDC */
+                        return TERM_CMD_DECDC;
+                break;
+        }
+
+        return TERM_CMD_NONE;
+}
+
+/*
+ * State Machine
+ * This parser controls the parser-state and returns any detected sequence to
+ * the caller. The parser is based on this state-diagram from Paul Williams:
+ *   http://vt100.net/emu/
+ * It was written from scratch and extended where needed.
+ * This parser is fully compatible up to the vt500 series. We expect UCS-4 as
+ * input. It's the callers responsibility to do any UTF-8 parsing.
+ */
+
+enum parser_state {
+        STATE_NONE,             /* placeholder */
+        STATE_GROUND,           /* initial state and ground */
+        STATE_ESC,              /* ESC sequence was started */
+        STATE_ESC_INT,          /* intermediate escape characters */
+        STATE_CSI_ENTRY,        /* starting CSI sequence */
+        STATE_CSI_PARAM,        /* CSI parameters */
+        STATE_CSI_INT,          /* intermediate CSI characters */
+        STATE_CSI_IGNORE,       /* CSI error; ignore this CSI sequence */
+        STATE_DCS_ENTRY,        /* starting DCS sequence */
+        STATE_DCS_PARAM,        /* DCS parameters */
+        STATE_DCS_INT,          /* intermediate DCS characters */
+        STATE_DCS_PASS,         /* DCS data passthrough */
+        STATE_DCS_IGNORE,       /* DCS error; ignore this DCS sequence */
+        STATE_OSC_STRING,       /* parsing OSC sequence */
+        STATE_ST_IGNORE,        /* unimplemented seq; ignore until ST */
+        STATE_NUM
+};
+
+enum parser_action {
+        ACTION_NONE,            /* placeholder */
+        ACTION_CLEAR,           /* clear parameters */
+        ACTION_IGNORE,          /* ignore the character entirely */
+        ACTION_PRINT,           /* print the character on the console */
+        ACTION_EXECUTE,         /* execute single control character (C0/C1) */
+        ACTION_COLLECT,         /* collect intermediate character */
+        ACTION_PARAM,           /* collect parameter character */
+        ACTION_ESC_DISPATCH,    /* dispatch escape sequence */
+        ACTION_CSI_DISPATCH,    /* dispatch csi sequence */
+        ACTION_DCS_START,       /* start of DCS data */
+        ACTION_DCS_COLLECT,     /* collect DCS data */
+        ACTION_DCS_CONSUME,     /* consume DCS terminator */
+        ACTION_DCS_DISPATCH,    /* dispatch dcs sequence */
+        ACTION_OSC_START,       /* start of OSC data */
+        ACTION_OSC_COLLECT,     /* collect OSC data */
+        ACTION_OSC_CONSUME,     /* consume OSC terminator */
+        ACTION_OSC_DISPATCH,    /* dispatch osc sequence */
+        ACTION_NUM
+};
+
+int term_parser_new(term_parser **out, bool host) {
+        _term_parser_free_ term_parser *parser = NULL;
+
+        assert_return(out, -EINVAL);
+
+        parser = new0(term_parser, 1);
+        if (!parser)
+                return -ENOMEM;
+
+        parser->is_host = host;
+        parser->st_alloc = 64;
+        parser->seq.st = new0(char, parser->st_alloc + 1);
+        if (!parser->seq.st)
+                return -ENOMEM;
+
+        *out = parser;
+        parser = NULL;
+        return 0;
+}
+
+term_parser *term_parser_free(term_parser *parser) {
+        if (!parser)
+                return NULL;
+
+        free(parser->seq.st);
+        free(parser);
+        return NULL;
+}
+
+static inline void parser_clear(term_parser *parser) {
+        unsigned int i;
+
+        parser->seq.command = TERM_CMD_NONE;
+        parser->seq.terminator = 0;
+        parser->seq.intermediates = 0;
+        parser->seq.charset = TERM_CHARSET_NONE;
+        parser->seq.n_args = 0;
+        for (i = 0; i < TERM_PARSER_ARG_MAX; ++i)
+                parser->seq.args[i] = -1;
+
+        parser->seq.n_st = 0;
+        parser->seq.st[0] = 0;
+}
+
+static int parser_ignore(term_parser *parser, uint32_t raw) {
+        parser_clear(parser);
+        parser->seq.type = TERM_SEQ_IGNORE;
+        parser->seq.command = TERM_CMD_NONE;
+        parser->seq.terminator = raw;
+        parser->seq.charset = TERM_CHARSET_NONE;
+
+        return parser->seq.type;
+}
+
+static int parser_print(term_parser *parser, uint32_t raw) {
+        parser_clear(parser);
+        parser->seq.type = TERM_SEQ_GRAPHIC;
+        parser->seq.command = TERM_CMD_GRAPHIC;
+        parser->seq.terminator = raw;
+        parser->seq.charset = TERM_CHARSET_NONE;
+
+        return parser->seq.type;
+}
+
+static int parser_execute(term_parser *parser, uint32_t raw) {
+        parser_clear(parser);
+        parser->seq.type = TERM_SEQ_CONTROL;
+        parser->seq.command = TERM_CMD_GRAPHIC;
+        parser->seq.terminator = raw;
+        parser->seq.charset = TERM_CHARSET_NONE;
+        if (!parser->is_host)
+                parser->seq.command = term_parse_host_control(&parser->seq);
+
+        return parser->seq.type;
+}
+
+static void parser_collect(term_parser *parser, uint32_t raw) {
+        /*
+         * Usually, characters from 0x30 to 0x3f are only allowed as leading
+         * markers (or as part of the parameters), characters from 0x20 to 0x2f
+         * are only allowed as trailing markers. However, our state-machine
+         * already verifies those restrictions so we can handle them the same
+         * way here. Note that we safely allow markers to be specified multiple
+         * times.
+         */
+
+        if (raw >= 0x20 && raw <= 0x3f)
+                parser->seq.intermediates |= 1 << (raw - 0x20);
+}
+
+static void parser_param(term_parser *parser, uint32_t raw) {
+        int new;
+
+        if (raw == ';') {
+                if (parser->seq.n_args < TERM_PARSER_ARG_MAX)
+                        ++parser->seq.n_args;
+
+                return;
+        }
+
+        if (parser->seq.n_args >= TERM_PARSER_ARG_MAX)
+                return;
+
+        if (raw >= '0' && raw <= '9') {
+                new = parser->seq.args[parser->seq.n_args];
+                if (new < 0)
+                        new = 0;
+                new = new * 10 + raw - '0';
+
+                /* VT510 tells us to clamp all values to [0, 9999], however, it
+                 * also allows commands with values up to 2^15-1. We simply use
+                 * 2^16 as maximum here to be compatible to all commands, but
+                 * avoid overflows in any calculations. */
+                if (new > 0xffff)
+                        new = 0xffff;
+
+                parser->seq.args[parser->seq.n_args] = new;
+        }
+}
+
+static int parser_esc(term_parser *parser, uint32_t raw) {
+        parser->seq.type = TERM_SEQ_ESCAPE;
+        parser->seq.command = TERM_CMD_NONE;
+        parser->seq.terminator = raw;
+        parser->seq.charset = TERM_CHARSET_NONE;
+        if (!parser->is_host)
+                parser->seq.command = term_parse_host_escape(&parser->seq, &parser->seq.charset);
+
+        return parser->seq.type;
+}
+
+static int parser_csi(term_parser *parser, uint32_t raw) {
+        /* parser->seq is cleared during CSI-ENTER state, thus there's no need
+         * to clear invalid fields here. */
+
+        if (parser->seq.n_args < TERM_PARSER_ARG_MAX) {
+                if (parser->seq.n_args > 0 ||
+                    parser->seq.args[parser->seq.n_args] >= 0)
+                        ++parser->seq.n_args;
+        }
+
+        parser->seq.type = TERM_SEQ_CSI;
+        parser->seq.command = TERM_CMD_NONE;
+        parser->seq.terminator = raw;
+        parser->seq.charset = TERM_CHARSET_NONE;
+        if (!parser->is_host)
+                parser->seq.command = term_parse_host_csi(&parser->seq);
+
+        return parser->seq.type;
+}
+
+/* perform state transition and dispatch related actions */
+static int parser_transition(term_parser *parser, uint32_t raw, unsigned int state, unsigned int action) {
+        if (state != STATE_NONE)
+                parser->state = state;
+
+        switch (action) {
+        case ACTION_NONE:
+                return TERM_SEQ_NONE;
+        case ACTION_CLEAR:
+                parser_clear(parser);
+                return TERM_SEQ_NONE;
+        case ACTION_IGNORE:
+                return parser_ignore(parser, raw);
+        case ACTION_PRINT:
+                return parser_print(parser, raw);
+        case ACTION_EXECUTE:
+                return parser_execute(parser, raw);
+        case ACTION_COLLECT:
+                parser_collect(parser, raw);
+                return TERM_SEQ_NONE;
+        case ACTION_PARAM:
+                parser_param(parser, raw);
+                return TERM_SEQ_NONE;
+        case ACTION_ESC_DISPATCH:
+                return parser_esc(parser, raw);
+        case ACTION_CSI_DISPATCH:
+                return parser_csi(parser, raw);
+        case ACTION_DCS_START:
+                /* not implemented */
+                return TERM_SEQ_NONE;
+        case ACTION_DCS_COLLECT:
+                /* not implemented */
+                return TERM_SEQ_NONE;
+        case ACTION_DCS_CONSUME:
+                /* not implemented */
+                return TERM_SEQ_NONE;
+        case ACTION_DCS_DISPATCH:
+                /* not implemented */
+                return TERM_SEQ_NONE;
+        case ACTION_OSC_START:
+                /* not implemented */
+                return TERM_SEQ_NONE;
+        case ACTION_OSC_COLLECT:
+                /* not implemented */
+                return TERM_SEQ_NONE;
+        case ACTION_OSC_CONSUME:
+                /* not implemented */
+                return TERM_SEQ_NONE;
+        case ACTION_OSC_DISPATCH:
+                /* not implemented */
+                return TERM_SEQ_NONE;
+        default:
+                assert_not_reached("invalid vte-parser action");
+                return TERM_SEQ_NONE;
+        }
+}
+
+static int parser_feed_to_state(term_parser *parser, uint32_t raw) {
+        switch (parser->state) {
+        case STATE_NONE:
+                /*
+                 * During initialization, parser->state is cleared. Treat this
+                 * as STATE_GROUND. We will then never get to STATE_NONE again.
+                 */
+        case STATE_GROUND:
+                switch (raw) {
+                case 0x00 ... 0x1f:     /* C0 */
+                case 0x80 ... 0x9b:     /* C1 \ { ST } */
+                case 0x9d ... 0x9f:
+                        return parser_transition(parser, raw, STATE_NONE, ACTION_EXECUTE);
+                case 0x9c:              /* ST */
+                        return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE);
+                }
+
+                return parser_transition(parser, raw, STATE_NONE, ACTION_PRINT);
+        case STATE_ESC:
+                switch (raw) {
+                case 0x00 ... 0x1f:     /* C0 */
+                        return parser_transition(parser, raw, STATE_NONE, ACTION_EXECUTE);
+                case 0x20 ... 0x2f:     /* [' ' - '\'] */
+                        return parser_transition(parser, raw, STATE_ESC_INT, ACTION_COLLECT);
+                case 0x30 ... 0x4f:     /* ['0' - '~'] \ { 'P', 'X', '[', ']', '^', '_' } */
+                case 0x51 ... 0x57:
+                case 0x59 ... 0x5a:
+                case 0x5c:
+                case 0x60 ... 0x7e:
+                        return parser_transition(parser, raw, STATE_GROUND, ACTION_ESC_DISPATCH);
+                case 0x50:              /* 'P' */
+                        return parser_transition(parser, raw, STATE_DCS_ENTRY, ACTION_CLEAR);
+                case 0x5b:              /* '[' */
+                        return parser_transition(parser, raw, STATE_CSI_ENTRY, ACTION_CLEAR);
+                case 0x5d:              /* ']' */
+                        return parser_transition(parser, raw, STATE_OSC_STRING, ACTION_CLEAR);
+                case 0x58:              /* 'X' */
+                case 0x5e:              /* '^' */
+                case 0x5f:              /* '_' */
+                        return parser_transition(parser, raw, STATE_ST_IGNORE, ACTION_NONE);
+                case 0x7f:              /* DEL */
+                        return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE);
+                case 0x9c:              /* ST */
+                        return parser_transition(parser, raw, STATE_GROUND, ACTION_IGNORE);
+                }
+
+                return parser_transition(parser, raw, STATE_ESC_INT, ACTION_COLLECT);
+        case STATE_ESC_INT:
+                switch (raw) {
+                case 0x00 ... 0x1f:     /* C0 */
+                        return parser_transition(parser, raw, STATE_NONE, ACTION_EXECUTE);
+                case 0x20 ... 0x2f:     /* [' ' - '\'] */
+                        return parser_transition(parser, raw, STATE_NONE, ACTION_COLLECT);
+                case 0x30 ... 0x7e:     /* ['0' - '~'] */
+                        return parser_transition(parser, raw, STATE_GROUND, ACTION_ESC_DISPATCH);
+                case 0x7f:              /* DEL */
+                        return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE);
+                case 0x9c:              /* ST */
+                        return parser_transition(parser, raw, STATE_GROUND, ACTION_IGNORE);
+                }
+
+                return parser_transition(parser, raw, STATE_NONE, ACTION_COLLECT);
+        case STATE_CSI_ENTRY:
+                switch (raw) {
+                case 0x00 ... 0x1f:     /* C0 */
+                        return parser_transition(parser, raw, STATE_NONE, ACTION_EXECUTE);
+                case 0x20 ... 0x2f:     /* [' ' - '\'] */
+                        return parser_transition(parser, raw, STATE_CSI_INT, ACTION_COLLECT);
+                case 0x3a:              /* ':' */
+                        return parser_transition(parser, raw, STATE_CSI_IGNORE, ACTION_NONE);
+                case 0x30 ... 0x39:     /* ['0' - '9'] */
+                case 0x3b:              /* ';' */
+                        return parser_transition(parser, raw, STATE_CSI_PARAM, ACTION_PARAM);
+                case 0x3c ... 0x3f:     /* ['<' - '?'] */
+                        return parser_transition(parser, raw, STATE_CSI_PARAM, ACTION_COLLECT);
+                case 0x40 ... 0x7e:     /* ['@' - '~'] */
+                        return parser_transition(parser, raw, STATE_GROUND, ACTION_CSI_DISPATCH);
+                case 0x7f:              /* DEL */
+                        return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE);
+                case 0x9c:              /* ST */
+                        return parser_transition(parser, raw, STATE_GROUND, ACTION_IGNORE);
+                }
+
+                return parser_transition(parser, raw, STATE_CSI_IGNORE, ACTION_NONE);
+        case STATE_CSI_PARAM:
+                switch (raw) {
+                case 0x00 ... 0x1f:     /* C0 */
+                        return parser_transition(parser, raw, STATE_NONE, ACTION_EXECUTE);
+                case 0x20 ... 0x2f:     /* [' ' - '\'] */
+                        return parser_transition(parser, raw, STATE_CSI_INT, ACTION_COLLECT);
+                case 0x30 ... 0x39:     /* ['0' - '9'] */
+                case 0x3b:              /* ';' */
+                        return parser_transition(parser, raw, STATE_NONE, ACTION_PARAM);
+                case 0x3a:              /* ':' */
+                case 0x3c ... 0x3f:     /* ['<' - '?'] */
+                        return parser_transition(parser, raw, STATE_CSI_IGNORE, ACTION_NONE);
+                case 0x40 ... 0x7e:     /* ['@' - '~'] */
+                        return parser_transition(parser, raw, STATE_GROUND, ACTION_CSI_DISPATCH);
+                case 0x7f:              /* DEL */
+                        return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE);
+                case 0x9c:              /* ST */
+                        return parser_transition(parser, raw, STATE_GROUND, ACTION_IGNORE);
+                }
+
+                return parser_transition(parser, raw, STATE_CSI_IGNORE, ACTION_NONE);
+        case STATE_CSI_INT:
+                switch (raw) {
+                case 0x00 ... 0x1f:     /* C0 */
+                        return parser_transition(parser, raw, STATE_NONE, ACTION_EXECUTE);
+                case 0x20 ... 0x2f:     /* [' ' - '\'] */
+                        return parser_transition(parser, raw, STATE_NONE, ACTION_COLLECT);
+                case 0x30 ... 0x3f:     /* ['0' - '?'] */
+                        return parser_transition(parser, raw, STATE_CSI_IGNORE, ACTION_NONE);
+                case 0x40 ... 0x7e:     /* ['@' - '~'] */
+                        return parser_transition(parser, raw, STATE_GROUND, ACTION_CSI_DISPATCH);
+                case 0x7f:              /* DEL */
+                        return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE);
+                case 0x9c:              /* ST */
+                        return parser_transition(parser, raw, STATE_GROUND, ACTION_IGNORE);
+                }
+
+                return parser_transition(parser, raw, STATE_CSI_IGNORE, ACTION_NONE);
+        case STATE_CSI_IGNORE:
+                switch (raw) {
+                case 0x00 ... 0x1f:     /* C0 */
+                        return parser_transition(parser, raw, STATE_NONE, ACTION_EXECUTE);
+                case 0x20 ... 0x3f:     /* [' ' - '?'] */
+                        return parser_transition(parser, raw, STATE_NONE, ACTION_NONE);
+                case 0x40 ... 0x7e:     /* ['@' - '~'] */
+                        return parser_transition(parser, raw, STATE_GROUND, ACTION_NONE);
+                case 0x7f:              /* DEL */
+                        return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE);
+                case 0x9c:              /* ST */
+                        return parser_transition(parser, raw, STATE_GROUND, ACTION_IGNORE);
+                }
+
+                return parser_transition(parser, raw, STATE_NONE, ACTION_NONE);
+        case STATE_DCS_ENTRY:
+                switch (raw) {
+                case 0x00 ... 0x1f:     /* C0 */
+                        return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE);
+                case 0x20 ... 0x2f:     /* [' ' - '\'] */
+                        return parser_transition(parser, raw, STATE_DCS_INT, ACTION_COLLECT);
+                case 0x3a:              /* ':' */
+                        return parser_transition(parser, raw, STATE_DCS_IGNORE, ACTION_NONE);
+                case 0x30 ... 0x39:     /* ['0' - '9'] */
+                case 0x3b:              /* ';' */
+                        return parser_transition(parser, raw, STATE_DCS_PARAM, ACTION_PARAM);
+                case 0x3c ... 0x3f:     /* ['<' - '?'] */
+                        return parser_transition(parser, raw, STATE_DCS_PARAM, ACTION_COLLECT);
+                case 0x40 ... 0x7e:     /* ['@' - '~'] */
+                        return parser_transition(parser, raw, STATE_DCS_PASS, ACTION_DCS_CONSUME);
+                case 0x7f:              /* DEL */
+                        return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE);
+                case 0x9c:              /* ST */
+                        return parser_transition(parser, raw, STATE_GROUND, ACTION_IGNORE);
+                }
+
+                return parser_transition(parser, raw, STATE_DCS_PASS, ACTION_DCS_CONSUME);
+        case STATE_DCS_PARAM:
+                switch (raw) {
+                case 0x00 ... 0x1f:     /* C0 */
+                        return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE);
+                case 0x20 ... 0x2f:     /* [' ' - '\'] */
+                        return parser_transition(parser, raw, STATE_DCS_INT, ACTION_COLLECT);
+                case 0x30 ... 0x39:     /* ['0' - '9'] */
+                case 0x3b:              /* ';' */
+                        return parser_transition(parser, raw, STATE_NONE, ACTION_PARAM);
+                case 0x3a:              /* ':' */
+                case 0x3c ... 0x3f:     /* ['<' - '?'] */
+                        return parser_transition(parser, raw, STATE_DCS_IGNORE, ACTION_NONE);
+                case 0x40 ... 0x7e:     /* ['@' - '~'] */
+                        return parser_transition(parser, raw, STATE_DCS_PASS, ACTION_DCS_CONSUME);
+                case 0x7f:              /* DEL */
+                        return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE);
+                case 0x9c:              /* ST */
+                        return parser_transition(parser, raw, STATE_GROUND, ACTION_IGNORE);
+                }
+
+                return parser_transition(parser, raw, STATE_DCS_PASS, ACTION_DCS_CONSUME);
+        case STATE_DCS_INT:
+                switch (raw) {
+                case 0x00 ... 0x1f:     /* C0 */
+                        return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE);
+                case 0x20 ... 0x2f:     /* [' ' - '\'] */
+                        return parser_transition(parser, raw, STATE_NONE, ACTION_COLLECT);
+                case 0x30 ... 0x3f:     /* ['0' - '?'] */
+                        return parser_transition(parser, raw, STATE_DCS_IGNORE, ACTION_NONE);
+                case 0x40 ... 0x7e:     /* ['@' - '~'] */
+                        return parser_transition(parser, raw, STATE_DCS_PASS, ACTION_DCS_CONSUME);
+                case 0x7f:              /* DEL */
+                        return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE);
+                case 0x9c:              /* ST */
+                        return parser_transition(parser, raw, STATE_GROUND, ACTION_IGNORE);
+                }
+
+                return parser_transition(parser, raw, STATE_DCS_PASS, ACTION_DCS_CONSUME);
+        case STATE_DCS_PASS:
+                switch (raw) {
+                case 0x00 ... 0x7e:     /* ASCII \ { DEL } */
+                        return parser_transition(parser, raw, STATE_NONE, ACTION_DCS_COLLECT);
+                case 0x7f:              /* DEL */
+                        return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE);
+                case 0x9c:              /* ST */
+                        return parser_transition(parser, raw, STATE_GROUND, ACTION_DCS_DISPATCH);
+                }
+
+                return parser_transition(parser, raw, STATE_NONE, ACTION_DCS_COLLECT);
+        case STATE_DCS_IGNORE:
+                switch (raw) {
+                case 0x00 ... 0x7f:     /* ASCII */
+                        return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE);
+                case 0x9c:              /* ST */
+                        return parser_transition(parser, raw, STATE_GROUND, ACTION_NONE);
+                }
+
+                return parser_transition(parser, raw, STATE_NONE, ACTION_NONE);
+        case STATE_OSC_STRING:
+                switch (raw) {
+                case 0x00 ... 0x06:     /* C0 \ { BEL } */
+                case 0x08 ... 0x1f:
+                        return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE);
+                case 0x20 ... 0x7f:     /* [' ' - DEL] */
+                        return parser_transition(parser, raw, STATE_NONE, ACTION_OSC_COLLECT);
+                case 0x07:              /* BEL */
+                case 0x9c:              /* ST */
+                        return parser_transition(parser, raw, STATE_GROUND, ACTION_OSC_DISPATCH);
+                }
+
+                return parser_transition(parser, raw, STATE_NONE, ACTION_OSC_COLLECT);
+        case STATE_ST_IGNORE:
+                switch (raw) {
+                case 0x00 ... 0x7f:     /* ASCII */
+                        return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE);
+                case 0x9c:              /* ST */
+                        return parser_transition(parser, raw, STATE_GROUND, ACTION_IGNORE);
+                }
+
+                return parser_transition(parser, raw, STATE_NONE, ACTION_NONE);
+        }
+
+        assert_not_reached("bad vte-parser state");
+        return -EINVAL;
+}
+
+int term_parser_feed(term_parser *parser, const term_seq **seq_out, uint32_t raw) {
+        int r;
+
+        assert_return(parser, -EINVAL);
+        assert_return(seq_out, -EINVAL);
+
+        /*
+         * Notes:
+         *  * DEC treats GR codes as GL. We don't do that as we require UTF-8
+         *    as charset and, thus, it doesn't make sense to treat GR special.
+         *  * During control sequences, unexpected C1 codes cancel the sequence
+         *    and immediately start a new one. C0 codes, however, may or may not
+         *    be ignored/executed depending on the sequence.
+         */
+
+        switch (raw) {
+        case 0x18:              /* CAN */
+                r = parser_transition(parser, raw, STATE_GROUND, ACTION_IGNORE);
+                break;
+        case 0x1a:              /* SUB */
+                r = parser_transition(parser, raw, STATE_GROUND, ACTION_EXECUTE);
+                break;
+        case 0x80 ... 0x8f:     /* C1 \ {DCS, SOS, CSI, ST, OSC, PM, APC} */
+        case 0x91 ... 0x97:
+        case 0x99 ... 0x9a:
+                r = parser_transition(parser, raw, STATE_GROUND, ACTION_EXECUTE);
+                break;
+        case 0x1b:              /* ESC */
+                r = parser_transition(parser, raw, STATE_ESC, ACTION_CLEAR);
+                break;
+        case 0x98:              /* SOS */
+        case 0x9e:              /* PM */
+        case 0x9f:              /* APC */
+                r = parser_transition(parser, raw, STATE_ST_IGNORE, ACTION_NONE);
+                break;
+        case 0x90:              /* DCS */
+                r = parser_transition(parser, raw, STATE_DCS_ENTRY, ACTION_CLEAR);
+                break;
+        case 0x9d:              /* OSC */
+                r = parser_transition(parser, raw, STATE_OSC_STRING, ACTION_CLEAR);
+                break;
+        case 0x9b:              /* CSI */
+                r = parser_transition(parser, raw, STATE_CSI_ENTRY, ACTION_CLEAR);
+                break;
+        default:
+                r = parser_feed_to_state(parser, raw);
+                break;
+        }
+
+        if (r <= 0)
+                *seq_out = NULL;
+        else
+                *seq_out = &parser->seq;
+
+        return r;
+}
diff --git a/src/libsystemd-terminal/test-term-parser.c b/src/libsystemd-terminal/test-term-parser.c
new file mode 100644
index 0000000..ed16f5f
--- /dev/null
+++ b/src/libsystemd-terminal/test-term-parser.c
@@ -0,0 +1,143 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+/***
+  This file is part of systemd.
+
+  Copyright (C) 2014 David Herrmann <dh.herrmann at 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/>.
+***/
+
+/*
+ * Terminal Parser Tests
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "macro.h"
+#include "term-internal.h"
+#include "util.h"
+
+static void test_term_utf8_invalid(void) {
+        term_utf8 p = { };
+        const uint32_t *res;
+        size_t len;
+
+        res = term_utf8_decode(NULL, NULL, 0);
+        assert_se(res == NULL);
+
+        res = term_utf8_decode(&p, NULL, 0);
+        assert_se(res != NULL);
+
+        len = 5;
+        res = term_utf8_decode(NULL, &len, 0);
+        assert_se(res == NULL);
+        assert_se(len == 0);
+
+        len = 5;
+        res = term_utf8_decode(&p, &len, 0);
+        assert_se(res != NULL);
+        assert_se(len == 1);
+
+        len = 5;
+        res = term_utf8_decode(&p, &len, 0xCf);
+        assert_se(res == NULL);
+        assert_se(len == 0);
+
+        len = 5;
+        res = term_utf8_decode(&p, &len, 0x0);
+        assert_se(res != NULL);
+        assert_se(len == 2);
+}
+
+static void test_term_utf8_range(void) {
+        term_utf8 p = { };
+        const uint32_t *res;
+        char u8[4];
+        uint32_t i, j;
+        size_t ulen, len;
+
+        /* Convert all ucs-4 chars to utf-8 and back */
+
+        for (i = 0; i < 0x10FFFF; ++i) {
+                ulen = term_utf8_encode(u8, i);
+                if (!ulen)
+                        continue;
+
+                for (j = 0; j < ulen; ++j) {
+                        res = term_utf8_decode(&p, &len, u8[j]);
+                        if (!res) {
+                                assert_se(j + 1 != ulen);
+                                continue;
+                        }
+
+                        assert_se(j + 1 == ulen);
+                        assert_se(len == 1 && *res == i);
+                        assert_se(i <= 127 || ulen >= 2);
+                }
+        }
+}
+
+static void test_term_utf8_mix(void) {
+        static const char source[] = {
+                0x00,                           /* normal 0 */
+                0xC0, 0x80,                     /* overlong 0 */
+                0xC0, 0x81,                     /* overlong 1 */
+                0xE0, 0x80, 0x81,               /* overlong 1 */
+                0xF0, 0x80, 0x80, 0x81,         /* overlong 1 */
+                0xC0, 0x00,                     /* invalid continuation */
+                0xC0, 0xC0, 0x81,               /* invalid continuation with a following overlong 1 */
+                0xF8, 0x80, 0x80, 0x80, 0x81,   /* overlong 1 with 5 bytes */
+                0xE0, 0x80, 0xC0, 0x81,         /* invalid 3-byte followed by valid 2-byte */
+                0xF0, 0x80, 0x80, 0xC0, 0x81,   /* invalid 4-byte followed by valid 2-byte */
+        };
+        static const uint32_t result[] = {
+                0x0000,
+                0x0000,
+                0x0001,
+                0x0001,
+                0x0001,
+                0x00C0, 0x0000,
+                0x00C0, 0x0001,
+                0x00F8, 0x0080, 0x0080, 0x0080, 0x0081,
+                0x00E0, 0x0080, 0x0001,
+                0x00F0, 0x0080, 0x0080, 0x0001,
+        };
+        term_utf8 p = { };
+        const uint32_t *res;
+        unsigned int i, j;
+        size_t len;
+
+        for (i = 0, j = 0; i < sizeof(source); ++i) {
+                res = term_utf8_decode(&p, &len, source[i]);
+                if (!res)
+                        continue;
+
+                assert_se(j + len <= ELEMENTSOF(result));
+                assert_se(!memcmp(res, &result[j], sizeof(uint32_t) * len));
+                j += len;
+        }
+
+        assert_se(j == ELEMENTSOF(result));
+}
+
+int main(int argc, char *argv[]) {
+        test_term_utf8_invalid();
+        test_term_utf8_range();
+        test_term_utf8_mix();
+
+        return 0;
+}

commit 28622e8f5b28412d97bf2f3a5df49c419be1e2c5
Author: David Herrmann <dh.herrmann at gmail.com>
Date:   Fri Jun 13 19:00:29 2014 +0200

    terminal: add page handling for terminals
    
    The page-layer is a one-dimensional array of lines. Combined with the
    one-dimensional lines, you get a two-dimensional page. However, both
    implementations, lines and pages only deal with their own dimension. That
    means, lines don't know anything about other lines, and pages don't know
    anything about cells.
    
    Apart from pages, this also introduces history objects. A history object
    is a scroll-back buffer. As some pages like alt-buffers don't have
    histories, we keep them separate.
    
    Pages itself forward all cell-related operations to the related line. Only
    line-related operations are directly handled by the page. This is mostly
    scrolling and history. To support proper resizing, we also keep a
    fill-state just like lines do for cells.

diff --git a/src/libsystemd-terminal/term-internal.h b/src/libsystemd-terminal/term-internal.h
index 56ebd30..d7d2f98 100644
--- a/src/libsystemd-terminal/term-internal.h
+++ b/src/libsystemd-terminal/term-internal.h
@@ -34,6 +34,9 @@ typedef struct term_attr term_attr;
 typedef struct term_cell term_cell;
 typedef struct term_line term_line;
 
+typedef struct term_page term_page;
+typedef struct term_history term_history;
+
 /*
  * Miscellaneous
  * Sundry things and external helpers.
@@ -253,3 +256,82 @@ void term_line_unlink(term_line *line, term_line **first, term_line **last);
 #define TERM_LINE_LINK(_line, _head) term_line_link((_line), &(_head)->lines_first, &(_head)->lines_last)
 #define TERM_LINE_LINK_TAIL(_line, _head) term_line_link_tail((_line), &(_head)->lines_first, &(_head)->lines_last)
 #define TERM_LINE_UNLINK(_line, _head) term_line_unlink((_line), &(_head)->lines_first, &(_head)->lines_last)
+
+/*
+ * Pages
+ * A page represents the 2D table containing all cells of a terminal. It stores
+ * lines as an array of pointers so scrolling becomes a simple line-shuffle
+ * operation.
+ * Scrolling is always targeted only at the scroll-region defined via scroll_idx
+ * and scroll_num. The fill-state keeps track of the number of touched lines in
+ * the scroll-region. @width and @height describe the visible region of the page
+ * and are guaranteed to be allocated at all times.
+ */
+
+struct term_page {
+        term_age_t age;                 /* page age */
+
+        term_line **lines;              /* array of line-pointers */
+        term_line **line_cache;         /* cache for temporary operations */
+        unsigned int n_lines;           /* # of allocated lines */
+
+        unsigned int width;             /* width of visible area */
+        unsigned int height;            /* height of visible area */
+        unsigned int scroll_idx;        /* scrolling-region start index */
+        unsigned int scroll_num;        /* scrolling-region length in lines */
+        unsigned int scroll_fill;       /* # of valid scroll-lines */
+};
+
+int term_page_new(term_page **out);
+term_page *term_page_free(term_page *page);
+
+#define _term_page_free_ _cleanup_(term_page_freep)
+DEFINE_TRIVIAL_CLEANUP_FUNC(term_page*, term_page_free);
+
+term_cell *term_page_get_cell(term_page *page, unsigned int x, unsigned int y);
+
+int term_page_reserve(term_page *page, unsigned int cols, unsigned int rows, const term_attr *attr, term_age_t age);
+void term_page_resize(term_page *page, unsigned int cols, unsigned int rows, const term_attr *attr, term_age_t age, term_history *history);
+void term_page_write(term_page *page, unsigned int pos_x, unsigned int pos_y, term_char_t ch, unsigned int cwidth, const term_attr *attr, term_age_t age, bool insert_mode);
+void term_page_insert_cells(term_page *page, unsigned int from_x, unsigned int from_y, unsigned int num, const term_attr *attr, term_age_t age);
+void term_page_delete_cells(term_page *page, unsigned int from_x, unsigned int from_y, unsigned int num, const term_attr *attr, term_age_t age);
+void term_page_append_combchar(term_page *page, unsigned int pos_x, unsigned int pos_y, uint32_t ucs4, term_age_t age);
+void term_page_erase(term_page *page, unsigned int from_x, unsigned int from_y, unsigned int to_x, unsigned int to_y, const term_attr *attr, term_age_t age, bool keep_protected);
+void term_page_reset(term_page *page, const term_attr *attr, term_age_t age);
+
+void term_page_set_scroll_region(term_page *page, unsigned int idx, unsigned int num);
+void term_page_scroll_up(term_page *page, unsigned int num, const term_attr *attr, term_age_t age, term_history *history);
+void term_page_scroll_down(term_page *page, unsigned int num, const term_attr *attr, term_age_t age, term_history *history);
+void term_page_insert_lines(term_page *page, unsigned int pos_y, unsigned int num, const term_attr *attr, term_age_t age);
+void term_page_delete_lines(term_page *page, unsigned int pos_y, unsigned int num, const term_attr *attr, term_age_t age);
+
+/*
+ * Histories
+ * Scroll-back buffers use term_history objects to store scroll-back lines. A
+ * page is independent of the history used. All page operations that modify a
+ * history take it as separate argument. You're free to pass NULL at all times
+ * if no history should be used.
+ * Lines are stored in a linked list as no complex operations are ever done on
+ * history lines, besides pushing/poping. Note that history lines do not have a
+ * guaranteed minimum length. Any kind of line might be stored there. Missing
+ * cells should be cleared to the background color.
+ */
+
+struct term_history {
+        term_line *lines_first;
+        term_line *lines_last;
+        unsigned int n_lines;
+        unsigned int max_lines;
+};
+
+int term_history_new(term_history **out);
+term_history *term_history_free(term_history *history);
+
+#define _term_history_free_ _cleanup_(term_history_freep)
+DEFINE_TRIVIAL_CLEANUP_FUNC(term_history*, term_history_free);
+
+void term_history_clear(term_history *history);
+void term_history_trim(term_history *history, unsigned int max);
+void term_history_push(term_history *history, term_line *line);
+term_line *term_history_pop(term_history *history, unsigned int reserve_width, const term_attr *attr, term_age_t age);
+unsigned int term_history_peek(term_history *history, unsigned int max, unsigned int reserve_width, const term_attr *attr, term_age_t age);
diff --git a/src/libsystemd-terminal/term-page.c b/src/libsystemd-terminal/term-page.c
index bfff3b1..7ae90e2 100644
--- a/src/libsystemd-terminal/term-page.c
+++ b/src/libsystemd-terminal/term-page.c
@@ -62,6 +62,14 @@
  * allocations on others.
  * Anyhow, until we have proper benchmarks, we will keep the current code. It
  * seems to compete very well with other solutions so far.
+ *
+ * The page-layer is a one-dimensional array of lines. Considering that each
+ * line is a one-dimensional array of cells, the page layer provides the
+ * two-dimensional cell-page required for terminals. The page itself only
+ * operates on lines. All cell-related operations are forwarded to the correct
+ * line.
+ * A page does not contain any cursor tracking. It only provides the raw
+ * operations to shuffle lines and modify the page.
  */
 
 #include <stdbool.h>
@@ -1140,3 +1148,949 @@ void term_line_unlink(term_line *line, term_line **first, term_line **last) {
         line->lines_prev = NULL;
         line->lines_next = NULL;
 }
+
+/**
+ * term_page_new() - Allocate new page
+ * @out: storage for pointer to new page
+ *
+ * Allocate a new page. The initial dimensions are 0/0.
+ *
+ * Returns: 0 on success, negative error code on failure.
+ */
+int term_page_new(term_page **out) {
+        _term_page_free_ term_page *page = NULL;
+
+        assert_return(out, -EINVAL);
+
+        page = new0(term_page, 1);
+        if (!page)
+                return -ENOMEM;
+
+        *out = page;
+        page = NULL;
+        return 0;
+}
+
+/**
+ * term_page_free() - Free page
+ * @page: page to free or NULL
+ *
+ * Free a previously allocated page and all associated data. If @page is NULL,
+ * this is a no-op.
+ *
+ * Returns: NULL
+ */
+term_page *term_page_free(term_page *page) {
+        unsigned int i;
+
+        if (!page)
+                return NULL;
+
+        for (i = 0; i < page->n_lines; ++i)
+                term_line_free(page->lines[i]);
+
+        free(page->line_cache);
+        free(page->lines);
+        free(page);
+
+        return NULL;
+}
+
+/**
+ * term_page_get_cell() - Return pointer to requested cell
+ * @page: page to operate on
+ * @x: x-position of cell
+ * @y: y-position of cell
+ *
+ * This returns a pointer to the cell at position @x/@y. You're free to modify
+ * this cell as much as you like. However, once you call any other function on
+ * the page, you must drop the pointer to the cell.
+ *
+ * Returns: Pointer to the cell or NULL if out of the visible area.
+ */
+term_cell *term_page_get_cell(term_page *page, unsigned int x, unsigned int y) {
+        assert_return(page, NULL);
+
+        if (x >= page->width)
+                return NULL;
+        if (y >= page->height)
+                return NULL;
+
+        return &page->lines[y]->cells[x];
+}
+
+/**
+ * page_scroll_up() - Scroll up
+ * @page: page to operate on
+ * @new_width: width to use for any new line moved into the visible area
+ * @num: number of lines to scroll up
+ * @attr: attributes to set on new lines
+ * @age: age to use for all modifications
+ * @history: history to use for old lines or NULL
+ *
+ * This scrolls the scroll-region by @num lines. New lines are cleared and reset
+ * with the given attributes. Old lines are moved into the history if non-NULL.
+ * If a new line is allocated, moved from the history buffer or moved from
+ * outside the visible region into the visible region, this call makes sure it
+ * has at least @width cells allocated. If a possible memory-allocation fails,
+ * the previous line is reused. This has the side effect, that it will not be
+ * linked into the history buffer.
+ *
+ * If the scroll-region is empty, this is a no-op.
+ */
+static void page_scroll_up(term_page *page, unsigned int new_width, unsigned int num, const term_attr *attr, term_age_t age, term_history *history) {
+        term_line *line, **cache;
+        unsigned int i;
+        int r;
+
+        assert(page);
+
+        if (num > page->scroll_num)
+                num = page->scroll_num;
+        if (num < 1)
+                return;
+
+        /* Better safe than sorry: avoid under-allocating lines, even when
+         * resizing. */
+        new_width = MAX(new_width, page->width);
+
+        cache = page->line_cache;
+
+        /* Try moving lines into history and allocate new lines for each moved
+         * line. In case allocation fails, or if we have no history, reuse the
+         * line.
+         * We keep the lines in the line-cache so we can safely move the
+         * remaining lines around. */
+        for (i = 0; i < num; ++i) {
+                line = page->lines[page->scroll_idx + i];
+
+                r = -EAGAIN;
+                if (history) {
+                        r = term_line_new(&cache[i]);
+                        if (r >= 0) {
+                                r = term_line_reserve(cache[i],
+                                                      new_width,
+                                                      attr,
+                                                      age,
+                                                      0);
+                                if (r < 0)
+                                        term_line_free(cache[i]);
+                                else
+                                        term_line_set_width(cache[i], page->width);
+                        }
+                }
+
+                if (r >= 0) {
+                        term_history_push(history, line);
+                } else {
+                        cache[i] = line;
+                        term_line_reset(line, attr, age);
+                }
+        }
+
+        if (num < page->scroll_num) {
+                memmove(page->lines + page->scroll_idx,
+                        page->lines + page->scroll_idx + num,
+                        sizeof(*page->lines) * (page->scroll_num - num));
+
+                /* update age of moved lines */
+                for (i = 0; i < page->scroll_num - num; ++i)
+                        page->lines[page->scroll_idx + i]->age = age;
+        }
+
+        /* copy remaining lines from cache; age is already updated */
+        memcpy(page->lines + page->scroll_idx + page->scroll_num - num,
+               cache,
+               sizeof(*cache) * num);
+
+        /* update fill */
+        page->scroll_fill -= MIN(page->scroll_fill, num);
+}
+
+/**
+ * page_scroll_down() - Scroll down
+ * @page: page to operate on
+ * @new_width: width to use for any new line moved into the visible area
+ * @num: number of lines to scroll down
+ * @attr: attributes to set on new lines
+ * @age: age to use for all modifications
+ * @history: history to use for new lines or NULL
+ *
+ * This scrolls the scroll-region by @num lines. New lines are retrieved from
+ * the history or cleared if the history is empty or NULL.
+ *
+ * Usually, scroll-down implies that new lines are cleared. Therefore, you're
+ * highly encouraged to set @history to NULL. However, if you resize a terminal,
+ * you might want to include history-lines in the new area. In that case, you
+ * should set @history to non-NULL.
+ *
+ * If a new line is allocated, moved from the history buffer or moved from
+ * outside the visible region into the visible region, this call makes sure it
+ * has at least @width cells allocated. If a possible memory-allocation fails,
+ * the previous line is reused. This will have the side-effect that lines from
+ * the history will not get visible on-screen but kept in history.
+ *
+ * If the scroll-region is empty, this is a no-op.
+ */
+static void page_scroll_down(term_page *page, unsigned int new_width, unsigned int num, const term_attr *attr, term_age_t age, term_history *history) {
+        term_line *line, **cache, *t;
+        unsigned int i, last_idx;
+
+        assert(page);
+
+        if (num > page->scroll_num)
+                num = page->scroll_num;
+        if (num < 1)
+                return;
+
+        /* Better safe than sorry: avoid under-allocating lines, even when
+         * resizing. */
+        new_width = MAX(new_width, page->width);
+
+        cache = page->line_cache;
+        last_idx = page->scroll_idx + page->scroll_num - 1;
+
+        /* Try pulling out lines from history; if history is empty or if no
+         * history is given, we reuse the to-be-removed lines. Otherwise, those
+         * lines are released. */
+        for (i = 0; i < num; ++i) {
+                line = page->lines[last_idx - i];
+
+                t = NULL;
+                if (history)
+                        t = term_history_pop(history, new_width, attr, age);
+
+                if (t) {
+                        cache[num - 1 - i] = t;
+                        term_line_free(line);
+                } else {
+                        cache[num - 1 - i] = line;
+                        term_line_reset(line, attr, age);
+                }
+        }
+
+        if (num < page->scroll_num) {
+                memmove(page->lines + page->scroll_idx + num,
+                        page->lines + page->scroll_idx,
+                        sizeof(*page->lines) * (page->scroll_num - num));
+
+                /* update age of moved lines */
+                for (i = 0; i < page->scroll_num - num; ++i)
+                        page->lines[page->scroll_idx + num + i]->age = age;
+        }
+
+        /* copy remaining lines from cache; age is already updated */
+        memcpy(page->lines + page->scroll_idx,
+               cache,
+               sizeof(*cache) * num);
+
+        /* update fill; but only if there's already content in it */
+        if (page->scroll_fill > 0)
+                page->scroll_fill = MIN(page->scroll_num,
+                                        page->scroll_fill + num);
+}
+
+/**
+ * page_reserve() - Reserve page area
+ * @page: page to modify
+ * @cols: required columns (width)
+ * @rows: required rows (height)
+ * @attr: attributes for newly allocated cells
+ * @age: age to set on any modified cells
+ *
+ * This allocates the required amount of lines and cells to guarantee that the
+ * page has at least the demanded dimensions of @cols x @rows. Note that this
+ * never shrinks the page-memory. We keep cells allocated for performance
+ * reasons.
+ *
+ * Additionally to allocating lines, this also clears any newly added cells so
+ * you can safely change the size afterwards without clearing new cells.
+ *
+ * Note that you must be careful what operations you call on the page between
+ * page_reserve() and updating page->width/height. Any newly allocated line (or
+ * shifted line) might not meet your new width/height expectations.
+ *
+ * Returns: 0 on success, negative error code on failure.
+ */
+int term_page_reserve(term_page *page, unsigned int cols, unsigned int rows, const term_attr *attr, term_age_t age) {
+        _term_line_free_ term_line *line = NULL;
+        unsigned int i, min_lines;
+        term_line **t;
+        int r;
+
+        assert_return(page, -EINVAL);
+
+        /*
+         * First make sure the first MIN(page->n_lines, rows) lines have at
+         * least the required width of @cols. This does not modify any visible
+         * cells in the existing @page->width x @page->height area, therefore,
+         * we can safely bail out afterwards in case anything else fails.
+         * Note that lines in between page->height and page->n_lines might be
+         * shorter than page->width. Hence, we need to resize them all, but we
+         * can skip some of them for better performance.
+         */
+        min_lines = MIN(page->n_lines, rows);
+        for (i = 0; i < min_lines; ++i) {
+                /* lines below page->height have at least page->width cells */
+                if (cols < page->width && i < page->height)
+                        continue;
+
+                r = term_line_reserve(page->lines[i],
+                                      cols,
+                                      attr,
+                                      age,
+                                      (i < page->height) ? page->width : 0);
+                if (r < 0)
+                        return r;
+        }
+
+        /*
+         * We now know the first @min_lines lines have at least width @cols and
+         * are prepared for resizing. We now only have to allocate any
+         * additional lines below @min_lines in case @rows is greater than
+         * page->n_lines.
+         */
+        if (rows > page->n_lines) {
+                t = realloc_multiply(page->lines, sizeof(*t), rows);
+                if (!t)
+                        return -ENOMEM;
+                page->lines = t;
+
+                t = realloc_multiply(page->line_cache, sizeof(*t), rows);
+                if (!t)
+                        return -ENOMEM;
+                page->line_cache = t;
+
+                while (page->n_lines < rows) {
+                        r = term_line_new(&line);
+                        if (r < 0)
+                                return r;
+
+                        r = term_line_reserve(line, cols, attr, age, 0);
+                        if (r < 0)
+                                return r;
+
+                        page->lines[page->n_lines++] = line;
+                        line = NULL;
+                }
+        }
+
+        return 0;
+}
+
+/**
+ * term_page_resize() - Resize page
+ * @page: page to modify
+ * @cols: number of columns (width)
+ * @rows: number of rows (height)
+ * @attr: attributes for newly allocated cells
+ * @age: age to set on any modified cells
+ * @history: history buffer to use for new/old lines or NULL
+ *
+ * This changes the visible dimensions of a page. You must have called
+ * term_page_reserve() beforehand, otherwise, this will fail.
+ *
+ * Returns: 0 on success, negative error code on failure.
+ */
+void term_page_resize(term_page *page, unsigned int cols, unsigned int rows, const term_attr *attr, term_age_t age, term_history *history) {
+        unsigned int i, num, empty, max, old_height;
+        term_line *line;
+
+        assert(page);
+        assert(page->n_lines >= rows);
+
+        old_height = page->height;
+
+        if (rows < old_height) {
+                /*
+                 * If we decrease the terminal-height, we emulate a scroll-up.
+                 * This way, existing data from the scroll-area is moved into
+                 * the history, making space at the bottom to reduce the screen
+                 * height. In case the scroll-fill indicates empty lines, we
+                 * reduce the amount of scrolled lines.
+                 * Once scrolled, we have to move the lower margin from below
+                 * the scroll area up so it is preserved.
+                 */
+
+                /* move lines to history if scroll region is filled */
+                num = old_height - rows;
+                empty = page->scroll_num - page->scroll_fill;
+                if (num > empty)
+                        page_scroll_up(page,
+                                       cols,
+                                       num - empty,
+                                       attr,
+                                       age,
+                                       history);
+
+                /* move lower margin up; drop its lines if not enough space */
+                num = LESS_BY(old_height, page->scroll_idx + page->scroll_num);
+                max = LESS_BY(rows, page->scroll_idx);
+                num = MIN(num, max);
+                if (num > 0) {
+                        unsigned int top, bottom;
+
+                        top = rows - num;
+                        bottom = page->scroll_idx + page->scroll_num;
+
+                        /* might overlap; must run topdown, not bottomup */
+                        for (i = 0; i < num; ++i) {
+                                line = page->lines[top + i];
+                                page->lines[top + i] = page->lines[bottom + i];
+                                page->lines[bottom + i] = line;
+                        }
+                }
+
+                /* update vertical extents */
+                page->height = rows;
+                page->scroll_idx = MIN(page->scroll_idx, rows);
+                page->scroll_num -= MIN(page->scroll_num, old_height - rows);
+                /* fill is already up-to-date or 0 due to scroll-up */
+        } else if (rows > old_height) {
+                /*
+                 * If we increase the terminal-height, we emulate a scroll-down
+                 * and fetch new lines from the history.
+                 * New lines are always accounted to the scroll-region. Thus we
+                 * have to preserve the lower margin first, by moving it down.
+                 */
+
+                /* move lower margin down */
+                num = LESS_BY(old_height, page->scroll_idx + page->scroll_num);
+                if (num > 0) {
+                        unsigned int top, bottom;
+
+                        top = page->scroll_idx + page->scroll_num;
+                        bottom = top + (rows - old_height);
+
+                        /* might overlap; must run bottomup, not topdown */
+                        for (i = num; i-- > 0; ) {
+                                line = page->lines[top + i];
+                                page->lines[top + i] = page->lines[bottom + i];
+                                page->lines[bottom + i] = line;
+                        }
+                }
+
+                /* update vertical extents */
+                page->height = rows;
+                page->scroll_num = MIN(LESS_BY(rows, page->scroll_idx),
+                                       page->scroll_num + (rows - old_height));
+
+                /* check how many lines can be received from history */
+                if (history)
+                        num = term_history_peek(history,
+                                                rows - old_height,
+                                                cols,
+                                                attr,
+                                                age);
+                else
+                        num = 0;
+
+                /* retrieve new lines from history if available */
+                if (num > 0)
+                        page_scroll_down(page,
+                                         cols,
+                                         num,
+                                         attr,
+                                         age,
+                                         history);
+        }
+
+        /* set horizontal extents */
+        page->width = cols;
+        for (i = 0; i < page->height; ++i)
+                term_line_set_width(page->lines[i], cols);
+}
+
+/**
+ * term_page_write() - Write to a single cell
+ * @page: page to operate on
+ * @pos_x: x-position of cell to write to
+ * @pos_y: y-position of cell to write to
+ * @ch: character to write
+ * @cwidth: character-width of @ch
+ * @attr: attributes to set on the cell or NULL
+ * @age: age to use for all modifications
+ * @insert_mode: true if INSERT-MODE is enabled
+ *
+ * This writes a character to a specific cell. If the cell is beyond bounds,
+ * this is a no-op. @attr and @age are used to update the cell. @flags can be
+ * used to alter the behavior of this function.
+ *
+ * This is a wrapper around term_line_write().
+ *
+ * This call does not wrap around lines. That is, this only operates on a single
+ * line.
+ */
+void term_page_write(term_page *page, unsigned int pos_x, unsigned int pos_y, term_char_t ch, unsigned int cwidth, const term_attr *attr, term_age_t age, bool insert_mode) {
+        assert(page);
+
+        if (pos_y >= page->height)
+                return;
+
+        term_line_write(page->lines[pos_y], pos_x, ch, cwidth, attr, age, insert_mode);
+}
+
+/**
+ * term_page_insert_cells() - Insert cells into a line
+ * @page: page to operate on
+ * @from_x: x-position where to insert new cells
+ * @from_y: y-position where to insert new cells
+ * @num: number of cells to insert
+ * @attr: attributes to set on new cells or NULL
+ * @age: age to use for all modifications
+ *
+ * This inserts new cells into a given line. This is a wrapper around
+ * term_line_insert().
+ *
+ * This call does not wrap around lines. That is, this only operates on a single
+ * line.
+ */
+void term_page_insert_cells(term_page *page, unsigned int from_x, unsigned int from_y, unsigned int num, const term_attr *attr, term_age_t age) {
+        assert(page);
+
+        if (from_y >= page->height)
+                return;
+
+        term_line_insert(page->lines[from_y], from_x, num, attr, age);
+}
+
+/**
+ * term_page_delete_cells() - Delete cells from a line
+ * @page: page to operate on
+ * @from_x: x-position where to delete cells
+ * @from_y: y-position where to delete cells
+ * @num: number of cells to delete
+ * @attr: attributes to set on new cells or NULL
+ * @age: age to use for all modifications
+ *
+ * This deletes cells from a given line. This is a wrapper around
+ * term_line_delete().
+ *
+ * This call does not wrap around lines. That is, this only operates on a single
+ * line.
+ */
+void term_page_delete_cells(term_page *page, unsigned int from_x, unsigned int from_y, unsigned int num, const term_attr *attr, term_age_t age) {
+        assert(page);
+
+        if (from_y >= page->height)
+                return;
+
+        term_line_delete(page->lines[from_y], from_x, num, attr, age);
+}
+
+/**
+ * term_page_append_combchar() - Append combining-character to a cell
+ * @page: page to operate on
+ * @pos_x: x-position of target cell
+ * @pos_y: y-position of target cell
+ * @ucs4: combining character to append
+ * @age: age to use for all modifications
+ *
+ * This appends a combining-character to a specific cell. This is a wrapper
+ * around term_line_append_combchar().
+ */
+void term_page_append_combchar(term_page *page, unsigned int pos_x, unsigned int pos_y, uint32_t ucs4, term_age_t age) {
+        assert(page);
+
+        if (pos_y >= page->height)
+                return;
+
+        term_line_append_combchar(page->lines[pos_y], pos_x, ucs4, age);
+}
+
+/**
+ * term_page_erase() - Erase parts of a page
+ * @page: page to operate on
+ * @from_x: x-position where to start erasure (inclusive)
+ * @from_y: y-position where to start erasure (inclusive)
+ * @to_x: x-position where to stop erasure (inclusive)
+ * @to_y: y-position where to stop erasure (inclusive)
+ * @attr: attributes to set on cells
+ * @age: age to use for all modifications
+ * @keep_protected: true if protected cells should be kept
+ *
+ * This erases all cells starting at @from_x/@from_y up to @to_x/@to_y. Note
+ * that this wraps around line-boundaries so lines between @from_y and @to_y
+ * are cleared entirely.
+ *
+ * Lines outside the visible area are left untouched.
+ */
+void term_page_erase(term_page *page, unsigned int from_x, unsigned int from_y, unsigned int to_x, unsigned int to_y, const term_attr *attr, term_age_t age, bool keep_protected) {
+        unsigned int i, from, to;
+
+        assert(page);
+
+        for (i = from_y; i <= to_y && i < page->height; ++i) {
+                from = 0;
+                to = page->width;
+
+                if (i == from_y)
+                        from = from_x;
+                if (i == to_y)
+                        to = to_x;
+
+                term_line_erase(page->lines[i],
+                                from,
+                                LESS_BY(to, from),
+                                attr,
+                                age,
+                                keep_protected);
+        }
+}
+
+/**
+ * term_page_reset() - Reset page
+ * @page: page to modify
+ * @attr: attributes to set on cells
+ * @age: age to use for all modifications
+ *
+ * This erases the whole visible page. See term_page_erase().
+ */
+void term_page_reset(term_page *page, const term_attr *attr, term_age_t age) {
+        assert(page);
+
+        return term_page_erase(page,
+                               0, 0,
+                               page->width - 1, page->height - 1,
+                               attr,
+                               age,
+                               0);
+}
+
+/**
+ * term_page_set_scroll_region() - Set scroll region
+ * @page: page to operate on
+ * @idx: start-index of scroll region
+ * @num: number of lines in scroll region
+ *
+ * This sets the scroll region of a page. Whenever an operation needs to scroll
+ * lines, it scrolls them inside of that region. Lines outside the region are
+ * left untouched. In case a scroll-operation is targeted outside of this
+ * region, it will implicitly get a scroll-region of only one line (i.e., no
+ * scroll region at all).
+ *
+ * Note that the scroll-region is clipped to the current page-extents. Growing
+ * or shrinking the page always accounts new/old lines to the scroll region and
+ * moves top/bottom margins accordingly so they're preserved.
+ */
+void term_page_set_scroll_region(term_page *page, unsigned int idx, unsigned int num) {
+        assert(page);
+
+        if (page->height < 1) {
+                page->scroll_idx = 0;
+                page->scroll_num = 0;
+        } else {
+                page->scroll_idx = MIN(idx, page->height - 1);
+                page->scroll_num = MIN(num, page->height - page->scroll_idx);
+        }
+}
+
+/**
+ * term_page_scroll_up() - Scroll up
+ * @page: page to operate on
+ * @num: number of lines to scroll up
+ * @attr: attributes to set on new lines
+ * @age: age to use for all modifications
+ * @history: history to use for old lines or NULL
+ *
+ * This scrolls the scroll-region by @num lines. New lines are cleared and reset
+ * with the given attributes. Old lines are moved into the history if non-NULL.
+ *
+ * If the scroll-region is empty, this is a no-op.
+ */
+void term_page_scroll_up(term_page *page, unsigned int num, const term_attr *attr, term_age_t age, term_history *history) {
+        page_scroll_up(page, page->width, num, attr, age, history);
+}
+
+/**
+ * term_page_scroll_down() - Scroll down
+ * @page: page to operate on
+ * @num: number of lines to scroll down
+ * @attr: attributes to set on new lines
+ * @age: age to use for all modifications
+ * @history: history to use for new lines or NULL
+ *
+ * This scrolls the scroll-region by @num lines. New lines are retrieved from
+ * the history or cleared if the history is empty or NULL.
+ *
+ * Usually, scroll-down implies that new lines are cleared. Therefore, you're
+ * highly encouraged to set @history to NULL. However, if you resize a terminal,
+ * you might want to include history-lines in the new area. In that case, you
+ * should set @history to non-NULL.
+ *
+ * If the scroll-region is empty, this is a no-op.
+ */
+void term_page_scroll_down(term_page *page, unsigned int num, const term_attr *attr, term_age_t age, term_history *history) {
+        page_scroll_down(page, page->width, num, attr, age, history);
+}
+
+/**
+ * term_page_insert_lines() - Insert new lines
+ * @page: page to operate on
+ * @pos_y: y-position where to insert new lines
+ * @num: number of lines to insert
+ * @attr: attributes to set on new lines
+ * @age: age to use for all modifications
+ *
+ * This inserts @num new lines at position @pos_y. If @pos_y is beyond
+ * boundaries or @num is 0, this is a no-op.
+ * All lines below @pos_y are moved down to make space for the new lines. Lines
+ * on the bottom are dropped. Note that this only moves lines above or inside
+ * the scroll-region. If @pos_y is below the scroll-region, a scroll-region of
+ * one line is implied (which means the line is simply cleared).
+ */
+void term_page_insert_lines(term_page *page, unsigned int pos_y, unsigned int num, const term_attr *attr, term_age_t age) {
+        unsigned int scroll_idx, scroll_num;
+
+        assert(page);
+
+        if (pos_y >= page->height)
+                return;
+        if (num >= page->height)
+                num = page->height;
+
+        /* remember scroll-region */
+        scroll_idx = page->scroll_idx;
+        scroll_num = page->scroll_num;
+
+        /* set scroll-region temporarily so we can reuse scroll_down() */
+        {
+                page->scroll_idx = pos_y;
+                if (pos_y >= scroll_idx + scroll_num)
+                        page->scroll_num = 1;
+                else if (pos_y >= scroll_idx)
+                        page->scroll_num -= pos_y - scroll_idx;
+                else
+                        page->scroll_num += scroll_idx - pos_y;
+
+                term_page_scroll_down(page, num, attr, age, NULL);
+        }
+
+        /* reset scroll-region */
+        page->scroll_idx = scroll_idx;
+        page->scroll_num = scroll_num;
+}
+
+/**
+ * term_page_delete_lines() - Delete lines
+ * @page: page to operate on
+ * @pos_y: y-position where to delete lines
+ * @num: number of lines to delete
+ * @attr: attributes to set on new lines
+ * @age: age to use for all modifications
+ *
+ * This deletes @num lines at position @pos_y. If @pos_y is beyond boundaries or
+ * @num is 0, this is a no-op.
+ * All lines below @pos_y are moved up into the newly made space. New lines
+ * on the bottom are clear. Note that this only moves lines above or inside
+ * the scroll-region. If @pos_y is below the scroll-region, a scroll-region of
+ * one line is implied (which means the line is simply cleared).
+ */
+void term_page_delete_lines(term_page *page, unsigned int pos_y, unsigned int num, const term_attr *attr, term_age_t age) {
+        unsigned int scroll_idx, scroll_num;
+
+        assert(page);
+
+        if (pos_y >= page->height)
+                return;
+        if (num >= page->height)
+                num = page->height;
+
+        /* remember scroll-region */
+        scroll_idx = page->scroll_idx;
+        scroll_num = page->scroll_num;
+
+        /* set scroll-region temporarily so we can reuse scroll_up() */
+        {
+                page->scroll_idx = pos_y;
+                if (pos_y >= scroll_idx + scroll_num)
+                        page->scroll_num = 1;
+                else if (pos_y > scroll_idx)
+                        page->scroll_num -= pos_y - scroll_idx;
+                else
+                        page->scroll_num += scroll_idx - pos_y;
+
+                term_page_scroll_up(page, num, attr, age, NULL);
+        }
+
+        /* reset scroll-region */
+        page->scroll_idx = scroll_idx;
+        page->scroll_num = scroll_num;
+}
+
+/**
+ * term_history_new() - Create new history object
+ * @out: storage for pointer to new history
+ *
+ * Create a new history object. Histories are used to store scrollback-lines
+ * from VTE pages. You're highly recommended to set a history-limit on
+ * history->max_lines and trim it via term_history_trim(), otherwise history
+ * allocations are unlimited.
+ *
+ * Returns: 0 on success, negative error code on failure.
+ */
+int term_history_new(term_history **out) {
+        _term_history_free_ term_history *history = NULL;
+
+        assert_return(out, -EINVAL);
+
+        history = new0(term_history, 1);
+        if (!history)
+                return -ENOMEM;
+
+        history->max_lines = 4096;
+
+        *out = history;
+        history = NULL;
+        return 0;
+}
+
+/**
+ * term_history_free() - Free history
+ * @history: history to free
+ *
+ * Clear and free history. You must not access the object afterwards.
+ *
+ * Returns: NULL
+ */
+term_history *term_history_free(term_history *history) {
+        if (!history)
+                return NULL;
+
+        term_history_clear(history);
+        free(history);
+        return NULL;
+}
+
+/**
+ * term_history_clear() - Clear history
+ * @history: history to clear
+ *
+ * Remove all linked lines from a history and reset it to its initial state.
+ */
+void term_history_clear(term_history *history) {
+        return term_history_trim(history, 0);
+}
+
+/**
+ * term_history_trim() - Trim history
+ * @history: history to trim
+ * @max: maximum number of lines to be left in history
+ *
+ * This removes lines from the history until it is smaller than @max. Lines are
+ * removed from the top.
+ */
+void term_history_trim(term_history *history, unsigned int max) {
+        term_line *line;
+
+        if (!history)
+                return;
+
+        while (history->n_lines > max && (line = history->lines_first)) {
+                TERM_LINE_UNLINK(line, history);
+                term_line_free(line);
+                --history->n_lines;
+        }
+}
+
+/**
+ * term_history_push() - Push line into history
+ * @history: history to work on
+ * @line: line to push into history
+ *
+ * This pushes a line into the given history. It is linked at the tail. In case
+ * the history is limited, the top-most line might be freed.
+ */
+void term_history_push(term_history *history, term_line *line) {
+        assert(history);
+        assert(line);
+
+        TERM_LINE_LINK_TAIL(line, history);
+        if (history->max_lines > 0 && history->n_lines >= history->max_lines) {
+                line = history->lines_first;
+                TERM_LINE_UNLINK(line, history);
+                term_line_free(line);
+        } else {
+                ++history->n_lines;
+        }
+}
+
+/**
+ * term_history_pop() - Retrieve last line from history
+ * @history: history to work on
+ * @new_width: width to reserve and set on the line
+ * @attr: attributes to use for cell reservation
+ * @age: age to use for cell reservation
+ *
+ * This unlinks the last linked line of the history and returns it. This also
+ * makes sure the line has the given width pre-allocated (see
+ * term_line_reserve()). If the pre-allocation fails, this returns NULL, so it
+ * is treated like there's no line in history left. This simplifies
+ * history-handling on the caller's side in case of allocation errors. No need
+ * to throw lines away just because the reservation failed. We can keep them in
+ * history safely, and make them available as scrollback.
+ *
+ * Returns: Line from history or NULL
+ */
+term_line *term_history_pop(term_history *history, unsigned int new_width, const term_attr *attr, term_age_t age) {
+        term_line *line;
+        int r;
+
+        assert_return(history, NULL);
+
+        line = history->lines_last;
+        if (!line)
+                return NULL;
+
+        r = term_line_reserve(line, new_width, attr, age, line->width);
+        if (r < 0)
+                return NULL;
+
+        term_line_set_width(line, new_width);
+        TERM_LINE_UNLINK(line, history);
+        --history->n_lines;
+
+        return line;
+}
+
+/**
+ * term_history_peek() - Return number of available history-lines
+ * @history: history to work on
+ * @max: maximum number of lines to look at
+ * @reserve_width: width to reserve on the line
+ * @attr: attributes to use for cell reservation
+ * @age: age to use for cell reservation
+ *
+ * This returns the number of available lines in the history given as @history.
+ * It returns at most @max. For each line that is looked at, the line is
+ * verified to have at least @reserve_width cells. Valid cells are preserved,
+ * new cells are initialized with @attr and @age. In case an allocation fails,
+ * we bail out and return the number of lines that are valid so far.
+ *
+ * Usually, this function should be used before running a loop on
+ * term_history_pop(). This function guarantees that term_history_pop() (with
+ * the same arguments) will succeed at least the returned number of times.
+ *
+ * Returns: Number of valid lines that can be received via term_history_pop().
+ */
+unsigned int term_history_peek(term_history *history, unsigned int max, unsigned int reserve_width, const term_attr *attr, term_age_t age) {
+        unsigned int num;
+        term_line *line;
+        int r;
+
+        assert(history);
+
+        num = 0;
+        line = history->lines_last;
+
+        while (num < max && line) {
+                r = term_line_reserve(line, reserve_width, attr, age, line->width);
+                if (r < 0)
+                        break;
+
+                ++num;
+                line = line->lines_prev;
+        }
+
+        return num;
+}

commit 952f4b59592088f15615eb404c828aaac31bfe82
Author: David Herrmann <dh.herrmann at gmail.com>
Date:   Fri Jul 18 12:41:37 2014 +0200

    terminal: extend RGB attributes
    
    There're 3 supported color-modes: term-color-codes, 256-color-code and
    rgb-color. We now use the term-color as default so zero(attr) will do what
    you'd expect. Furthermore, we split rgb and 256color so users can forward
    them properly without requiring an internal RGB converter.
    
    Furthermore, a "hidden" field according to VT510rm manual is added.

diff --git a/src/libsystemd-terminal/term-internal.h b/src/libsystemd-terminal/term-internal.h
index af1c723..56ebd30 100644
--- a/src/libsystemd-terminal/term-internal.h
+++ b/src/libsystemd-terminal/term-internal.h
@@ -145,6 +145,11 @@ static inline void term_char_freep(term_char_t *p) {
  */
 
 enum {
+        /* special color-codes */
+        TERM_CCODE_DEFAULT,                                             /* default foreground/background color */
+        TERM_CCODE_256,                                                 /* 256color code */
+        TERM_CCODE_RGB,                                                 /* color is specified as RGB */
+
         /* dark color-codes */
         TERM_CCODE_BLACK,
         TERM_CCODE_RED,
@@ -165,16 +170,12 @@ enum {
         TERM_CCODE_LIGHT_CYAN           = TERM_CCODE_CYAN + 8,
         TERM_CCODE_LIGHT_WHITE          = TERM_CCODE_WHITE + 8,
 
-        /* pseudo colors */
-        TERM_CCODE_FG,                                                  /* selected foreground color */
-        TERM_CCODE_BG,                                                  /* selected background color */
-        TERM_CCODE_RGB,                                                 /* color is specified as RGB */
-
         TERM_CCODE_CNT,
 };
 
 struct term_color {
         uint8_t ccode;
+        uint8_t c256;
         uint8_t red;
         uint8_t green;
         uint8_t blue;
@@ -190,6 +191,7 @@ struct term_attr {
         unsigned int inverse : 1;               /* inverse fg/bg */
         unsigned int protect : 1;               /* protect from erase */
         unsigned int blink : 1;                 /* blink text */
+        unsigned int hidden : 1;                /* hidden */
 };
 
 /*



More information about the systemd-commits mailing list