chiark / gitweb /
terminal: add screen-handling
authorDavid Herrmann <dh.herrmann@gmail.com>
Tue, 8 Jul 2014 13:11:29 +0000 (15:11 +0200)
committerDavid Herrmann <dh.herrmann@gmail.com>
Fri, 18 Jul 2014 10:53:41 +0000 (12:53 +0200)
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.

Makefile.am
src/libsystemd-terminal/term-internal.h
src/libsystemd-terminal/term-screen.c [new file with mode: 0644]

index 73f1252..0de6014 100644 (file)
@@ -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 = \
index a3d1f54..345996b 100644 (file)
@@ -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 (file)
index 0000000..a19c684
--- /dev/null
@@ -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@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;
+}