Salt the earth to prevent necromancy of this dead project.

Go through the go/android-3p process if you need this in future.

Change-Id: I8917a378bc2ec5a6fb681e809c484643e2c85202
diff --git a/Android.bp b/Android.bp
deleted file mode 100644
index 217eca0..0000000
--- a/Android.bp
+++ /dev/null
@@ -1,45 +0,0 @@
-package {
-    default_applicable_licenses: ["external_libvterm_license"],
-}
-
-license {
-    name: "external_libvterm_license",
-    visibility: [":__subpackages__"],
-    license_kinds: [
-        // src/unicode.c:
-        "SPDX-license-identifier-0BSD",
-        // Everything else:
-        "SPDX-license-identifier-MIT",
-    ],
-    license_text: [
-        "LICENSE",
-    ],
-}
-
-cc_library_static {
-    name: "libvterm",
-
-    export_include_dirs: ["include"],
-
-    srcs: [
-        "src/encoding.c",
-        "src/input.c",
-        "src/keyboard.c",
-        "src/parser.c",
-        "src/pen.c",
-        "src/screen.c",
-        "src/state.c",
-        "src/unicode.c",
-        "src/vterm.c",
-    ],
-
-    cflags: [
-        "-std=c99",
-        "-Wall",
-        "-Werror",
-        "-Wno-missing-field-initializers",
-        "-Wno-sign-compare",
-        "-Wno-unused-function",
-        "-Wno-unused-parameter",
-    ],
-}
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index 0d05163..0000000
--- a/LICENSE
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-The MIT License
-
-Copyright (c) 2008 Paul Evans <leonerd@leonerd.org.uk>
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
diff --git a/METADATA b/METADATA
deleted file mode 100644
index d97975c..0000000
--- a/METADATA
+++ /dev/null
@@ -1,3 +0,0 @@
-third_party {
-  license_type: NOTICE
-}
diff --git a/MODULE_LICENSE_MIT b/MODULE_LICENSE_MIT
deleted file mode 100644
index e69de29..0000000
--- a/MODULE_LICENSE_MIT
+++ /dev/null
diff --git a/OWNERS b/OWNERS
index 2f5ffee..7529cb9 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,2 +1 @@
-# only used by the Terminal app?
 include platform/system/core:/janitors/OWNERS
diff --git a/include/vterm.h b/include/vterm.h
deleted file mode 100644
index 51dc970..0000000
--- a/include/vterm.h
+++ /dev/null
@@ -1,511 +0,0 @@
-#ifndef __VTERM_H__
-#define __VTERM_H__
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#include <stdint.h>
-#include <stdlib.h>
-#include <stdbool.h>
-
-#include "vterm_keycodes.h"
-
-typedef struct VTerm VTerm;
-typedef struct VTermState VTermState;
-typedef struct VTermScreen VTermScreen;
-
-typedef struct {
-  int row;
-  int col;
-} VTermPos;
-
-/* some small utility functions; we can just keep these static here */
-
-/* order points by on-screen flow order */
-static inline int vterm_pos_cmp(VTermPos a, VTermPos b)
-{
-  return (a.row == b.row) ? a.col - b.col : a.row - b.row;
-}
-
-typedef struct {
-  int start_row;
-  int end_row;
-  int start_col;
-  int end_col;
-} VTermRect;
-
-/* true if the rect contains the point */
-static inline int vterm_rect_contains(VTermRect r, VTermPos p)
-{
-  return p.row >= r.start_row && p.row < r.end_row &&
-         p.col >= r.start_col && p.col < r.end_col;
-}
-
-/* move a rect */
-static inline void vterm_rect_move(VTermRect *rect, int row_delta, int col_delta)
-{
-  rect->start_row += row_delta; rect->end_row += row_delta;
-  rect->start_col += col_delta; rect->end_col += col_delta;
-}
-
-/**
- * Bit-field describing the content of the tagged union `VTermColor`.
- */
-typedef enum {
-  /**
-   * If the lower bit of `type` is not set, the colour is 24-bit RGB.
-   */
-  VTERM_COLOR_RGB = 0x00,
-
-  /**
-   * The colour is an index into a palette of 256 colours.
-   */
-  VTERM_COLOR_INDEXED = 0x01,
-
-  /**
-   * Mask that can be used to extract the RGB/Indexed bit.
-   */
-  VTERM_COLOR_TYPE_MASK = 0x01,
-
-  /**
-   * If set, indicates that this colour should be the default foreground
-   * color, i.e. there was no SGR request for another colour. When
-   * rendering this colour it is possible to ignore "idx" and just use a
-   * colour that is not in the palette.
-   */
-  VTERM_COLOR_DEFAULT_FG = 0x02,
-
-  /**
-   * If set, indicates that this colour should be the default background
-   * color, i.e. there was no SGR request for another colour. A common
-   * option when rendering this colour is to not render a background at
-   * all, for example by rendering the window transparently at this spot.
-   */
-  VTERM_COLOR_DEFAULT_BG = 0x04,
-
-  /**
-   * Mask that can be used to extract the default foreground/background bit.
-   */
-  VTERM_COLOR_DEFAULT_MASK = 0x06
-} VTermColorType;
-
-/**
- * Returns true if the VTERM_COLOR_RGB `type` flag is set, indicating that the
- * given VTermColor instance is an indexed colour.
- */
-#define VTERM_COLOR_IS_INDEXED(col) \
-  (((col)->type & VTERM_COLOR_TYPE_MASK) == VTERM_COLOR_INDEXED)
-
-/**
- * Returns true if the VTERM_COLOR_INDEXED `type` flag is set, indicating that
- * the given VTermColor instance is an rgb colour.
- */
-#define VTERM_COLOR_IS_RGB(col) \
-  (((col)->type & VTERM_COLOR_TYPE_MASK) == VTERM_COLOR_RGB)
-
-/**
- * Returns true if the VTERM_COLOR_DEFAULT_FG `type` flag is set, indicating
- * that the given VTermColor instance corresponds to the default foreground
- * color.
- */
-#define VTERM_COLOR_IS_DEFAULT_FG(col) \
-  (!!((col)->type & VTERM_COLOR_DEFAULT_FG))
-
-/**
- * Returns true if the VTERM_COLOR_DEFAULT_BG `type` flag is set, indicating
- * that the given VTermColor instance corresponds to the default background
- * color.
- */
-#define VTERM_COLOR_IS_DEFAULT_BG(col) \
-  (!!((col)->type & VTERM_COLOR_DEFAULT_BG))
-
-/**
- * Tagged union storing either an RGB color or an index into a colour palette.
- * In order to convert indexed colours to RGB, you may use the
- * vterm_state_convert_color_to_rgb() or vterm_screen_convert_color_to_rgb()
- * functions which lookup the RGB colour from the palette maintained by a
- * VTermState or VTermScreen instance.
- */
-typedef union {
-  /**
-   * Tag indicating which union member is actually valid. This variable
-   * coincides with the `type` member of the `rgb` and the `indexed` struct
-   * in memory. Please use the `VTERM_COLOR_IS_*` test macros to check whether
-   * a particular type flag is set.
-   */
-  uint8_t type;
-
-  /**
-   * Valid if `VTERM_COLOR_IS_RGB(type)` is true. Holds the RGB colour values.
-   */
-  struct {
-    /**
-     * Same as the top-level `type` member stored in VTermColor.
-     */
-    uint8_t type;
-
-    /**
-     * The actual 8-bit red, green, blue colour values.
-     */
-    uint8_t red, green, blue;
-  } rgb;
-
-  /**
-   * If `VTERM_COLOR_IS_INDEXED(type)` is true, this member holds the index into
-   * the colour palette.
-   */
-  struct {
-    /**
-     * Same as the top-level `type` member stored in VTermColor.
-     */
-    uint8_t type;
-
-    /**
-     * Index into the colour map.
-     */
-    uint8_t idx;
-  } indexed;
-} VTermColor;
-
-/**
- * Constructs a new VTermColor instance representing the given RGB values.
- */
-static inline void vterm_color_rgb(VTermColor *col, uint8_t red, uint8_t green,
-                                   uint8_t blue)
-{
-  col->type = VTERM_COLOR_RGB;
-  col->rgb.red   = red;
-  col->rgb.green = green;
-  col->rgb.blue  = blue;
-}
-
-/**
- * Construct a new VTermColor instance representing an indexed color with the
- * given index.
- */
-static inline void vterm_color_indexed(VTermColor *col, uint8_t idx)
-{
-  col->type = VTERM_COLOR_INDEXED;
-  col->indexed.idx = idx;
-}
-
-/**
- * Compares two colours. Returns true if the colors are equal, false otherwise.
- */
-int vterm_color_is_equal(const VTermColor *a, const VTermColor *b);
-
-typedef enum {
-  /* VTERM_VALUETYPE_NONE = 0 */
-  VTERM_VALUETYPE_BOOL = 1,
-  VTERM_VALUETYPE_INT,
-  VTERM_VALUETYPE_STRING,
-  VTERM_VALUETYPE_COLOR,
-
-  VTERM_N_VALUETYPES
-} VTermValueType;
-
-typedef union {
-  int boolean;
-  int number;
-  char *string;
-  VTermColor color;
-} VTermValue;
-
-typedef enum {
-  /* VTERM_ATTR_NONE = 0 */
-  VTERM_ATTR_BOLD = 1,   // bool:   1, 22
-  VTERM_ATTR_UNDERLINE,  // number: 4, 21, 24
-  VTERM_ATTR_ITALIC,     // bool:   3, 23
-  VTERM_ATTR_BLINK,      // bool:   5, 25
-  VTERM_ATTR_REVERSE,    // bool:   7, 27
-  VTERM_ATTR_STRIKE,     // bool:   9, 29
-  VTERM_ATTR_FONT,       // number: 10-19
-  VTERM_ATTR_FOREGROUND, // color:  30-39 90-97
-  VTERM_ATTR_BACKGROUND, // color:  40-49 100-107
-
-  VTERM_N_ATTRS
-} VTermAttr;
-
-typedef enum {
-  /* VTERM_PROP_NONE = 0 */
-  VTERM_PROP_CURSORVISIBLE = 1, // bool
-  VTERM_PROP_CURSORBLINK,       // bool
-  VTERM_PROP_ALTSCREEN,         // bool
-  VTERM_PROP_TITLE,             // string
-  VTERM_PROP_ICONNAME,          // string
-  VTERM_PROP_REVERSE,           // bool
-  VTERM_PROP_CURSORSHAPE,       // number
-  VTERM_PROP_MOUSE,             // number
-
-  VTERM_N_PROPS
-} VTermProp;
-
-enum {
-  VTERM_PROP_CURSORSHAPE_BLOCK = 1,
-  VTERM_PROP_CURSORSHAPE_UNDERLINE,
-  VTERM_PROP_CURSORSHAPE_BAR_LEFT,
-
-  VTERM_N_PROP_CURSORSHAPES
-};
-
-enum {
-  VTERM_PROP_MOUSE_NONE = 0,
-  VTERM_PROP_MOUSE_CLICK,
-  VTERM_PROP_MOUSE_DRAG,
-  VTERM_PROP_MOUSE_MOVE,
-
-  VTERM_N_PROP_MOUSES
-};
-
-typedef struct {
-  const uint32_t *chars;
-  int             width;
-  unsigned int    protected_cell:1;  /* DECSCA-protected against DECSEL/DECSED */
-  unsigned int    dwl:1;             /* DECDWL or DECDHL double-width line */
-  unsigned int    dhl:2;             /* DECDHL double-height line (1=top 2=bottom) */
-} VTermGlyphInfo;
-
-typedef struct {
-  unsigned int    doublewidth:1;     /* DECDWL or DECDHL line */
-  unsigned int    doubleheight:2;    /* DECDHL line (1=top 2=bottom) */
-} VTermLineInfo;
-
-typedef struct {
-  /* libvterm relies on this memory to be zeroed out before it is returned
-   * by the allocator. */
-  void *(*malloc)(size_t size, void *allocdata);
-  void  (*free)(void *ptr, void *allocdata);
-} VTermAllocatorFunctions;
-
-VTerm *vterm_new(int rows, int cols);
-VTerm *vterm_new_with_allocator(int rows, int cols, VTermAllocatorFunctions *funcs, void *allocdata);
-void   vterm_free(VTerm* vt);
-
-void vterm_get_size(const VTerm *vt, int *rowsp, int *colsp);
-void vterm_set_size(VTerm *vt, int rows, int cols);
-
-int  vterm_get_utf8(const VTerm *vt);
-void vterm_set_utf8(VTerm *vt, int is_utf8);
-
-size_t vterm_input_write(VTerm *vt, const char *bytes, size_t len);
-
-size_t vterm_output_get_buffer_size(const VTerm *vt);
-size_t vterm_output_get_buffer_current(const VTerm *vt);
-size_t vterm_output_get_buffer_remaining(const VTerm *vt);
-
-size_t vterm_output_read(VTerm *vt, char *buffer, size_t len);
-
-void vterm_keyboard_unichar(VTerm *vt, uint32_t c, VTermModifier mod);
-void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod);
-
-void vterm_keyboard_start_paste(VTerm *vt);
-void vterm_keyboard_end_paste(VTerm *vt);
-
-void vterm_mouse_move(VTerm *vt, int row, int col, VTermModifier mod);
-void vterm_mouse_button(VTerm *vt, int button, bool pressed, VTermModifier mod);
-
-// ------------
-// Parser layer
-// ------------
-
-/* Flag to indicate non-final subparameters in a single CSI parameter.
- * Consider
- *   CSI 1;2:3:4;5a
- * 1 4 and 5 are final.
- * 2 and 3 are non-final and will have this bit set
- *
- * Don't confuse this with the final byte of the CSI escape; 'a' in this case.
- */
-#define CSI_ARG_FLAG_MORE (1U<<31)
-#define CSI_ARG_MASK      (~(1U<<31))
-
-#define CSI_ARG_HAS_MORE(a) ((a) & CSI_ARG_FLAG_MORE)
-#define CSI_ARG(a)          ((a) & CSI_ARG_MASK)
-
-/* Can't use -1 to indicate a missing argument; use this instead */
-#define CSI_ARG_MISSING ((1UL<<31)-1)
-
-#define CSI_ARG_IS_MISSING(a) (CSI_ARG(a) == CSI_ARG_MISSING)
-#define CSI_ARG_OR(a,def)     (CSI_ARG(a) == CSI_ARG_MISSING ? (def) : CSI_ARG(a))
-#define CSI_ARG_COUNT(a)      (CSI_ARG(a) == CSI_ARG_MISSING || CSI_ARG(a) == 0 ? 1 : CSI_ARG(a))
-
-typedef struct {
-  int (*text)(const char *bytes, size_t len, void *user);
-  int (*control)(unsigned char control, void *user);
-  int (*escape)(const char *bytes, size_t len, void *user);
-  int (*csi)(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user);
-  int (*osc)(const char *command, size_t cmdlen, void *user);
-  int (*dcs)(const char *command, size_t cmdlen, void *user);
-  int (*resize)(int rows, int cols, void *user);
-} VTermParserCallbacks;
-
-void  vterm_parser_set_callbacks(VTerm *vt, const VTermParserCallbacks *callbacks, void *user);
-void *vterm_parser_get_cbdata(VTerm *vt);
-
-// -----------
-// State layer
-// -----------
-
-typedef struct {
-  int (*putglyph)(VTermGlyphInfo *info, VTermPos pos, void *user);
-  int (*movecursor)(VTermPos pos, VTermPos oldpos, int visible, void *user);
-  int (*scrollrect)(VTermRect rect, int downward, int rightward, void *user);
-  int (*moverect)(VTermRect dest, VTermRect src, void *user);
-  int (*erase)(VTermRect rect, int selective, void *user);
-  int (*initpen)(void *user);
-  int (*setpenattr)(VTermAttr attr, VTermValue *val, void *user);
-  int (*settermprop)(VTermProp prop, VTermValue *val, void *user);
-  int (*bell)(void *user);
-  int (*resize)(int rows, int cols, VTermPos *delta, void *user);
-  int (*setlineinfo)(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user);
-} VTermStateCallbacks;
-
-VTermState *vterm_obtain_state(VTerm *vt);
-
-void  vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user);
-void *vterm_state_get_cbdata(VTermState *state);
-
-// Only invokes control, csi, osc, dcs
-void  vterm_state_set_unrecognised_fallbacks(VTermState *state, const VTermParserCallbacks *fallbacks, void *user);
-void *vterm_state_get_unrecognised_fbdata(VTermState *state);
-
-void vterm_state_reset(VTermState *state, int hard);
-void vterm_state_get_cursorpos(const VTermState *state, VTermPos *cursorpos);
-void vterm_state_get_default_colors(const VTermState *state, VTermColor *default_fg, VTermColor *default_bg);
-void vterm_state_get_palette_color(const VTermState *state, int index, VTermColor *col);
-void vterm_state_set_default_colors(VTermState *state, const VTermColor *default_fg, const VTermColor *default_bg);
-void vterm_state_set_palette_color(VTermState *state, int index, const VTermColor *col);
-void vterm_state_set_bold_highbright(VTermState *state, int bold_is_highbright);
-int  vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTermValue *val);
-int  vterm_state_set_termprop(VTermState *state, VTermProp prop, VTermValue *val);
-void vterm_state_focus_in(VTermState *state);
-void vterm_state_focus_out(VTermState *state);
-const VTermLineInfo *vterm_state_get_lineinfo(const VTermState *state, int row);
-
-/**
- * Makes sure that the given color `col` is indeed an RGB colour. After this
- * function returns, VTERM_COLOR_IS_RGB(col) will return true, while all other
- * flags stored in `col->type` will have been reset.
- *
- * @param state is the VTermState instance from which the colour palette should
- * be extracted.
- * @param col is a pointer at the VTermColor instance that should be converted
- * to an RGB colour.
- */
-void vterm_state_convert_color_to_rgb(const VTermState *state, VTermColor *col);
-
-// ------------
-// Screen layer
-// ------------
-
-typedef struct {
-    unsigned int bold      : 1;
-    unsigned int underline : 2;
-    unsigned int italic    : 1;
-    unsigned int blink     : 1;
-    unsigned int reverse   : 1;
-    unsigned int strike    : 1;
-    unsigned int font      : 4; /* 0 to 9 */
-    unsigned int dwl       : 1; /* On a DECDWL or DECDHL line */
-    unsigned int dhl       : 2; /* On a DECDHL line (1=top 2=bottom) */
-} VTermScreenCellAttrs;
-
-typedef struct {
-#define VTERM_MAX_CHARS_PER_CELL 6
-  uint32_t chars[VTERM_MAX_CHARS_PER_CELL];
-  char     width;
-  VTermScreenCellAttrs attrs;
-  VTermColor fg, bg;
-} VTermScreenCell;
-
-typedef struct {
-  int (*damage)(VTermRect rect, void *user);
-  int (*moverect)(VTermRect dest, VTermRect src, void *user);
-  int (*movecursor)(VTermPos pos, VTermPos oldpos, int visible, void *user);
-  int (*settermprop)(VTermProp prop, VTermValue *val, void *user);
-  int (*bell)(void *user);
-  int (*resize)(int rows, int cols, void *user);
-  int (*sb_pushline)(int cols, const VTermScreenCell *cells, void *user);
-  int (*sb_popline)(int cols, VTermScreenCell *cells, void *user);
-} VTermScreenCallbacks;
-
-VTermScreen *vterm_obtain_screen(VTerm *vt);
-
-void  vterm_screen_set_callbacks(VTermScreen *screen, const VTermScreenCallbacks *callbacks, void *user);
-void *vterm_screen_get_cbdata(VTermScreen *screen);
-
-// Only invokes control, csi, osc, dcs
-void  vterm_screen_set_unrecognised_fallbacks(VTermScreen *screen, const VTermParserCallbacks *fallbacks, void *user);
-void *vterm_screen_get_unrecognised_fbdata(VTermScreen *screen);
-
-void vterm_screen_enable_altscreen(VTermScreen *screen, int altscreen);
-
-typedef enum {
-  VTERM_DAMAGE_CELL,    /* every cell */
-  VTERM_DAMAGE_ROW,     /* entire rows */
-  VTERM_DAMAGE_SCREEN,  /* entire screen */
-  VTERM_DAMAGE_SCROLL,  /* entire screen + scrollrect */
-
-  VTERM_N_DAMAGES
-} VTermDamageSize;
-
-void vterm_screen_flush_damage(VTermScreen *screen);
-void vterm_screen_set_damage_merge(VTermScreen *screen, VTermDamageSize size);
-
-void   vterm_screen_reset(VTermScreen *screen, int hard);
-
-/* Neither of these functions NUL-terminate the buffer */
-size_t vterm_screen_get_chars(const VTermScreen *screen, uint32_t *chars, size_t len, const VTermRect rect);
-size_t vterm_screen_get_text(const VTermScreen *screen, char *str, size_t len, const VTermRect rect);
-
-typedef enum {
-  VTERM_ATTR_BOLD_MASK       = 1 << 0,
-  VTERM_ATTR_UNDERLINE_MASK  = 1 << 1,
-  VTERM_ATTR_ITALIC_MASK     = 1 << 2,
-  VTERM_ATTR_BLINK_MASK      = 1 << 3,
-  VTERM_ATTR_REVERSE_MASK    = 1 << 4,
-  VTERM_ATTR_STRIKE_MASK     = 1 << 5,
-  VTERM_ATTR_FONT_MASK       = 1 << 6,
-  VTERM_ATTR_FOREGROUND_MASK = 1 << 7,
-  VTERM_ATTR_BACKGROUND_MASK = 1 << 8,
-
-  VTERM_ALL_ATTRS_MASK = (1 << 9) - 1
-} VTermAttrMask;
-
-int vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect *extent, VTermPos pos, VTermAttrMask attrs);
-
-int vterm_screen_get_cell(const VTermScreen *screen, VTermPos pos, VTermScreenCell *cell);
-
-int vterm_screen_is_eol(const VTermScreen *screen, VTermPos pos);
-
-/**
- * Same as vterm_state_convert_color_to_rgb(), but takes a `screen` instead of a `state`
- * instance.
- */
-void vterm_screen_convert_color_to_rgb(const VTermScreen *screen, VTermColor *col);
-
-// ---------
-// Utilities
-// ---------
-
-VTermValueType vterm_get_attr_type(VTermAttr attr);
-VTermValueType vterm_get_prop_type(VTermProp prop);
-
-void vterm_scroll_rect(VTermRect rect,
-                       int downward,
-                       int rightward,
-                       int (*moverect)(VTermRect src, VTermRect dest, void *user),
-                       int (*eraserect)(VTermRect rect, int selective, void *user),
-                       void *user);
-
-void vterm_copy_cells(VTermRect dest,
-                      VTermRect src,
-                      void (*copycell)(VTermPos dest, VTermPos src, void *user),
-                      void *user);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif
diff --git a/include/vterm_keycodes.h b/include/vterm_keycodes.h
deleted file mode 100644
index 661759f..0000000
--- a/include/vterm_keycodes.h
+++ /dev/null
@@ -1,61 +0,0 @@
-#ifndef __VTERM_INPUT_H__
-#define __VTERM_INPUT_H__
-
-typedef enum {
-  VTERM_MOD_NONE  = 0x00,
-  VTERM_MOD_SHIFT = 0x01,
-  VTERM_MOD_ALT   = 0x02,
-  VTERM_MOD_CTRL  = 0x04,
-
-  VTERM_ALL_MODS_MASK = 0x07 
-} VTermModifier;
-
-typedef enum {
-  VTERM_KEY_NONE,
-
-  VTERM_KEY_ENTER,
-  VTERM_KEY_TAB,
-  VTERM_KEY_BACKSPACE,
-  VTERM_KEY_ESCAPE,
-
-  VTERM_KEY_UP,
-  VTERM_KEY_DOWN,
-  VTERM_KEY_LEFT,
-  VTERM_KEY_RIGHT,
-
-  VTERM_KEY_INS,
-  VTERM_KEY_DEL,
-  VTERM_KEY_HOME,
-  VTERM_KEY_END,
-  VTERM_KEY_PAGEUP,
-  VTERM_KEY_PAGEDOWN,
-
-  VTERM_KEY_FUNCTION_0   = 256,
-  VTERM_KEY_FUNCTION_MAX = VTERM_KEY_FUNCTION_0 + 255,
-
-  VTERM_KEY_KP_0,
-  VTERM_KEY_KP_1,
-  VTERM_KEY_KP_2,
-  VTERM_KEY_KP_3,
-  VTERM_KEY_KP_4,
-  VTERM_KEY_KP_5,
-  VTERM_KEY_KP_6,
-  VTERM_KEY_KP_7,
-  VTERM_KEY_KP_8,
-  VTERM_KEY_KP_9,
-  VTERM_KEY_KP_MULT,
-  VTERM_KEY_KP_PLUS,
-  VTERM_KEY_KP_COMMA,
-  VTERM_KEY_KP_MINUS,
-  VTERM_KEY_KP_PERIOD,
-  VTERM_KEY_KP_DIVIDE,
-  VTERM_KEY_KP_ENTER,
-  VTERM_KEY_KP_EQUAL,
-
-  VTERM_KEY_MAX, // Must be last
-  VTERM_N_KEYS = VTERM_KEY_MAX
-} VTermKey;
-
-#define VTERM_KEY_FUNCTION(n) (VTERM_KEY_FUNCTION_0+(n))
-
-#endif
diff --git a/src/encoding.c b/src/encoding.c
deleted file mode 100644
index 434ac3f..0000000
--- a/src/encoding.c
+++ /dev/null
@@ -1,230 +0,0 @@
-#include "vterm_internal.h"
-
-#define UNICODE_INVALID 0xFFFD
-
-#if defined(DEBUG) && DEBUG > 1
-# define DEBUG_PRINT_UTF8
-#endif
-
-struct UTF8DecoderData {
-  // number of bytes remaining in this codepoint
-  int bytes_remaining;
-
-  // number of bytes total in this codepoint once it's finished
-  // (for detecting overlongs)
-  int bytes_total;
-
-  int this_cp;
-};
-
-static void init_utf8(VTermEncoding *enc, void *data_)
-{
-  struct UTF8DecoderData *data = data_;
-
-  data->bytes_remaining = 0;
-  data->bytes_total     = 0;
-}
-
-static void decode_utf8(VTermEncoding *enc, void *data_,
-                        uint32_t cp[], int *cpi, int cplen,
-                        const char bytes[], size_t *pos, size_t bytelen)
-{
-  struct UTF8DecoderData *data = data_;
-
-#ifdef DEBUG_PRINT_UTF8
-  printf("BEGIN UTF-8\n");
-#endif
-
-  for(; *pos < bytelen && *cpi < cplen; (*pos)++) {
-    unsigned char c = bytes[*pos];
-
-#ifdef DEBUG_PRINT_UTF8
-    printf(" pos=%zd c=%02x rem=%d\n", *pos, c, data->bytes_remaining);
-#endif
-
-    if(c < 0x20) // C0
-      return;
-
-    else if(c >= 0x20 && c < 0x7f) {
-      if(data->bytes_remaining)
-        cp[(*cpi)++] = UNICODE_INVALID;
-
-      cp[(*cpi)++] = c;
-#ifdef DEBUG_PRINT_UTF8
-      printf(" UTF-8 char: U+%04x\n", c);
-#endif
-      data->bytes_remaining = 0;
-    }
-
-    else if(c == 0x7f) // DEL
-      return;
-
-    else if(c >= 0x80 && c < 0xc0) {
-      if(!data->bytes_remaining) {
-        cp[(*cpi)++] = UNICODE_INVALID;
-        continue;
-      }
-
-      data->this_cp <<= 6;
-      data->this_cp |= c & 0x3f;
-      data->bytes_remaining--;
-
-      if(!data->bytes_remaining) {
-#ifdef DEBUG_PRINT_UTF8
-        printf(" UTF-8 raw char U+%04x bytelen=%d ", data->this_cp, data->bytes_total);
-#endif
-        // Check for overlong sequences
-        switch(data->bytes_total) {
-        case 2:
-          if(data->this_cp <  0x0080) data->this_cp = UNICODE_INVALID;
-          break;
-        case 3:
-          if(data->this_cp <  0x0800) data->this_cp = UNICODE_INVALID;
-          break;
-        case 4:
-          if(data->this_cp < 0x10000) data->this_cp = UNICODE_INVALID;
-          break;
-        case 5:
-          if(data->this_cp < 0x200000) data->this_cp = UNICODE_INVALID;
-          break;
-        case 6:
-          if(data->this_cp < 0x4000000) data->this_cp = UNICODE_INVALID;
-          break;
-        }
-        // Now look for plain invalid ones
-        if((data->this_cp >= 0xD800 && data->this_cp <= 0xDFFF) ||
-           data->this_cp == 0xFFFE ||
-           data->this_cp == 0xFFFF)
-          data->this_cp = UNICODE_INVALID;
-#ifdef DEBUG_PRINT_UTF8
-        printf(" char: U+%04x\n", data->this_cp);
-#endif
-        cp[(*cpi)++] = data->this_cp;
-      }
-    }
-
-    else if(c >= 0xc0 && c < 0xe0) {
-      if(data->bytes_remaining)
-        cp[(*cpi)++] = UNICODE_INVALID;
-
-      data->this_cp = c & 0x1f;
-      data->bytes_total = 2;
-      data->bytes_remaining = 1;
-    }
-
-    else if(c >= 0xe0 && c < 0xf0) {
-      if(data->bytes_remaining)
-        cp[(*cpi)++] = UNICODE_INVALID;
-
-      data->this_cp = c & 0x0f;
-      data->bytes_total = 3;
-      data->bytes_remaining = 2;
-    }
-
-    else if(c >= 0xf0 && c < 0xf8) {
-      if(data->bytes_remaining)
-        cp[(*cpi)++] = UNICODE_INVALID;
-
-      data->this_cp = c & 0x07;
-      data->bytes_total = 4;
-      data->bytes_remaining = 3;
-    }
-
-    else if(c >= 0xf8 && c < 0xfc) {
-      if(data->bytes_remaining)
-        cp[(*cpi)++] = UNICODE_INVALID;
-
-      data->this_cp = c & 0x03;
-      data->bytes_total = 5;
-      data->bytes_remaining = 4;
-    }
-
-    else if(c >= 0xfc && c < 0xfe) {
-      if(data->bytes_remaining)
-        cp[(*cpi)++] = UNICODE_INVALID;
-
-      data->this_cp = c & 0x01;
-      data->bytes_total = 6;
-      data->bytes_remaining = 5;
-    }
-
-    else {
-      cp[(*cpi)++] = UNICODE_INVALID;
-    }
-  }
-}
-
-static VTermEncoding encoding_utf8 = {
-  .init   = &init_utf8,
-  .decode = &decode_utf8,
-};
-
-static void decode_usascii(VTermEncoding *enc, void *data,
-                           uint32_t cp[], int *cpi, int cplen,
-                           const char bytes[], size_t *pos, size_t bytelen)
-{
-  int is_gr = bytes[*pos] & 0x80;
-
-  for(; *pos < bytelen && *cpi < cplen; (*pos)++) {
-    unsigned char c = bytes[*pos] ^ is_gr;
-
-    if(c < 0x20 || c == 0x7f || c >= 0x80)
-      return;
-
-    cp[(*cpi)++] = c;
-  }
-}
-
-static VTermEncoding encoding_usascii = {
-  .decode = &decode_usascii,
-};
-
-struct StaticTableEncoding {
-  const VTermEncoding enc;
-  const uint32_t chars[128];
-};
-
-static void decode_table(VTermEncoding *enc, void *data,
-                         uint32_t cp[], int *cpi, int cplen,
-                         const char bytes[], size_t *pos, size_t bytelen)
-{
-  struct StaticTableEncoding *table = (struct StaticTableEncoding *)enc;
-  int is_gr = bytes[*pos] & 0x80;
-
-  for(; *pos < bytelen && *cpi < cplen; (*pos)++) {
-    unsigned char c = bytes[*pos] ^ is_gr;
-
-    if(c < 0x20 || c == 0x7f || c >= 0x80)
-      return;
-
-    if(table->chars[c])
-      cp[(*cpi)++] = table->chars[c];
-    else
-      cp[(*cpi)++] = c;
-  }
-}
-
-#include "encoding/DECdrawing.inc"
-#include "encoding/uk.inc"
-
-static struct {
-  VTermEncodingType type;
-  char designation;
-  VTermEncoding *enc;
-}
-encodings[] = {
-  { ENC_UTF8,      'u', &encoding_utf8 },
-  { ENC_SINGLE_94, '0', (VTermEncoding*)&encoding_DECdrawing },
-  { ENC_SINGLE_94, 'A', (VTermEncoding*)&encoding_uk },
-  { ENC_SINGLE_94, 'B', &encoding_usascii },
-  { 0 },
-};
-
-/* This ought to be INTERNAL but isn't because it's used by unit testing */
-VTermEncoding *vterm_lookup_encoding(VTermEncodingType type, char designation)
-{
-  for(int i = 0; encodings[i].designation; i++)
-    if(encodings[i].type == type && encodings[i].designation == designation)
-      return encodings[i].enc;
-  return NULL;
-}
diff --git a/src/encoding/DECdrawing.inc b/src/encoding/DECdrawing.inc
deleted file mode 100644
index 47093ed..0000000
--- a/src/encoding/DECdrawing.inc
+++ /dev/null
@@ -1,36 +0,0 @@
-static const struct StaticTableEncoding encoding_DECdrawing = {
-  { .decode = &decode_table },
-  {
-    [0x60] = 0x25C6,
-    [0x61] = 0x2592,
-    [0x62] = 0x2409,
-    [0x63] = 0x240C,
-    [0x64] = 0x240D,
-    [0x65] = 0x240A,
-    [0x66] = 0x00B0,
-    [0x67] = 0x00B1,
-    [0x68] = 0x2424,
-    [0x69] = 0x240B,
-    [0x6a] = 0x2518,
-    [0x6b] = 0x2510,
-    [0x6c] = 0x250C,
-    [0x6d] = 0x2514,
-    [0x6e] = 0x253C,
-    [0x6f] = 0x23BA,
-    [0x70] = 0x23BB,
-    [0x71] = 0x2500,
-    [0x72] = 0x23BC,
-    [0x73] = 0x23BD,
-    [0x74] = 0x251C,
-    [0x75] = 0x2524,
-    [0x76] = 0x2534,
-    [0x77] = 0x252C,
-    [0x78] = 0x2502,
-    [0x79] = 0x2A7D,
-    [0x7a] = 0x2A7E,
-    [0x7b] = 0x03C0,
-    [0x7c] = 0x2260,
-    [0x7d] = 0x00A3,
-    [0x7e] = 0x00B7,
-  }
-};
diff --git a/src/encoding/DECdrawing.tbl b/src/encoding/DECdrawing.tbl
deleted file mode 100644
index 6e19c50..0000000
--- a/src/encoding/DECdrawing.tbl
+++ /dev/null
@@ -1,31 +0,0 @@
-6/0 = U+25C6 # BLACK DIAMOND
-6/1 = U+2592 # MEDIUM SHADE (checkerboard)
-6/2 = U+2409 # SYMBOL FOR HORIZONTAL TAB
-6/3 = U+240C # SYMBOL FOR FORM FEED
-6/4 = U+240D # SYMBOL FOR CARRIAGE RETURN
-6/5 = U+240A # SYMBOL FOR LINE FEED
-6/6 = U+00B0 # DEGREE SIGN
-6/7 = U+00B1 # PLUS-MINUS SIGN (plus or minus)
-6/8 = U+2424 # SYMBOL FOR NEW LINE
-6/9 = U+240B # SYMBOL FOR VERTICAL TAB
-6/10 = U+2518 # BOX DRAWINGS LIGHT UP AND LEFT (bottom-right corner)
-6/11 = U+2510 # BOX DRAWINGS LIGHT DOWN AND LEFT (top-right corner)
-6/12 = U+250C # BOX DRAWINGS LIGHT DOWN AND RIGHT (top-left corner)
-6/13 = U+2514 # BOX DRAWINGS LIGHT UP AND RIGHT (bottom-left corner)
-6/14 = U+253C # BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL (crossing lines)
-6/15 = U+23BA # HORIZONTAL SCAN LINE-1
-7/0 = U+23BB # HORIZONTAL SCAN LINE-3
-7/1 = U+2500 # BOX DRAWINGS LIGHT HORIZONTAL
-7/2 = U+23BC # HORIZONTAL SCAN LINE-7
-7/3 = U+23BD # HORIZONTAL SCAN LINE-9
-7/4 = U+251C # BOX DRAWINGS LIGHT VERTICAL AND RIGHT
-7/5 = U+2524 # BOX DRAWINGS LIGHT VERTICAL AND LEFT
-7/6 = U+2534 # BOX DRAWINGS LIGHT UP AND HORIZONTAL
-7/7 = U+252C # BOX DRAWINGS LIGHT DOWN AND HORIZONTAL
-7/8 = U+2502 # BOX DRAWINGS LIGHT VERTICAL
-7/9 = U+2A7D # LESS-THAN OR SLANTED EQUAL-TO
-7/10 = U+2A7E # GREATER-THAN OR SLANTED EQUAL-TO
-7/11 = U+03C0 # GREEK SMALL LETTER PI
-7/12 = U+2260 # NOT EQUAL TO
-7/13 = U+00A3 # POUND SIGN
-7/14 = U+00B7 # MIDDLE DOT
diff --git a/src/encoding/uk.inc b/src/encoding/uk.inc
deleted file mode 100644
index da1445d..0000000
--- a/src/encoding/uk.inc
+++ /dev/null
@@ -1,6 +0,0 @@
-static const struct StaticTableEncoding encoding_uk = {
-  { .decode = &decode_table },
-  {
-    [0x23] = 0x00a3,
-  }
-};
diff --git a/src/encoding/uk.tbl b/src/encoding/uk.tbl
deleted file mode 100644
index b27b1a2..0000000
--- a/src/encoding/uk.tbl
+++ /dev/null
@@ -1 +0,0 @@
-2/3 = "£"
diff --git a/src/fullwidth.inc b/src/fullwidth.inc
deleted file mode 100644
index 7ff142f..0000000
--- a/src/fullwidth.inc
+++ /dev/null
@@ -1,104 +0,0 @@
-  { 0x1100, 0x115f },
-  { 0x231a, 0x231b },
-  { 0x2329, 0x232a },
-  { 0x23e9, 0x23ec },
-  { 0x23f0, 0x23f0 },
-  { 0x23f3, 0x23f3 },
-  { 0x25fd, 0x25fe },
-  { 0x2614, 0x2615 },
-  { 0x2648, 0x2653 },
-  { 0x267f, 0x267f },
-  { 0x2693, 0x2693 },
-  { 0x26a1, 0x26a1 },
-  { 0x26aa, 0x26ab },
-  { 0x26bd, 0x26be },
-  { 0x26c4, 0x26c5 },
-  { 0x26ce, 0x26ce },
-  { 0x26d4, 0x26d4 },
-  { 0x26ea, 0x26ea },
-  { 0x26f2, 0x26f3 },
-  { 0x26f5, 0x26f5 },
-  { 0x26fa, 0x26fa },
-  { 0x26fd, 0x26fd },
-  { 0x2705, 0x2705 },
-  { 0x270a, 0x270b },
-  { 0x2728, 0x2728 },
-  { 0x274c, 0x274c },
-  { 0x274e, 0x274e },
-  { 0x2753, 0x2755 },
-  { 0x2757, 0x2757 },
-  { 0x2795, 0x2797 },
-  { 0x27b0, 0x27b0 },
-  { 0x27bf, 0x27bf },
-  { 0x2b1b, 0x2b1c },
-  { 0x2b50, 0x2b50 },
-  { 0x2b55, 0x2b55 },
-  { 0x2e80, 0x2e99 },
-  { 0x2e9b, 0x2ef3 },
-  { 0x2f00, 0x2fd5 },
-  { 0x2ff0, 0x2ffb },
-  { 0x3000, 0x303e },
-  { 0x3041, 0x3096 },
-  { 0x3099, 0x30ff },
-  { 0x3105, 0x312d },
-  { 0x3131, 0x318e },
-  { 0x3190, 0x31ba },
-  { 0x31c0, 0x31e3 },
-  { 0x31f0, 0x321e },
-  { 0x3220, 0x3247 },
-  { 0x3250, 0x32fe },
-  { 0x3300, 0x4dbf },
-  { 0x4e00, 0xa48c },
-  { 0xa490, 0xa4c6 },
-  { 0xa960, 0xa97c },
-  { 0xac00, 0xd7a3 },
-  { 0xf900, 0xfaff },
-  { 0xfe10, 0xfe19 },
-  { 0xfe30, 0xfe52 },
-  { 0xfe54, 0xfe66 },
-  { 0xfe68, 0xfe6b },
-  { 0xff01, 0xff60 },
-  { 0xffe0, 0xffe6 },
-  { 0x16fe0, 0x16fe0 },
-  { 0x17000, 0x187ec },
-  { 0x18800, 0x18af2 },
-  { 0x1b000, 0x1b001 },
-  { 0x1f004, 0x1f004 },
-  { 0x1f0cf, 0x1f0cf },
-  { 0x1f18e, 0x1f18e },
-  { 0x1f191, 0x1f19a },
-  { 0x1f200, 0x1f202 },
-  { 0x1f210, 0x1f23b },
-  { 0x1f240, 0x1f248 },
-  { 0x1f250, 0x1f251 },
-  { 0x1f300, 0x1f320 },
-  { 0x1f32d, 0x1f335 },
-  { 0x1f337, 0x1f37c },
-  { 0x1f37e, 0x1f393 },
-  { 0x1f3a0, 0x1f3ca },
-  { 0x1f3cf, 0x1f3d3 },
-  { 0x1f3e0, 0x1f3f0 },
-  { 0x1f3f4, 0x1f3f4 },
-  { 0x1f3f8, 0x1f43e },
-  { 0x1f440, 0x1f440 },
-  { 0x1f442, 0x1f4fc },
-  { 0x1f4ff, 0x1f53d },
-  { 0x1f54b, 0x1f54e },
-  { 0x1f550, 0x1f567 },
-  { 0x1f57a, 0x1f57a },
-  { 0x1f595, 0x1f596 },
-  { 0x1f5a4, 0x1f5a4 },
-  { 0x1f5fb, 0x1f64f },
-  { 0x1f680, 0x1f6c5 },
-  { 0x1f6cc, 0x1f6cc },
-  { 0x1f6d0, 0x1f6d2 },
-  { 0x1f6eb, 0x1f6ec },
-  { 0x1f6f4, 0x1f6f6 },
-  { 0x1f910, 0x1f91e },
-  { 0x1f920, 0x1f927 },
-  { 0x1f930, 0x1f930 },
-  { 0x1f933, 0x1f93e },
-  { 0x1f940, 0x1f94b },
-  { 0x1f950, 0x1f95e },
-  { 0x1f980, 0x1f991 },
-  { 0x1f9c0, 0x1f9c0 },
diff --git a/src/input.c b/src/input.c
deleted file mode 100644
index 0eaf0e9..0000000
--- a/src/input.c
+++ /dev/null
@@ -1,210 +0,0 @@
-#include "vterm_internal.h"
-
-#include <stdio.h>
-
-#include "utf8.h"
-
-void vterm_input_push_char(VTerm *vt, VTermModifier mod, uint32_t c)
-{
-  /* The shift modifier is never important for Unicode characters
-   * apart from Space
-   */
-  if(c != ' ')
-    mod &= ~VTERM_MOD_SHIFT;
-
-  if(mod == 0) {
-    // Normal text - ignore just shift
-    char str[6];
-    int seqlen = fill_utf8(c, str);
-    vterm_push_output_bytes(vt, str, seqlen);
-    return;
-  }
-
-  int needs_CSIu;
-  switch(c) {
-    /* Special Ctrl- letters that can't be represented elsewise */
-    case 'h': case 'i': case 'j': case 'm': case '[':
-      needs_CSIu = 1;
-      break;
-    /* Ctrl-\ ] ^ _ don't need CSUu */
-    case '\\': case ']': case '^': case '_':
-      needs_CSIu = 0;
-      break;
-    /* All other characters needs CSIu except for letters a-z */
-    default:
-      needs_CSIu = (c < 'a' || c > 'z');
-  }
-
-  /* ALT we can just prefix with ESC; anything else requires CSI u */
-  if(needs_CSIu && (mod & ~VTERM_MOD_ALT)) {
-    vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", c, mod+1);
-    return;
-  }
-
-  if(mod & VTERM_MOD_CTRL)
-    c &= 0x1f;
-
-  vterm_push_output_sprintf(vt, "%s%c", mod & VTERM_MOD_ALT ? "\e" : "", c);
-}
-
-typedef struct {
-  enum {
-    KEYCODE_NONE,
-    KEYCODE_LITERAL,
-    KEYCODE_TAB,
-    KEYCODE_ENTER,
-    KEYCODE_SS3,
-    KEYCODE_CSI,
-    KEYCODE_CSI_CURSOR,
-    KEYCODE_CSINUM,
-    KEYCODE_KEYPAD,
-  } type;
-  char literal;
-  int csinum;
-} keycodes_s;
-
-static keycodes_s keycodes[] = {
-  { KEYCODE_NONE }, // NONE
-
-  { KEYCODE_ENTER,   '\r'   }, // ENTER
-  { KEYCODE_TAB,     '\t'   }, // TAB
-  { KEYCODE_LITERAL, '\x7f' }, // BACKSPACE == ASCII DEL
-  { KEYCODE_LITERAL, '\e'   }, // ESCAPE
-
-  { KEYCODE_CSI_CURSOR, 'A' }, // UP
-  { KEYCODE_CSI_CURSOR, 'B' }, // DOWN
-  { KEYCODE_CSI_CURSOR, 'D' }, // LEFT
-  { KEYCODE_CSI_CURSOR, 'C' }, // RIGHT
-
-  { KEYCODE_CSINUM, '~', 2 },  // INS
-  { KEYCODE_CSINUM, '~', 3 },  // DEL
-  { KEYCODE_CSI_CURSOR, 'H' }, // HOME
-  { KEYCODE_CSI_CURSOR, 'F' }, // END
-  { KEYCODE_CSINUM, '~', 5 },  // PAGEUP
-  { KEYCODE_CSINUM, '~', 6 },  // PAGEDOWN
-};
-
-static keycodes_s keycodes_fn[] = {
-  { KEYCODE_NONE },            // F0 - shouldn't happen
-  { KEYCODE_CSI_CURSOR, 'P' }, // F1
-  { KEYCODE_CSI_CURSOR, 'Q' }, // F2
-  { KEYCODE_CSI_CURSOR, 'R' }, // F3
-  { KEYCODE_CSI_CURSOR, 'S' }, // F4
-  { KEYCODE_CSINUM, '~', 15 }, // F5
-  { KEYCODE_CSINUM, '~', 17 }, // F6
-  { KEYCODE_CSINUM, '~', 18 }, // F7
-  { KEYCODE_CSINUM, '~', 19 }, // F8
-  { KEYCODE_CSINUM, '~', 20 }, // F9
-  { KEYCODE_CSINUM, '~', 21 }, // F10
-  { KEYCODE_CSINUM, '~', 23 }, // F11
-  { KEYCODE_CSINUM, '~', 24 }, // F12
-};
-
-static keycodes_s keycodes_kp[] = {
-  { KEYCODE_KEYPAD, '0', 'p' }, // KP_0
-  { KEYCODE_KEYPAD, '1', 'q' }, // KP_1
-  { KEYCODE_KEYPAD, '2', 'r' }, // KP_2
-  { KEYCODE_KEYPAD, '3', 's' }, // KP_3
-  { KEYCODE_KEYPAD, '4', 't' }, // KP_4
-  { KEYCODE_KEYPAD, '5', 'u' }, // KP_5
-  { KEYCODE_KEYPAD, '6', 'v' }, // KP_6
-  { KEYCODE_KEYPAD, '7', 'w' }, // KP_7
-  { KEYCODE_KEYPAD, '8', 'x' }, // KP_8
-  { KEYCODE_KEYPAD, '9', 'y' }, // KP_9
-  { KEYCODE_KEYPAD, '*', 'j' }, // KP_MULT
-  { KEYCODE_KEYPAD, '+', 'k' }, // KP_PLUS
-  { KEYCODE_KEYPAD, ',', 'l' }, // KP_COMMA
-  { KEYCODE_KEYPAD, '-', 'm' }, // KP_MINUS
-  { KEYCODE_KEYPAD, '.', 'n' }, // KP_PERIOD
-  { KEYCODE_KEYPAD, '/', 'o' }, // KP_DIVIDE
-  { KEYCODE_KEYPAD, '\n', 'M' }, // KP_ENTER
-  { KEYCODE_KEYPAD, '=', 'X' }, // KP_EQUAL
-};
-
-void vterm_input_push_key(VTerm *vt, VTermModifier mod, VTermKey key)
-{
-  if(key == VTERM_KEY_NONE)
-    return;
-
-  keycodes_s k;
-  if(key < VTERM_KEY_FUNCTION_0) {
-    if(key >= sizeof(keycodes)/sizeof(keycodes[0]))
-      return;
-    k = keycodes[key];
-  }
-  else if(key >= VTERM_KEY_FUNCTION_0 && key <= VTERM_KEY_FUNCTION_MAX) {
-    if((key - VTERM_KEY_FUNCTION_0) >= sizeof(keycodes_fn)/sizeof(keycodes_fn[0]))
-      return;
-    k = keycodes_fn[key - VTERM_KEY_FUNCTION_0];
-  }
-  else if(key >= VTERM_KEY_KP_0) {
-    if((key - VTERM_KEY_KP_0) >= sizeof(keycodes_kp)/sizeof(keycodes_kp[0]))
-      return;
-    k = keycodes_kp[key - VTERM_KEY_KP_0];
-  }
-
-  switch(k.type) {
-  case KEYCODE_NONE:
-    break;
-
-  case KEYCODE_TAB:
-    /* Shift-Tab is CSI Z but plain Tab is 0x09 */
-    if(mod == VTERM_MOD_SHIFT)
-      vterm_push_output_sprintf_ctrl(vt, C1_CSI, "Z");
-    else if(mod & VTERM_MOD_SHIFT)
-      vterm_push_output_sprintf_ctrl(vt, C1_CSI, "1;%dZ", mod+1);
-    else
-      goto case_LITERAL;
-    break;
-
-  case KEYCODE_ENTER:
-    /* Enter is CRLF in newline mode, but just LF in linefeed */
-    if(vt->state->mode.newline)
-      vterm_push_output_sprintf(vt, "\r\n");
-    else
-      goto case_LITERAL;
-    break;
-
-  case KEYCODE_LITERAL: case_LITERAL:
-    if(mod & (VTERM_MOD_SHIFT|VTERM_MOD_CTRL))
-      vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", k.literal, mod+1);
-    else
-      vterm_push_output_sprintf(vt, mod & VTERM_MOD_ALT ? "\e%c" : "%c", k.literal);
-    break;
-
-  case KEYCODE_SS3: case_SS3:
-    if(mod == 0)
-      vterm_push_output_sprintf_ctrl(vt, C1_SS3, "%c", k.literal);
-    else
-      goto case_CSI;
-    break;
-
-  case KEYCODE_CSI: case_CSI:
-    if(mod == 0)
-      vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%c", k.literal);
-    else
-      vterm_push_output_sprintf_ctrl(vt, C1_CSI, "1;%d%c", mod + 1, k.literal);
-    break;
-
-  case KEYCODE_CSINUM:
-    if(mod == 0)
-      vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d%c", k.csinum, k.literal);
-    else
-      vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%d%c", k.csinum, mod + 1, k.literal);
-    break;
-
-  case KEYCODE_CSI_CURSOR:
-    if(vt->state->mode.cursor)
-      goto case_SS3;
-    else
-      goto case_CSI;
-
-  case KEYCODE_KEYPAD:
-    if(vt->state->mode.keypad) {
-      k.literal = k.csinum;
-      goto case_SS3;
-    }
-    else
-      goto case_LITERAL;
-  }
-}
diff --git a/src/keyboard.c b/src/keyboard.c
deleted file mode 100644
index b541fb1..0000000
--- a/src/keyboard.c
+++ /dev/null
@@ -1,226 +0,0 @@
-#include "vterm_internal.h"
-
-#include <stdio.h>
-
-#include "utf8.h"
-
-void vterm_keyboard_unichar(VTerm *vt, uint32_t c, VTermModifier mod)
-{
-  /* The shift modifier is never important for Unicode characters
-   * apart from Space
-   */
-  if(c != ' ')
-    mod &= ~VTERM_MOD_SHIFT;
-
-  if(mod == 0) {
-    // Normal text - ignore just shift
-    char str[6];
-    int seqlen = fill_utf8(c, str);
-    vterm_push_output_bytes(vt, str, seqlen);
-    return;
-  }
-
-  int needs_CSIu;
-  switch(c) {
-    /* Special Ctrl- letters that can't be represented elsewise */
-    case 'i': case 'j': case 'm': case '[':
-      needs_CSIu = 1;
-      break;
-    /* Ctrl-\ ] ^ _ don't need CSUu */
-    case '\\': case ']': case '^': case '_':
-      needs_CSIu = 0;
-      break;
-    /* Shift-space needs CSIu */
-    case ' ':
-      needs_CSIu = !!(mod & VTERM_MOD_SHIFT);
-      break;
-    /* All other characters needs CSIu except for letters a-z */
-    default:
-      needs_CSIu = (c < 'a' || c > 'z');
-  }
-
-  /* ALT we can just prefix with ESC; anything else requires CSI u */
-  if(needs_CSIu && (mod & ~VTERM_MOD_ALT)) {
-    vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", c, mod+1);
-    return;
-  }
-
-  if(mod & VTERM_MOD_CTRL)
-    c &= 0x1f;
-
-  vterm_push_output_sprintf(vt, "%s%c", mod & VTERM_MOD_ALT ? ESC_S : "", c);
-}
-
-typedef struct {
-  enum {
-    KEYCODE_NONE,
-    KEYCODE_LITERAL,
-    KEYCODE_TAB,
-    KEYCODE_ENTER,
-    KEYCODE_SS3,
-    KEYCODE_CSI,
-    KEYCODE_CSI_CURSOR,
-    KEYCODE_CSINUM,
-    KEYCODE_KEYPAD,
-  } type;
-  char literal;
-  int csinum;
-} keycodes_s;
-
-static keycodes_s keycodes[] = {
-  { KEYCODE_NONE }, // NONE
-
-  { KEYCODE_ENTER,   '\r'   }, // ENTER
-  { KEYCODE_TAB,     '\t'   }, // TAB
-  { KEYCODE_LITERAL, '\x7f' }, // BACKSPACE == ASCII DEL
-  { KEYCODE_LITERAL, '\x1b' }, // ESCAPE
-
-  { KEYCODE_CSI_CURSOR, 'A' }, // UP
-  { KEYCODE_CSI_CURSOR, 'B' }, // DOWN
-  { KEYCODE_CSI_CURSOR, 'D' }, // LEFT
-  { KEYCODE_CSI_CURSOR, 'C' }, // RIGHT
-
-  { KEYCODE_CSINUM, '~', 2 },  // INS
-  { KEYCODE_CSINUM, '~', 3 },  // DEL
-  { KEYCODE_CSI_CURSOR, 'H' }, // HOME
-  { KEYCODE_CSI_CURSOR, 'F' }, // END
-  { KEYCODE_CSINUM, '~', 5 },  // PAGEUP
-  { KEYCODE_CSINUM, '~', 6 },  // PAGEDOWN
-};
-
-static keycodes_s keycodes_fn[] = {
-  { KEYCODE_NONE },            // F0 - shouldn't happen
-  { KEYCODE_CSI_CURSOR, 'P' }, // F1
-  { KEYCODE_CSI_CURSOR, 'Q' }, // F2
-  { KEYCODE_CSI_CURSOR, 'R' }, // F3
-  { KEYCODE_CSI_CURSOR, 'S' }, // F4
-  { KEYCODE_CSINUM, '~', 15 }, // F5
-  { KEYCODE_CSINUM, '~', 17 }, // F6
-  { KEYCODE_CSINUM, '~', 18 }, // F7
-  { KEYCODE_CSINUM, '~', 19 }, // F8
-  { KEYCODE_CSINUM, '~', 20 }, // F9
-  { KEYCODE_CSINUM, '~', 21 }, // F10
-  { KEYCODE_CSINUM, '~', 23 }, // F11
-  { KEYCODE_CSINUM, '~', 24 }, // F12
-};
-
-static keycodes_s keycodes_kp[] = {
-  { KEYCODE_KEYPAD, '0', 'p' }, // KP_0
-  { KEYCODE_KEYPAD, '1', 'q' }, // KP_1
-  { KEYCODE_KEYPAD, '2', 'r' }, // KP_2
-  { KEYCODE_KEYPAD, '3', 's' }, // KP_3
-  { KEYCODE_KEYPAD, '4', 't' }, // KP_4
-  { KEYCODE_KEYPAD, '5', 'u' }, // KP_5
-  { KEYCODE_KEYPAD, '6', 'v' }, // KP_6
-  { KEYCODE_KEYPAD, '7', 'w' }, // KP_7
-  { KEYCODE_KEYPAD, '8', 'x' }, // KP_8
-  { KEYCODE_KEYPAD, '9', 'y' }, // KP_9
-  { KEYCODE_KEYPAD, '*', 'j' }, // KP_MULT
-  { KEYCODE_KEYPAD, '+', 'k' }, // KP_PLUS
-  { KEYCODE_KEYPAD, ',', 'l' }, // KP_COMMA
-  { KEYCODE_KEYPAD, '-', 'm' }, // KP_MINUS
-  { KEYCODE_KEYPAD, '.', 'n' }, // KP_PERIOD
-  { KEYCODE_KEYPAD, '/', 'o' }, // KP_DIVIDE
-  { KEYCODE_KEYPAD, '\n', 'M' }, // KP_ENTER
-  { KEYCODE_KEYPAD, '=', 'X' }, // KP_EQUAL
-};
-
-void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod)
-{
-  if(key == VTERM_KEY_NONE)
-    return;
-
-  keycodes_s k;
-  if(key < VTERM_KEY_FUNCTION_0) {
-    if(key >= sizeof(keycodes)/sizeof(keycodes[0]))
-      return;
-    k = keycodes[key];
-  }
-  else if(key >= VTERM_KEY_FUNCTION_0 && key <= VTERM_KEY_FUNCTION_MAX) {
-    if((key - VTERM_KEY_FUNCTION_0) >= sizeof(keycodes_fn)/sizeof(keycodes_fn[0]))
-      return;
-    k = keycodes_fn[key - VTERM_KEY_FUNCTION_0];
-  }
-  else if(key >= VTERM_KEY_KP_0) {
-    if((key - VTERM_KEY_KP_0) >= sizeof(keycodes_kp)/sizeof(keycodes_kp[0]))
-      return;
-    k = keycodes_kp[key - VTERM_KEY_KP_0];
-  }
-
-  switch(k.type) {
-  case KEYCODE_NONE:
-    break;
-
-  case KEYCODE_TAB:
-    /* Shift-Tab is CSI Z but plain Tab is 0x09 */
-    if(mod == VTERM_MOD_SHIFT)
-      vterm_push_output_sprintf_ctrl(vt, C1_CSI, "Z");
-    else if(mod & VTERM_MOD_SHIFT)
-      vterm_push_output_sprintf_ctrl(vt, C1_CSI, "1;%dZ", mod+1);
-    else
-      goto case_LITERAL;
-    break;
-
-  case KEYCODE_ENTER:
-    /* Enter is CRLF in newline mode, but just LF in linefeed */
-    if(vt->state->mode.newline)
-      vterm_push_output_sprintf(vt, "\r\n");
-    else
-      goto case_LITERAL;
-    break;
-
-  case KEYCODE_LITERAL: case_LITERAL:
-    if(mod & (VTERM_MOD_SHIFT|VTERM_MOD_CTRL))
-      vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", k.literal, mod+1);
-    else
-      vterm_push_output_sprintf(vt, mod & VTERM_MOD_ALT ? ESC_S "%c" : "%c", k.literal);
-    break;
-
-  case KEYCODE_SS3: case_SS3:
-    if(mod == 0)
-      vterm_push_output_sprintf_ctrl(vt, C1_SS3, "%c", k.literal);
-    else
-      goto case_CSI;
-    break;
-
-  case KEYCODE_CSI: case_CSI:
-    if(mod == 0)
-      vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%c", k.literal);
-    else
-      vterm_push_output_sprintf_ctrl(vt, C1_CSI, "1;%d%c", mod + 1, k.literal);
-    break;
-
-  case KEYCODE_CSINUM:
-    if(mod == 0)
-      vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d%c", k.csinum, k.literal);
-    else
-      vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%d%c", k.csinum, mod + 1, k.literal);
-    break;
-
-  case KEYCODE_CSI_CURSOR:
-    if(vt->state->mode.cursor)
-      goto case_SS3;
-    else
-      goto case_CSI;
-
-  case KEYCODE_KEYPAD:
-    if(vt->state->mode.keypad) {
-      k.literal = k.csinum;
-      goto case_SS3;
-    }
-    else
-      goto case_LITERAL;
-  }
-}
-
-void vterm_keyboard_start_paste(VTerm *vt)
-{
-  if(vt->state->mode.bracketpaste)
-    vterm_push_output_sprintf_ctrl(vt, C1_CSI, "200~");
-}
-
-void vterm_keyboard_end_paste(VTerm *vt)
-{
-  if(vt->state->mode.bracketpaste)
-    vterm_push_output_sprintf_ctrl(vt, C1_CSI, "201~");
-}
diff --git a/src/mouse.c b/src/mouse.c
deleted file mode 100644
index 9962e4f..0000000
--- a/src/mouse.c
+++ /dev/null
@@ -1,96 +0,0 @@
-#include "vterm_internal.h"
-
-#include "utf8.h"
-
-static void output_mouse(VTermState *state, int code, int pressed, int modifiers, int col, int row)
-{
-  modifiers <<= 2;
-
-  switch(state->mouse_protocol) {
-  case MOUSE_X10:
-    if(col + 0x21 > 0xff)
-      col = 0xff - 0x21;
-    if(row + 0x21 > 0xff)
-      row = 0xff - 0x21;
-
-    if(!pressed)
-      code = 3;
-
-    vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "M%c%c%c",
-        (code | modifiers) + 0x20, col + 0x21, row + 0x21);
-    break;
-
-  case MOUSE_UTF8:
-    {
-      char utf8[18]; size_t len = 0;
-
-      if(!pressed)
-        code = 3;
-
-      len += fill_utf8((code | modifiers) + 0x20, utf8 + len);
-      len += fill_utf8(col + 0x21, utf8 + len);
-      len += fill_utf8(row + 0x21, utf8 + len);
-      utf8[len] = 0;
-
-      vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "M%s", utf8);
-    }
-    break;
-
-  case MOUSE_SGR:
-    vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "<%d;%d;%d%c",
-        code | modifiers, col + 1, row + 1, pressed ? 'M' : 'm');
-    break;
-
-  case MOUSE_RXVT:
-    if(!pressed)
-      code = 3;
-
-    vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%d;%d;%dM",
-        code | modifiers, col + 1, row + 1);
-    break;
-  }
-}
-
-void vterm_mouse_move(VTerm *vt, int row, int col, VTermModifier mod)
-{
-  VTermState *state = vt->state;
-
-  if(col == state->mouse_col && row == state->mouse_row)
-    return;
-
-  state->mouse_col = col;
-  state->mouse_row = row;
-
-  if((state->mouse_flags & MOUSE_WANT_DRAG && state->mouse_buttons) ||
-     (state->mouse_flags & MOUSE_WANT_MOVE)) {
-    int button = state->mouse_buttons & 0x01 ? 1 :
-                 state->mouse_buttons & 0x02 ? 2 :
-                 state->mouse_buttons & 0x04 ? 3 : 4;
-    output_mouse(state, button-1 + 0x20, 1, mod, col, row);
-  }
-}
-
-void vterm_mouse_button(VTerm *vt, int button, bool pressed, VTermModifier mod)
-{
-  VTermState *state = vt->state;
-
-  int old_buttons = state->mouse_buttons;
-
-  if(button > 0 && button <= 3) {
-    if(pressed)
-      state->mouse_buttons |= (1 << (button-1));
-    else
-      state->mouse_buttons &= ~(1 << (button-1));
-  }
-
-  /* Most of the time we don't get button releases from 4/5 */
-  if(state->mouse_buttons == old_buttons && button < 4)
-    return;
-
-  if(button < 4) {
-    output_mouse(state, button-1, pressed, mod, state->mouse_col, state->mouse_row);
-  }
-  else if(button < 6) {
-    output_mouse(state, button-4 + 0x40, pressed, mod, state->mouse_col, state->mouse_row);
-  }
-}
diff --git a/src/parser.c b/src/parser.c
deleted file mode 100644
index a01cd71..0000000
--- a/src/parser.c
+++ /dev/null
@@ -1,340 +0,0 @@
-#include "vterm_internal.h"
-
-#include <stdio.h>
-#include <string.h>
-
-#undef DEBUG_PARSER
-
-static bool is_intermed(unsigned char c)
-{
-  return c >= 0x20 && c <= 0x2f;
-}
-
-static void do_control(VTerm *vt, unsigned char control)
-{
-  if(vt->parser.callbacks && vt->parser.callbacks->control)
-    if((*vt->parser.callbacks->control)(control, vt->parser.cbdata))
-      return;
-
-  DEBUG_LOG("libvterm: Unhandled control 0x%02x\n", control);
-}
-
-static void do_csi(VTerm *vt, char command)
-{
-#ifdef DEBUG_PARSER
-  printf("Parsed CSI args as:\n", arglen, args);
-  printf(" leader: %s\n", vt->parser.csi_leader);
-  for(int argi = 0; argi < vt->parser.csi_argi; argi++) {
-    printf(" %lu", CSI_ARG(vt->parser.csi_args[argi]));
-    if(!CSI_ARG_HAS_MORE(vt->parser.csi_args[argi]))
-      printf("\n");
-  printf(" intermed: %s\n", vt->parser.intermed);
-  }
-#endif
-
-  if(vt->parser.callbacks && vt->parser.callbacks->csi)
-    if((*vt->parser.callbacks->csi)(
-          vt->parser.csi_leaderlen ? vt->parser.csi_leader : NULL, 
-          vt->parser.csi_args,
-          vt->parser.csi_argi,
-          vt->parser.intermedlen ? vt->parser.intermed : NULL,
-          command,
-          vt->parser.cbdata))
-      return;
-
-  DEBUG_LOG("libvterm: Unhandled CSI %c\n", command);
-}
-
-static void do_escape(VTerm *vt, char command)
-{
-  char seq[INTERMED_MAX+1];
-
-  size_t len = vt->parser.intermedlen;
-  strncpy(seq, vt->parser.intermed, len);
-  seq[len++] = command;
-  seq[len]   = 0;
-
-  if(vt->parser.callbacks && vt->parser.callbacks->escape)
-    if((*vt->parser.callbacks->escape)(seq, len, vt->parser.cbdata))
-      return;
-
-  DEBUG_LOG("libvterm: Unhandled escape ESC 0x%02x\n", command);
-}
-
-static void append_strbuffer(VTerm *vt, const char *str, size_t len)
-{
-  if(len > vt->parser.strbuffer_len - vt->parser.strbuffer_cur) {
-    len = vt->parser.strbuffer_len - vt->parser.strbuffer_cur;
-    DEBUG_LOG("Truncating strbuffer preserve to %zd bytes\n", len);
-  }
-
-  if(len > 0) {
-    strncpy(vt->parser.strbuffer + vt->parser.strbuffer_cur, str, len);
-    vt->parser.strbuffer_cur += len;
-  }
-}
-
-static void start_string(VTerm *vt, VTermParserStringType type)
-{
-  vt->parser.stringtype = type;
-
-  vt->parser.strbuffer_cur = 0;
-}
-
-static void more_string(VTerm *vt, const char *str, size_t len)
-{
-  append_strbuffer(vt, str, len);
-}
-
-static void done_string(VTerm *vt, const char *str, size_t len)
-{
-  if(vt->parser.strbuffer_cur) {
-    if(str)
-      append_strbuffer(vt, str, len);
-
-    str = vt->parser.strbuffer;
-    len = vt->parser.strbuffer_cur;
-  }
-  else if(!str) {
-    DEBUG_LOG("parser.c: TODO: No strbuffer _and_ no final fragment???\n");
-    len = 0;
-  }
-
-  switch(vt->parser.stringtype) {
-  case VTERM_PARSER_OSC:
-    if(vt->parser.callbacks && vt->parser.callbacks->osc)
-      if((*vt->parser.callbacks->osc)(str, len, vt->parser.cbdata))
-        return;
-
-    DEBUG_LOG("libvterm: Unhandled OSC %.*s\n", (int)len, str);
-    return;
-
-  case VTERM_PARSER_DCS:
-    if(vt->parser.callbacks && vt->parser.callbacks->dcs)
-      if((*vt->parser.callbacks->dcs)(str, len, vt->parser.cbdata))
-        return;
-
-    DEBUG_LOG("libvterm: Unhandled DCS %.*s\n", (int)len, str);
-    return;
-
-  case VTERM_N_PARSER_TYPES:
-    return;
-  }
-}
-
-size_t vterm_input_write(VTerm *vt, const char *bytes, size_t len)
-{
-  size_t pos = 0;
-  const char *string_start;
-
-  switch(vt->parser.state) {
-  case NORMAL:
-  case CSI_LEADER:
-  case CSI_ARGS:
-  case CSI_INTERMED:
-  case ESC:
-    string_start = NULL;
-    break;
-  case STRING:
-  case ESC_IN_STRING:
-    string_start = bytes;
-    break;
-  }
-
-#define ENTER_STRING_STATE(st) do { vt->parser.state = STRING; string_start = bytes + pos + 1; } while(0)
-#define ENTER_STATE(st)        do { vt->parser.state = st; string_start = NULL; } while(0)
-#define ENTER_NORMAL_STATE()   ENTER_STATE(NORMAL)
-
-  for( ; pos < len; pos++) {
-    unsigned char c = bytes[pos];
-
-    if(c == 0x00 || c == 0x7f) { // NUL, DEL
-      if(vt->parser.state >= STRING) {
-        more_string(vt, string_start, bytes + pos - string_start);
-        string_start = bytes + pos + 1;
-      }
-      continue;
-    }
-    if(c == 0x18 || c == 0x1a) { // CAN, SUB
-      ENTER_NORMAL_STATE();
-      continue;
-    }
-    else if(c == 0x1b) { // ESC
-      vt->parser.intermedlen = 0;
-      if(vt->parser.state == STRING)
-        vt->parser.state = ESC_IN_STRING;
-      else
-        ENTER_STATE(ESC);
-      continue;
-    }
-    else if(c == 0x07 &&  // BEL, can stand for ST in OSC or DCS state
-            vt->parser.state == STRING) {
-      // fallthrough
-    }
-    else if(c < 0x20) { // other C0
-      if(vt->parser.state >= STRING)
-        more_string(vt, string_start, bytes + pos - string_start);
-      do_control(vt, c);
-      if(vt->parser.state >= STRING)
-        string_start = bytes + pos + 1;
-      continue;
-    }
-    // else fallthrough
-
-    switch(vt->parser.state) {
-    case ESC_IN_STRING:
-      if(c == 0x5c) { // ST
-        vt->parser.state = STRING;
-        done_string(vt, string_start, bytes + pos - string_start - 1);
-        ENTER_NORMAL_STATE();
-        break;
-      }
-      vt->parser.state = ESC;
-      // else fallthrough
-
-    case ESC:
-      switch(c) {
-      case 0x50: // DCS
-        start_string(vt, VTERM_PARSER_DCS);
-        ENTER_STRING_STATE();
-        break;
-      case 0x5b: // CSI
-        vt->parser.csi_leaderlen = 0;
-        ENTER_STATE(CSI_LEADER);
-        break;
-      case 0x5d: // OSC
-        start_string(vt, VTERM_PARSER_OSC);
-        ENTER_STRING_STATE();
-        break;
-      default:
-        if(is_intermed(c)) {
-          if(vt->parser.intermedlen < INTERMED_MAX-1)
-            vt->parser.intermed[vt->parser.intermedlen++] = c;
-        }
-        else if(!vt->parser.intermedlen && c >= 0x40 && c < 0x60) {
-          do_control(vt, c + 0x40);
-          ENTER_NORMAL_STATE();
-        }
-        else if(c >= 0x30 && c < 0x7f) {
-          do_escape(vt, c);
-          ENTER_NORMAL_STATE();
-        }
-        else {
-          DEBUG_LOG("TODO: Unhandled byte %02x in Escape\n", c);
-        }
-      }
-      break;
-
-    case CSI_LEADER:
-      /* Extract leader bytes 0x3c to 0x3f */
-      if(c >= 0x3c && c <= 0x3f) {
-        if(vt->parser.csi_leaderlen < CSI_LEADER_MAX-1)
-          vt->parser.csi_leader[vt->parser.csi_leaderlen++] = c;
-        break;
-      }
-
-      /* else fallthrough */
-      vt->parser.csi_leader[vt->parser.csi_leaderlen] = 0;
-
-      vt->parser.csi_argi = 0;
-      vt->parser.csi_args[0] = CSI_ARG_MISSING;
-      vt->parser.state = CSI_ARGS;
-
-      /* fallthrough */
-    case CSI_ARGS:
-      /* Numerical value of argument */
-      if(c >= '0' && c <= '9') {
-        if(vt->parser.csi_args[vt->parser.csi_argi] == CSI_ARG_MISSING)
-          vt->parser.csi_args[vt->parser.csi_argi] = 0;
-        vt->parser.csi_args[vt->parser.csi_argi] *= 10;
-        vt->parser.csi_args[vt->parser.csi_argi] += c - '0';
-        break;
-      }
-      if(c == ':') {
-        vt->parser.csi_args[vt->parser.csi_argi] |= CSI_ARG_FLAG_MORE;
-        c = ';';
-      }
-      if(c == ';') {
-        vt->parser.csi_argi++;
-        vt->parser.csi_args[vt->parser.csi_argi] = CSI_ARG_MISSING;
-        break;
-      }
-
-      /* else fallthrough */
-      vt->parser.csi_argi++;
-      vt->parser.intermedlen = 0;
-      vt->parser.state = CSI_INTERMED;
-    case CSI_INTERMED:
-      if(is_intermed(c)) {
-        if(vt->parser.intermedlen < INTERMED_MAX-1)
-          vt->parser.intermed[vt->parser.intermedlen++] = c;
-        break;
-      }
-      else if(c == 0x1b) {
-        /* ESC in CSI cancels */
-      }
-      else if(c >= 0x40 && c <= 0x7e) {
-        vt->parser.intermed[vt->parser.intermedlen] = 0;
-        do_csi(vt, c);
-      }
-      /* else was invalid CSI */
-
-      ENTER_NORMAL_STATE();
-      break;
-
-    case STRING:
-      if(c == 0x07 || (c == 0x9c && !vt->mode.utf8)) {
-        done_string(vt, string_start, bytes + pos - string_start);
-        ENTER_NORMAL_STATE();
-      }
-      break;
-
-    case NORMAL:
-      if(c >= 0x80 && c < 0xa0 && !vt->mode.utf8) {
-        switch(c) {
-        case 0x90: // DCS
-          start_string(vt, VTERM_PARSER_DCS);
-          ENTER_STRING_STATE();
-          break;
-        case 0x9b: // CSI
-          ENTER_STATE(CSI_LEADER);
-          break;
-        case 0x9d: // OSC
-          start_string(vt, VTERM_PARSER_OSC);
-          ENTER_STRING_STATE();
-          break;
-        default:
-          do_control(vt, c);
-          break;
-        }
-      }
-      else {
-        size_t eaten = 0;
-        if(vt->parser.callbacks && vt->parser.callbacks->text)
-          eaten = (*vt->parser.callbacks->text)(bytes + pos, len - pos, vt->parser.cbdata);
-
-        if(!eaten) {
-          DEBUG_LOG("libvterm: Text callback did not consume any input\n");
-          /* force it to make progress */
-          eaten = 1;
-        }
-
-        pos += (eaten - 1); // we'll ++ it again in a moment
-      }
-      break;
-    }
-  }
-
-  return len;
-}
-
-void vterm_parser_set_callbacks(VTerm *vt, const VTermParserCallbacks *callbacks, void *user)
-{
-  vt->parser.callbacks = callbacks;
-  vt->parser.cbdata = user;
-}
-
-void *vterm_parser_get_cbdata(VTerm *vt)
-{
-  return vt->parser.cbdata;
-}
diff --git a/src/pen.c b/src/pen.c
deleted file mode 100644
index 7488203..0000000
--- a/src/pen.c
+++ /dev/null
@@ -1,535 +0,0 @@
-#include "vterm_internal.h"
-
-#include <stdio.h>
-
-/**
- * Structure used to store RGB triples without the additional metadata stored in
- * VTermColor.
- */
-typedef struct {
-  uint8_t red, green, blue;
-} VTermRGB;
-
-static const VTermRGB ansi_colors[] = {
-  /* R    G    B */
-  {   0,   0,   0 }, // black
-  { 224,   0,   0 }, // red
-  {   0, 224,   0 }, // green
-  { 224, 224,   0 }, // yellow
-  {   0,   0, 224 }, // blue
-  { 224,   0, 224 }, // magenta
-  {   0, 224, 224 }, // cyan
-  { 224, 224, 224 }, // white == light grey
-
-  // high intensity
-  { 128, 128, 128 }, // black
-  { 255,  64,  64 }, // red
-  {  64, 255,  64 }, // green
-  { 255, 255,  64 }, // yellow
-  {  64,  64, 255 }, // blue
-  { 255,  64, 255 }, // magenta
-  {  64, 255, 255 }, // cyan
-  { 255, 255, 255 }, // white for real
-};
-
-static int ramp6[] = {
-  0x00, 0x33, 0x66, 0x99, 0xCC, 0xFF,
-};
-
-static int ramp24[] = {
-  0x00, 0x0B, 0x16, 0x21, 0x2C, 0x37, 0x42, 0x4D, 0x58, 0x63, 0x6E, 0x79,
-  0x85, 0x90, 0x9B, 0xA6, 0xB1, 0xBC, 0xC7, 0xD2, 0xDD, 0xE8, 0xF3, 0xFF,
-};
-
-static void lookup_default_colour_ansi(long idx, VTermColor *col)
-{
-  if (idx >= 0 && idx < 16) {
-    vterm_color_rgb(
-        col,
-        ansi_colors[idx].red, ansi_colors[idx].green, ansi_colors[idx].blue);
-  }
-}
-
-static bool lookup_colour_ansi(const VTermState *state, long index, VTermColor *col)
-{
-  if(index >= 0 && index < 16) {
-    *col = state->colors[index];
-    return true;
-  }
-
-  return false;
-}
-
-static bool lookup_colour_palette(const VTermState *state, long index, VTermColor *col)
-{
-  if(index >= 0 && index < 16) {
-    // Normal 8 colours or high intensity - parse as palette 0
-    return lookup_colour_ansi(state, index, col);
-  }
-  else if(index >= 16 && index < 232) {
-    // 216-colour cube
-    index -= 16;
-
-    vterm_color_rgb(col, ramp6[index/6/6 % 6],
-                         ramp6[index/6   % 6],
-                         ramp6[index     % 6]);
-
-    return true;
-  }
-  else if(index >= 232 && index < 256) {
-    // 24 greyscales
-    index -= 232;
-
-    vterm_color_rgb(col, ramp24[index], ramp24[index], ramp24[index]);
-
-    return true;
-  }
-
-  return false;
-}
-
-static int lookup_colour(const VTermState *state, int palette, const long args[], int argcount, VTermColor *col)
-{
-  switch(palette) {
-  case 2: // RGB mode - 3 args contain colour values directly
-    if(argcount < 3)
-      return argcount;
-
-    vterm_color_rgb(col, CSI_ARG(args[0]), CSI_ARG(args[1]), CSI_ARG(args[2]));
-
-    return 3;
-
-  case 5: // XTerm 256-colour mode
-    if (!argcount || CSI_ARG_IS_MISSING(args[0])) {
-      return argcount ? 1 : 0;
-    }
-
-    vterm_color_indexed(col, args[0]);
-
-    return argcount ? 1 : 0;
-
-  default:
-    DEBUG_LOG("Unrecognised colour palette %d\n", palette);
-    return 0;
-  }
-}
-
-// Some conveniences
-
-static void setpenattr(VTermState *state, VTermAttr attr, VTermValueType type, VTermValue *val)
-{
-#ifdef DEBUG
-  if(type != vterm_get_attr_type(attr)) {
-    DEBUG_LOG("Cannot set attr %d as it has type %d, not type %d\n",
-        attr, vterm_get_attr_type(attr), type);
-    return;
-  }
-#endif
-  if(state->callbacks && state->callbacks->setpenattr)
-    (*state->callbacks->setpenattr)(attr, val, state->cbdata);
-}
-
-static void setpenattr_bool(VTermState *state, VTermAttr attr, int boolean)
-{
-  VTermValue val = { .boolean = boolean };
-  setpenattr(state, attr, VTERM_VALUETYPE_BOOL, &val);
-}
-
-static void setpenattr_int(VTermState *state, VTermAttr attr, int number)
-{
-  VTermValue val = { .number = number };
-  setpenattr(state, attr, VTERM_VALUETYPE_INT, &val);
-}
-
-static void setpenattr_col(VTermState *state, VTermAttr attr, VTermColor color)
-{
-  VTermValue val = { .color = color };
-  setpenattr(state, attr, VTERM_VALUETYPE_COLOR, &val);
-}
-
-static void set_pen_col_ansi(VTermState *state, VTermAttr attr, long col)
-{
-  VTermColor *colp = (attr == VTERM_ATTR_BACKGROUND) ? &state->pen.bg : &state->pen.fg;
-
-  vterm_color_indexed(colp, col);
-
-  setpenattr_col(state, attr, *colp);
-}
-
-INTERNAL void vterm_state_newpen(VTermState *state)
-{
-  // 90% grey so that pure white is brighter
-  vterm_color_rgb(&state->default_fg, 240, 240, 240);
-  vterm_color_rgb(&state->default_bg, 0, 0, 0);
-  vterm_state_set_default_colors(state, &state->default_fg, &state->default_bg);
-
-  for(int col = 0; col < 16; col++)
-    lookup_default_colour_ansi(col, &state->colors[col]);
-}
-
-INTERNAL void vterm_state_resetpen(VTermState *state)
-{
-  state->pen.bold = 0;      setpenattr_bool(state, VTERM_ATTR_BOLD, 0);
-  state->pen.underline = 0; setpenattr_int( state, VTERM_ATTR_UNDERLINE, 0);
-  state->pen.italic = 0;    setpenattr_bool(state, VTERM_ATTR_ITALIC, 0);
-  state->pen.blink = 0;     setpenattr_bool(state, VTERM_ATTR_BLINK, 0);
-  state->pen.reverse = 0;   setpenattr_bool(state, VTERM_ATTR_REVERSE, 0);
-  state->pen.strike = 0;    setpenattr_bool(state, VTERM_ATTR_STRIKE, 0);
-  state->pen.font = 0;      setpenattr_int( state, VTERM_ATTR_FONT, 0);
-
-  state->pen.fg = state->default_fg;  setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->default_fg);
-  state->pen.bg = state->default_bg;  setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->default_bg);
-}
-
-INTERNAL void vterm_state_savepen(VTermState *state, int save)
-{
-  if(save) {
-    state->saved.pen = state->pen;
-  }
-  else {
-    state->pen = state->saved.pen;
-
-    setpenattr_bool(state, VTERM_ATTR_BOLD,       state->pen.bold);
-    setpenattr_int( state, VTERM_ATTR_UNDERLINE,  state->pen.underline);
-    setpenattr_bool(state, VTERM_ATTR_ITALIC,     state->pen.italic);
-    setpenattr_bool(state, VTERM_ATTR_BLINK,      state->pen.blink);
-    setpenattr_bool(state, VTERM_ATTR_REVERSE,    state->pen.reverse);
-    setpenattr_bool(state, VTERM_ATTR_STRIKE,     state->pen.strike);
-    setpenattr_int( state, VTERM_ATTR_FONT,       state->pen.font);
-    setpenattr_col( state, VTERM_ATTR_FOREGROUND, state->pen.fg);
-    setpenattr_col( state, VTERM_ATTR_BACKGROUND, state->pen.bg);
-  }
-}
-
-int vterm_color_is_equal(const VTermColor *a, const VTermColor *b)
-{
-  /* First make sure that the two colours are of the same type (RGB/Indexed) */
-  if (a->type != b->type) {
-    return false;
-  }
-
-  /* Depending on the type inspect the corresponding members */
-  if (VTERM_COLOR_IS_INDEXED(a)) {
-    return a->indexed.idx == b->indexed.idx;
-  }
-  else if (VTERM_COLOR_IS_RGB(a)) {
-    return    (a->rgb.red   == b->rgb.red)
-           && (a->rgb.green == b->rgb.green)
-           && (a->rgb.blue  == b->rgb.blue);
-  }
-
-  return 0;
-}
-
-void vterm_state_get_default_colors(const VTermState *state, VTermColor *default_fg, VTermColor *default_bg)
-{
-  *default_fg = state->default_fg;
-  *default_bg = state->default_bg;
-}
-
-void vterm_state_get_palette_color(const VTermState *state, int index, VTermColor *col)
-{
-  lookup_colour_palette(state, index, col);
-}
-
-void vterm_state_set_default_colors(VTermState *state, const VTermColor *default_fg, const VTermColor *default_bg)
-{
-  /* Copy the given colors */
-  state->default_fg = *default_fg;
-  state->default_bg = *default_bg;
-
-  /* Make sure the correct type flags are set */
-  state->default_fg.type = (state->default_fg.type & ~VTERM_COLOR_DEFAULT_MASK)
-                         | VTERM_COLOR_DEFAULT_FG;
-  state->default_bg.type = (state->default_bg.type & ~VTERM_COLOR_DEFAULT_MASK)
-                         | VTERM_COLOR_DEFAULT_BG;
-}
-
-void vterm_state_set_palette_color(VTermState *state, int index, const VTermColor *col)
-{
-  if(index >= 0 && index < 16)
-    state->colors[index] = *col;
-}
-
-void vterm_state_convert_color_to_rgb(const VTermState *state, VTermColor *col)
-{
-  if (VTERM_COLOR_IS_INDEXED(col)) { /* Convert indexed colors to RGB */
-    lookup_colour_palette(state, col->indexed.idx, col);
-  }
-  col->type &= VTERM_COLOR_TYPE_MASK; /* Reset any metadata but the type */
-}
-
-void vterm_state_set_bold_highbright(VTermState *state, int bold_is_highbright)
-{
-  state->bold_is_highbright = bold_is_highbright;
-}
-
-INTERNAL void vterm_state_setpen(VTermState *state, const long args[], int argcount)
-{
-  // SGR - ECMA-48 8.3.117
-
-  int argi = 0;
-  int value;
-
-  while(argi < argcount) {
-    // This logic is easier to do 'done' backwards; set it true, and make it
-    // false again in the 'default' case
-    int done = 1;
-
-    long arg;
-    switch(arg = CSI_ARG(args[argi])) {
-    case CSI_ARG_MISSING:
-    case 0: // Reset
-      vterm_state_resetpen(state);
-      break;
-
-    case 1: { // Bold on
-      const VTermColor *fg = &state->pen.fg;
-      state->pen.bold = 1;
-      setpenattr_bool(state, VTERM_ATTR_BOLD, 1);
-      if(!VTERM_COLOR_IS_DEFAULT_FG(fg) && VTERM_COLOR_IS_INDEXED(fg) && fg->indexed.idx < 8 && state->bold_is_highbright)
-        set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, fg->indexed.idx + (state->pen.bold ? 8 : 0));
-      break;
-    }
-
-    case 3: // Italic on
-      state->pen.italic = 1;
-      setpenattr_bool(state, VTERM_ATTR_ITALIC, 1);
-      break;
-
-    case 4: // Underline single
-      state->pen.underline = 1;
-      setpenattr_int(state, VTERM_ATTR_UNDERLINE, 1);
-      break;
-
-    case 5: // Blink
-      state->pen.blink = 1;
-      setpenattr_bool(state, VTERM_ATTR_BLINK, 1);
-      break;
-
-    case 7: // Reverse on
-      state->pen.reverse = 1;
-      setpenattr_bool(state, VTERM_ATTR_REVERSE, 1);
-      break;
-
-    case 9: // Strikethrough on
-      state->pen.strike = 1;
-      setpenattr_bool(state, VTERM_ATTR_STRIKE, 1);
-      break;
-
-    case 10: case 11: case 12: case 13: case 14:
-    case 15: case 16: case 17: case 18: case 19: // Select font
-      state->pen.font = CSI_ARG(args[argi]) - 10;
-      setpenattr_int(state, VTERM_ATTR_FONT, state->pen.font);
-      break;
-
-    case 21: // Underline double
-      state->pen.underline = 2;
-      setpenattr_int(state, VTERM_ATTR_UNDERLINE, 2);
-      break;
-
-    case 22: // Bold off
-      state->pen.bold = 0;
-      setpenattr_bool(state, VTERM_ATTR_BOLD, 0);
-      break;
-
-    case 23: // Italic and Gothic (currently unsupported) off
-      state->pen.italic = 0;
-      setpenattr_bool(state, VTERM_ATTR_ITALIC, 0);
-      break;
-
-    case 24: // Underline off
-      state->pen.underline = 0;
-      setpenattr_int(state, VTERM_ATTR_UNDERLINE, 0);
-      break;
-
-    case 25: // Blink off
-      state->pen.blink = 0;
-      setpenattr_bool(state, VTERM_ATTR_BLINK, 0);
-      break;
-
-    case 27: // Reverse off
-      state->pen.reverse = 0;
-      setpenattr_bool(state, VTERM_ATTR_REVERSE, 0);
-      break;
-
-    case 29: // Strikethrough off
-      state->pen.strike = 0;
-      setpenattr_bool(state, VTERM_ATTR_STRIKE, 0);
-      break;
-
-    case 30: case 31: case 32: case 33:
-    case 34: case 35: case 36: case 37: // Foreground colour palette
-      value = CSI_ARG(args[argi]) - 30;
-      if(state->pen.bold && state->bold_is_highbright)
-        value += 8;
-      set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, value);
-      break;
-
-    case 38: // Foreground colour alternative palette
-      if(argcount - argi < 1)
-        return;
-      argi += 1 + lookup_colour(state, CSI_ARG(args[argi+1]), args+argi+2, argcount-argi-2, &state->pen.fg);
-      setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg);
-      break;
-
-    case 39: // Foreground colour default
-      state->pen.fg = state->default_fg;
-      setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg);
-      break;
-
-    case 40: case 41: case 42: case 43:
-    case 44: case 45: case 46: case 47: // Background colour palette
-      value = CSI_ARG(args[argi]) - 40;
-      set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, value);
-      break;
-
-    case 48: // Background colour alternative palette
-      if(argcount - argi < 1)
-        return;
-      argi += 1 + lookup_colour(state, CSI_ARG(args[argi+1]), args+argi+2, argcount-argi-2, &state->pen.bg);
-      setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg);
-      break;
-
-    case 49: // Default background
-      state->pen.bg = state->default_bg;
-      setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg);
-      break;
-
-    case 90: case 91: case 92: case 93:
-    case 94: case 95: case 96: case 97: // Foreground colour high-intensity palette
-      value = CSI_ARG(args[argi]) - 90 + 8;
-      set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, value);
-      break;
-
-    case 100: case 101: case 102: case 103:
-    case 104: case 105: case 106: case 107: // Background colour high-intensity palette
-      value = CSI_ARG(args[argi]) - 100 + 8;
-      set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, value);
-      break;
-
-    default:
-      done = 0;
-      break;
-    }
-
-    if(!done)
-      DEBUG_LOG("libvterm: Unhandled CSI SGR %lu\n", arg);
-
-    while(CSI_ARG_HAS_MORE(args[argi++]));
-  }
-}
-
-static int vterm_state_getpen_color(const VTermColor *col, int argi, long args[], int fg)
-{
-    /* Do nothing if the given color is the default color */
-    if (( fg && VTERM_COLOR_IS_DEFAULT_FG(col)) ||
-        (!fg && VTERM_COLOR_IS_DEFAULT_BG(col))) {
-        return argi;
-    }
-
-    /* Decide whether to send an indexed color or an RGB color */
-    if (VTERM_COLOR_IS_INDEXED(col)) {
-        const uint8_t idx = col->indexed.idx;
-        if (idx < 8) {
-            args[argi++] = (idx + (fg ? 30 : 40));
-        }
-        else if (idx < 16) {
-            args[argi++] = (idx - 8 + (fg ? 90 : 100));
-        }
-        else {
-            args[argi++] = CSI_ARG_FLAG_MORE | (fg ? 38 : 48);
-            args[argi++] = CSI_ARG_FLAG_MORE | 5;
-            args[argi++] = idx;
-        }
-    }
-    else if (VTERM_COLOR_IS_RGB(col)) {
-        args[argi++] = CSI_ARG_FLAG_MORE | (fg ? 38 : 48);
-        args[argi++] = CSI_ARG_FLAG_MORE | 2;
-        args[argi++] = CSI_ARG_FLAG_MORE | col->rgb.red;
-        args[argi++] = CSI_ARG_FLAG_MORE | col->rgb.green;
-        args[argi++] = col->rgb.blue;
-    }
-    return argi;
-}
-
-INTERNAL int vterm_state_getpen(VTermState *state, long args[], int argcount)
-{
-  int argi = 0;
-
-  if(state->pen.bold)
-    args[argi++] = 1;
-
-  if(state->pen.italic)
-    args[argi++] = 3;
-
-  if(state->pen.underline == 1)
-    args[argi++] = 4;
-
-  if(state->pen.blink)
-    args[argi++] = 5;
-
-  if(state->pen.reverse)
-    args[argi++] = 7;
-
-  if(state->pen.strike)
-    args[argi++] = 9;
-
-  if(state->pen.font)
-    args[argi++] = 10 + state->pen.font;
-
-  if(state->pen.underline == 2)
-    args[argi++] = 21;
-
-  argi = vterm_state_getpen_color(&state->pen.fg, argi, args, true);
-
-  argi = vterm_state_getpen_color(&state->pen.bg, argi, args, false);
-
-  return argi;
-}
-
-int vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTermValue *val)
-{
-  switch(attr) {
-  case VTERM_ATTR_BOLD:
-    val->boolean = state->pen.bold;
-    return 1;
-
-  case VTERM_ATTR_UNDERLINE:
-    val->number = state->pen.underline;
-    return 1;
-
-  case VTERM_ATTR_ITALIC:
-    val->boolean = state->pen.italic;
-    return 1;
-
-  case VTERM_ATTR_BLINK:
-    val->boolean = state->pen.blink;
-    return 1;
-
-  case VTERM_ATTR_REVERSE:
-    val->boolean = state->pen.reverse;
-    return 1;
-
-  case VTERM_ATTR_STRIKE:
-    val->boolean = state->pen.strike;
-    return 1;
-
-  case VTERM_ATTR_FONT:
-    val->number = state->pen.font;
-    return 1;
-
-  case VTERM_ATTR_FOREGROUND:
-    val->color = state->pen.fg;
-    return 1;
-
-  case VTERM_ATTR_BACKGROUND:
-    val->color = state->pen.bg;
-    return 1;
-
-  case VTERM_N_ATTRS:
-    return 0;
-  }
-
-  return 0;
-}
diff --git a/src/rect.h b/src/rect.h
deleted file mode 100644
index 2114f24..0000000
--- a/src/rect.h
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Some utility functions on VTermRect structures
- */
-
-#define STRFrect "(%d,%d-%d,%d)"
-#define ARGSrect(r) (r).start_row, (r).start_col, (r).end_row, (r).end_col
-
-/* Expand dst to contain src as well */
-static void rect_expand(VTermRect *dst, VTermRect *src)
-{
-  if(dst->start_row > src->start_row) dst->start_row = src->start_row;
-  if(dst->start_col > src->start_col) dst->start_col = src->start_col;
-  if(dst->end_row   < src->end_row)   dst->end_row   = src->end_row;
-  if(dst->end_col   < src->end_col)   dst->end_col   = src->end_col;
-}
-
-/* Clip the dst to ensure it does not step outside of bounds */
-static void rect_clip(VTermRect *dst, VTermRect *bounds)
-{
-  if(dst->start_row < bounds->start_row) dst->start_row = bounds->start_row;
-  if(dst->start_col < bounds->start_col) dst->start_col = bounds->start_col;
-  if(dst->end_row   > bounds->end_row)   dst->end_row   = bounds->end_row;
-  if(dst->end_col   > bounds->end_col)   dst->end_col   = bounds->end_col;
-  /* Ensure it doesn't end up negatively-sized */
-  if(dst->end_row < dst->start_row) dst->end_row = dst->start_row;
-  if(dst->end_col < dst->start_col) dst->end_col = dst->start_col;
-}
-
-/* True if the two rectangles are equal */
-static int rect_equal(VTermRect *a, VTermRect *b)
-{
-  return (a->start_row == b->start_row) &&
-         (a->start_col == b->start_col) &&
-         (a->end_row   == b->end_row)   &&
-         (a->end_col   == b->end_col);
-}
-
-/* True if small is contained entirely within big */
-static int rect_contains(VTermRect *big, VTermRect *small)
-{
-  if(small->start_row < big->start_row) return 0;
-  if(small->start_col < big->start_col) return 0;
-  if(small->end_row   > big->end_row)   return 0;
-  if(small->end_col   > big->end_col)   return 0;
-  return 1;
-}
-
-/* True if the rectangles overlap at all */
-static int rect_intersects(VTermRect *a, VTermRect *b)
-{
-  if(a->start_row > b->end_row || b->start_row > a->end_row)
-    return 0;
-  if(a->start_col > b->end_col || b->start_col > a->end_col)
-    return 0;
-  return 1;
-}
diff --git a/src/screen.c b/src/screen.c
deleted file mode 100644
index 1d4d86c..0000000
--- a/src/screen.c
+++ /dev/null
@@ -1,936 +0,0 @@
-#include "vterm_internal.h"
-
-#include <stdio.h>
-#include <string.h>
-
-#include "rect.h"
-#include "utf8.h"
-
-#define UNICODE_SPACE 0x20
-#define UNICODE_LINEFEED 0x0a
-
-/* State of the pen at some moment in time, also used in a cell */
-typedef struct
-{
-  /* After the bitfield */
-  VTermColor   fg, bg;
-
-  unsigned int bold      : 1;
-  unsigned int underline : 2;
-  unsigned int italic    : 1;
-  unsigned int blink     : 1;
-  unsigned int reverse   : 1;
-  unsigned int strike    : 1;
-  unsigned int font      : 4; /* 0 to 9 */
-
-  /* Extra state storage that isn't strictly pen-related */
-  unsigned int protected_cell : 1;
-  unsigned int dwl            : 1; /* on a DECDWL or DECDHL line */
-  unsigned int dhl            : 2; /* on a DECDHL line (1=top 2=bottom) */
-} ScreenPen;
-
-/* Internal representation of a screen cell */
-typedef struct
-{
-  uint32_t chars[VTERM_MAX_CHARS_PER_CELL];
-  ScreenPen pen;
-} ScreenCell;
-
-static int vterm_screen_set_cell(VTermScreen *screen, VTermPos pos, const VTermScreenCell *cell);
-
-struct VTermScreen
-{
-  VTerm *vt;
-  VTermState *state;
-
-  const VTermScreenCallbacks *callbacks;
-  void *cbdata;
-
-  VTermDamageSize damage_merge;
-  /* start_row == -1 => no damage */
-  VTermRect damaged;
-  VTermRect pending_scrollrect;
-  int pending_scroll_downward, pending_scroll_rightward;
-
-  int rows;
-  int cols;
-  int global_reverse;
-
-  /* Primary and Altscreen. buffers[1] is lazily allocated as needed */
-  ScreenCell *buffers[2];
-
-  /* buffer will == buffers[0] or buffers[1], depending on altscreen */
-  ScreenCell *buffer;
-
-  /* buffer for a single screen row used in scrollback storage callbacks */
-  VTermScreenCell *sb_buffer;
-
-  ScreenPen pen;
-};
-
-static inline ScreenCell *getcell(const VTermScreen *screen, int row, int col)
-{
-  if(row < 0 || row >= screen->rows)
-    return NULL;
-  if(col < 0 || col >= screen->cols)
-    return NULL;
-  return screen->buffer + (screen->cols * row) + col;
-}
-
-static ScreenCell *realloc_buffer(VTermScreen *screen, ScreenCell *buffer, int new_rows, int new_cols)
-{
-  ScreenCell *new_buffer = vterm_allocator_malloc(screen->vt, sizeof(ScreenCell) * new_rows * new_cols);
-
-  for(int row = 0; row < new_rows; row++) {
-    for(int col = 0; col < new_cols; col++) {
-      ScreenCell *new_cell = new_buffer + row*new_cols + col;
-
-      if(buffer && row < screen->rows && col < screen->cols)
-        *new_cell = buffer[row * screen->cols + col];
-      else {
-        new_cell->chars[0] = 0;
-        new_cell->pen = screen->pen;
-      }
-    }
-  }
-
-  if(buffer)
-    vterm_allocator_free(screen->vt, buffer);
-
-  return new_buffer;
-}
-
-static void damagerect(VTermScreen *screen, VTermRect rect)
-{
-  VTermRect emit;
-
-  switch(screen->damage_merge) {
-  case VTERM_DAMAGE_CELL:
-    /* Always emit damage event */
-    emit = rect;
-    break;
-
-  case VTERM_DAMAGE_ROW:
-    /* Emit damage longer than one row. Try to merge with existing damage in
-     * the same row */
-    if(rect.end_row > rect.start_row + 1) {
-      // Bigger than 1 line - flush existing, emit this
-      vterm_screen_flush_damage(screen);
-      emit = rect;
-    }
-    else if(screen->damaged.start_row == -1) {
-      // None stored yet
-      screen->damaged = rect;
-      return;
-    }
-    else if(rect.start_row == screen->damaged.start_row) {
-      // Merge with the stored line
-      if(screen->damaged.start_col > rect.start_col)
-        screen->damaged.start_col = rect.start_col;
-      if(screen->damaged.end_col < rect.end_col)
-        screen->damaged.end_col = rect.end_col;
-      return;
-    }
-    else {
-      // Emit the currently stored line, store a new one
-      emit = screen->damaged;
-      screen->damaged = rect;
-    }
-    break;
-
-  case VTERM_DAMAGE_SCREEN:
-  case VTERM_DAMAGE_SCROLL:
-    /* Never emit damage event */
-    if(screen->damaged.start_row == -1)
-      screen->damaged = rect;
-    else {
-      rect_expand(&screen->damaged, &rect);
-    }
-    return;
-
-  default:
-    DEBUG_LOG("TODO: Maybe merge damage for level %d\n", screen->damage_merge);
-    return;
-  }
-
-  if(screen->callbacks && screen->callbacks->damage)
-    (*screen->callbacks->damage)(emit, screen->cbdata);
-}
-
-static void damagescreen(VTermScreen *screen)
-{
-  VTermRect rect = {
-    .start_row = 0,
-    .end_row   = screen->rows,
-    .start_col = 0,
-    .end_col   = screen->cols,
-  };
-
-  damagerect(screen, rect);
-}
-
-static int putglyph(VTermGlyphInfo *info, VTermPos pos, void *user)
-{
-  VTermScreen *screen = user;
-  ScreenCell *cell = getcell(screen, pos.row, pos.col);
-
-  if(!cell)
-    return 0;
-
-  int i;
-  for(i = 0; i < VTERM_MAX_CHARS_PER_CELL && info->chars[i]; i++) {
-    cell->chars[i] = info->chars[i];
-    cell->pen = screen->pen;
-  }
-  if(i < VTERM_MAX_CHARS_PER_CELL)
-    cell->chars[i] = 0;
-
-  for(int col = 1; col < info->width; col++)
-    getcell(screen, pos.row, pos.col + col)->chars[0] = (uint32_t)-1;
-
-  VTermRect rect = {
-    .start_row = pos.row,
-    .end_row   = pos.row+1,
-    .start_col = pos.col,
-    .end_col   = pos.col+info->width,
-  };
-
-  cell->pen.protected_cell = info->protected_cell;
-  cell->pen.dwl            = info->dwl;
-  cell->pen.dhl            = info->dhl;
-
-  damagerect(screen, rect);
-
-  return 1;
-}
-
-static int moverect_internal(VTermRect dest, VTermRect src, void *user)
-{
-  VTermScreen *screen = user;
-
-  if(screen->callbacks && screen->callbacks->sb_pushline &&
-     dest.start_row == 0 && dest.start_col == 0 &&  // starts top-left corner
-     dest.end_col == screen->cols &&                // full width
-     screen->buffer == screen->buffers[0]) {        // not altscreen
-    VTermPos pos;
-    for(pos.row = 0; pos.row < src.start_row; pos.row++) {
-      for(pos.col = 0; pos.col < screen->cols; pos.col++)
-        vterm_screen_get_cell(screen, pos, screen->sb_buffer + pos.col);
-
-      (screen->callbacks->sb_pushline)(screen->cols, screen->sb_buffer, screen->cbdata);
-    }
-  }
-
-  int cols = src.end_col - src.start_col;
-  int downward = src.start_row - dest.start_row;
-
-  int init_row, test_row, inc_row;
-  if(downward < 0) {
-    init_row = dest.end_row - 1;
-    test_row = dest.start_row - 1;
-    inc_row  = -1;
-  }
-  else {
-    init_row = dest.start_row;
-    test_row = dest.end_row;
-    inc_row  = +1;
-  }
-
-  for(int row = init_row; row != test_row; row += inc_row)
-    memmove(getcell(screen, row, dest.start_col),
-            getcell(screen, row + downward, src.start_col),
-            cols * sizeof(ScreenCell));
-
-  return 1;
-}
-
-static int moverect_user(VTermRect dest, VTermRect src, void *user)
-{
-  VTermScreen *screen = user;
-
-  if(screen->callbacks && screen->callbacks->moverect) {
-    if(screen->damage_merge != VTERM_DAMAGE_SCROLL)
-      // Avoid an infinite loop
-      vterm_screen_flush_damage(screen);
-
-    if((*screen->callbacks->moverect)(dest, src, screen->cbdata))
-      return 1;
-  }
-
-  damagerect(screen, dest);
-
-  return 1;
-}
-
-static int erase_internal(VTermRect rect, int selective, void *user)
-{
-  VTermScreen *screen = user;
-
-  for(int row = rect.start_row; row < screen->state->rows && row < rect.end_row; row++) {
-    const VTermLineInfo *info = vterm_state_get_lineinfo(screen->state, row);
-
-    for(int col = rect.start_col; col < rect.end_col; col++) {
-      ScreenCell *cell = getcell(screen, row, col);
-
-      if(selective && cell->pen.protected_cell)
-        continue;
-
-      cell->chars[0] = 0;
-      cell->pen = screen->pen;
-      cell->pen.dwl = info->doublewidth;
-      cell->pen.dhl = info->doubleheight;
-    }
-  }
-
-  return 1;
-}
-
-static int erase_user(VTermRect rect, int selective, void *user)
-{
-  VTermScreen *screen = user;
-
-  damagerect(screen, rect);
-
-  return 1;
-}
-
-static int erase(VTermRect rect, int selective, void *user)
-{
-  erase_internal(rect, selective, user);
-  return erase_user(rect, 0, user);
-}
-
-static int scrollrect(VTermRect rect, int downward, int rightward, void *user)
-{
-  VTermScreen *screen = user;
-
-  if(screen->damage_merge != VTERM_DAMAGE_SCROLL) {
-    vterm_scroll_rect(rect, downward, rightward,
-        moverect_internal, erase_internal, screen);
-
-    vterm_screen_flush_damage(screen);
-
-    vterm_scroll_rect(rect, downward, rightward,
-        moverect_user, erase_user, screen);
-
-    return 1;
-  }
-
-  if(screen->damaged.start_row != -1 &&
-     !rect_intersects(&rect, &screen->damaged)) {
-    vterm_screen_flush_damage(screen);
-  }
-
-  if(screen->pending_scrollrect.start_row == -1) {
-    screen->pending_scrollrect = rect;
-    screen->pending_scroll_downward  = downward;
-    screen->pending_scroll_rightward = rightward;
-  }
-  else if(rect_equal(&screen->pending_scrollrect, &rect) &&
-     ((screen->pending_scroll_downward  == 0 && downward  == 0) ||
-      (screen->pending_scroll_rightward == 0 && rightward == 0))) {
-    screen->pending_scroll_downward  += downward;
-    screen->pending_scroll_rightward += rightward;
-  }
-  else {
-    vterm_screen_flush_damage(screen);
-
-    screen->pending_scrollrect = rect;
-    screen->pending_scroll_downward  = downward;
-    screen->pending_scroll_rightward = rightward;
-  }
-
-  vterm_scroll_rect(rect, downward, rightward,
-      moverect_internal, erase_internal, screen);
-
-  if(screen->damaged.start_row == -1)
-    return 1;
-
-  if(rect_contains(&rect, &screen->damaged)) {
-    /* Scroll region entirely contains the damage; just move it */
-    vterm_rect_move(&screen->damaged, -downward, -rightward);
-    rect_clip(&screen->damaged, &rect);
-  }
-  /* There are a number of possible cases here, but lets restrict this to only
-   * the common case where we might actually gain some performance by
-   * optimising it. Namely, a vertical scroll that neatly cuts the damage
-   * region in half.
-   */
-  else if(rect.start_col <= screen->damaged.start_col &&
-          rect.end_col   >= screen->damaged.end_col &&
-          rightward == 0) {
-    if(screen->damaged.start_row >= rect.start_row &&
-       screen->damaged.start_row  < rect.end_row) {
-      screen->damaged.start_row -= downward;
-      if(screen->damaged.start_row < rect.start_row)
-        screen->damaged.start_row = rect.start_row;
-      if(screen->damaged.start_row > rect.end_row)
-        screen->damaged.start_row = rect.end_row;
-    }
-    if(screen->damaged.end_row >= rect.start_row &&
-       screen->damaged.end_row  < rect.end_row) {
-      screen->damaged.end_row -= downward;
-      if(screen->damaged.end_row < rect.start_row)
-        screen->damaged.end_row = rect.start_row;
-      if(screen->damaged.end_row > rect.end_row)
-        screen->damaged.end_row = rect.end_row;
-    }
-  }
-  else {
-    DEBUG_LOG("TODO: Just flush and redo damaged=" STRFrect " rect=" STRFrect "\n",
-        ARGSrect(screen->damaged), ARGSrect(rect));
-  }
-
-  return 1;
-}
-
-static int movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user)
-{
-  VTermScreen *screen = user;
-
-  if(screen->callbacks && screen->callbacks->movecursor)
-    return (*screen->callbacks->movecursor)(pos, oldpos, visible, screen->cbdata);
-
-  return 0;
-}
-
-static int setpenattr(VTermAttr attr, VTermValue *val, void *user)
-{
-  VTermScreen *screen = user;
-
-  switch(attr) {
-  case VTERM_ATTR_BOLD:
-    screen->pen.bold = val->boolean;
-    return 1;
-  case VTERM_ATTR_UNDERLINE:
-    screen->pen.underline = val->number;
-    return 1;
-  case VTERM_ATTR_ITALIC:
-    screen->pen.italic = val->boolean;
-    return 1;
-  case VTERM_ATTR_BLINK:
-    screen->pen.blink = val->boolean;
-    return 1;
-  case VTERM_ATTR_REVERSE:
-    screen->pen.reverse = val->boolean;
-    return 1;
-  case VTERM_ATTR_STRIKE:
-    screen->pen.strike = val->boolean;
-    return 1;
-  case VTERM_ATTR_FONT:
-    screen->pen.font = val->number;
-    return 1;
-  case VTERM_ATTR_FOREGROUND:
-    screen->pen.fg = val->color;
-    return 1;
-  case VTERM_ATTR_BACKGROUND:
-    screen->pen.bg = val->color;
-    return 1;
-
-  case VTERM_N_ATTRS:
-    return 0;
-  }
-
-  return 0;
-}
-
-static int settermprop(VTermProp prop, VTermValue *val, void *user)
-{
-  VTermScreen *screen = user;
-
-  switch(prop) {
-  case VTERM_PROP_ALTSCREEN:
-    if(val->boolean && !screen->buffers[1])
-      return 0;
-
-    screen->buffer = val->boolean ? screen->buffers[1] : screen->buffers[0];
-    /* only send a damage event on disable; because during enable there's an
-     * erase that sends a damage anyway
-     */
-    if(!val->boolean)
-      damagescreen(screen);
-    break;
-  case VTERM_PROP_REVERSE:
-    screen->global_reverse = val->boolean;
-    damagescreen(screen);
-    break;
-  default:
-    ; /* ignore */
-  }
-
-  if(screen->callbacks && screen->callbacks->settermprop)
-    return (*screen->callbacks->settermprop)(prop, val, screen->cbdata);
-
-  return 1;
-}
-
-static int bell(void *user)
-{
-  VTermScreen *screen = user;
-
-  if(screen->callbacks && screen->callbacks->bell)
-    return (*screen->callbacks->bell)(screen->cbdata);
-
-  return 0;
-}
-
-static int resize(int new_rows, int new_cols, VTermPos *delta, void *user)
-{
-  VTermScreen *screen = user;
-
-  int is_altscreen = (screen->buffers[1] && screen->buffer == screen->buffers[1]);
-
-  int old_rows = screen->rows;
-  int old_cols = screen->cols;
-
-  if(!is_altscreen && new_rows < old_rows) {
-    // Fewer rows - determine if we're going to scroll at all, and if so, push
-    // those lines to scrollback
-    VTermPos pos = { 0, 0 };
-    VTermPos cursor = screen->state->pos;
-    // Find the first blank row after the cursor.
-    for(pos.row = old_rows - 1; pos.row >= new_rows; pos.row--)
-      if(!vterm_screen_is_eol(screen, pos) || cursor.row == pos.row)
-        break;
-
-    int first_blank_row = pos.row + 1;
-    if(first_blank_row > new_rows) {
-      VTermRect rect = {
-        .start_row = 0,
-        .end_row   = old_rows,
-        .start_col = 0,
-        .end_col   = old_cols,
-      };
-      scrollrect(rect, first_blank_row - new_rows, 0, user);
-      vterm_screen_flush_damage(screen);
-
-      delta->row -= first_blank_row - new_rows;
-    }
-  }
-
-  screen->buffers[0] = realloc_buffer(screen, screen->buffers[0], new_rows, new_cols);
-  if(screen->buffers[1])
-    screen->buffers[1] = realloc_buffer(screen, screen->buffers[1], new_rows, new_cols);
-
-  screen->buffer = is_altscreen ? screen->buffers[1] : screen->buffers[0];
-
-  screen->rows = new_rows;
-  screen->cols = new_cols;
-
-  if(screen->sb_buffer)
-    vterm_allocator_free(screen->vt, screen->sb_buffer);
-
-  screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * new_cols);
-
-  if(new_cols > old_cols) {
-    VTermRect rect = {
-      .start_row = 0,
-      .end_row   = old_rows,
-      .start_col = old_cols,
-      .end_col   = new_cols,
-    };
-    damagerect(screen, rect);
-  }
-
-  if(new_rows > old_rows) {
-    if(!is_altscreen && screen->callbacks && screen->callbacks->sb_popline) {
-      int rows = new_rows - old_rows;
-      while(rows) {
-        if(!(screen->callbacks->sb_popline(screen->cols, screen->sb_buffer, screen->cbdata)))
-          break;
-
-        VTermRect rect = {
-          .start_row = 0,
-          .end_row   = screen->rows,
-          .start_col = 0,
-          .end_col   = screen->cols,
-        };
-        scrollrect(rect, -1, 0, user);
-
-        VTermPos pos = { 0, 0 };
-        for(pos.col = 0; pos.col < screen->cols; pos.col += screen->sb_buffer[pos.col].width)
-          vterm_screen_set_cell(screen, pos, screen->sb_buffer + pos.col);
-
-        rect.end_row = 1;
-        damagerect(screen, rect);
-
-        vterm_screen_flush_damage(screen);
-
-        rows--;
-        delta->row++;
-      }
-    }
-
-    VTermRect rect = {
-      .start_row = old_rows,
-      .end_row   = new_rows,
-      .start_col = 0,
-      .end_col   = new_cols,
-    };
-    damagerect(screen, rect);
-  }
-
-  if(screen->callbacks && screen->callbacks->resize)
-    return (*screen->callbacks->resize)(new_rows, new_cols, screen->cbdata);
-
-  return 1;
-}
-
-static int setlineinfo(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user)
-{
-  VTermScreen *screen = user;
-
-  if(newinfo->doublewidth != oldinfo->doublewidth ||
-     newinfo->doubleheight != oldinfo->doubleheight) {
-    for(int col = 0; col < screen->cols; col++) {
-      ScreenCell *cell = getcell(screen, row, col);
-      cell->pen.dwl = newinfo->doublewidth;
-      cell->pen.dhl = newinfo->doubleheight;
-    }
-
-    VTermRect rect = {
-      .start_row = row,
-      .end_row   = row + 1,
-      .start_col = 0,
-      .end_col   = newinfo->doublewidth ? screen->cols / 2 : screen->cols,
-    };
-    damagerect(screen, rect);
-
-    if(newinfo->doublewidth) {
-      rect.start_col = screen->cols / 2;
-      rect.end_col   = screen->cols;
-
-      erase_internal(rect, 0, user);
-    }
-  }
-
-  return 1;
-}
-
-static VTermStateCallbacks state_cbs = {
-  .putglyph    = &putglyph,
-  .movecursor  = &movecursor,
-  .scrollrect  = &scrollrect,
-  .erase       = &erase,
-  .setpenattr  = &setpenattr,
-  .settermprop = &settermprop,
-  .bell        = &bell,
-  .resize      = &resize,
-  .setlineinfo = &setlineinfo,
-};
-
-static VTermScreen *screen_new(VTerm *vt)
-{
-  VTermState *state = vterm_obtain_state(vt);
-  if(!state)
-    return NULL;
-
-  VTermScreen *screen = vterm_allocator_malloc(vt, sizeof(VTermScreen));
-  int rows, cols;
-
-  vterm_get_size(vt, &rows, &cols);
-
-  screen->vt = vt;
-  screen->state = state;
-
-  screen->damage_merge = VTERM_DAMAGE_CELL;
-  screen->damaged.start_row = -1;
-  screen->pending_scrollrect.start_row = -1;
-
-  screen->rows = rows;
-  screen->cols = cols;
-
-  screen->callbacks = NULL;
-  screen->cbdata    = NULL;
-
-  screen->buffers[0] = realloc_buffer(screen, NULL, rows, cols);
-
-  screen->buffer = screen->buffers[0];
-
-  screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * cols);
-
-  vterm_state_set_callbacks(screen->state, &state_cbs, screen);
-
-  return screen;
-}
-
-INTERNAL void vterm_screen_free(VTermScreen *screen)
-{
-  vterm_allocator_free(screen->vt, screen->buffers[0]);
-  if(screen->buffers[1])
-    vterm_allocator_free(screen->vt, screen->buffers[1]);
-
-  vterm_allocator_free(screen->vt, screen->sb_buffer);
-
-  vterm_allocator_free(screen->vt, screen);
-}
-
-void vterm_screen_reset(VTermScreen *screen, int hard)
-{
-  screen->damaged.start_row = -1;
-  screen->pending_scrollrect.start_row = -1;
-  vterm_state_reset(screen->state, hard);
-  vterm_screen_flush_damage(screen);
-}
-
-static size_t _get_chars(const VTermScreen *screen, const int utf8, void *buffer, size_t len, const VTermRect rect)
-{
-  size_t outpos = 0;
-  int padding = 0;
-
-#define PUT(c)                                             \
-  if(utf8) {                                               \
-    size_t thislen = utf8_seqlen(c);                       \
-    if(buffer && outpos + thislen <= len)                  \
-      outpos += fill_utf8((c), (char *)buffer + outpos);   \
-    else                                                   \
-      outpos += thislen;                                   \
-  }                                                        \
-  else {                                                   \
-    if(buffer && outpos + 1 <= len)                        \
-      ((uint32_t*)buffer)[outpos++] = (c);                 \
-    else                                                   \
-      outpos++;                                            \
-  }
-
-  for(int row = rect.start_row; row < rect.end_row; row++) {
-    for(int col = rect.start_col; col < rect.end_col; col++) {
-      ScreenCell *cell = getcell(screen, row, col);
-
-      if(cell->chars[0] == 0)
-        // Erased cell, might need a space
-        padding++;
-      else if(cell->chars[0] == (uint32_t)-1)
-        // Gap behind a double-width char, do nothing
-        ;
-      else {
-        while(padding) {
-          PUT(UNICODE_SPACE);
-          padding--;
-        }
-        for(int i = 0; i < VTERM_MAX_CHARS_PER_CELL && cell->chars[i]; i++) {
-          PUT(cell->chars[i]);
-        }
-      }
-    }
-
-    if(row < rect.end_row - 1) {
-      PUT(UNICODE_LINEFEED);
-      padding = 0;
-    }
-  }
-
-  return outpos;
-}
-
-size_t vterm_screen_get_chars(const VTermScreen *screen, uint32_t *chars, size_t len, const VTermRect rect)
-{
-  return _get_chars(screen, 0, chars, len, rect);
-}
-
-size_t vterm_screen_get_text(const VTermScreen *screen, char *str, size_t len, const VTermRect rect)
-{
-  return _get_chars(screen, 1, str, len, rect);
-}
-
-/* Copy internal to external representation of a screen cell */
-int vterm_screen_get_cell(const VTermScreen *screen, VTermPos pos, VTermScreenCell *cell)
-{
-  ScreenCell *intcell = getcell(screen, pos.row, pos.col);
-  if(!intcell)
-    return 0;
-
-  for(int i = 0; ; i++) {
-    cell->chars[i] = intcell->chars[i];
-    if(!intcell->chars[i])
-      break;
-  }
-
-  cell->attrs.bold      = intcell->pen.bold;
-  cell->attrs.underline = intcell->pen.underline;
-  cell->attrs.italic    = intcell->pen.italic;
-  cell->attrs.blink     = intcell->pen.blink;
-  cell->attrs.reverse   = intcell->pen.reverse ^ screen->global_reverse;
-  cell->attrs.strike    = intcell->pen.strike;
-  cell->attrs.font      = intcell->pen.font;
-
-  cell->attrs.dwl = intcell->pen.dwl;
-  cell->attrs.dhl = intcell->pen.dhl;
-
-  cell->fg = intcell->pen.fg;
-  cell->bg = intcell->pen.bg;
-
-  if(pos.col < (screen->cols - 1) &&
-     getcell(screen, pos.row, pos.col + 1)->chars[0] == (uint32_t)-1)
-    cell->width = 2;
-  else
-    cell->width = 1;
-
-  return 1;
-}
-
-/* Copy external to internal representation of a screen cell */
-/* static because it's only used internally for sb_popline during resize */
-static int vterm_screen_set_cell(VTermScreen *screen, VTermPos pos, const VTermScreenCell *cell)
-{
-  ScreenCell *intcell = getcell(screen, pos.row, pos.col);
-  if(!intcell)
-    return 0;
-
-  for(int i = 0; ; i++) {
-    intcell->chars[i] = cell->chars[i];
-    if(!cell->chars[i])
-      break;
-  }
-
-  intcell->pen.bold      = cell->attrs.bold;
-  intcell->pen.underline = cell->attrs.underline;
-  intcell->pen.italic    = cell->attrs.italic;
-  intcell->pen.blink     = cell->attrs.blink;
-  intcell->pen.reverse   = cell->attrs.reverse ^ screen->global_reverse;
-  intcell->pen.strike    = cell->attrs.strike;
-  intcell->pen.font      = cell->attrs.font;
-
-  intcell->pen.fg = cell->fg;
-  intcell->pen.bg = cell->bg;
-
-  if(cell->width == 2)
-    getcell(screen, pos.row, pos.col + 1)->chars[0] = (uint32_t)-1;
-
-  return 1;
-}
-
-int vterm_screen_is_eol(const VTermScreen *screen, VTermPos pos)
-{
-  /* This cell is EOL if this and every cell to the right is black */
-  for(; pos.col < screen->cols; pos.col++) {
-    ScreenCell *cell = getcell(screen, pos.row, pos.col);
-    if(cell->chars[0] != 0)
-      return 0;
-  }
-
-  return 1;
-}
-
-VTermScreen *vterm_obtain_screen(VTerm *vt)
-{
-  if(vt->screen)
-    return vt->screen;
-
-  VTermScreen *screen = screen_new(vt);
-  vt->screen = screen;
-
-  return screen;
-}
-
-void vterm_screen_enable_altscreen(VTermScreen *screen, int altscreen)
-{
-
-  if(!screen->buffers[1] && altscreen) {
-    int rows, cols;
-    vterm_get_size(screen->vt, &rows, &cols);
-
-    screen->buffers[1] = realloc_buffer(screen, NULL, rows, cols);
-  }
-}
-
-void vterm_screen_set_callbacks(VTermScreen *screen, const VTermScreenCallbacks *callbacks, void *user)
-{
-  screen->callbacks = callbacks;
-  screen->cbdata = user;
-}
-
-void *vterm_screen_get_cbdata(VTermScreen *screen)
-{
-  return screen->cbdata;
-}
-
-void vterm_screen_set_unrecognised_fallbacks(VTermScreen *screen, const VTermParserCallbacks *fallbacks, void *user)
-{
-  vterm_state_set_unrecognised_fallbacks(screen->state, fallbacks, user);
-}
-
-void *vterm_screen_get_unrecognised_fbdata(VTermScreen *screen)
-{
-  return vterm_state_get_unrecognised_fbdata(screen->state);
-}
-
-void vterm_screen_flush_damage(VTermScreen *screen)
-{
-  if(screen->pending_scrollrect.start_row != -1) {
-    vterm_scroll_rect(screen->pending_scrollrect, screen->pending_scroll_downward, screen->pending_scroll_rightward,
-        moverect_user, erase_user, screen);
-
-    screen->pending_scrollrect.start_row = -1;
-  }
-
-  if(screen->damaged.start_row != -1) {
-    if(screen->callbacks && screen->callbacks->damage)
-      (*screen->callbacks->damage)(screen->damaged, screen->cbdata);
-
-    screen->damaged.start_row = -1;
-  }
-}
-
-void vterm_screen_set_damage_merge(VTermScreen *screen, VTermDamageSize size)
-{
-  vterm_screen_flush_damage(screen);
-  screen->damage_merge = size;
-}
-
-static int attrs_differ(VTermAttrMask attrs, ScreenCell *a, ScreenCell *b)
-{
-  if((attrs & VTERM_ATTR_BOLD_MASK)       && (a->pen.bold != b->pen.bold))
-    return 1;
-  if((attrs & VTERM_ATTR_UNDERLINE_MASK)  && (a->pen.underline != b->pen.underline))
-    return 1;
-  if((attrs & VTERM_ATTR_ITALIC_MASK)     && (a->pen.italic != b->pen.italic))
-    return 1;
-  if((attrs & VTERM_ATTR_BLINK_MASK)      && (a->pen.blink != b->pen.blink))
-    return 1;
-  if((attrs & VTERM_ATTR_REVERSE_MASK)    && (a->pen.reverse != b->pen.reverse))
-    return 1;
-  if((attrs & VTERM_ATTR_STRIKE_MASK)     && (a->pen.strike != b->pen.strike))
-    return 1;
-  if((attrs & VTERM_ATTR_FONT_MASK)       && (a->pen.font != b->pen.font))
-    return 1;
-  if((attrs & VTERM_ATTR_FOREGROUND_MASK) && !vterm_color_is_equal(&a->pen.fg, &b->pen.fg))
-    return 1;
-  if((attrs & VTERM_ATTR_BACKGROUND_MASK) && !vterm_color_is_equal(&a->pen.bg, &b->pen.bg))
-    return 1;
-
-  return 0;
-}
-
-int vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect *extent, VTermPos pos, VTermAttrMask attrs)
-{
-  ScreenCell *target = getcell(screen, pos.row, pos.col);
-
-  // TODO: bounds check
-  extent->start_row = pos.row;
-  extent->end_row   = pos.row + 1;
-
-  if(extent->start_col < 0)
-    extent->start_col = 0;
-  if(extent->end_col < 0)
-    extent->end_col = screen->cols;
-
-  int col;
-
-  for(col = pos.col - 1; col >= extent->start_col; col--)
-    if(attrs_differ(attrs, target, getcell(screen, pos.row, col)))
-      break;
-  extent->start_col = col + 1;
-
-  for(col = pos.col + 1; col < extent->end_col; col++)
-    if(attrs_differ(attrs, target, getcell(screen, pos.row, col)))
-      break;
-  extent->end_col = col - 1;
-
-  return 1;
-}
-
-void vterm_screen_convert_color_to_rgb(const VTermScreen *screen, VTermColor *col)
-{
-  vterm_state_convert_color_to_rgb(screen->state, col);
-}
diff --git a/src/state.c b/src/state.c
deleted file mode 100644
index 68cc4f6..0000000
--- a/src/state.c
+++ /dev/null
@@ -1,1868 +0,0 @@
-#include "vterm_internal.h"
-
-#include <stdio.h>
-#include <string.h>
-
-#define strneq(a,b,n) (strncmp(a,b,n)==0)
-
-#if defined(DEBUG) && DEBUG > 1
-# define DEBUG_GLYPH_COMBINE
-#endif
-
-/* Some convenient wrappers to make callback functions easier */
-
-static void putglyph(VTermState *state, const uint32_t chars[], int width, VTermPos pos)
-{
-  VTermGlyphInfo info = {
-    .chars = chars,
-    .width = width,
-    .protected_cell = state->protected_cell,
-    .dwl = state->lineinfo[pos.row].doublewidth,
-    .dhl = state->lineinfo[pos.row].doubleheight,
-  };
-
-  if(state->callbacks && state->callbacks->putglyph)
-    if((*state->callbacks->putglyph)(&info, pos, state->cbdata))
-      return;
-
-  DEBUG_LOG("libvterm: Unhandled putglyph U+%04x at (%d,%d)\n", chars[0], pos.col, pos.row);
-}
-
-static void updatecursor(VTermState *state, VTermPos *oldpos, int cancel_phantom)
-{
-  if(state->pos.col == oldpos->col && state->pos.row == oldpos->row)
-    return;
-
-  if(cancel_phantom)
-    state->at_phantom = 0;
-
-  if(state->callbacks && state->callbacks->movecursor)
-    if((*state->callbacks->movecursor)(state->pos, *oldpos, state->mode.cursor_visible, state->cbdata))
-      return;
-}
-
-static void erase(VTermState *state, VTermRect rect, int selective)
-{
-  if(state->callbacks && state->callbacks->erase)
-    if((*state->callbacks->erase)(rect, selective, state->cbdata))
-      return;
-}
-
-static VTermState *vterm_state_new(VTerm *vt)
-{
-  VTermState *state = vterm_allocator_malloc(vt, sizeof(VTermState));
-
-  state->vt = vt;
-
-  state->rows = vt->rows;
-  state->cols = vt->cols;
-
-  state->mouse_col     = 0;
-  state->mouse_row     = 0;
-  state->mouse_buttons = 0;
-
-  state->mouse_protocol = MOUSE_X10;
-
-  state->callbacks = NULL;
-  state->cbdata    = NULL;
-
-  vterm_state_newpen(state);
-
-  state->bold_is_highbright = 0;
-
-  return state;
-}
-
-INTERNAL void vterm_state_free(VTermState *state)
-{
-  vterm_allocator_free(state->vt, state->tabstops);
-  vterm_allocator_free(state->vt, state->lineinfo);
-  vterm_allocator_free(state->vt, state->combine_chars);
-  vterm_allocator_free(state->vt, state);
-}
-
-static void scroll(VTermState *state, VTermRect rect, int downward, int rightward)
-{
-  if(!downward && !rightward)
-    return;
-
-  int rows = rect.end_row - rect.start_row;
-  if(downward > rows)
-    downward = rows;
-  else if(downward < -rows)
-    downward = -rows;
-
-  int cols = rect.end_col - rect.start_col;
-  if(rightward > cols)
-    rightward = cols;
-  else if(rightward < -cols)
-    rightward = -cols;
-
-  // Update lineinfo if full line
-  if(rect.start_col == 0 && rect.end_col == state->cols && rightward == 0) {
-    int height = rect.end_row - rect.start_row - abs(downward);
-
-    if(downward > 0)
-      memmove(state->lineinfo + rect.start_row,
-              state->lineinfo + rect.start_row + downward,
-              height * sizeof(state->lineinfo[0]));
-    else
-      memmove(state->lineinfo + rect.start_row - downward,
-              state->lineinfo + rect.start_row,
-              height * sizeof(state->lineinfo[0]));
-  }
-
-  if(state->callbacks && state->callbacks->scrollrect)
-    if((*state->callbacks->scrollrect)(rect, downward, rightward, state->cbdata))
-      return;
-
-  if(state->callbacks)
-    vterm_scroll_rect(rect, downward, rightward,
-        state->callbacks->moverect, state->callbacks->erase, state->cbdata);
-}
-
-static void linefeed(VTermState *state)
-{
-  if(state->pos.row == SCROLLREGION_BOTTOM(state) - 1) {
-    VTermRect rect = {
-      .start_row = state->scrollregion_top,
-      .end_row   = SCROLLREGION_BOTTOM(state),
-      .start_col = SCROLLREGION_LEFT(state),
-      .end_col   = SCROLLREGION_RIGHT(state),
-    };
-
-    scroll(state, rect, 1, 0);
-  }
-  else if(state->pos.row < state->rows-1)
-    state->pos.row++;
-}
-
-static void grow_combine_buffer(VTermState *state)
-{
-  size_t    new_size = state->combine_chars_size * 2;
-  uint32_t *new_chars = vterm_allocator_malloc(state->vt, new_size * sizeof(new_chars[0]));
-
-  memcpy(new_chars, state->combine_chars, state->combine_chars_size * sizeof(new_chars[0]));
-
-  vterm_allocator_free(state->vt, state->combine_chars);
-
-  state->combine_chars = new_chars;
-  state->combine_chars_size = new_size;
-}
-
-static void set_col_tabstop(VTermState *state, int col)
-{
-  unsigned char mask = 1 << (col & 7);
-  state->tabstops[col >> 3] |= mask;
-}
-
-static void clear_col_tabstop(VTermState *state, int col)
-{
-  unsigned char mask = 1 << (col & 7);
-  state->tabstops[col >> 3] &= ~mask;
-}
-
-static int is_col_tabstop(VTermState *state, int col)
-{
-  unsigned char mask = 1 << (col & 7);
-  return state->tabstops[col >> 3] & mask;
-}
-
-static int is_cursor_in_scrollregion(const VTermState *state)
-{
-  if(state->pos.row < state->scrollregion_top ||
-     state->pos.row >= SCROLLREGION_BOTTOM(state))
-    return 0;
-  if(state->pos.col < SCROLLREGION_LEFT(state) ||
-     state->pos.col >= SCROLLREGION_RIGHT(state))
-    return 0;
-
-  return 1;
-}
-
-static void tab(VTermState *state, int count, int direction)
-{
-  while(count > 0) {
-    if(direction > 0) {
-      if(state->pos.col >= THISROWWIDTH(state)-1)
-        return;
-
-      state->pos.col++;
-    }
-    else if(direction < 0) {
-      if(state->pos.col < 1)
-        return;
-
-      state->pos.col--;
-    }
-
-    if(is_col_tabstop(state, state->pos.col))
-      count--;
-  }
-}
-
-#define NO_FORCE 0
-#define FORCE    1
-
-#define DWL_OFF 0
-#define DWL_ON  1
-
-#define DHL_OFF    0
-#define DHL_TOP    1
-#define DHL_BOTTOM 2
-
-static void set_lineinfo(VTermState *state, int row, int force, int dwl, int dhl)
-{
-  VTermLineInfo info = state->lineinfo[row];
-
-  if(dwl == DWL_OFF)
-    info.doublewidth = DWL_OFF;
-  else if(dwl == DWL_ON)
-    info.doublewidth = DWL_ON;
-  // else -1 to ignore
-
-  if(dhl == DHL_OFF)
-    info.doubleheight = DHL_OFF;
-  else if(dhl == DHL_TOP)
-    info.doubleheight = DHL_TOP;
-  else if(dhl == DHL_BOTTOM)
-    info.doubleheight = DHL_BOTTOM;
-
-  if((state->callbacks &&
-      state->callbacks->setlineinfo &&
-      (*state->callbacks->setlineinfo)(row, &info, state->lineinfo + row, state->cbdata))
-      || force)
-    state->lineinfo[row] = info;
-}
-
-static int on_text(const char bytes[], size_t len, void *user)
-{
-  VTermState *state = user;
-
-  VTermPos oldpos = state->pos;
-
-  // We'll have at most len codepoints
-  uint32_t codepoints[len];
-  int npoints = 0;
-  size_t eaten = 0;
-
-  VTermEncodingInstance *encoding =
-    state->gsingle_set     ? &state->encoding[state->gsingle_set] :
-    !(bytes[eaten] & 0x80) ? &state->encoding[state->gl_set] :
-    state->vt->mode.utf8   ? &state->encoding_utf8 :
-                             &state->encoding[state->gr_set];
-
-  (*encoding->enc->decode)(encoding->enc, encoding->data,
-      codepoints, &npoints, state->gsingle_set ? 1 : len,
-      bytes, &eaten, len);
-
-  /* There's a chance an encoding (e.g. UTF-8) hasn't found enough bytes yet
-   * for even a single codepoint
-   */
-  if(!npoints)
-    return eaten;
-
-  if(state->gsingle_set && npoints)
-    state->gsingle_set = 0;
-
-  int i = 0;
-
-  /* This is a combining char. that needs to be merged with the previous
-   * glyph output */
-  if(vterm_unicode_is_combining(codepoints[i])) {
-    /* See if the cursor has moved since */
-    if(state->pos.row == state->combine_pos.row && state->pos.col == state->combine_pos.col + state->combine_width) {
-#ifdef DEBUG_GLYPH_COMBINE
-      int printpos;
-      printf("DEBUG: COMBINING SPLIT GLYPH of chars {");
-      for(printpos = 0; state->combine_chars[printpos]; printpos++)
-        printf("U+%04x ", state->combine_chars[printpos]);
-      printf("} + {");
-#endif
-
-      /* Find where we need to append these combining chars */
-      int saved_i = 0;
-      while(state->combine_chars[saved_i])
-        saved_i++;
-
-      /* Add extra ones */
-      while(i < npoints && vterm_unicode_is_combining(codepoints[i])) {
-        if(saved_i >= state->combine_chars_size)
-          grow_combine_buffer(state);
-        state->combine_chars[saved_i++] = codepoints[i++];
-      }
-      if(saved_i >= state->combine_chars_size)
-        grow_combine_buffer(state);
-      state->combine_chars[saved_i] = 0;
-
-#ifdef DEBUG_GLYPH_COMBINE
-      for(; state->combine_chars[printpos]; printpos++)
-        printf("U+%04x ", state->combine_chars[printpos]);
-      printf("}\n");
-#endif
-
-      /* Now render it */
-      putglyph(state, state->combine_chars, state->combine_width, state->combine_pos);
-    }
-    else {
-      DEBUG_LOG("libvterm: TODO: Skip over split char+combining\n");
-    }
-  }
-
-  for(; i < npoints; i++) {
-    // Try to find combining characters following this
-    int glyph_starts = i;
-    int glyph_ends;
-    for(glyph_ends = i + 1; glyph_ends < npoints; glyph_ends++)
-      if(!vterm_unicode_is_combining(codepoints[glyph_ends]))
-        break;
-
-    int width = 0;
-
-    uint32_t chars[glyph_ends - glyph_starts + 1];
-
-    for( ; i < glyph_ends; i++) {
-      chars[i - glyph_starts] = codepoints[i];
-      int this_width = vterm_unicode_width(codepoints[i]);
-#ifdef DEBUG
-      if(this_width < 0) {
-        fprintf(stderr, "Text with negative-width codepoint U+%04x\n", codepoints[i]);
-        abort();
-      }
-#endif
-      width += this_width;
-    }
-
-    chars[glyph_ends - glyph_starts] = 0;
-    i--;
-
-#ifdef DEBUG_GLYPH_COMBINE
-    int printpos;
-    printf("DEBUG: COMBINED GLYPH of %d chars {", glyph_ends - glyph_starts);
-    for(printpos = 0; printpos < glyph_ends - glyph_starts; printpos++)
-      printf("U+%04x ", chars[printpos]);
-    printf("}, onscreen width %d\n", width);
-#endif
-
-    if(state->at_phantom || state->pos.col + width > THISROWWIDTH(state)) {
-      linefeed(state);
-      state->pos.col = 0;
-      state->at_phantom = 0;
-    }
-
-    if(state->mode.insert) {
-      /* TODO: This will be a little inefficient for large bodies of text, as
-       * it'll have to 'ICH' effectively before every glyph. We should scan
-       * ahead and ICH as many times as required
-       */
-      VTermRect rect = {
-        .start_row = state->pos.row,
-        .end_row   = state->pos.row + 1,
-        .start_col = state->pos.col,
-        .end_col   = THISROWWIDTH(state),
-      };
-      scroll(state, rect, 0, -1);
-    }
-
-    putglyph(state, chars, width, state->pos);
-
-    if(i == npoints - 1) {
-      /* End of the buffer. Save the chars in case we have to combine with
-       * more on the next call */
-      int save_i;
-      for(save_i = 0; chars[save_i]; save_i++) {
-        if(save_i >= state->combine_chars_size)
-          grow_combine_buffer(state);
-        state->combine_chars[save_i] = chars[save_i];
-      }
-      if(save_i >= state->combine_chars_size)
-        grow_combine_buffer(state);
-      state->combine_chars[save_i] = 0;
-      state->combine_width = width;
-      state->combine_pos = state->pos;
-    }
-
-    if(state->pos.col + width >= THISROWWIDTH(state)) {
-      if(state->mode.autowrap)
-        state->at_phantom = 1;
-    }
-    else {
-      state->pos.col += width;
-    }
-  }
-
-  updatecursor(state, &oldpos, 0);
-
-#ifdef DEBUG
-  if(state->pos.row < 0 || state->pos.row >= state->rows ||
-     state->pos.col < 0 || state->pos.col >= state->cols) {
-    fprintf(stderr, "Position out of bounds after text: (%d,%d)\n",
-        state->pos.row, state->pos.col);
-    abort();
-  }
-#endif
-
-  return eaten;
-}
-
-static int on_control(unsigned char control, void *user)
-{
-  VTermState *state = user;
-
-  VTermPos oldpos = state->pos;
-
-  switch(control) {
-  case 0x07: // BEL - ECMA-48 8.3.3
-    if(state->callbacks && state->callbacks->bell)
-      (*state->callbacks->bell)(state->cbdata);
-    break;
-
-  case 0x08: // BS - ECMA-48 8.3.5
-    if(state->pos.col > 0)
-      state->pos.col--;
-    break;
-
-  case 0x09: // HT - ECMA-48 8.3.60
-    tab(state, 1, +1);
-    break;
-
-  case 0x0a: // LF - ECMA-48 8.3.74
-  case 0x0b: // VT
-  case 0x0c: // FF
-    linefeed(state);
-    if(state->mode.newline)
-      state->pos.col = 0;
-    break;
-
-  case 0x0d: // CR - ECMA-48 8.3.15
-    state->pos.col = 0;
-    break;
-
-  case 0x0e: // LS1 - ECMA-48 8.3.76
-    state->gl_set = 1;
-    break;
-
-  case 0x0f: // LS0 - ECMA-48 8.3.75
-    state->gl_set = 0;
-    break;
-
-  case 0x84: // IND - DEPRECATED but implemented for completeness
-    linefeed(state);
-    break;
-
-  case 0x85: // NEL - ECMA-48 8.3.86
-    linefeed(state);
-    state->pos.col = 0;
-    break;
-
-  case 0x88: // HTS - ECMA-48 8.3.62
-    set_col_tabstop(state, state->pos.col);
-    break;
-
-  case 0x8d: // RI - ECMA-48 8.3.104
-    if(state->pos.row == state->scrollregion_top) {
-      VTermRect rect = {
-        .start_row = state->scrollregion_top,
-        .end_row   = SCROLLREGION_BOTTOM(state),
-        .start_col = SCROLLREGION_LEFT(state),
-        .end_col   = SCROLLREGION_RIGHT(state),
-      };
-
-      scroll(state, rect, -1, 0);
-    }
-    else if(state->pos.row > 0)
-        state->pos.row--;
-    break;
-
-  case 0x8e: // SS2 - ECMA-48 8.3.141
-    state->gsingle_set = 2;
-    break;
-
-  case 0x8f: // SS3 - ECMA-48 8.3.142
-    state->gsingle_set = 3;
-    break;
-
-  default:
-    if(state->fallbacks && state->fallbacks->control)
-      if((*state->fallbacks->control)(control, state->fbdata))
-        return 1;
-
-    return 0;
-  }
-
-  updatecursor(state, &oldpos, 1);
-
-#ifdef DEBUG
-  if(state->pos.row < 0 || state->pos.row >= state->rows ||
-     state->pos.col < 0 || state->pos.col >= state->cols) {
-    fprintf(stderr, "Position out of bounds after Ctrl %02x: (%d,%d)\n",
-        control, state->pos.row, state->pos.col);
-    abort();
-  }
-#endif
-
-  return 1;
-}
-
-static int settermprop_bool(VTermState *state, VTermProp prop, int v)
-{
-  VTermValue val = { .boolean = v };
-  return vterm_state_set_termprop(state, prop, &val);
-}
-
-static int settermprop_int(VTermState *state, VTermProp prop, int v)
-{
-  VTermValue val = { .number = v };
-  return vterm_state_set_termprop(state, prop, &val);
-}
-
-static int settermprop_string(VTermState *state, VTermProp prop, const char *str, size_t len)
-{
-  char strvalue[len+1];
-  strncpy(strvalue, str, len);
-  strvalue[len] = 0;
-
-  VTermValue val = { .string = strvalue };
-  return vterm_state_set_termprop(state, prop, &val);
-}
-
-static void savecursor(VTermState *state, int save)
-{
-  if(save) {
-    state->saved.pos = state->pos;
-    state->saved.mode.cursor_visible = state->mode.cursor_visible;
-    state->saved.mode.cursor_blink   = state->mode.cursor_blink;
-    state->saved.mode.cursor_shape   = state->mode.cursor_shape;
-
-    vterm_state_savepen(state, 1);
-  }
-  else {
-    VTermPos oldpos = state->pos;
-
-    state->pos = state->saved.pos;
-
-    settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, state->saved.mode.cursor_visible);
-    settermprop_bool(state, VTERM_PROP_CURSORBLINK,   state->saved.mode.cursor_blink);
-    settermprop_int (state, VTERM_PROP_CURSORSHAPE,   state->saved.mode.cursor_shape);
-
-    vterm_state_savepen(state, 0);
-
-    updatecursor(state, &oldpos, 1);
-  }
-}
-
-static int on_escape(const char *bytes, size_t len, void *user)
-{
-  VTermState *state = user;
-
-  /* Easier to decode this from the first byte, even though the final
-   * byte terminates it
-   */
-  switch(bytes[0]) {
-  case ' ':
-    if(len != 2)
-      return 0;
-
-    switch(bytes[1]) {
-      case 'F': // S7C1T
-        state->vt->mode.ctrl8bit = 0;
-        break;
-
-      case 'G': // S8C1T
-        state->vt->mode.ctrl8bit = 1;
-        break;
-
-      default:
-        return 0;
-    }
-    return 2;
-
-  case '#':
-    if(len != 2)
-      return 0;
-
-    switch(bytes[1]) {
-      case '3': // DECDHL top
-        if(state->mode.leftrightmargin)
-          break;
-        set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_TOP);
-        break;
-
-      case '4': // DECDHL bottom
-        if(state->mode.leftrightmargin)
-          break;
-        set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_BOTTOM);
-        break;
-
-      case '5': // DECSWL
-        if(state->mode.leftrightmargin)
-          break;
-        set_lineinfo(state, state->pos.row, NO_FORCE, DWL_OFF, DHL_OFF);
-        break;
-
-      case '6': // DECDWL
-        if(state->mode.leftrightmargin)
-          break;
-        set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_OFF);
-        break;
-
-      case '8': // DECALN
-      {
-        VTermPos pos;
-        uint32_t E[] = { 'E', 0 };
-        for(pos.row = 0; pos.row < state->rows; pos.row++)
-          for(pos.col = 0; pos.col < ROWWIDTH(state, pos.row); pos.col++)
-            putglyph(state, E, 1, pos);
-        break;
-      }
-
-      default:
-        return 0;
-    }
-    return 2;
-
-  case '(': case ')': case '*': case '+': // SCS
-    if(len != 2)
-      return 0;
-
-    {
-      int setnum = bytes[0] - 0x28;
-      VTermEncoding *newenc = vterm_lookup_encoding(ENC_SINGLE_94, bytes[1]);
-
-      if(newenc) {
-        state->encoding[setnum].enc = newenc;
-
-        if(newenc->init)
-          (*newenc->init)(newenc, state->encoding[setnum].data);
-      }
-    }
-
-    return 2;
-
-  case '7': // DECSC
-    savecursor(state, 1);
-    return 1;
-
-  case '8': // DECRC
-    savecursor(state, 0);
-    return 1;
-
-  case '<': // Ignored by VT100. Used in VT52 mode to switch up to VT100
-    return 1;
-
-  case '=': // DECKPAM
-    state->mode.keypad = 1;
-    return 1;
-
-  case '>': // DECKPNM
-    state->mode.keypad = 0;
-    return 1;
-
-  case 'c': // RIS - ECMA-48 8.3.105
-  {
-    VTermPos oldpos = state->pos;
-    vterm_state_reset(state, 1);
-    if(state->callbacks && state->callbacks->movecursor)
-      (*state->callbacks->movecursor)(state->pos, oldpos, state->mode.cursor_visible, state->cbdata);
-    return 1;
-  }
-
-  case 'n': // LS2 - ECMA-48 8.3.78
-    state->gl_set = 2;
-    return 1;
-
-  case 'o': // LS3 - ECMA-48 8.3.80
-    state->gl_set = 3;
-    return 1;
-
-  case '~': // LS1R - ECMA-48 8.3.77
-    state->gr_set = 1;
-    return 1;
-
-  case '}': // LS2R - ECMA-48 8.3.79
-    state->gr_set = 2;
-    return 1;
-
-  case '|': // LS3R - ECMA-48 8.3.81
-    state->gr_set = 3;
-    return 1;
-
-  default:
-    return 0;
-  }
-}
-
-static void set_mode(VTermState *state, int num, int val)
-{
-  switch(num) {
-  case 4: // IRM - ECMA-48 7.2.10
-    state->mode.insert = val;
-    break;
-
-  case 20: // LNM - ANSI X3.4-1977
-    state->mode.newline = val;
-    break;
-
-  default:
-    DEBUG_LOG("libvterm: Unknown mode %d\n", num);
-    return;
-  }
-}
-
-static void set_dec_mode(VTermState *state, int num, int val)
-{
-  switch(num) {
-  case 1:
-    state->mode.cursor = val;
-    break;
-
-  case 5: // DECSCNM - screen mode
-    settermprop_bool(state, VTERM_PROP_REVERSE, val);
-    break;
-
-  case 6: // DECOM - origin mode
-    {
-      VTermPos oldpos = state->pos;
-      state->mode.origin = val;
-      state->pos.row = state->mode.origin ? state->scrollregion_top : 0;
-      state->pos.col = state->mode.origin ? SCROLLREGION_LEFT(state) : 0;
-      updatecursor(state, &oldpos, 1);
-    }
-    break;
-
-  case 7:
-    state->mode.autowrap = val;
-    break;
-
-  case 12:
-    settermprop_bool(state, VTERM_PROP_CURSORBLINK, val);
-    break;
-
-  case 25:
-    settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, val);
-    break;
-
-  case 69: // DECVSSM - vertical split screen mode
-           // DECLRMM - left/right margin mode
-    state->mode.leftrightmargin = val;
-    if(val) {
-      // Setting DECVSSM must clear doublewidth/doubleheight state of every line
-      for(int row = 0; row < state->rows; row++)
-        set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);
-    }
-
-    break;
-
-  case 1000:
-  case 1002:
-  case 1003:
-    settermprop_int(state, VTERM_PROP_MOUSE,
-        !val          ? VTERM_PROP_MOUSE_NONE  :
-        (num == 1000) ? VTERM_PROP_MOUSE_CLICK :
-        (num == 1002) ? VTERM_PROP_MOUSE_DRAG  :
-                        VTERM_PROP_MOUSE_MOVE);
-    break;
-
-  case 1004:
-    state->mode.report_focus = val;
-    break;
-
-  case 1005:
-    state->mouse_protocol = val ? MOUSE_UTF8 : MOUSE_X10;
-    break;
-
-  case 1006:
-    state->mouse_protocol = val ? MOUSE_SGR : MOUSE_X10;
-    break;
-
-  case 1015:
-    state->mouse_protocol = val ? MOUSE_RXVT : MOUSE_X10;
-    break;
-
-  case 1047:
-    settermprop_bool(state, VTERM_PROP_ALTSCREEN, val);
-    break;
-
-  case 1048:
-    savecursor(state, val);
-    break;
-
-  case 1049:
-    settermprop_bool(state, VTERM_PROP_ALTSCREEN, val);
-    savecursor(state, val);
-    break;
-
-  case 2004:
-    state->mode.bracketpaste = val;
-    break;
-
-  default:
-    DEBUG_LOG("libvterm: Unknown DEC mode %d\n", num);
-    return;
-  }
-}
-
-static void request_dec_mode(VTermState *state, int num)
-{
-  int reply;
-
-  switch(num) {
-    case 1:
-      reply = state->mode.cursor;
-      break;
-
-    case 5:
-      reply = state->mode.screen;
-      break;
-
-    case 6:
-      reply = state->mode.origin;
-      break;
-
-    case 7:
-      reply = state->mode.autowrap;
-      break;
-
-    case 12:
-      reply = state->mode.cursor_blink;
-      break;
-
-    case 25:
-      reply = state->mode.cursor_visible;
-      break;
-
-    case 69:
-      reply = state->mode.leftrightmargin;
-      break;
-
-    case 1000:
-      reply = state->mouse_flags == MOUSE_WANT_CLICK;
-      break;
-
-    case 1002:
-      reply = state->mouse_flags == (MOUSE_WANT_CLICK|MOUSE_WANT_DRAG);
-      break;
-
-    case 1003:
-      reply = state->mouse_flags == (MOUSE_WANT_CLICK|MOUSE_WANT_MOVE);
-      break;
-
-    case 1004:
-      reply = state->mode.report_focus;
-      break;
-
-    case 1005:
-      reply = state->mouse_protocol == MOUSE_UTF8;
-      break;
-
-    case 1006:
-      reply = state->mouse_protocol == MOUSE_SGR;
-      break;
-
-    case 1015:
-      reply = state->mouse_protocol == MOUSE_RXVT;
-      break;
-
-    case 1047:
-      reply = state->mode.alt_screen;
-      break;
-
-    case 2004:
-      reply = state->mode.bracketpaste;
-      break;
-
-    default:
-      vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%d;%d$y", num, 0);
-      return;
-  }
-
-  vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%d;%d$y", num, reply ? 1 : 2);
-}
-
-static int on_csi(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user)
-{
-  VTermState *state = user;
-  int leader_byte = 0;
-  int intermed_byte = 0;
-  int cancel_phantom = 1;
-
-  if(leader && leader[0]) {
-    if(leader[1]) // longer than 1 char
-      return 0;
-
-    switch(leader[0]) {
-    case '?':
-    case '>':
-      leader_byte = leader[0];
-      break;
-    default:
-      return 0;
-    }
-  }
-
-  if(intermed && intermed[0]) {
-    if(intermed[1]) // longer than 1 char
-      return 0;
-
-    switch(intermed[0]) {
-    case ' ':
-    case '"':
-    case '$':
-    case '\'':
-      intermed_byte = intermed[0];
-      break;
-    default:
-      return 0;
-    }
-  }
-
-  VTermPos oldpos = state->pos;
-
-  // Some temporaries for later code
-  int count, val;
-  int row, col;
-  VTermRect rect;
-  int selective;
-
-#define LBOUND(v,min) if((v) < (min)) (v) = (min)
-#define UBOUND(v,max) if((v) > (max)) (v) = (max)
-
-#define LEADER(l,b) ((l << 8) | b)
-#define INTERMED(i,b) ((i << 16) | b)
-
-  switch(intermed_byte << 16 | leader_byte << 8 | command) {
-  case 0x40: // ICH - ECMA-48 8.3.64
-    count = CSI_ARG_COUNT(args[0]);
-
-    if(!is_cursor_in_scrollregion(state))
-      break;
-
-    rect.start_row = state->pos.row;
-    rect.end_row   = state->pos.row + 1;
-    rect.start_col = state->pos.col;
-    if(state->mode.leftrightmargin)
-      rect.end_col = SCROLLREGION_RIGHT(state);
-    else
-      rect.end_col = THISROWWIDTH(state);
-
-    scroll(state, rect, 0, -count);
-
-    break;
-
-  case 0x41: // CUU - ECMA-48 8.3.22
-    count = CSI_ARG_COUNT(args[0]);
-    state->pos.row -= count;
-    state->at_phantom = 0;
-    break;
-
-  case 0x42: // CUD - ECMA-48 8.3.19
-    count = CSI_ARG_COUNT(args[0]);
-    state->pos.row += count;
-    state->at_phantom = 0;
-    break;
-
-  case 0x43: // CUF - ECMA-48 8.3.20
-    count = CSI_ARG_COUNT(args[0]);
-    state->pos.col += count;
-    state->at_phantom = 0;
-    break;
-
-  case 0x44: // CUB - ECMA-48 8.3.18
-    count = CSI_ARG_COUNT(args[0]);
-    state->pos.col -= count;
-    state->at_phantom = 0;
-    break;
-
-  case 0x45: // CNL - ECMA-48 8.3.12
-    count = CSI_ARG_COUNT(args[0]);
-    state->pos.col = 0;
-    state->pos.row += count;
-    state->at_phantom = 0;
-    break;
-
-  case 0x46: // CPL - ECMA-48 8.3.13
-    count = CSI_ARG_COUNT(args[0]);
-    state->pos.col = 0;
-    state->pos.row -= count;
-    state->at_phantom = 0;
-    break;
-
-  case 0x47: // CHA - ECMA-48 8.3.9
-    val = CSI_ARG_OR(args[0], 1);
-    state->pos.col = val-1;
-    state->at_phantom = 0;
-    break;
-
-  case 0x48: // CUP - ECMA-48 8.3.21
-    row = CSI_ARG_OR(args[0], 1);
-    col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]);
-    // zero-based
-    state->pos.row = row-1;
-    state->pos.col = col-1;
-    if(state->mode.origin) {
-      state->pos.row += state->scrollregion_top;
-      state->pos.col += SCROLLREGION_LEFT(state);
-    }
-    state->at_phantom = 0;
-    break;
-
-  case 0x49: // CHT - ECMA-48 8.3.10
-    count = CSI_ARG_COUNT(args[0]);
-    tab(state, count, +1);
-    break;
-
-  case 0x4a: // ED - ECMA-48 8.3.39
-  case LEADER('?', 0x4a): // DECSED - Selective Erase in Display
-    selective = (leader_byte == '?');
-    switch(CSI_ARG(args[0])) {
-    case CSI_ARG_MISSING:
-    case 0:
-      rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1;
-      rect.start_col = state->pos.col; rect.end_col = state->cols;
-      if(rect.end_col > rect.start_col)
-        erase(state, rect, selective);
-
-      rect.start_row = state->pos.row + 1; rect.end_row = state->rows;
-      rect.start_col = 0;
-      for(int row = rect.start_row; row < rect.end_row; row++)
-        set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);
-      if(rect.end_row > rect.start_row)
-        erase(state, rect, selective);
-      break;
-
-    case 1:
-      rect.start_row = 0; rect.end_row = state->pos.row;
-      rect.start_col = 0; rect.end_col = state->cols;
-      for(int row = rect.start_row; row < rect.end_row; row++)
-        set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);
-      if(rect.end_col > rect.start_col)
-        erase(state, rect, selective);
-
-      rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1;
-                          rect.end_col = state->pos.col + 1;
-      if(rect.end_row > rect.start_row)
-        erase(state, rect, selective);
-      break;
-
-    case 2:
-      rect.start_row = 0; rect.end_row = state->rows;
-      rect.start_col = 0; rect.end_col = state->cols;
-      for(int row = rect.start_row; row < rect.end_row; row++)
-        set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);
-      erase(state, rect, selective);
-      break;
-    }
-    break;
-
-  case 0x4b: // EL - ECMA-48 8.3.41
-  case LEADER('?', 0x4b): // DECSEL - Selective Erase in Line
-    selective = (leader_byte == '?');
-    rect.start_row = state->pos.row;
-    rect.end_row   = state->pos.row + 1;
-
-    switch(CSI_ARG(args[0])) {
-    case CSI_ARG_MISSING:
-    case 0:
-      rect.start_col = state->pos.col; rect.end_col = THISROWWIDTH(state); break;
-    case 1:
-      rect.start_col = 0; rect.end_col = state->pos.col + 1; break;
-    case 2:
-      rect.start_col = 0; rect.end_col = THISROWWIDTH(state); break;
-    default:
-      return 0;
-    }
-
-    if(rect.end_col > rect.start_col)
-      erase(state, rect, selective);
-
-    break;
-
-  case 0x4c: // IL - ECMA-48 8.3.67
-    count = CSI_ARG_COUNT(args[0]);
-
-    if(!is_cursor_in_scrollregion(state))
-      break;
-
-    rect.start_row = state->pos.row;
-    rect.end_row   = SCROLLREGION_BOTTOM(state);
-    rect.start_col = SCROLLREGION_LEFT(state);
-    rect.end_col   = SCROLLREGION_RIGHT(state);
-
-    scroll(state, rect, -count, 0);
-
-    break;
-
-  case 0x4d: // DL - ECMA-48 8.3.32
-    count = CSI_ARG_COUNT(args[0]);
-
-    if(!is_cursor_in_scrollregion(state))
-      break;
-
-    rect.start_row = state->pos.row;
-    rect.end_row   = SCROLLREGION_BOTTOM(state);
-    rect.start_col = SCROLLREGION_LEFT(state);
-    rect.end_col   = SCROLLREGION_RIGHT(state);
-
-    scroll(state, rect, count, 0);
-
-    break;
-
-  case 0x50: // DCH - ECMA-48 8.3.26
-    count = CSI_ARG_COUNT(args[0]);
-
-    if(!is_cursor_in_scrollregion(state))
-      break;
-
-    rect.start_row = state->pos.row;
-    rect.end_row   = state->pos.row + 1;
-    rect.start_col = state->pos.col;
-    if(state->mode.leftrightmargin)
-      rect.end_col = SCROLLREGION_RIGHT(state);
-    else
-      rect.end_col = THISROWWIDTH(state);
-
-    scroll(state, rect, 0, count);
-
-    break;
-
-  case 0x53: // SU - ECMA-48 8.3.147
-    count = CSI_ARG_COUNT(args[0]);
-
-    rect.start_row = state->scrollregion_top;
-    rect.end_row   = SCROLLREGION_BOTTOM(state);
-    rect.start_col = SCROLLREGION_LEFT(state);
-    rect.end_col   = SCROLLREGION_RIGHT(state);
-
-    scroll(state, rect, count, 0);
-
-    break;
-
-  case 0x54: // SD - ECMA-48 8.3.113
-    count = CSI_ARG_COUNT(args[0]);
-
-    rect.start_row = state->scrollregion_top;
-    rect.end_row   = SCROLLREGION_BOTTOM(state);
-    rect.start_col = SCROLLREGION_LEFT(state);
-    rect.end_col   = SCROLLREGION_RIGHT(state);
-
-    scroll(state, rect, -count, 0);
-
-    break;
-
-  case 0x58: // ECH - ECMA-48 8.3.38
-    count = CSI_ARG_COUNT(args[0]);
-
-    rect.start_row = state->pos.row;
-    rect.end_row   = state->pos.row + 1;
-    rect.start_col = state->pos.col;
-    rect.end_col   = state->pos.col + count;
-    UBOUND(rect.end_col, THISROWWIDTH(state));
-
-    erase(state, rect, 0);
-    break;
-
-  case 0x5a: // CBT - ECMA-48 8.3.7
-    count = CSI_ARG_COUNT(args[0]);
-    tab(state, count, -1);
-    break;
-
-  case 0x60: // HPA - ECMA-48 8.3.57
-    col = CSI_ARG_OR(args[0], 1);
-    state->pos.col = col-1;
-    state->at_phantom = 0;
-    break;
-
-  case 0x61: // HPR - ECMA-48 8.3.59
-    count = CSI_ARG_COUNT(args[0]);
-    state->pos.col += count;
-    state->at_phantom = 0;
-    break;
-
-  case 0x62: { // REP - ECMA-48 8.3.103
-    const int row_width = THISROWWIDTH(state);
-    count = CSI_ARG_COUNT(args[0]);
-    col = state->pos.col + count;
-    UBOUND(col, row_width);
-    while (state->pos.col < col) {
-      putglyph(state, state->combine_chars, state->combine_width, state->pos);
-      state->pos.col += state->combine_width;
-    }
-    if (state->pos.col + state->combine_width >= row_width) {
-      if (state->mode.autowrap) {
-        state->at_phantom = 1;
-        cancel_phantom = 0;
-      }
-    }
-    break;
-  }
-
-  case 0x63: // DA - ECMA-48 8.3.24
-    val = CSI_ARG_OR(args[0], 0);
-    if(val == 0)
-      // DEC VT100 response
-      vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?1;2c");
-    break;
-
-  case LEADER('>', 0x63): // DEC secondary Device Attributes
-    vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, ">%d;%d;%dc", 0, 100, 0);
-    break;
-
-  case 0x64: // VPA - ECMA-48 8.3.158
-    row = CSI_ARG_OR(args[0], 1);
-    state->pos.row = row-1;
-    if(state->mode.origin)
-      state->pos.row += state->scrollregion_top;
-    state->at_phantom = 0;
-    break;
-
-  case 0x65: // VPR - ECMA-48 8.3.160
-    count = CSI_ARG_COUNT(args[0]);
-    state->pos.row += count;
-    state->at_phantom = 0;
-    break;
-
-  case 0x66: // HVP - ECMA-48 8.3.63
-    row = CSI_ARG_OR(args[0], 1);
-    col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]);
-    // zero-based
-    state->pos.row = row-1;
-    state->pos.col = col-1;
-    if(state->mode.origin) {
-      state->pos.row += state->scrollregion_top;
-      state->pos.col += SCROLLREGION_LEFT(state);
-    }
-    state->at_phantom = 0;
-    break;
-
-  case 0x67: // TBC - ECMA-48 8.3.154
-    val = CSI_ARG_OR(args[0], 0);
-
-    switch(val) {
-    case 0:
-      clear_col_tabstop(state, state->pos.col);
-      break;
-    case 3:
-    case 5:
-      for(col = 0; col < state->cols; col++)
-        clear_col_tabstop(state, col);
-      break;
-    case 1:
-    case 2:
-    case 4:
-      break;
-    /* TODO: 1, 2 and 4 aren't meaningful yet without line tab stops */
-    default:
-      return 0;
-    }
-    break;
-
-  case 0x68: // SM - ECMA-48 8.3.125
-    if(!CSI_ARG_IS_MISSING(args[0]))
-      set_mode(state, CSI_ARG(args[0]), 1);
-    break;
-
-  case LEADER('?', 0x68): // DEC private mode set
-    if(!CSI_ARG_IS_MISSING(args[0]))
-      set_dec_mode(state, CSI_ARG(args[0]), 1);
-    break;
-
-  case 0x6a: // HPB - ECMA-48 8.3.58
-    count = CSI_ARG_COUNT(args[0]);
-    state->pos.col -= count;
-    state->at_phantom = 0;
-    break;
-
-  case 0x6b: // VPB - ECMA-48 8.3.159
-    count = CSI_ARG_COUNT(args[0]);
-    state->pos.row -= count;
-    state->at_phantom = 0;
-    break;
-
-  case 0x6c: // RM - ECMA-48 8.3.106
-    if(!CSI_ARG_IS_MISSING(args[0]))
-      set_mode(state, CSI_ARG(args[0]), 0);
-    break;
-
-  case LEADER('?', 0x6c): // DEC private mode reset
-    if(!CSI_ARG_IS_MISSING(args[0]))
-      set_dec_mode(state, CSI_ARG(args[0]), 0);
-    break;
-
-  case 0x6d: // SGR - ECMA-48 8.3.117
-    vterm_state_setpen(state, args, argcount);
-    break;
-
-  case 0x6e: // DSR - ECMA-48 8.3.35
-  case LEADER('?', 0x6e): // DECDSR
-    val = CSI_ARG_OR(args[0], 0);
-
-    {
-      char *qmark = (leader_byte == '?') ? "?" : "";
-
-      switch(val) {
-      case 0: case 1: case 2: case 3: case 4:
-        // ignore - these are replies
-        break;
-      case 5:
-        vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%s0n", qmark);
-        break;
-      case 6: // CPR - cursor position report
-        vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%s%d;%dR", qmark, state->pos.row + 1, state->pos.col + 1);
-        break;
-      }
-    }
-    break;
-
-
-  case LEADER('!', 0x70): // DECSTR - DEC soft terminal reset
-    vterm_state_reset(state, 0);
-    break;
-
-  case LEADER('?', INTERMED('$', 0x70)):
-    request_dec_mode(state, CSI_ARG(args[0]));
-    break;
-
-  case INTERMED(' ', 0x71): // DECSCUSR - DEC set cursor shape
-    val = CSI_ARG_OR(args[0], 1);
-
-    switch(val) {
-    case 0: case 1:
-      settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1);
-      settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK);
-      break;
-    case 2:
-      settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0);
-      settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK);
-      break;
-    case 3:
-      settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1);
-      settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_UNDERLINE);
-      break;
-    case 4:
-      settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0);
-      settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_UNDERLINE);
-      break;
-    case 5:
-      settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1);
-      settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BAR_LEFT);
-      break;
-    case 6:
-      settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0);
-      settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BAR_LEFT);
-      break;
-    }
-
-    break;
-
-  case INTERMED('"', 0x71): // DECSCA - DEC select character protection attribute
-    val = CSI_ARG_OR(args[0], 0);
-
-    switch(val) {
-    case 0: case 2:
-      state->protected_cell = 0;
-      break;
-    case 1:
-      state->protected_cell = 1;
-      break;
-    }
-
-    break;
-
-  case 0x72: // DECSTBM - DEC custom
-    state->scrollregion_top = CSI_ARG_OR(args[0], 1) - 1;
-    state->scrollregion_bottom = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]);
-    LBOUND(state->scrollregion_top, 0);
-    UBOUND(state->scrollregion_top, state->rows);
-    LBOUND(state->scrollregion_bottom, -1);
-    if(state->scrollregion_top == 0 && state->scrollregion_bottom == state->rows)
-      state->scrollregion_bottom = -1;
-    else
-      UBOUND(state->scrollregion_bottom, state->rows);
-
-    if(SCROLLREGION_BOTTOM(state) <= state->scrollregion_top) {
-      // Invalid
-      state->scrollregion_top    = 0;
-      state->scrollregion_bottom = -1;
-    }
-
-    break;
-
-  case 0x73: // DECSLRM - DEC custom
-    // Always allow setting these margins, just they won't take effect without DECVSSM
-    state->scrollregion_left = CSI_ARG_OR(args[0], 1) - 1;
-    state->scrollregion_right = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]);
-    LBOUND(state->scrollregion_left, 0);
-    UBOUND(state->scrollregion_left, state->cols);
-    LBOUND(state->scrollregion_right, -1);
-    if(state->scrollregion_left == 0 && state->scrollregion_right == state->cols)
-      state->scrollregion_right = -1;
-    else
-      UBOUND(state->scrollregion_right, state->cols);
-
-    if(state->scrollregion_right > -1 &&
-       state->scrollregion_right <= state->scrollregion_left) {
-      // Invalid
-      state->scrollregion_left  = 0;
-      state->scrollregion_right = -1;
-    }
-
-    break;
-
-  case INTERMED('\'', 0x7D): // DECIC
-    count = CSI_ARG_COUNT(args[0]);
-
-    if(!is_cursor_in_scrollregion(state))
-      break;
-
-    rect.start_row = state->scrollregion_top;
-    rect.end_row   = SCROLLREGION_BOTTOM(state);
-    rect.start_col = state->pos.col;
-    rect.end_col   = SCROLLREGION_RIGHT(state);
-
-    scroll(state, rect, 0, -count);
-
-    break;
-
-  case INTERMED('\'', 0x7E): // DECDC
-    count = CSI_ARG_COUNT(args[0]);
-
-    if(!is_cursor_in_scrollregion(state))
-      break;
-
-    rect.start_row = state->scrollregion_top;
-    rect.end_row   = SCROLLREGION_BOTTOM(state);
-    rect.start_col = state->pos.col;
-    rect.end_col   = SCROLLREGION_RIGHT(state);
-
-    scroll(state, rect, 0, count);
-
-    break;
-
-  default:
-    if(state->fallbacks && state->fallbacks->csi)
-      if((*state->fallbacks->csi)(leader, args, argcount, intermed, command, state->fbdata))
-        return 1;
-
-    return 0;
-  }
-
-  if(state->mode.origin) {
-    LBOUND(state->pos.row, state->scrollregion_top);
-    UBOUND(state->pos.row, SCROLLREGION_BOTTOM(state)-1);
-    LBOUND(state->pos.col, SCROLLREGION_LEFT(state));
-    UBOUND(state->pos.col, SCROLLREGION_RIGHT(state)-1);
-  }
-  else {
-    LBOUND(state->pos.row, 0);
-    UBOUND(state->pos.row, state->rows-1);
-    LBOUND(state->pos.col, 0);
-    UBOUND(state->pos.col, THISROWWIDTH(state)-1);
-  }
-
-  updatecursor(state, &oldpos, cancel_phantom);
-
-#ifdef DEBUG
-  if(state->pos.row < 0 || state->pos.row >= state->rows ||
-     state->pos.col < 0 || state->pos.col >= state->cols) {
-    fprintf(stderr, "Position out of bounds after CSI %c: (%d,%d)\n",
-        command, state->pos.row, state->pos.col);
-    abort();
-  }
-
-  if(SCROLLREGION_BOTTOM(state) <= state->scrollregion_top) {
-    fprintf(stderr, "Scroll region height out of bounds after CSI %c: %d <= %d\n",
-        command, SCROLLREGION_BOTTOM(state), state->scrollregion_top);
-    abort();
-  }
-
-  if(SCROLLREGION_RIGHT(state) <= SCROLLREGION_LEFT(state)) {
-    fprintf(stderr, "Scroll region width out of bounds after CSI %c: %d <= %d\n",
-        command, SCROLLREGION_RIGHT(state), SCROLLREGION_LEFT(state));
-    abort();
-  }
-#endif
-
-  return 1;
-}
-
-static int on_osc(const char *command, size_t cmdlen, void *user)
-{
-  VTermState *state = user;
-
-  if(cmdlen < 2)
-    return 0;
-
-  if(strneq(command, "0;", 2)) {
-    settermprop_string(state, VTERM_PROP_ICONNAME, command + 2, cmdlen - 2);
-    settermprop_string(state, VTERM_PROP_TITLE, command + 2, cmdlen - 2);
-    return 1;
-  }
-  else if(strneq(command, "1;", 2)) {
-    settermprop_string(state, VTERM_PROP_ICONNAME, command + 2, cmdlen - 2);
-    return 1;
-  }
-  else if(strneq(command, "2;", 2)) {
-    settermprop_string(state, VTERM_PROP_TITLE, command + 2, cmdlen - 2);
-    return 1;
-  }
-  else if(state->fallbacks && state->fallbacks->osc)
-    if((*state->fallbacks->osc)(command, cmdlen, state->fbdata))
-      return 1;
-
-  return 0;
-}
-
-static void request_status_string(VTermState *state, const char *command, size_t cmdlen)
-{
-  if(cmdlen == 1)
-    switch(command[0]) {
-      case 'm': // Query SGR
-        {
-          long args[20];
-          int argc = vterm_state_getpen(state, args, sizeof(args)/sizeof(args[0]));
-          vterm_push_output_sprintf_ctrl(state->vt, C1_DCS, "1$r");
-          for(int argi = 0; argi < argc; argi++)
-            vterm_push_output_sprintf(state->vt,
-                argi == argc - 1             ? "%d" :
-                CSI_ARG_HAS_MORE(args[argi]) ? "%d:" :
-                                               "%d;",
-                CSI_ARG(args[argi]));
-          vterm_push_output_sprintf(state->vt, "m");
-          vterm_push_output_sprintf_ctrl(state->vt, C1_ST, "");
-        }
-        return;
-      case 'r': // Query DECSTBM
-        vterm_push_output_sprintf_dcs(state->vt, "1$r%d;%dr", state->scrollregion_top+1, SCROLLREGION_BOTTOM(state));
-        return;
-      case 's': // Query DECSLRM
-        vterm_push_output_sprintf_dcs(state->vt, "1$r%d;%ds", SCROLLREGION_LEFT(state)+1, SCROLLREGION_RIGHT(state));
-        return;
-    }
-
-  if(cmdlen == 2) {
-    if(strneq(command, " q", 2)) {
-      int reply;
-      switch(state->mode.cursor_shape) {
-        case VTERM_PROP_CURSORSHAPE_BLOCK:     reply = 2; break;
-        case VTERM_PROP_CURSORSHAPE_UNDERLINE: reply = 4; break;
-        case VTERM_PROP_CURSORSHAPE_BAR_LEFT:  reply = 6; break;
-      }
-      if(state->mode.cursor_blink)
-        reply--;
-      vterm_push_output_sprintf_dcs(state->vt, "1$r%d q", reply);
-      return;
-    }
-    else if(strneq(command, "\"q", 2)) {
-      vterm_push_output_sprintf_dcs(state->vt, "1$r%d\"q", state->protected_cell ? 1 : 2);
-      return;
-    }
-  }
-
-  vterm_push_output_sprintf_dcs(state->vt, "0$r%.s", (int)cmdlen, command);
-}
-
-static int on_dcs(const char *command, size_t cmdlen, void *user)
-{
-  VTermState *state = user;
-
-  if(cmdlen >= 2 && strneq(command, "$q", 2)) {
-    request_status_string(state, command+2, cmdlen-2);
-    return 1;
-  }
-  else if(state->fallbacks && state->fallbacks->dcs)
-    if((*state->fallbacks->dcs)(command, cmdlen, state->fbdata))
-      return 1;
-
-  return 0;
-}
-
-static int on_resize(int rows, int cols, void *user)
-{
-  VTermState *state = user;
-  VTermPos oldpos = state->pos;
-
-  if(cols != state->cols) {
-    unsigned char *newtabstops = vterm_allocator_malloc(state->vt, (cols + 7) / 8);
-
-    /* TODO: This can all be done much more efficiently bytewise */
-    int col;
-    for(col = 0; col < state->cols && col < cols; col++) {
-      unsigned char mask = 1 << (col & 7);
-      if(state->tabstops[col >> 3] & mask)
-        newtabstops[col >> 3] |= mask;
-      else
-        newtabstops[col >> 3] &= ~mask;
-      }
-
-    for( ; col < cols; col++) {
-      unsigned char mask = 1 << (col & 7);
-      if(col % 8 == 0)
-        newtabstops[col >> 3] |= mask;
-      else
-        newtabstops[col >> 3] &= ~mask;
-    }
-
-    vterm_allocator_free(state->vt, state->tabstops);
-    state->tabstops = newtabstops;
-  }
-
-  if(rows != state->rows) {
-    VTermLineInfo *newlineinfo = vterm_allocator_malloc(state->vt, rows * sizeof(VTermLineInfo));
-
-    int row;
-    for(row = 0; row < state->rows && row < rows; row++) {
-      newlineinfo[row] = state->lineinfo[row];
-    }
-
-    for( ; row < rows; row++) {
-      newlineinfo[row] = (VTermLineInfo){
-        .doublewidth = 0,
-      };
-    }
-
-    vterm_allocator_free(state->vt, state->lineinfo);
-    state->lineinfo = newlineinfo;
-  }
-
-  state->rows = rows;
-  state->cols = cols;
-
-  if(state->scrollregion_bottom > -1)
-    UBOUND(state->scrollregion_bottom, state->rows);
-  if(state->scrollregion_right > -1)
-    UBOUND(state->scrollregion_right, state->cols);
-
-  VTermPos delta = { 0, 0 };
-
-  if(state->callbacks && state->callbacks->resize)
-    (*state->callbacks->resize)(rows, cols, &delta, state->cbdata);
-
-  if(state->at_phantom && state->pos.col < cols-1) {
-    state->at_phantom = 0;
-    state->pos.col++;
-  }
-
-  state->pos.row += delta.row;
-  state->pos.col += delta.col;
-
-  if(state->pos.row >= rows)
-    state->pos.row = rows - 1;
-  if(state->pos.col >= cols)
-    state->pos.col = cols - 1;
-
-  updatecursor(state, &oldpos, 1);
-
-  return 1;
-}
-
-static const VTermParserCallbacks parser_callbacks = {
-  .text    = on_text,
-  .control = on_control,
-  .escape  = on_escape,
-  .csi     = on_csi,
-  .osc     = on_osc,
-  .dcs     = on_dcs,
-  .resize  = on_resize,
-};
-
-VTermState *vterm_obtain_state(VTerm *vt)
-{
-  if(vt->state)
-    return vt->state;
-
-  VTermState *state = vterm_state_new(vt);
-  vt->state = state;
-
-  state->combine_chars_size = 16;
-  state->combine_chars = vterm_allocator_malloc(state->vt, state->combine_chars_size * sizeof(state->combine_chars[0]));
-
-  state->tabstops = vterm_allocator_malloc(state->vt, (state->cols + 7) / 8);
-
-  state->lineinfo = vterm_allocator_malloc(state->vt, state->rows * sizeof(VTermLineInfo));
-
-  state->encoding_utf8.enc = vterm_lookup_encoding(ENC_UTF8, 'u');
-  if(*state->encoding_utf8.enc->init)
-    (*state->encoding_utf8.enc->init)(state->encoding_utf8.enc, state->encoding_utf8.data);
-
-  vterm_parser_set_callbacks(vt, &parser_callbacks, state);
-
-  return state;
-}
-
-void vterm_state_reset(VTermState *state, int hard)
-{
-  state->scrollregion_top = 0;
-  state->scrollregion_bottom = -1;
-  state->scrollregion_left = 0;
-  state->scrollregion_right = -1;
-
-  state->mode.keypad          = 0;
-  state->mode.cursor          = 0;
-  state->mode.autowrap        = 1;
-  state->mode.insert          = 0;
-  state->mode.newline         = 0;
-  state->mode.alt_screen      = 0;
-  state->mode.origin          = 0;
-  state->mode.leftrightmargin = 0;
-  state->mode.bracketpaste    = 0;
-  state->mode.report_focus    = 0;
-
-  state->vt->mode.ctrl8bit   = 0;
-
-  for(int col = 0; col < state->cols; col++)
-    if(col % 8 == 0)
-      set_col_tabstop(state, col);
-    else
-      clear_col_tabstop(state, col);
-
-  for(int row = 0; row < state->rows; row++)
-    set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);
-
-  if(state->callbacks && state->callbacks->initpen)
-    (*state->callbacks->initpen)(state->cbdata);
-
-  vterm_state_resetpen(state);
-
-  VTermEncoding *default_enc = state->vt->mode.utf8 ?
-      vterm_lookup_encoding(ENC_UTF8,      'u') :
-      vterm_lookup_encoding(ENC_SINGLE_94, 'B');
-
-  for(int i = 0; i < 4; i++) {
-    state->encoding[i].enc = default_enc;
-    if(default_enc->init)
-      (*default_enc->init)(default_enc, state->encoding[i].data);
-  }
-
-  state->gl_set = 0;
-  state->gr_set = 1;
-  state->gsingle_set = 0;
-
-  state->protected_cell = 0;
-
-  // Initialise the props
-  settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, 1);
-  settermprop_bool(state, VTERM_PROP_CURSORBLINK,   1);
-  settermprop_int (state, VTERM_PROP_CURSORSHAPE,   VTERM_PROP_CURSORSHAPE_BLOCK);
-
-  if(hard) {
-    state->pos.row = 0;
-    state->pos.col = 0;
-    state->at_phantom = 0;
-
-    VTermRect rect = { 0, state->rows, 0, state->cols };
-    erase(state, rect, 0);
-  }
-}
-
-void vterm_state_get_cursorpos(const VTermState *state, VTermPos *cursorpos)
-{
-  *cursorpos = state->pos;
-}
-
-void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user)
-{
-  if(callbacks) {
-    state->callbacks = callbacks;
-    state->cbdata = user;
-
-    if(state->callbacks && state->callbacks->initpen)
-      (*state->callbacks->initpen)(state->cbdata);
-  }
-  else {
-    state->callbacks = NULL;
-    state->cbdata = NULL;
-  }
-}
-
-void *vterm_state_get_cbdata(VTermState *state)
-{
-  return state->cbdata;
-}
-
-void vterm_state_set_unrecognised_fallbacks(VTermState *state, const VTermParserCallbacks *fallbacks, void *user)
-{
-  if(fallbacks) {
-    state->fallbacks = fallbacks;
-    state->fbdata = user;
-  }
-  else {
-    state->fallbacks = NULL;
-    state->fbdata = NULL;
-  }
-}
-
-void *vterm_state_get_unrecognised_fbdata(VTermState *state)
-{
-  return state->fbdata;
-}
-
-int vterm_state_set_termprop(VTermState *state, VTermProp prop, VTermValue *val)
-{
-  /* Only store the new value of the property if usercode said it was happy.
-   * This is especially important for altscreen switching */
-  if(state->callbacks && state->callbacks->settermprop)
-    if(!(*state->callbacks->settermprop)(prop, val, state->cbdata))
-      return 0;
-
-  switch(prop) {
-  case VTERM_PROP_TITLE:
-  case VTERM_PROP_ICONNAME:
-    // we don't store these, just transparently pass through
-    return 1;
-  case VTERM_PROP_CURSORVISIBLE:
-    state->mode.cursor_visible = val->boolean;
-    return 1;
-  case VTERM_PROP_CURSORBLINK:
-    state->mode.cursor_blink = val->boolean;
-    return 1;
-  case VTERM_PROP_CURSORSHAPE:
-    state->mode.cursor_shape = val->number;
-    return 1;
-  case VTERM_PROP_REVERSE:
-    state->mode.screen = val->boolean;
-    return 1;
-  case VTERM_PROP_ALTSCREEN:
-    state->mode.alt_screen = val->boolean;
-    if(state->mode.alt_screen) {
-      VTermRect rect = {
-        .start_row = 0,
-        .start_col = 0,
-        .end_row = state->rows,
-        .end_col = state->cols,
-      };
-      erase(state, rect, 0);
-    }
-    return 1;
-  case VTERM_PROP_MOUSE:
-    state->mouse_flags = 0;
-    if(val->number)
-      state->mouse_flags |= MOUSE_WANT_CLICK;
-    if(val->number == VTERM_PROP_MOUSE_DRAG)
-      state->mouse_flags |= MOUSE_WANT_DRAG;
-    if(val->number == VTERM_PROP_MOUSE_MOVE)
-      state->mouse_flags |= MOUSE_WANT_MOVE;
-    return 1;
-
-  case VTERM_N_PROPS:
-    return 0;
-  }
-
-  return 0;
-}
-
-void vterm_state_focus_in(VTermState *state)
-{
-  if(state->mode.report_focus)
-    vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "I");
-}
-
-void vterm_state_focus_out(VTermState *state)
-{
-  if(state->mode.report_focus)
-    vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "O");
-}
-
-const VTermLineInfo *vterm_state_get_lineinfo(const VTermState *state, int row)
-{
-  return state->lineinfo + row;
-}
diff --git a/src/unicode.c b/src/unicode.c
deleted file mode 100644
index 0d1b5ff..0000000
--- a/src/unicode.c
+++ /dev/null
@@ -1,337 +0,0 @@
-#include "vterm_internal.h"
-
-// ### The following from http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
-// With modifications:
-//   made functions static
-//   moved 'combining' table to file scope, so other functions can see it
-// ###################################################################
-
-/*
- * This is an implementation of wcwidth() and wcswidth() (defined in
- * IEEE Std 1002.1-2001) for Unicode.
- *
- * http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html
- * http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html
- *
- * In fixed-width output devices, Latin characters all occupy a single
- * "cell" position of equal width, whereas ideographic CJK characters
- * occupy two such cells. Interoperability between terminal-line
- * applications and (teletype-style) character terminals using the
- * UTF-8 encoding requires agreement on which character should advance
- * the cursor by how many cell positions. No established formal
- * standards exist at present on which Unicode character shall occupy
- * how many cell positions on character terminals. These routines are
- * a first attempt of defining such behavior based on simple rules
- * applied to data provided by the Unicode Consortium.
- *
- * For some graphical characters, the Unicode standard explicitly
- * defines a character-cell width via the definition of the East Asian
- * FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes.
- * In all these cases, there is no ambiguity about which width a
- * terminal shall use. For characters in the East Asian Ambiguous (A)
- * class, the width choice depends purely on a preference of backward
- * compatibility with either historic CJK or Western practice.
- * Choosing single-width for these characters is easy to justify as
- * the appropriate long-term solution, as the CJK practice of
- * displaying these characters as double-width comes from historic
- * implementation simplicity (8-bit encoded characters were displayed
- * single-width and 16-bit ones double-width, even for Greek,
- * Cyrillic, etc.) and not any typographic considerations.
- *
- * Much less clear is the choice of width for the Not East Asian
- * (Neutral) class. Existing practice does not dictate a width for any
- * of these characters. It would nevertheless make sense
- * typographically to allocate two character cells to characters such
- * as for instance EM SPACE or VOLUME INTEGRAL, which cannot be
- * represented adequately with a single-width glyph. The following
- * routines at present merely assign a single-cell width to all
- * neutral characters, in the interest of simplicity. This is not
- * entirely satisfactory and should be reconsidered before
- * establishing a formal standard in this area. At the moment, the
- * decision which Not East Asian (Neutral) characters should be
- * represented by double-width glyphs cannot yet be answered by
- * applying a simple rule from the Unicode database content. Setting
- * up a proper standard for the behavior of UTF-8 character terminals
- * will require a careful analysis not only of each Unicode character,
- * but also of each presentation form, something the author of these
- * routines has avoided to do so far.
- *
- * http://www.unicode.org/unicode/reports/tr11/
- *
- * Markus Kuhn -- 2007-05-26 (Unicode 5.0)
- *
- * Permission to use, copy, modify, and distribute this software
- * for any purpose and without fee is hereby granted. The author
- * disclaims all warranties with regard to this software.
- *
- * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
- */
-
-struct interval {
-  int first;
-  int last;
-};
-
-/* sorted list of non-overlapping intervals of non-spacing characters */
-/* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */
-static const struct interval combining[] = {
-  { 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 },
-  { 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 },
-  { 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 },
-  { 0x0610, 0x0615 }, { 0x064B, 0x065E }, { 0x0670, 0x0670 },
-  { 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED },
-  { 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A },
-  { 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0901, 0x0902 },
-  { 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D },
-  { 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 },
-  { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD },
-  { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C },
-  { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D },
-  { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC },
-  { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD },
-  { 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C },
-  { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D },
-  { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 },
-  { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 },
-  { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBC, 0x0CBC },
-  { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD },
-  { 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D },
-  { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 },
-  { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E },
-  { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC },
-  { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 },
-  { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E },
-  { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 },
-  { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 },
-  { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 },
-  { 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x135F, 0x135F },
-  { 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 },
-  { 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD },
-  { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD },
-  { 0x180B, 0x180D }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 },
-  { 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B },
-  { 0x1A17, 0x1A18 }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 },
-  { 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 },
-  { 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF },
-  { 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 },
-  { 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x302A, 0x302F },
-  { 0x3099, 0x309A }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B },
-  { 0xA825, 0xA826 }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F },
-  { 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB },
-  { 0x10A01, 0x10A03 }, { 0x10A05, 0x10A06 }, { 0x10A0C, 0x10A0F },
-  { 0x10A38, 0x10A3A }, { 0x10A3F, 0x10A3F }, { 0x1D167, 0x1D169 },
-  { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD },
-  { 0x1D242, 0x1D244 }, { 0xE0001, 0xE0001 }, { 0xE0020, 0xE007F },
-  { 0xE0100, 0xE01EF }
-};
-
-
-/* auxiliary function for binary search in interval table */
-static int bisearch(uint32_t ucs, const struct interval *table, int max) {
-  int min = 0;
-  int mid;
-
-  if (ucs < table[0].first || ucs > table[max].last)
-    return 0;
-  while (max >= min) {
-    mid = (min + max) / 2;
-    if (ucs > table[mid].last)
-      min = mid + 1;
-    else if (ucs < table[mid].first)
-      max = mid - 1;
-    else
-      return 1;
-  }
-
-  return 0;
-}
-
-
-/* The following two functions define the column width of an ISO 10646
- * character as follows:
- *
- *    - The null character (U+0000) has a column width of 0.
- *
- *    - Other C0/C1 control characters and DEL will lead to a return
- *      value of -1.
- *
- *    - Non-spacing and enclosing combining characters (general
- *      category code Mn or Me in the Unicode database) have a
- *      column width of 0.
- *
- *    - SOFT HYPHEN (U+00AD) has a column width of 1.
- *
- *    - Other format characters (general category code Cf in the Unicode
- *      database) and ZERO WIDTH SPACE (U+200B) have a column width of 0.
- *
- *    - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF)
- *      have a column width of 0.
- *
- *    - Spacing characters in the East Asian Wide (W) or East Asian
- *      Full-width (F) category as defined in Unicode Technical
- *      Report #11 have a column width of 2.
- *
- *    - All remaining characters (including all printable
- *      ISO 8859-1 and WGL4 characters, Unicode control characters,
- *      etc.) have a column width of 1.
- *
- * This implementation assumes that uint32_t characters are encoded
- * in ISO 10646.
- */
-
-
-static int mk_wcwidth(uint32_t ucs)
-{
-  /* test for 8-bit control characters */
-  if (ucs == 0)
-    return 0;
-  if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0))
-    return -1;
-
-  /* binary search in table of non-spacing characters */
-  if (bisearch(ucs, combining,
-               sizeof(combining) / sizeof(struct interval) - 1))
-    return 0;
-
-  /* if we arrive here, ucs is not a combining or C0/C1 control character */
-
-  return 1 + 
-    (ucs >= 0x1100 &&
-     (ucs <= 0x115f ||                    /* Hangul Jamo init. consonants */
-      ucs == 0x2329 || ucs == 0x232a ||
-      (ucs >= 0x2e80 && ucs <= 0xa4cf &&
-       ucs != 0x303f) ||                  /* CJK ... Yi */
-      (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */
-      (ucs >= 0xf900 && ucs <= 0xfaff) || /* CJK Compatibility Ideographs */
-      (ucs >= 0xfe10 && ucs <= 0xfe19) || /* Vertical forms */
-      (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */
-      (ucs >= 0xff00 && ucs <= 0xff60) || /* Fullwidth Forms */
-      (ucs >= 0xffe0 && ucs <= 0xffe6) ||
-      (ucs >= 0x20000 && ucs <= 0x2fffd) ||
-      (ucs >= 0x30000 && ucs <= 0x3fffd)));
-}
-
-
-static int mk_wcswidth(const uint32_t *pwcs, size_t n)
-{
-  int w, width = 0;
-
-  for (;*pwcs && n-- > 0; pwcs++)
-    if ((w = mk_wcwidth(*pwcs)) < 0)
-      return -1;
-    else
-      width += w;
-
-  return width;
-}
-
-
-/*
- * The following functions are the same as mk_wcwidth() and
- * mk_wcswidth(), except that spacing characters in the East Asian
- * Ambiguous (A) category as defined in Unicode Technical Report #11
- * have a column width of 2. This variant might be useful for users of
- * CJK legacy encodings who want to migrate to UCS without changing
- * the traditional terminal character-width behaviour. It is not
- * otherwise recommended for general use.
- */
-static int mk_wcwidth_cjk(uint32_t ucs)
-{
-  /* sorted list of non-overlapping intervals of East Asian Ambiguous
-   * characters, generated by "uniset +WIDTH-A -cat=Me -cat=Mn -cat=Cf c" */
-  static const struct interval ambiguous[] = {
-    { 0x00A1, 0x00A1 }, { 0x00A4, 0x00A4 }, { 0x00A7, 0x00A8 },
-    { 0x00AA, 0x00AA }, { 0x00AE, 0x00AE }, { 0x00B0, 0x00B4 },
-    { 0x00B6, 0x00BA }, { 0x00BC, 0x00BF }, { 0x00C6, 0x00C6 },
-    { 0x00D0, 0x00D0 }, { 0x00D7, 0x00D8 }, { 0x00DE, 0x00E1 },
-    { 0x00E6, 0x00E6 }, { 0x00E8, 0x00EA }, { 0x00EC, 0x00ED },
-    { 0x00F0, 0x00F0 }, { 0x00F2, 0x00F3 }, { 0x00F7, 0x00FA },
-    { 0x00FC, 0x00FC }, { 0x00FE, 0x00FE }, { 0x0101, 0x0101 },
-    { 0x0111, 0x0111 }, { 0x0113, 0x0113 }, { 0x011B, 0x011B },
-    { 0x0126, 0x0127 }, { 0x012B, 0x012B }, { 0x0131, 0x0133 },
-    { 0x0138, 0x0138 }, { 0x013F, 0x0142 }, { 0x0144, 0x0144 },
-    { 0x0148, 0x014B }, { 0x014D, 0x014D }, { 0x0152, 0x0153 },
-    { 0x0166, 0x0167 }, { 0x016B, 0x016B }, { 0x01CE, 0x01CE },
-    { 0x01D0, 0x01D0 }, { 0x01D2, 0x01D2 }, { 0x01D4, 0x01D4 },
-    { 0x01D6, 0x01D6 }, { 0x01D8, 0x01D8 }, { 0x01DA, 0x01DA },
-    { 0x01DC, 0x01DC }, { 0x0251, 0x0251 }, { 0x0261, 0x0261 },
-    { 0x02C4, 0x02C4 }, { 0x02C7, 0x02C7 }, { 0x02C9, 0x02CB },
-    { 0x02CD, 0x02CD }, { 0x02D0, 0x02D0 }, { 0x02D8, 0x02DB },
-    { 0x02DD, 0x02DD }, { 0x02DF, 0x02DF }, { 0x0391, 0x03A1 },
-    { 0x03A3, 0x03A9 }, { 0x03B1, 0x03C1 }, { 0x03C3, 0x03C9 },
-    { 0x0401, 0x0401 }, { 0x0410, 0x044F }, { 0x0451, 0x0451 },
-    { 0x2010, 0x2010 }, { 0x2013, 0x2016 }, { 0x2018, 0x2019 },
-    { 0x201C, 0x201D }, { 0x2020, 0x2022 }, { 0x2024, 0x2027 },
-    { 0x2030, 0x2030 }, { 0x2032, 0x2033 }, { 0x2035, 0x2035 },
-    { 0x203B, 0x203B }, { 0x203E, 0x203E }, { 0x2074, 0x2074 },
-    { 0x207F, 0x207F }, { 0x2081, 0x2084 }, { 0x20AC, 0x20AC },
-    { 0x2103, 0x2103 }, { 0x2105, 0x2105 }, { 0x2109, 0x2109 },
-    { 0x2113, 0x2113 }, { 0x2116, 0x2116 }, { 0x2121, 0x2122 },
-    { 0x2126, 0x2126 }, { 0x212B, 0x212B }, { 0x2153, 0x2154 },
-    { 0x215B, 0x215E }, { 0x2160, 0x216B }, { 0x2170, 0x2179 },
-    { 0x2190, 0x2199 }, { 0x21B8, 0x21B9 }, { 0x21D2, 0x21D2 },
-    { 0x21D4, 0x21D4 }, { 0x21E7, 0x21E7 }, { 0x2200, 0x2200 },
-    { 0x2202, 0x2203 }, { 0x2207, 0x2208 }, { 0x220B, 0x220B },
-    { 0x220F, 0x220F }, { 0x2211, 0x2211 }, { 0x2215, 0x2215 },
-    { 0x221A, 0x221A }, { 0x221D, 0x2220 }, { 0x2223, 0x2223 },
-    { 0x2225, 0x2225 }, { 0x2227, 0x222C }, { 0x222E, 0x222E },
-    { 0x2234, 0x2237 }, { 0x223C, 0x223D }, { 0x2248, 0x2248 },
-    { 0x224C, 0x224C }, { 0x2252, 0x2252 }, { 0x2260, 0x2261 },
-    { 0x2264, 0x2267 }, { 0x226A, 0x226B }, { 0x226E, 0x226F },
-    { 0x2282, 0x2283 }, { 0x2286, 0x2287 }, { 0x2295, 0x2295 },
-    { 0x2299, 0x2299 }, { 0x22A5, 0x22A5 }, { 0x22BF, 0x22BF },
-    { 0x2312, 0x2312 }, { 0x2460, 0x24E9 }, { 0x24EB, 0x254B },
-    { 0x2550, 0x2573 }, { 0x2580, 0x258F }, { 0x2592, 0x2595 },
-    { 0x25A0, 0x25A1 }, { 0x25A3, 0x25A9 }, { 0x25B2, 0x25B3 },
-    { 0x25B6, 0x25B7 }, { 0x25BC, 0x25BD }, { 0x25C0, 0x25C1 },
-    { 0x25C6, 0x25C8 }, { 0x25CB, 0x25CB }, { 0x25CE, 0x25D1 },
-    { 0x25E2, 0x25E5 }, { 0x25EF, 0x25EF }, { 0x2605, 0x2606 },
-    { 0x2609, 0x2609 }, { 0x260E, 0x260F }, { 0x2614, 0x2615 },
-    { 0x261C, 0x261C }, { 0x261E, 0x261E }, { 0x2640, 0x2640 },
-    { 0x2642, 0x2642 }, { 0x2660, 0x2661 }, { 0x2663, 0x2665 },
-    { 0x2667, 0x266A }, { 0x266C, 0x266D }, { 0x266F, 0x266F },
-    { 0x273D, 0x273D }, { 0x2776, 0x277F }, { 0xE000, 0xF8FF },
-    { 0xFFFD, 0xFFFD }, { 0xF0000, 0xFFFFD }, { 0x100000, 0x10FFFD }
-  };
-
-  /* binary search in table of non-spacing characters */
-  if (bisearch(ucs, ambiguous,
-               sizeof(ambiguous) / sizeof(struct interval) - 1))
-    return 2;
-
-  return mk_wcwidth(ucs);
-}
-
-
-static int mk_wcswidth_cjk(const uint32_t *pwcs, size_t n)
-{
-  int w, width = 0;
-
-  for (;*pwcs && n-- > 0; pwcs++)
-    if ((w = mk_wcwidth_cjk(*pwcs)) < 0)
-      return -1;
-    else
-      width += w;
-
-  return width;
-}
-
-// ################################
-// ### The rest added by Paul Evans
-
-static const struct interval fullwidth[] = {
-#include "fullwidth.inc"
-};
-
-INTERNAL int vterm_unicode_width(uint32_t codepoint)
-{
-  if(bisearch(codepoint, fullwidth, sizeof(fullwidth) / sizeof(fullwidth[0]) - 1))
-    return 2;
-
-  return mk_wcwidth(codepoint);
-}
-
-INTERNAL int vterm_unicode_is_combining(uint32_t codepoint)
-{
-  return bisearch(codepoint, combining, sizeof(combining) / sizeof(struct interval) - 1);
-}
diff --git a/src/utf8.h b/src/utf8.h
deleted file mode 100644
index 9a336d3..0000000
--- a/src/utf8.h
+++ /dev/null
@@ -1,39 +0,0 @@
-/* The following functions copied and adapted from libtermkey
- *
- * http://www.leonerd.org.uk/code/libtermkey/
- */
-static inline unsigned int utf8_seqlen(long codepoint)
-{
-  if(codepoint < 0x0000080) return 1;
-  if(codepoint < 0x0000800) return 2;
-  if(codepoint < 0x0010000) return 3;
-  if(codepoint < 0x0200000) return 4;
-  if(codepoint < 0x4000000) return 5;
-  return 6;
-}
-
-/* Does NOT NUL-terminate the buffer */
-static int fill_utf8(long codepoint, char *str)
-{
-  int nbytes = utf8_seqlen(codepoint);
-
-  // This is easier done backwards
-  int b = nbytes;
-  while(b > 1) {
-    b--;
-    str[b] = 0x80 | (codepoint & 0x3f);
-    codepoint >>= 6;
-  }
-
-  switch(nbytes) {
-    case 1: str[0] =        (codepoint & 0x7f); break;
-    case 2: str[0] = 0xc0 | (codepoint & 0x1f); break;
-    case 3: str[0] = 0xe0 | (codepoint & 0x0f); break;
-    case 4: str[0] = 0xf0 | (codepoint & 0x07); break;
-    case 5: str[0] = 0xf8 | (codepoint & 0x03); break;
-    case 6: str[0] = 0xfc | (codepoint & 0x01); break;
-  }
-
-  return nbytes;
-}
-/* end copy */
diff --git a/src/vterm.c b/src/vterm.c
deleted file mode 100644
index 843bb47..0000000
--- a/src/vterm.c
+++ /dev/null
@@ -1,367 +0,0 @@
-#include "vterm_internal.h"
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdarg.h>
-#include <string.h>
-
-/*****************
- * API functions *
- *****************/
-
-static void *default_malloc(size_t size, void *allocdata)
-{
-  void *ptr = malloc(size);
-  if(ptr)
-    memset(ptr, 0, size);
-  return ptr;
-}
-
-static void default_free(void *ptr, void *allocdata)
-{
-  free(ptr);
-}
-
-static VTermAllocatorFunctions default_allocator = {
-  .malloc = &default_malloc,
-  .free   = &default_free,
-};
-
-VTerm *vterm_new(int rows, int cols)
-{
-  return vterm_new_with_allocator(rows, cols, &default_allocator, NULL);
-}
-
-VTerm *vterm_new_with_allocator(int rows, int cols, VTermAllocatorFunctions *funcs, void *allocdata)
-{
-  /* Need to bootstrap using the allocator function directly */
-  VTerm *vt = (*funcs->malloc)(sizeof(VTerm), allocdata);
-
-  vt->allocator = funcs;
-  vt->allocdata = allocdata;
-
-  vt->rows = rows;
-  vt->cols = cols;
-
-  vt->parser.state = NORMAL;
-
-  vt->parser.callbacks = NULL;
-  vt->parser.cbdata    = NULL;
-
-  vt->parser.strbuffer_len = 64;
-  vt->parser.strbuffer_cur = 0;
-  vt->parser.strbuffer = vterm_allocator_malloc(vt, vt->parser.strbuffer_len);
-
-  vt->outbuffer_len = 64;
-  vt->outbuffer_cur = 0;
-  vt->outbuffer = vterm_allocator_malloc(vt, vt->outbuffer_len);
-
-  return vt;
-}
-
-void vterm_free(VTerm *vt)
-{
-  if(vt->screen)
-    vterm_screen_free(vt->screen);
-
-  if(vt->state)
-    vterm_state_free(vt->state);
-
-  vterm_allocator_free(vt, vt->parser.strbuffer);
-  vterm_allocator_free(vt, vt->outbuffer);
-
-  vterm_allocator_free(vt, vt);
-}
-
-INTERNAL void *vterm_allocator_malloc(VTerm *vt, size_t size)
-{
-  return (*vt->allocator->malloc)(size, vt->allocdata);
-}
-
-INTERNAL void vterm_allocator_free(VTerm *vt, void *ptr)
-{
-  (*vt->allocator->free)(ptr, vt->allocdata);
-}
-
-void vterm_get_size(const VTerm *vt, int *rowsp, int *colsp)
-{
-  if(rowsp)
-    *rowsp = vt->rows;
-  if(colsp)
-    *colsp = vt->cols;
-}
-
-void vterm_set_size(VTerm *vt, int rows, int cols)
-{
-  vt->rows = rows;
-  vt->cols = cols;
-
-  if(vt->parser.callbacks && vt->parser.callbacks->resize)
-    (*vt->parser.callbacks->resize)(rows, cols, vt->parser.cbdata);
-}
-
-int vterm_get_utf8(const VTerm *vt)
-{
-  return vt->mode.utf8;
-}
-
-void vterm_set_utf8(VTerm *vt, int is_utf8)
-{
-  vt->mode.utf8 = is_utf8;
-}
-
-INTERNAL void vterm_push_output_bytes(VTerm *vt, const char *bytes, size_t len)
-{
-  if(len > vt->outbuffer_len - vt->outbuffer_cur) {
-    DEBUG_LOG("vterm_push_output(): buffer overflow; truncating output\n");
-    len = vt->outbuffer_len - vt->outbuffer_cur;
-  }
-
-  memcpy(vt->outbuffer + vt->outbuffer_cur, bytes, len);
-  vt->outbuffer_cur += len;
-}
-
-static int outbuffer_is_full(VTerm *vt)
-{
-  return vt->outbuffer_cur >= vt->outbuffer_len - 1;
-}
-
-INTERNAL void vterm_push_output_vsprintf(VTerm *vt, const char *format, va_list args)
-{
-  if(outbuffer_is_full(vt)) {
-    DEBUG_LOG("vterm_push_output(): buffer overflow; truncating output\n");
-    return;
-  }
-
-  int written = vsnprintf(vt->outbuffer + vt->outbuffer_cur,
-      vt->outbuffer_len - vt->outbuffer_cur,
-      format, args);
-
-  if(written == vt->outbuffer_len - vt->outbuffer_cur) {
-    /* output was truncated */
-    vt->outbuffer_cur = vt->outbuffer_len - 1;
-  }
-  else
-    vt->outbuffer_cur += written;
-}
-
-INTERNAL void vterm_push_output_sprintf(VTerm *vt, const char *format, ...)
-{
-  va_list args;
-  va_start(args, format);
-  vterm_push_output_vsprintf(vt, format, args);
-  va_end(args);
-}
-
-INTERNAL void vterm_push_output_sprintf_ctrl(VTerm *vt, unsigned char ctrl, const char *fmt, ...)
-{
-  size_t orig_cur = vt->outbuffer_cur;
-
-  if(ctrl >= 0x80 && !vt->mode.ctrl8bit)
-    vterm_push_output_sprintf(vt, ESC_S "%c", ctrl - 0x40);
-  else
-    vterm_push_output_sprintf(vt, "%c", ctrl);
-
-  va_list args;
-  va_start(args, fmt);
-  vterm_push_output_vsprintf(vt, fmt, args);
-  va_end(args);
-
-  if(outbuffer_is_full(vt))
-    vt->outbuffer_cur = orig_cur;
-}
-
-INTERNAL void vterm_push_output_sprintf_dcs(VTerm *vt, const char *fmt, ...)
-{
-  size_t orig_cur = vt->outbuffer_cur;
-
-  if(!vt->mode.ctrl8bit)
-    vterm_push_output_sprintf(vt, ESC_S "%c", C1_DCS - 0x40);
-  else
-    vterm_push_output_sprintf(vt, "%c", C1_DCS);
-
-  va_list args;
-  va_start(args, fmt);
-  vterm_push_output_vsprintf(vt, fmt, args);
-  va_end(args);
-
-  vterm_push_output_sprintf_ctrl(vt, C1_ST, "");
-
-  if(outbuffer_is_full(vt))
-    vt->outbuffer_cur = orig_cur;
-}
-
-size_t vterm_output_get_buffer_size(const VTerm *vt)
-{
-  return vt->outbuffer_len;
-}
-
-size_t vterm_output_get_buffer_current(const VTerm *vt)
-{
-  return vt->outbuffer_cur;
-}
-
-size_t vterm_output_get_buffer_remaining(const VTerm *vt)
-{
-  return vt->outbuffer_len - vt->outbuffer_cur;
-}
-
-size_t vterm_output_read(VTerm *vt, char *buffer, size_t len)
-{
-  if(len > vt->outbuffer_cur)
-    len = vt->outbuffer_cur;
-
-  memcpy(buffer, vt->outbuffer, len);
-
-  if(len < vt->outbuffer_cur)
-    memmove(vt->outbuffer, vt->outbuffer + len, vt->outbuffer_cur - len);
-
-  vt->outbuffer_cur -= len;
-
-  return len;
-}
-
-VTermValueType vterm_get_attr_type(VTermAttr attr)
-{
-  switch(attr) {
-    case VTERM_ATTR_BOLD:       return VTERM_VALUETYPE_BOOL;
-    case VTERM_ATTR_UNDERLINE:  return VTERM_VALUETYPE_INT;
-    case VTERM_ATTR_ITALIC:     return VTERM_VALUETYPE_BOOL;
-    case VTERM_ATTR_BLINK:      return VTERM_VALUETYPE_BOOL;
-    case VTERM_ATTR_REVERSE:    return VTERM_VALUETYPE_BOOL;
-    case VTERM_ATTR_STRIKE:     return VTERM_VALUETYPE_BOOL;
-    case VTERM_ATTR_FONT:       return VTERM_VALUETYPE_INT;
-    case VTERM_ATTR_FOREGROUND: return VTERM_VALUETYPE_COLOR;
-    case VTERM_ATTR_BACKGROUND: return VTERM_VALUETYPE_COLOR;
-
-    case VTERM_N_ATTRS: return 0;
-  }
-  return 0; /* UNREACHABLE */
-}
-
-VTermValueType vterm_get_prop_type(VTermProp prop)
-{
-  switch(prop) {
-    case VTERM_PROP_CURSORVISIBLE: return VTERM_VALUETYPE_BOOL;
-    case VTERM_PROP_CURSORBLINK:   return VTERM_VALUETYPE_BOOL;
-    case VTERM_PROP_ALTSCREEN:     return VTERM_VALUETYPE_BOOL;
-    case VTERM_PROP_TITLE:         return VTERM_VALUETYPE_STRING;
-    case VTERM_PROP_ICONNAME:      return VTERM_VALUETYPE_STRING;
-    case VTERM_PROP_REVERSE:       return VTERM_VALUETYPE_BOOL;
-    case VTERM_PROP_CURSORSHAPE:   return VTERM_VALUETYPE_INT;
-    case VTERM_PROP_MOUSE:         return VTERM_VALUETYPE_INT;
-
-    case VTERM_N_PROPS: return 0;
-  }
-  return 0; /* UNREACHABLE */
-}
-
-void vterm_scroll_rect(VTermRect rect,
-    int downward,
-    int rightward,
-    int (*moverect)(VTermRect src, VTermRect dest, void *user),
-    int (*eraserect)(VTermRect rect, int selective, void *user),
-    void *user)
-{
-  VTermRect src;
-  VTermRect dest;
-
-  if(abs(downward)  >= rect.end_row - rect.start_row ||
-     abs(rightward) >= rect.end_col - rect.start_col) {
-    /* Scroll more than area; just erase the lot */
-    (*eraserect)(rect, 0, user);
-    return;
-  }
-
-  if(rightward >= 0) {
-    /* rect: [XXX................]
-     * src:     [----------------]
-     * dest: [----------------]
-     */
-    dest.start_col = rect.start_col;
-    dest.end_col   = rect.end_col   - rightward;
-    src.start_col  = rect.start_col + rightward;
-    src.end_col    = rect.end_col;
-  }
-  else {
-    /* rect: [................XXX]
-     * src:  [----------------]
-     * dest:    [----------------]
-     */
-    int leftward = -rightward;
-    dest.start_col = rect.start_col + leftward;
-    dest.end_col   = rect.end_col;
-    src.start_col  = rect.start_col;
-    src.end_col    = rect.end_col - leftward;
-  }
-
-  if(downward >= 0) {
-    dest.start_row = rect.start_row;
-    dest.end_row   = rect.end_row   - downward;
-    src.start_row  = rect.start_row + downward;
-    src.end_row    = rect.end_row;
-  }
-  else {
-    int upward = -downward;
-    dest.start_row = rect.start_row + upward;
-    dest.end_row   = rect.end_row;
-    src.start_row  = rect.start_row;
-    src.end_row    = rect.end_row - upward;
-  }
-
-  if(moverect)
-    (*moverect)(dest, src, user);
-
-  if(downward > 0)
-    rect.start_row = rect.end_row - downward;
-  else if(downward < 0)
-    rect.end_row = rect.start_row - downward;
-
-  if(rightward > 0)
-    rect.start_col = rect.end_col - rightward;
-  else if(rightward < 0)
-    rect.end_col = rect.start_col - rightward;
-
-  (*eraserect)(rect, 0, user);
-}
-
-void vterm_copy_cells(VTermRect dest,
-    VTermRect src,
-    void (*copycell)(VTermPos dest, VTermPos src, void *user),
-    void *user)
-{
-  int downward  = src.start_row - dest.start_row;
-  int rightward = src.start_col - dest.start_col;
-
-  int init_row, test_row, init_col, test_col;
-  int inc_row, inc_col;
-
-  if(downward < 0) {
-    init_row = dest.end_row - 1;
-    test_row = dest.start_row - 1;
-    inc_row = -1;
-  }
-  else /* downward >= 0 */ {
-    init_row = dest.start_row;
-    test_row = dest.end_row;
-    inc_row = +1;
-  }
-
-  if(rightward < 0) {
-    init_col = dest.end_col - 1;
-    test_col = dest.start_col - 1;
-    inc_col = -1;
-  }
-  else /* rightward >= 0 */ {
-    init_col = dest.start_col;
-    test_col = dest.end_col;
-    inc_col = +1;
-  }
-
-  VTermPos pos;
-  for(pos.row = init_row; pos.row != test_row; pos.row += inc_row)
-    for(pos.col = init_col; pos.col != test_col; pos.col += inc_col) {
-      VTermPos srcpos = { pos.row + downward, pos.col + rightward };
-      (*copycell)(pos, srcpos, user);
-    }
-}
diff --git a/src/vterm_internal.h b/src/vterm_internal.h
deleted file mode 100644
index 363faee..0000000
--- a/src/vterm_internal.h
+++ /dev/null
@@ -1,246 +0,0 @@
-#ifndef __VTERM_INTERNAL_H__
-#define __VTERM_INTERNAL_H__
-
-#include "vterm.h"
-
-#include <stdarg.h>
-
-#if defined(__GNUC__)
-# define INTERNAL __attribute__((visibility("internal")))
-#else
-# define INTERNAL
-#endif
-
-#ifdef DEBUG
-# define DEBUG_LOG(...) fprintf(stderr, __VA_ARGS__)
-#else
-# define DEBUG_LOG(...)
-#endif
-
-#define ESC_S "\x1b"
-
-#define INTERMED_MAX 16
-
-#define CSI_ARGS_MAX 16
-#define CSI_LEADER_MAX 16
-
-typedef struct VTermEncoding VTermEncoding;
-
-typedef struct {
-  VTermEncoding *enc;
-
-  // This size should be increased if required by other stateful encodings
-  char           data[4*sizeof(uint32_t)];
-} VTermEncodingInstance;
-
-struct VTermPen
-{
-  VTermColor fg;
-  VTermColor bg;
-  unsigned int bold:1;
-  unsigned int underline:2;
-  unsigned int italic:1;
-  unsigned int blink:1;
-  unsigned int reverse:1;
-  unsigned int strike:1;
-  unsigned int font:4; /* To store 0-9 */
-};
-
-struct VTermState
-{
-  VTerm *vt;
-
-  const VTermStateCallbacks *callbacks;
-  void *cbdata;
-
-  const VTermParserCallbacks *fallbacks;
-  void *fbdata;
-
-  int rows;
-  int cols;
-
-  /* Current cursor position */
-  VTermPos pos;
-
-  int at_phantom; /* True if we're on the "81st" phantom column to defer a wraparound */
-
-  int scrollregion_top;
-  int scrollregion_bottom; /* -1 means unbounded */
-#define SCROLLREGION_BOTTOM(state) ((state)->scrollregion_bottom > -1 ? (state)->scrollregion_bottom : (state)->rows)
-  int scrollregion_left;
-#define SCROLLREGION_LEFT(state)  ((state)->mode.leftrightmargin ? (state)->scrollregion_left : 0)
-  int scrollregion_right; /* -1 means unbounded */
-#define SCROLLREGION_RIGHT(state) ((state)->mode.leftrightmargin && (state)->scrollregion_right > -1 ? (state)->scrollregion_right : (state)->cols)
-
-  /* Bitvector of tab stops */
-  unsigned char *tabstops;
-
-  VTermLineInfo *lineinfo;
-#define ROWWIDTH(state,row) ((state)->lineinfo[(row)].doublewidth ? ((state)->cols / 2) : (state)->cols)
-#define THISROWWIDTH(state) ROWWIDTH(state, (state)->pos.row)
-
-  /* Mouse state */
-  int mouse_col, mouse_row;
-  int mouse_buttons;
-  int mouse_flags;
-#define MOUSE_WANT_CLICK 0x01
-#define MOUSE_WANT_DRAG  0x02
-#define MOUSE_WANT_MOVE  0x04
-
-  enum { MOUSE_X10, MOUSE_UTF8, MOUSE_SGR, MOUSE_RXVT } mouse_protocol;
-
-  /* Last glyph output, for Unicode recombining purposes */
-  uint32_t *combine_chars;
-  size_t combine_chars_size; // Number of ELEMENTS in the above
-  int combine_width; // The width of the glyph above
-  VTermPos combine_pos;   // Position before movement
-
-  struct {
-    unsigned int keypad:1;
-    unsigned int cursor:1;
-    unsigned int autowrap:1;
-    unsigned int insert:1;
-    unsigned int newline:1;
-    unsigned int cursor_visible:1;
-    unsigned int cursor_blink:1;
-    unsigned int cursor_shape:2;
-    unsigned int alt_screen:1;
-    unsigned int origin:1;
-    unsigned int screen:1;
-    unsigned int leftrightmargin:1;
-    unsigned int bracketpaste:1;
-    unsigned int report_focus:1;
-  } mode;
-
-  VTermEncodingInstance encoding[4], encoding_utf8;
-  int gl_set, gr_set, gsingle_set;
-
-  struct VTermPen pen;
-
-  VTermColor default_fg;
-  VTermColor default_bg;
-  VTermColor colors[16]; // Store the 8 ANSI and the 8 ANSI high-brights only
-
-  int bold_is_highbright;
-
-  unsigned int protected_cell : 1;
-
-  /* Saved state under DEC mode 1048/1049 */
-  struct {
-    VTermPos pos;
-    struct VTermPen pen;
-
-    struct {
-      unsigned int cursor_visible:1;
-      unsigned int cursor_blink:1;
-      unsigned int cursor_shape:2;
-    } mode;
-  } saved;
-};
-
-typedef enum {
-  VTERM_PARSER_OSC,
-  VTERM_PARSER_DCS,
-
-  VTERM_N_PARSER_TYPES
-} VTermParserStringType;
-
-struct VTerm
-{
-  VTermAllocatorFunctions *allocator;
-  void *allocdata;
-
-  int rows;
-  int cols;
-
-  struct {
-    unsigned int utf8:1;
-    unsigned int ctrl8bit:1;
-  } mode;
-
-  struct {
-    enum VTermParserState {
-      NORMAL,
-      CSI_LEADER,
-      CSI_ARGS,
-      CSI_INTERMED,
-      ESC,
-      /* below here are the "string states" */
-      STRING,
-      ESC_IN_STRING,
-    } state;
-
-    int intermedlen;
-    char intermed[INTERMED_MAX];
-
-    int csi_leaderlen;
-    char csi_leader[CSI_LEADER_MAX];
-
-    int csi_argi;
-    long csi_args[CSI_ARGS_MAX];
-
-    const VTermParserCallbacks *callbacks;
-    void *cbdata;
-
-    VTermParserStringType stringtype;
-    char  *strbuffer;
-    size_t strbuffer_len;
-    size_t strbuffer_cur;
-  } parser;
-
-  /* len == malloc()ed size; cur == number of valid bytes */
-
-  char  *outbuffer;
-  size_t outbuffer_len;
-  size_t outbuffer_cur;
-
-  VTermState *state;
-  VTermScreen *screen;
-};
-
-struct VTermEncoding {
-  void (*init) (VTermEncoding *enc, void *data);
-  void (*decode)(VTermEncoding *enc, void *data,
-                 uint32_t cp[], int *cpi, int cplen,
-                 const char bytes[], size_t *pos, size_t len);
-};
-
-typedef enum {
-  ENC_UTF8,
-  ENC_SINGLE_94
-} VTermEncodingType;
-
-void *vterm_allocator_malloc(VTerm *vt, size_t size);
-void  vterm_allocator_free(VTerm *vt, void *ptr);
-
-void vterm_push_output_bytes(VTerm *vt, const char *bytes, size_t len);
-void vterm_push_output_vsprintf(VTerm *vt, const char *format, va_list args);
-void vterm_push_output_sprintf(VTerm *vt, const char *format, ...);
-void vterm_push_output_sprintf_ctrl(VTerm *vt, unsigned char ctrl, const char *fmt, ...);
-void vterm_push_output_sprintf_dcs(VTerm *vt, const char *fmt, ...);
-
-void vterm_state_free(VTermState *state);
-
-void vterm_state_newpen(VTermState *state);
-void vterm_state_resetpen(VTermState *state);
-void vterm_state_setpen(VTermState *state, const long args[], int argcount);
-int  vterm_state_getpen(VTermState *state, long args[], int argcount);
-void vterm_state_savepen(VTermState *state, int save);
-
-enum {
-  C1_SS3 = 0x8f,
-  C1_DCS = 0x90,
-  C1_CSI = 0x9b,
-  C1_ST  = 0x9c,
-};
-
-void vterm_state_push_output_sprintf_CSI(VTermState *vts, const char *format, ...);
-
-void vterm_screen_free(VTermScreen *screen);
-
-VTermEncoding *vterm_lookup_encoding(VTermEncodingType type, char designation);
-
-int vterm_unicode_width(uint32_t codepoint);
-int vterm_unicode_is_combining(uint32_t codepoint);
-
-#endif