[haiku-commits] haiku: hrev47669 - in src/apps/serialconnect/libvterm: src include

  • From: pulkomandy@xxxxxxxxxxxxx
  • To: haiku-commits@xxxxxxxxxxxxx
  • Date: Mon, 11 Aug 2014 23:14:31 +0200 (CEST)

hrev47669 adds 3 changesets to branch 'master'
old head: c9a4d5248388606d7c2150f6dc567f915de24592
new head: 6a545a8eb1e3dd624c7a473fe9965a4c08e5a54f
overview: http://cgit.haiku-os.org/haiku/log/?qt=range&q=6a545a8+%5Ec9a4d52

----------------------------------------------------------------------------

b54c6f2: SerialConnect: fix font metrics for good.
  
  * Set the background color in AttachedToWindow to avoid white lines in
  initial drawing,
  * Fix computation of font size again so lines don't overlap.
  
  Note: lines are apparently spaced 1px less than in Terminal. But they
  don't seem to touch or overlap each other in SerialConnect.

1ec2551: Import libvterm r617.

6a545a8: libvterm: C89 backport.
  
  * As a separate commit so next time the update will be easier...

                             [ Adrien Destugues <pulkomandy@xxxxxxxxxxxxx> ]

----------------------------------------------------------------------------

15 files changed, 1421 insertions(+), 563 deletions(-)
src/apps/serialconnect/TermView.cpp              |  37 +-
src/apps/serialconnect/libvterm/include/vterm.h  | 231 +++---
.../serialconnect/libvterm/include/vterm_input.h |  21 +-
src/apps/serialconnect/libvterm/src/encoding.c   |  22 +-
.../libvterm/src/encoding/DECdrawing.tbl         |  31 -
.../serialconnect/libvterm/src/encoding/uk.tbl   |   1 -
src/apps/serialconnect/libvterm/src/input.c      | 112 ++-
src/apps/serialconnect/libvterm/src/parser.c     |   8 +-
src/apps/serialconnect/libvterm/src/pen.c        | 176 ++++-
src/apps/serialconnect/libvterm/src/screen.c     | 428 ++++++++--
src/apps/serialconnect/libvterm/src/state.c      | 780 +++++++++++++------
src/apps/serialconnect/libvterm/src/unicode.c    |   7 +-
src/apps/serialconnect/libvterm/src/utf8.h       |   6 +-
src/apps/serialconnect/libvterm/src/vterm.c      |  70 +-
.../serialconnect/libvterm/src/vterm_internal.h  |  54 +-

############################################################################

Commit:      b54c6f2e65084a02437cb396f915aa347440b390
URL:         http://cgit.haiku-os.org/haiku/commit/?id=b54c6f2
Author:      Adrien Destugues <pulkomandy@xxxxxxxxxxxxx>
Date:        Mon Aug 11 19:36:35 2014 UTC

SerialConnect: fix font metrics for good.

* Set the background color in AttachedToWindow to avoid white lines in
initial drawing,
* Fix computation of font size again so lines don't overlap.

Note: lines are apparently spaced 1px less than in Terminal. But they
don't seem to touch or overlap each other in SerialConnect.

----------------------------------------------------------------------------

diff --git a/src/apps/serialconnect/TermView.cpp 
b/src/apps/serialconnect/TermView.cpp
index 8adbdd3..925494b 100644
--- a/src/apps/serialconnect/TermView.cpp
+++ b/src/apps/serialconnect/TermView.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012, Adrien Destugues, pulkomandy@xxxxxxxxx
+ * Copyright 2012-2014, Adrien Destugues, pulkomandy@xxxxxxxxx
  * Distributed under the terms of the MIT licence.
  */
 
@@ -23,7 +23,7 @@ TermView::TermView()
 
        font_height height;
        GetFontHeight(&height);
-       fFontHeight = height.ascent + height.descent + height.leading;
+       fFontHeight = height.ascent + height.descent + height.leading + 1;
        fFontWidth = be_fixed_font->StringWidth("X");
        fTerm = vterm_new(kDefaultHeight, kDefaultWidth);
 
@@ -44,13 +44,27 @@ TermView::~TermView()
 void TermView::AttachedToWindow()
 {
        MakeFocus();
+
+       VTermScreenCell cell;
+       VTermPos firstPos;
+       firstPos.row = 0;
+       firstPos.col = 0;
+       vterm_screen_get_cell(fTermScreen, firstPos, &cell);
+
+       rgb_color background;
+       background.red = cell.bg.red;
+       background.green = cell.bg.green;
+       background.blue = cell.bg.blue;
+       background.alpha = 255;
+
+       SetViewColor(background);
 }
 
 
 void TermView::Draw(BRect updateRect)
 {
        VTermRect updatedChars = PixelsToGlyphs(updateRect);
-       
+
        VTermPos pos;
        font_height height;
        GetFontHeight(&height);
@@ -61,14 +75,14 @@ void TermView::Draw(BRect updateRect)
        for (pos.row = updatedChars.start_row; pos.row <= updatedChars.end_row;
                        pos.row++) {
                float x = updatedChars.start_col * fFontWidth + kBorderSpacing;
-               float y = pos.row * fFontHeight + height.ascent + 
kBorderSpacing + 1;
+               float y = pos.row * fFontHeight + height.ascent + 
kBorderSpacing;
                MovePenTo(x, y);
 
                for (pos.col = updatedChars.start_col;
                                pos.col <= updatedChars.end_col;) {
                        VTermScreenCell cell;
 
-                       if (pos.col < 0 || pos.row < 0 || pos.col >= 
availableCols 
+                       if (pos.col < 0 || pos.row < 0 || pos.col >= 
availableCols
                                        || pos.row >= availableRows) {
 
                                // All cells outside the used terminal area are 
drawn with the
@@ -92,7 +106,7 @@ void TermView::Draw(BRect updateRect)
                        background.blue = cell.bg.blue;
                        background.alpha = 255;
 
-                       if(cell.attrs.reverse) {
+                       if (cell.attrs.reverse) {
                                SetLowColor(foreground);
                                SetViewColor(foreground);
                                SetHighColor(background);
@@ -104,7 +118,8 @@ void TermView::Draw(BRect updateRect)
 
                        BPoint penLocation = PenLocation();
                        FillRect(BRect(penLocation.x, penLocation.y - 
height.ascent,
-                               penLocation.x + cell.width * fFontWidth, 
penLocation.y + 1),
+                               penLocation.x + cell.width * fFontWidth - 1,
+                               penLocation.y + height.descent + height.leading 
- 1),
                                B_SOLID_LOW);
 
                        if (cell.chars[0] == 0) {
@@ -155,7 +170,7 @@ void TermView::MessageReceived(BMessage* message)
                case 'DATA':
                {
                        entry_ref ref;
-                       if(message->FindRef("refs", &ref) == B_OK)
+                       if (message->FindRef("refs", &ref) == B_OK)
                        {
                                // The user just dropped a file on us
                                // TODO send it by XMODEM or so
@@ -174,7 +189,7 @@ void TermView::PushBytes(const char* bytes, size_t length)
 }
 
 
-//#pragma mark -
+// #pragma mark -
 
 
 VTermRect TermView::PixelsToGlyphs(BRect pixels) const
@@ -211,7 +226,7 @@ BRect TermView::GlyphsToPixels(const VTermRect& glyphs) 
const
        rect.right = glyphs.end_col * fFontWidth;
 
        rect.OffsetBy(kBorderSpacing, kBorderSpacing);
-/*
+#if 0
        printf(
                "TOP %d ch > %f px (%f)\n"
                "BTM %d ch > %f px\n"
@@ -222,7 +237,7 @@ BRect TermView::GlyphsToPixels(const VTermRect& glyphs) 
const
                glyphs.start_col, rect.left, fFontWidth,
                glyphs.end_col, rect.right
        );
-*/
+#endif
        return rect;
 }
 

############################################################################

Commit:      1ec255178aeadde306770a17119788580f8300a0
URL:         http://cgit.haiku-os.org/haiku/commit/?id=1ec2551
Author:      Adrien Destugues <pulkomandy@xxxxxxxxxxxxx>
Date:        Mon Aug 11 20:17:29 2014 UTC

Import libvterm r617.

----------------------------------------------------------------------------

diff --git a/src/apps/serialconnect/libvterm/include/vterm.h 
b/src/apps/serialconnect/libvterm/include/vterm.h
index 4728651..2c54d14 100644
--- a/src/apps/serialconnect/libvterm/include/vterm.h
+++ b/src/apps/serialconnect/libvterm/include/vterm.h
@@ -44,37 +44,6 @@ static inline void vterm_rect_move(VTermRect *rect, int 
row_delta, int col_delta
   rect->start_col += col_delta; rect->end_col += col_delta;
 }
 
-/* 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 (1<<31)
-#define CSI_ARG_MASK      (~(1<<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;
-
 typedef struct {
   uint8_t red, green, blue;
 } VTermColor;
@@ -121,33 +90,23 @@ typedef enum {
 enum {
   VTERM_PROP_CURSORSHAPE_BLOCK = 1,
   VTERM_PROP_CURSORSHAPE_UNDERLINE,
+  VTERM_PROP_CURSORSHAPE_BAR_LEFT,
 };
 
 typedef void (*VTermMouseFunc)(int x, int y, int button, int pressed, int 
modifiers, void *data);
 
 typedef struct {
-  int (*putglyph)(const uint32_t chars[], int width, 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, void *user);
-  int (*initpen)(void *user);
-  int (*setpenattr)(VTermAttr attr, VTermValue *val, void *user);
-  int (*settermprop)(VTermProp prop, VTermValue *val, void *user);
-  int (*setmousefunc)(VTermMouseFunc func, void *data, void *user);
-  int (*bell)(void *user);
-  int (*resize)(int rows, int cols, void *user);
-} VTermStateCallbacks;
+  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 {
-  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 (*setmousefunc)(VTermMouseFunc func, void *data, void *user);
-  int (*bell)(void *user);
-  int (*resize)(int rows, int cols, void *user);
-} VTermScreenCallbacks;
+  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
@@ -160,22 +119,127 @@ 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(VTerm *vt, int *rowsp, int *colsp);
+void vterm_get_size(const VTerm *vt, int *rowsp, int *colsp);
 void vterm_set_size(VTerm *vt, int rows, int cols);
 
+void vterm_push_bytes(VTerm *vt, const char *bytes, size_t len);
+
+void vterm_input_push_char(VTerm *vt, VTermModifier state, uint32_t c);
+void vterm_input_push_key(VTerm *vt, VTermModifier state, VTermKey key);
+
+size_t vterm_output_bufferlen(VTerm *vt); /* deprecated */
+
+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_bufferread(VTerm *vt, char *buffer, size_t len);
+
+// ------------
+// 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 (1<<31)
+#define CSI_ARG_MASK      (~(1<<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_set_parser_callbacks(VTerm *vt, const VTermParserCallbacks 
*callbacks, void *user);
 
+void vterm_parser_set_utf8(VTerm *vt, int is_utf8);
+
+// -----------
+// 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 (*setmousefunc)(VTermMouseFunc func, void *data, 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_reset(VTermState *state, int hard);
 void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks 
*callbacks, void *user);
-void vterm_state_get_cursorpos(VTermState *state, VTermPos *cursorpos);
-void vterm_state_set_default_colors(VTermState *state, VTermColor *default_fg, 
VTermColor *default_bg);
+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(VTermState *state, VTermAttr attr, VTermValue 
*val);
+int  vterm_state_get_penattr(const VTermState *state, VTermAttr attr, 
VTermValue *val);
+int  vterm_state_set_termprop(VTermState *state, VTermProp prop, VTermValue 
*val);
+const VTermLineInfo *vterm_state_get_lineinfo(const VTermState *state, int 
row);
 
-VTermValueType vterm_get_attr_type(VTermAttr attr);
-VTermValueType vterm_get_prop_type(VTermProp prop);
+// ------------
+// Screen layer
+// ------------
+
+typedef struct {
+#define VTERM_MAX_CHARS_PER_CELL 6
+  uint32_t chars[VTERM_MAX_CHARS_PER_CELL];
+  char     width;
+  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) */
+  } 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 (*setmousefunc)(VTermMouseFunc func, void *data, 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);
 
@@ -193,48 +257,41 @@ 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);
-size_t vterm_screen_get_chars(VTermScreen *screen, uint32_t *chars, size_t 
len, const VTermRect rect);
-size_t vterm_screen_get_text(VTermScreen *screen, char *str, size_t len, const 
VTermRect rect);
 
-typedef struct {
-#define VTERM_MAX_CHARS_PER_CELL 6
-  uint32_t chars[VTERM_MAX_CHARS_PER_CELL];
-  char     width;
-  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 */
-  } attrs;
-  VTermColor fg, bg;
-} VTermScreenCell;
-
-void vterm_screen_get_cell(VTermScreen *screen, VTermPos pos, VTermScreenCell 
*cell);
+/* 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);
 
-int vterm_screen_is_eol(VTermScreen *screen, VTermPos pos);
+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,
+} VTermAttrMask;
 
-void vterm_input_push_char(VTerm *vt, VTermModifier state, uint32_t c);
-void vterm_input_push_key(VTerm *vt, VTermModifier state, VTermKey key);
+int vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect 
*extent, VTermPos pos, VTermAttrMask attrs);
 
-void vterm_parser_set_utf8(VTerm *vt, int is_utf8);
-void vterm_push_bytes(VTerm *vt, const char *bytes, size_t len);
+int vterm_screen_get_cell(const VTermScreen *screen, VTermPos pos, 
VTermScreenCell *cell);
 
-size_t vterm_output_bufferlen(VTerm *vt); /* deprecated */
+int vterm_screen_is_eol(const VTermScreen *screen, VTermPos pos);
 
-size_t vterm_output_get_buffer_size(VTerm *vt);
-size_t vterm_output_get_buffer_current(VTerm *vt);
-size_t vterm_output_get_buffer_remaining(VTerm *vt);
+// ---------
+// Utilities
+// ---------
 
-size_t vterm_output_bufferread(VTerm *vt, char *buffer, size_t len);
+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, void *user),
+                       int (*eraserect)(VTermRect rect, int selective, void 
*user),
                        void *user);
 
 void vterm_copy_cells(VTermRect dest,
diff --git a/src/apps/serialconnect/libvterm/include/vterm_input.h 
b/src/apps/serialconnect/libvterm/include/vterm_input.h
index 69cc6cb..32f9175 100644
--- a/src/apps/serialconnect/libvterm/include/vterm_input.h
+++ b/src/apps/serialconnect/libvterm/include/vterm_input.h
@@ -28,9 +28,28 @@ typedef enum {
   VTERM_KEY_PAGEUP,
   VTERM_KEY_PAGEDOWN,
 
-  VTERM_KEY_FUNCTION_0,
+  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
 } VTermKey;
 
diff --git a/src/apps/serialconnect/libvterm/src/encoding.c 
b/src/apps/serialconnect/libvterm/src/encoding.c
index 373c9bc..1495855 100644
--- a/src/apps/serialconnect/libvterm/src/encoding.c
+++ b/src/apps/serialconnect/libvterm/src/encoding.c
@@ -1,10 +1,8 @@
 #include "vterm_internal.h"
 
-#include <stdio.h>
-
 #define UNICODE_INVALID 0xFFFD
 
-#ifdef DEBUG
+#if defined(DEBUG) && DEBUG > 1
 # define DEBUG_PRINT_UTF8
 #endif
 
@@ -37,7 +35,7 @@ static void decode_utf8(VTermEncoding *enc, void *data_,
   printf("BEGIN UTF-8\n");
 #endif
 
-  for( ; *pos < bytelen; (*pos)++) {
+  for(; *pos < bytelen && *cpi < cplen; (*pos)++) {
     unsigned char c = bytes[*pos];
 
 #ifdef DEBUG_PRINT_UTF8
@@ -157,8 +155,10 @@ static void decode_usascii(VTermEncoding *enc, void *data,
                            uint32_t cp[], int *cpi, int cplen,
                            const char bytes[], size_t *pos, size_t bytelen)
 {
-  for(; *pos < bytelen; (*pos)++) {
-    unsigned char c = bytes[*pos];
+  int is_gr = bytes[*pos] & 0x80;
+
+  for(; *pos < bytelen && *cpi < cplen; (*pos)++) {
+    unsigned char c = bytes[*pos] ^ is_gr;
 
     if(c < 0x20 || c >= 0x80)
       return;
@@ -181,11 +181,12 @@ static void decode_table(VTermEncoding *enc, void *data,
                          const char bytes[], size_t *pos, size_t bytelen)
 {
   struct StaticTableEncoding *table = (struct StaticTableEncoding *)enc;
+  int is_gr = bytes[*pos] & 0x80;
 
-  for(; *pos < bytelen; (*pos)++) {
-    unsigned char c = (bytes[*pos]) & 0x7f;
+  for(; *pos < bytelen && *cpi < cplen; (*pos)++) {
+    unsigned char c = bytes[*pos] ^ is_gr;
 
-    if(c < 0x20)
+    if(c < 0x20 || c >= 0x80)
       return;
 
     if(table->chars[c])
@@ -208,13 +209,13 @@ encodings[] = {
   { ENC_SINGLE_94, '0', (VTermEncoding*)&encoding_DECdrawing },
   { ENC_SINGLE_94, 'A', (VTermEncoding*)&encoding_uk },
   { ENC_SINGLE_94, 'B', &encoding_usascii },
-  { 0, 0 },
+  { 0 },
 };
 
+/* This ought to be INTERNAL but isn't because it's used by unit testing */
 VTermEncoding *vterm_lookup_encoding(VTermEncodingType type, char designation)
 {
-  int i;
-  for(i = 0; encodings[i].designation; i++)
+  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/apps/serialconnect/libvterm/src/encoding/DECdrawing.inc 
b/src/apps/serialconnect/libvterm/src/encoding/DECdrawing.inc
deleted file mode 100644
index 47093ed..0000000
--- a/src/apps/serialconnect/libvterm/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/apps/serialconnect/libvterm/src/encoding/uk.inc 
b/src/apps/serialconnect/libvterm/src/encoding/uk.inc
deleted file mode 100644
index da1445d..0000000
--- a/src/apps/serialconnect/libvterm/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/apps/serialconnect/libvterm/src/input.c 
b/src/apps/serialconnect/libvterm/src/input.c
index f851162..0eaf0e9 100644
--- a/src/apps/serialconnect/libvterm/src/input.c
+++ b/src/apps/serialconnect/libvterm/src/input.c
@@ -6,17 +6,11 @@
 
 void vterm_input_push_char(VTerm *vt, VTermModifier mod, uint32_t c)
 {
-  int needs_CSIu;
   /* The shift modifier is never important for Unicode characters
    * apart from Space
    */
   if(c != ' ')
     mod &= ~VTERM_MOD_SHIFT;
-  /* However, since Shift-Space is too easy to mistype accidentally, remove
-   * shift if it's the only modifier
-   */
-  else if(mod == VTERM_MOD_SHIFT)
-    mod = 0;
 
   if(mod == 0) {
     // Normal text - ignore just shift
@@ -26,6 +20,7 @@ void vterm_input_push_char(VTerm *vt, VTermModifier mod, 
uint32_t c)
     return;
   }
 
+  int needs_CSIu;
   switch(c) {
     /* Special Ctrl- letters that can't be represented elsewise */
     case 'h': case 'i': case 'j': case 'm': case '[':
@@ -42,7 +37,7 @@ void vterm_input_push_char(VTerm *vt, VTermModifier mod, 
uint32_t c)
 
   /* ALT we can just prefix with ESC; anything else requires CSI u */
   if(needs_CSIu && (mod & ~VTERM_MOD_ALT)) {
-    vterm_push_output_sprintf(vt, "\e[%d;%du", c, mod+1);
+    vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", c, mod+1);
     return;
   }
 
@@ -58,15 +53,17 @@ typedef struct {
     KEYCODE_LITERAL,
     KEYCODE_TAB,
     KEYCODE_ENTER,
+    KEYCODE_SS3,
     KEYCODE_CSI,
     KEYCODE_CSI_CURSOR,
     KEYCODE_CSINUM,
+    KEYCODE_KEYPAD,
   } type;
   char literal;
   int csinum;
 } keycodes_s;
 
-keycodes_s keycodes[] = {
+static keycodes_s keycodes[] = {
   { KEYCODE_NONE }, // NONE
 
   { KEYCODE_ENTER,   '\r'   }, // ENTER
@@ -85,7 +82,9 @@ keycodes_s keycodes[] = {
   { 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
@@ -101,22 +100,48 @@ keycodes_s keycodes[] = {
   { 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)
 {
-  keycodes_s k;
-  /* Since Shift-Enter and Shift-Backspace are too easy to mistype
-   * accidentally, remove shift if it's the only modifier
-   */
-  if((key == VTERM_KEY_ENTER || key == VTERM_KEY_BACKSPACE) && mod == 
VTERM_MOD_SHIFT)
-    mod = 0;
-
-  if(key == VTERM_KEY_NONE || key >= VTERM_KEY_MAX)
-    return;
-
-  if(key >= sizeof(keycodes)/sizeof(keycodes[0]))
+  if(key == VTERM_KEY_NONE)
     return;
 
-  k = keycodes[key];
+  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:
@@ -125,11 +150,11 @@ void vterm_input_push_key(VTerm *vt, VTermModifier mod, 
VTermKey key)
   case KEYCODE_TAB:
     /* Shift-Tab is CSI Z but plain Tab is 0x09 */
     if(mod == VTERM_MOD_SHIFT)
-      vterm_push_output_sprintf(vt, "\e[Z");
+      vterm_push_output_sprintf_ctrl(vt, C1_CSI, "Z");
     else if(mod & VTERM_MOD_SHIFT)
-      vterm_push_output_sprintf(vt, "\e[1;%dZ", mod+1);
+      vterm_push_output_sprintf_ctrl(vt, C1_CSI, "1;%dZ", mod+1);
     else
-      goto literal;
+      goto case_LITERAL;
     break;
 
   case KEYCODE_ENTER:
@@ -137,35 +162,49 @@ void vterm_input_push_key(VTerm *vt, VTermModifier mod, 
VTermKey key)
     if(vt->state->mode.newline)
       vterm_push_output_sprintf(vt, "\r\n");
     else
-      goto literal;
+      goto case_LITERAL;
     break;
 
-literal:
-  case KEYCODE_LITERAL:
+  case KEYCODE_LITERAL: case_LITERAL:
     if(mod & (VTERM_MOD_SHIFT|VTERM_MOD_CTRL))
-      vterm_push_output_sprintf(vt, "\e[%d;%du", k.literal, mod+1);
+      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_CSI_CURSOR:
-    if(vt->state->mode.cursor && mod == 0) {
-      vterm_push_output_sprintf(vt, "\eO%c", k.literal);
-      break;
-    }
-    /* else FALLTHROUGH */
-  case KEYCODE_CSI:
+  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(vt, "\e[%c", k.literal);
+      vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%c", k.literal);
     else
-      vterm_push_output_sprintf(vt, "\e[1;%d%c", mod + 1, k.literal);
+      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(vt, "\e[%d%c", k.csinum, k.literal);
+      vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d%c", k.csinum, k.literal);
     else
-      vterm_push_output_sprintf(vt, "\e[%d;%d%c", k.csinum, mod + 1, 
k.literal);
+      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/apps/serialconnect/libvterm/src/parser.c 
b/src/apps/serialconnect/libvterm/src/parser.c
index cfd7e2c..13bbc21 100644
--- a/src/apps/serialconnect/libvterm/src/parser.c
+++ b/src/apps/serialconnect/libvterm/src/parser.c
@@ -1,6 +1,5 @@
 #include "vterm_internal.h"
 
-#include <inttypes.h>
 #include <stdio.h>
 #include <string.h>
 
@@ -19,15 +18,10 @@ static void do_control(VTerm *vt, unsigned char control)
 
 static void do_string_csi(VTerm *vt, const char *args, size_t arglen, char 
command)
 {
-  size_t i = 0;
+  int i = 0;
 
   int leaderlen = 0;
   char leader[CSI_LEADER_MAX];
-  int argcount = 1; // Always at least 1 arg
-  long csi_args[CSI_ARGS_MAX];
-  int argi;
-  int intermedlen = 0;
-  char intermed[CSI_INTERMED_MAX];
 
   // Extract leader bytes 0x3c to 0x3f
   for( ; i < arglen; i++) {
@@ -39,14 +33,18 @@ static void do_string_csi(VTerm *vt, const char *args, 
size_t arglen, char comma
 
   leader[leaderlen] = 0;
 
+  int argcount = 1; // Always at least 1 arg
+
   for( ; i < arglen; i++)
     if(args[i] == 0x3b || args[i] == 0x3a) // ; or :
       argcount++;
 
   /* TODO: Consider if these buffers should live in the VTerm struct itself */
+  long csi_args[CSI_ARGS_MAX];
   if(argcount > CSI_ARGS_MAX)
     argcount = CSI_ARGS_MAX;
 
+  int argi;
   for(argi = 0; argi < argcount; argi++)
     csi_args[argi] = CSI_ARG_MISSING;
 
@@ -72,6 +70,8 @@ static void do_string_csi(VTerm *vt, const char *args, size_t 
arglen, char comma
   }
 done_leader: ;
 
+  int intermedlen = 0;
+  char intermed[CSI_INTERMED_MAX];
 
   for( ; i < arglen; i++) {
     if((args[i] & 0xf0) != 0x20)
@@ -118,8 +118,6 @@ static void append_strbuffer(VTerm *vt, const char *str, 
size_t len)
 
 static size_t do_string(VTerm *vt, const char *str_frag, size_t len)
 {
-  size_t eaten;
-
   if(vt->strbuffer_cur) {
     if(str_frag)
       append_strbuffer(vt, str_frag, len);
@@ -134,6 +132,8 @@ static size_t do_string(VTerm *vt, const char *str_frag, 
size_t len)
 
   vt->strbuffer_cur = 0;
 
+  size_t eaten;
+
   switch(vt->parser_state) {
   case NORMAL:
     if(vt->parser_callbacks && vt->parser_callbacks->text)
@@ -190,7 +190,7 @@ static size_t do_string(VTerm *vt, const char *str_frag, 
size_t len)
 void vterm_push_bytes(VTerm *vt, const char *bytes, size_t len)
 {
   size_t pos = 0;
-  const char *string_start = NULL;
+  const char *string_start;
 
   switch(vt->parser_state) {
   case NORMAL:
@@ -299,14 +299,14 @@ void vterm_push_bytes(VTerm *vt, const char *bytes, 
size_t len)
 
     case OSC:
     case DCS:
-      if(c == 0x07 || (c == 0x9c && !vt->is_utf8)) {
+      if(c == 0x07 || (c == 0x9c && !vt->mode.utf8)) {
         do_string(vt, string_start, bytes + pos - string_start);
         ENTER_NORMAL_STATE();
       }
       break;
 
     case NORMAL:
-      if(c >= 0x80 && c < 0xa0 && !vt->is_utf8) {
+      if(c >= 0x80 && c < 0xa0 && !vt->mode.utf8) {
         switch(c) {
         case 0x90: // DCS
           ENTER_STRING_STATE(DCS);
diff --git a/src/apps/serialconnect/libvterm/src/pen.c 
b/src/apps/serialconnect/libvterm/src/pen.c
index 3f42b60..fb8c8e3 100644
--- a/src/apps/serialconnect/libvterm/src/pen.c
+++ b/src/apps/serialconnect/libvterm/src/pen.c
@@ -33,17 +33,39 @@ static int ramp24[] = {
   0x85, 0x90, 0x9B, 0xA6, 0xB1, 0xBC, 0xC7, 0xD2, 0xDD, 0xE8, 0xF3, 0xFF,
 };
 
-static void lookup_colour_ansi(long index, char is_bg, VTermColor *col)
+static void lookup_colour_ansi(const VTermState *state, long index, VTermColor 
*col)
 {
   if(index >= 0 && index < 16) {
-    *col = ansi_colors[index];
+    *col = state->colors[index];
   }
 }
 
-static int lookup_colour(int palette, const long args[], int argcount, char 
is_bg, VTermColor *col)
+static void lookup_colour_palette(const VTermState *state, long index, 
VTermColor *col)
 {
-  long index;
+  if(index >= 0 && index < 16) {
+    // Normal 8 colours or high intensity - parse as palette 0
+    lookup_colour_ansi(state, index, col);
+  }
+  else if(index >= 16 && index < 232) {
+    // 216-colour cube
+    index -= 16;
+
+    col->blue  = ramp6[index     % 6];
+    col->green = ramp6[index/6   % 6];
+    col->red   = ramp6[index/6/6 % 6];
+  }
+  else if(index >= 232 && index < 256) {
+    // 24 greyscales
+    index -= 232;
 
+    col->red   = ramp24[index];
+    col->green = ramp24[index];
+    col->blue  = ramp24[index];
+  }
+}
+
+static int lookup_colour(const VTermState *state, int palette, const long 
args[], int argcount, VTermColor *col, int *index)
+{
   switch(palette) {
   case 2: // RGB mode - 3 args contain colour values directly
     if(argcount < 3)
@@ -56,28 +78,10 @@ static int lookup_colour(int palette, const long args[], 
int argcount, char is_b
     return 3;
 
   case 5: // XTerm 256-colour mode
-    index = argcount ? CSI_ARG_OR(args[0], -1) : -1;
-
-    if(index >= 0 && index < 16) {
-      // Normal 8 colours or high intensity - parse as palette 0
-      lookup_colour_ansi(index, is_bg, col);
-    }
-    else if(index >= 16 && index < 232) {
-      // 216-colour cube
-      index -= 16;
-
-      col->blue  = ramp6[index     % 6];
-      col->green = ramp6[index/6   % 6];
-      col->red   = ramp6[index/6/6 % 6];
-    }
-    else if(index >= 232 && index < 256) {
-      // 24 greyscales
-      index -= 232;
+    if(index)
+      *index = CSI_ARG_OR(args[0], -1);
 
-      col->red   = ramp24[index];
-      col->green = ramp24[index];
-      col->blue  = ramp24[index];
-    }
+    lookup_colour_palette(state, argcount ? CSI_ARG_OR(args[0], -1) : -1, col);
 
     return argcount ? 1 : 0;
 
@@ -124,12 +128,22 @@ static void set_pen_col_ansi(VTermState *state, VTermAttr 
attr, long col)
 {
   VTermColor *colp = (attr == VTERM_ATTR_BACKGROUND) ? &state->pen.bg : 
&state->pen.fg;
 
-  lookup_colour_ansi(col, attr == VTERM_ATTR_BACKGROUND, colp);
+  lookup_colour_ansi(state, col, colp);
 
   setpenattr_col(state, attr, *colp);
 }
 
-void vterm_state_resetpen(VTermState *state)
+INTERNAL void vterm_state_newpen(VTermState *state)
+{
+  // 90% grey so that pure white is brighter
+  state->default_fg.red = state->default_fg.green = state->default_fg.blue = 
240;
+  state->default_bg.red = state->default_bg.green = state->default_bg.blue = 0;
+
+  for(int col = 0; col < 16; col++)
+    state->colors[col] = ansi_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);
@@ -139,12 +153,13 @@ void vterm_state_resetpen(VTermState *state)
   state->pen.strike = 0;    setpenattr_bool(state, VTERM_ATTR_STRIKE, 0);
   state->pen.font = 0;      setpenattr_int( state, VTERM_ATTR_FONT, 0);
 
-  state->fg_ansi = -1;
+  state->fg_index = -1;
+  state->bg_index = -1;
   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);
 }
 
-void vterm_state_savepen(VTermState *state, int save)
+INTERNAL void vterm_state_savepen(VTermState *state, int save)
 {
   if(save) {
     state->saved.pen = state->pen;
@@ -164,18 +179,35 @@ void vterm_state_savepen(VTermState *state, int save)
   }
 }
 
-void vterm_state_set_default_colors(VTermState *state, VTermColor *default_fg, 
VTermColor *default_bg)
+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)
 {
   state->default_fg = *default_fg;
   state->default_bg = *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_set_bold_highbright(VTermState *state, int bold_is_highbright)
 {
   state->bold_is_highbright = bold_is_highbright;
 }
 
-void vterm_state_setpen(VTermState *state, const long args[], int argcount)
+INTERNAL void vterm_state_setpen(VTermState *state, const long args[], int 
argcount)
 {
   // SGR - ECMA-48 8.3.117
 
@@ -197,8 +229,8 @@ void vterm_state_setpen(VTermState *state, const long 
args[], int argcount)
     case 1: // Bold on
       state->pen.bold = 1;
       setpenattr_bool(state, VTERM_ATTR_BOLD, 1);
-      if(state->fg_ansi > -1 && state->bold_is_highbright)
-        set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, state->fg_ansi + 
(state->pen.bold ? 8 : 0));
+      if(state->fg_index > -1 && state->fg_index < 8 && 
state->bold_is_highbright)
+        set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, state->fg_index + 
(state->pen.bold ? 8 : 0));
       break;
 
     case 3: // Italic on
@@ -270,51 +302,59 @@ void vterm_state_setpen(VTermState *state, const long 
args[], int argcount)
     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;
-      state->fg_ansi = value;
+      state->fg_index = value;
       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
-      state->fg_ansi = -1;
+      state->fg_index = -1;
       if(argcount - argi < 1)
         return;
-      argi += 1 + lookup_colour(CSI_ARG(args[argi+1]), args+argi+2, 
argcount-argi-2, 0, &state->pen.fg);
+      argi += 1 + lookup_colour(state, CSI_ARG(args[argi+1]), args+argi+2, 
argcount-argi-2, &state->pen.fg, &state->fg_index);
       setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg);
       break;
 
     case 39: // Foreground colour default
-      state->fg_ansi = -1;
+      state->fg_index = -1;
       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
-      set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, CSI_ARG(args[argi]) - 40);
+      value = CSI_ARG(args[argi]) - 40;
+      state->bg_index = value;
+      set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, value);
       break;
 
     case 48: // Background colour alternative palette
+      state->bg_index = -1;
       if(argcount - argi < 1)
         return;
-      argi += 1 + lookup_colour(CSI_ARG(args[argi+1]), args+argi+2, 
argcount-argi-2, 1, &state->pen.bg);
+      argi += 1 + lookup_colour(state, CSI_ARG(args[argi+1]), args+argi+2, 
argcount-argi-2, &state->pen.bg, &state->bg_index);
       setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg);
       break;
 
     case 49: // Default background
+      state->bg_index = -1;
       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
-      set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, CSI_ARG(args[argi]) - 90 
+ 8);
+      value = CSI_ARG(args[argi]) - 90 + 8;
+      state->fg_index = value;
+      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
-      set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, CSI_ARG(args[argi]) - 100 
+ 8);
+      value = CSI_ARG(args[argi]) - 100 + 8;
+      state->bg_index = value;
+      set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, value);
       break;
 
     default:
@@ -329,7 +369,58 @@ void vterm_state_setpen(VTermState *state, const long 
args[], int argcount)
   }
 }
 
-int vterm_state_get_penattr(VTermState *state, VTermAttr attr, VTermValue *val)
+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;
+
+  if(state->fg_index >= 0 && state->fg_index < 8)
+    args[argi++] = 30 + state->fg_index;
+  else if(state->fg_index >= 8 && state->fg_index < 16)
+    args[argi++] = 90 + state->fg_index - 8;
+  else if(state->fg_index >= 16 && state->fg_index < 256) {
+    args[argi++] = CSI_ARG_FLAG_MORE|38;
+    args[argi++] = CSI_ARG_FLAG_MORE|5;
+    args[argi++] = state->fg_index;
+  }
+
+  if(state->bg_index >= 0 && state->bg_index < 8)
+    args[argi++] = 40 + state->bg_index;
+  else if(state->bg_index >= 8 && state->bg_index < 16)
+    args[argi++] = 100 + state->bg_index - 8;
+  else if(state->bg_index >= 16 && state->bg_index < 256) {
+    args[argi++] = CSI_ARG_FLAG_MORE|48;
+    args[argi++] = CSI_ARG_FLAG_MORE|5;
+    args[argi++] = state->bg_index;
+  }
+
+  return argi;
+}
+
+int vterm_state_get_penattr(const VTermState *state, VTermAttr attr, 
VTermValue *val)
 {
   switch(attr) {
   case VTERM_ATTR_BOLD:
diff --git a/src/apps/serialconnect/libvterm/src/screen.c 
b/src/apps/serialconnect/libvterm/src/screen.c
index 439d586..c4de59e 100644
--- a/src/apps/serialconnect/libvterm/src/screen.c
+++ b/src/apps/serialconnect/libvterm/src/screen.c
@@ -1,6 +1,7 @@
 #include "vterm_internal.h"
 
 #include <stdio.h>
+#include <string.h>
 
 #include "rect.h"
 #include "utf8.h"
@@ -21,6 +22,11 @@ typedef struct
   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 */
@@ -30,6 +36,8 @@ typedef struct
   ScreenPen pen;
 } ScreenCell;
 
+static int vterm_screen_set_cell(VTermScreen *screen, VTermPos pos, const 
VTermScreenCell *cell);
+
 struct VTermScreen
 {
   VTerm *vt;
@@ -54,22 +62,27 @@ struct VTermScreen
   /* 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(VTermScreen *screen, int row, int col)
+static inline ScreenCell *getcell(const VTermScreen *screen, int row, int col)
 {
-  /* TODO: Bounds checking */
+  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);
-  int row, col;
 
-  for(row = 0; row < new_rows; row++) {
-    for(col = 0; col < new_cols; col++) {
+  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)
@@ -156,49 +169,77 @@ static void damagescreen(VTermScreen *screen)
   damagerect(screen, rect);
 }
 
-static int putglyph(const uint32_t chars[], int width, VTermPos pos, void 
*user)
+static int putglyph(VTermGlyphInfo *info, VTermPos pos, void *user)
 {
   VTermScreen *screen = user;
   ScreenCell *cell = getcell(screen, pos.row, pos.col);
-  int i;
-  int col;
 
-  VTermRect rect = {
-    .start_row = pos.row,
-    .end_row   = pos.row+1,
-    .start_col = pos.col,
-    .end_col   = pos.col+width,
-  };
+  if(!cell)
+    return 0;
 
-  for(i = 0; i < VTERM_MAX_CHARS_PER_CELL && chars[i]; i++) {
-    cell->chars[i] = chars[i];
+  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(col = 1; col < width; col++)
+  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 void copycell(VTermPos dest, VTermPos src, void *user)
+static int moverect_internal(VTermRect dest, VTermRect src, void *user)
 {
   VTermScreen *screen = user;
-  ScreenCell *destcell = getcell(screen, dest.row, dest.col);
-  ScreenCell *srccell = getcell(screen, src.row, src.col);
 
-  *destcell = *srccell;
-}
+  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);
 
-static int moverect_internal(VTermRect dest, VTermRect src, void *user)
-{
-  VTermScreen *screen = user;
+      (screen->callbacks->sb_pushline)(screen->cols, screen->sb_buffer, 
screen->cbdata);
+    }
+  }
 
-  vterm_copy_cells(dest, src, &copycell, screen);
+  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;
 }
@@ -221,22 +262,30 @@ static int moverect_user(VTermRect dest, VTermRect src, 
void *user)
   return 1;
 }
 
-static int erase_internal(VTermRect rect, void *user)
+static int erase_internal(VTermRect rect, int selective, void *user)
 {
   VTermScreen *screen = user;
-  int row, col;
 
-  for(row = rect.start_row; row < rect.end_row; row++)
-    for(col = rect.start_col; col < rect.end_col; col++) {
+  for(int row = rect.start_row; 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, void *user)
+static int erase_user(VTermRect rect, int selective, void *user)
 {
   VTermScreen *screen = user;
 
@@ -245,10 +294,10 @@ static int erase_user(VTermRect rect, void *user)
   return 1;
 }
 
-static int erase(VTermRect rect, void *user)
+static int erase(VTermRect rect, int selective, void *user)
 {
-  erase_internal(rect, user);
-  return erase_user(rect, user);
+  erase_internal(rect, selective, user);
+  return erase_user(rect, 0, user);
 }
 
 static int scrollrect(VTermRect rect, int downward, int rightward, void *user)
@@ -258,48 +307,75 @@ static int scrollrect(VTermRect rect, int downward, int 
rightward, void *user)
   vterm_scroll_rect(rect, downward, rightward,
       moverect_internal, erase_internal, screen);
 
-  if(screen->damage_merge == VTERM_DAMAGE_SCROLL) {
-    if(screen->damaged.start_row != -1 &&
-       !rect_intersects(&rect, &screen->damaged)) {
-      vterm_screen_flush_damage(screen);
-    }
+  if(screen->damage_merge != VTERM_DAMAGE_SCROLL) {
+    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);
+    vterm_scroll_rect(rect, downward, rightward,
+        moverect_user, erase_user, screen);
 
-      screen->pending_scrollrect = rect;
-      screen->pending_scroll_downward  = downward;
-      screen->pending_scroll_rightward = rightward;
-    }
+    return 1;
+  }
 
-    if(screen->damaged.start_row != -1) {
-      if(rect_contains(&rect, &screen->damaged)) {
-        vterm_rect_move(&screen->damaged, -downward, -rightward);
-        rect_clip(&screen->damaged, &rect);
-      }
-      else {
-        fprintf(stderr, "TODO: scrollrect split damage\n");
-      }
-    }
+  if(screen->damaged.start_row != -1 &&
+     !rect_intersects(&rect, &screen->damaged)) {
+    vterm_screen_flush_damage(screen);
+  }
 
-    return 1;
+  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);
 
-  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_user, erase_user, screen);
+  if(screen->damaged.start_row == -1)
+    return 1;
+
+  if(rect_contains(&rect, &screen->damaged)) {
+    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 {
+    fprintf(stderr, "TODO: Just flush and redo damaged=" STRFrect " rect=" 
STRFrect "\n",
+        ARGSrect(screen->damaged), ARGSrect(rect));
+  }
 
   return 1;
 }
@@ -401,25 +477,52 @@ static int bell(void *user)
   return 0;
 }
 
-static int resize(int new_rows, int new_cols, void *user)
+static int resize(int new_rows, int new_cols, VTermPos *delta, void *user)
 {
   VTermScreen *screen = user;
-  int old_rows, old_cols;
 
   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 };
+    for(pos.row = old_rows - 1; pos.row >= new_rows; pos.row--)
+      if(!vterm_screen_is_eol(screen, pos))
+        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];
 
-  old_rows = screen->rows;
-  old_cols = screen->cols;
-
   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,
@@ -431,6 +534,34 @@ static int resize(int new_rows, int new_cols, void *user)
   }
 
   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,
@@ -446,6 +577,37 @@ static int resize(int new_rows, int new_cols, void *user)
   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,
@@ -456,18 +618,17 @@ static VTermStateCallbacks state_cbs = {
   .setmousefunc = &setmousefunc,
   .bell         = &bell,
   .resize       = &resize,
+  .setlineinfo  = &setlineinfo,
 };
 
 static VTermScreen *screen_new(VTerm *vt)
 {
   VTermState *state = vterm_obtain_state(vt);
-  VTermScreen *screen;
-  int rows, cols;
-
   if(!state)
     return NULL;
 
-  screen = vterm_allocator_malloc(vt, sizeof(VTermScreen));
+  VTermScreen *screen = vterm_allocator_malloc(vt, sizeof(VTermScreen));
+  int rows, cols;
 
   vterm_get_size(vt, &rows, &cols);
 
@@ -485,17 +646,21 @@ static VTermScreen *screen_new(VTerm *vt)
 
   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;
 }
 
-void vterm_screen_free(VTermScreen *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);
 }
 
@@ -507,12 +672,10 @@ void vterm_screen_reset(VTermScreen *screen, int hard)
   vterm_screen_flush_damage(screen);
 }
 
-static size_t _get_chars(VTermScreen *screen, const int utf8, void *buffer, 
size_t len, const VTermRect rect)
+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;
-  int row, col;
-  int i;
 
 #define PUT(c)                                             \
   if(utf8) {                                               \
@@ -529,8 +692,8 @@ static size_t _get_chars(VTermScreen *screen, const int 
utf8, void *buffer, size
       outpos++;                                            \
   }
 
-  for(row = rect.start_row; row < rect.end_row; row++) {
-    for(col = rect.start_col; col < rect.end_col; col++) {
+  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)
@@ -544,7 +707,7 @@ static size_t _get_chars(VTermScreen *screen, const int 
utf8, void *buffer, size
           PUT(UNICODE_SPACE);
           padding--;
         }
-        for(i = 0; i < VTERM_MAX_CHARS_PER_CELL && cell->chars[i]; i++) {
+        for(int i = 0; i < VTERM_MAX_CHARS_PER_CELL && cell->chars[i]; i++) {
           PUT(cell->chars[i]);
         }
       }
@@ -559,23 +722,24 @@ static size_t _get_chars(VTermScreen *screen, const int 
utf8, void *buffer, size
   return outpos;
 }
 
-size_t vterm_screen_get_chars(VTermScreen *screen, uint32_t *chars, size_t 
len, const VTermRect rect)
+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(VTermScreen *screen, char *str, size_t len, const 
VTermRect 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 */
-void vterm_screen_get_cell(VTermScreen *screen, VTermPos pos, VTermScreenCell 
*cell)
+int vterm_screen_get_cell(const VTermScreen *screen, VTermPos pos, 
VTermScreenCell *cell)
 {
   ScreenCell *intcell = getcell(screen, pos.row, pos.col);
-  int i;
+  if(!intcell)
+    return 0;
 
-  for(i = 0; ; i++) {
+  for(int i = 0; ; i++) {
     cell->chars[i] = intcell->chars[i];
     if(!intcell->chars[i])
       break;
@@ -589,6 +753,9 @@ void vterm_screen_get_cell(VTermScreen *screen, VTermPos 
pos, VTermScreenCell *c
   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;
 
@@ -597,9 +764,42 @@ void vterm_screen_get_cell(VTermScreen *screen, VTermPos 
pos, VTermScreenCell *c
     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(VTermScreen *screen, VTermPos pos)
+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++) {
@@ -613,11 +813,10 @@ int vterm_screen_is_eol(VTermScreen *screen, VTermPos pos)
 
 VTermScreen *vterm_obtain_screen(VTerm *vt)
 {
-  VTermScreen *screen;
   if(vt->screen)
     return vt->screen;
 
-  screen = screen_new(vt);
+  VTermScreen *screen = screen_new(vt);
   vt->screen = screen;
 
   return screen;
@@ -662,3 +861,55 @@ 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_equal(a->pen.fg, 
b->pen.fg))
+    return 1;
+  if((attrs & VTERM_ATTR_BACKGROUND_MASK) && !vterm_color_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;
+}
diff --git a/src/apps/serialconnect/libvterm/src/state.c 
b/src/apps/serialconnect/libvterm/src/state.c
index 47c0a49..e42be53 100644
--- a/src/apps/serialconnect/libvterm/src/state.c
+++ b/src/apps/serialconnect/libvterm/src/state.c
@@ -7,19 +7,28 @@
 
 #include "utf8.h"
 
-#ifdef DEBUG
+#if defined(DEBUG) && DEBUG > 1
 # define DEBUG_GLYPH_COMBINE
 #endif
 
-#define MOUSE_WANT_DRAG 0x01
-#define MOUSE_WANT_MOVE 0x02
+#define MOUSE_WANT_CLICK 0x01
+#define MOUSE_WANT_DRAG  0x02
+#define MOUSE_WANT_MOVE  0x04
 
 /* 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)(chars, width, pos, state->cbdata))
+    if((*state->callbacks->putglyph)(&info, pos, state->cbdata))
       return;
 
   fprintf(stderr, "libvterm: Unhandled putglyph U+%04x at (%d,%d)\n", 
chars[0], pos.col, pos.row);
@@ -38,10 +47,10 @@ static void updatecursor(VTermState *state, VTermPos 
*oldpos, int cancel_phantom
       return;
 }
 
-static void erase(VTermState *state, VTermRect rect)
+static void erase(VTermState *state, VTermRect rect, int selective)
 {
   if(state->callbacks && state->callbacks->erase)
-    if((*state->callbacks->erase)(rect, state->cbdata))
+    if((*state->callbacks->erase)(rect, selective, state->cbdata))
       return;
 }
 
@@ -54,17 +63,17 @@ static VTermState *vterm_state_new(VTerm *vt)
   state->rows = vt->rows;
   state->cols = vt->cols;
 
-  // 90% grey so that pure white is brighter
-  state->default_fg.red = state->default_fg.green = state->default_fg.blue = 
240;
-  state->default_bg.red = state->default_bg.green = state->default_bg.blue = 0;
+  vterm_state_newpen(state);
 
   state->bold_is_highbright = 0;
 
   return state;
 }
 
-void vterm_state_free(VTermState *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);
 }
@@ -74,6 +83,20 @@ static void scroll(VTermState *state, VTermRect rect, int 
downward, int rightwar
   if(!downward && !rightward)
     return;
 
+  // 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;
@@ -85,12 +108,12 @@ static void scroll(VTermState *state, VTermRect rect, int 
downward, int rightwar
 
 static void linefeed(VTermState *state)
 {
-  if(state->pos.row == SCROLLREGION_END(state) - 1) {
+  if(state->pos.row == SCROLLREGION_BOTTOM(state) - 1) {
     VTermRect rect = {
-      .start_row = state->scrollregion_start,
-      .end_row   = SCROLLREGION_END(state),
-      .start_col = 0,
-      .end_col   = state->cols,
+      .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);
@@ -107,7 +130,9 @@ static void grow_combine_buffer(VTermState *state)
   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)
@@ -131,7 +156,7 @@ static int is_col_tabstop(VTermState *state, int col)
 static void tab(VTermState *state, int count, int direction)
 {
   while(count--)
-    while(state->pos.col >= 0 && state->pos.col < state->cols-1) {
+    while(state->pos.col >= 0 && state->pos.col < THISROWWIDTH(state)-1) {
       state->pos.col += direction;
 
       if(is_col_tabstop(state, state->pos.col))
@@ -139,10 +164,43 @@ static void tab(VTermState *state, int count, int 
direction)
     }
 }
 
+#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;
-  uint32_t* chars;
 
   VTermPos oldpos = state->pos;
 
@@ -150,22 +208,27 @@ static int on_text(const char bytes[], size_t len, void 
*user)
   uint32_t codepoints[len];
   int npoints = 0;
   size_t eaten = 0;
-  int i = 0;
 
   VTermEncodingInstance *encoding =
+    state->gsingle_set     ? &state->encoding[state->gsingle_set] :
     !(bytes[eaten] & 0x80) ? &state->encoding[state->gl_set] :
-    state->vt->is_utf8     ? &state->encoding_utf8 :
+    state->vt->mode.utf8   ? &state->encoding_utf8 :
                              &state->encoding[state->gr_set];
 
   (*encoding->enc->decode)(encoding->enc, encoding->data,
-      codepoints, &npoints, len, bytes, &eaten, len);
+      codepoints, &npoints, state->gsingle_set ? 1 : len,
+      bytes, &eaten, len);
+
+  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) {
-      size_t saved_i = 0;
 #ifdef DEBUG_GLYPH_COMBINE
       int printpos;
       printf("DEBUG: COMBINING SPLIT GLYPH of chars {");
@@ -175,6 +238,7 @@ static int on_text(const char bytes[], size_t len, void 
*user)
 #endif
 
       /* Find where we need to append these combining chars */
+      int saved_i = 0;
       while(state->combine_chars[saved_i])
         saved_i++;
 
@@ -206,13 +270,13 @@ static int on_text(const char bytes[], size_t len, void 
*user)
     // Try to find combining characters following this
     int glyph_starts = i;
     int glyph_ends;
-    int width = 0;
-
     for(glyph_ends = i + 1; glyph_ends < npoints; glyph_ends++)
       if(!vterm_unicode_is_combining(codepoints[glyph_ends]))
         break;
 
-    chars = alloca(sizeof(uint32_t) * (glyph_ends - glyph_starts + 1));
+    int width = 0;
+
+    uint32_t chars[glyph_ends - glyph_starts + 1];
 
     for( ; i < glyph_ends; i++) {
       chars[i - glyph_starts] = codepoints[i];
@@ -223,16 +287,14 @@ static int on_text(const char bytes[], size_t len, void 
*user)
     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) {
+    if(state->at_phantom || state->pos.col + width > THISROWWIDTH(state)) {
       linefeed(state);
       state->pos.col = 0;
       state->at_phantom = 0;
@@ -247,16 +309,17 @@ static int on_text(const char bytes[], size_t len, void 
*user)
         .start_row = state->pos.row,
         .end_row   = state->pos.row + 1,
         .start_col = state->pos.col,
-        .end_col   = state->cols,
+        .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 */
-      unsigned int save_i;
+      int save_i;
       for(save_i = 0; chars[save_i]; save_i++) {
         if(save_i >= state->combine_chars_size)
           grow_combine_buffer(state);
@@ -269,7 +332,7 @@ static int on_text(const char bytes[], size_t len, void 
*user)
       state->combine_pos = state->pos;
     }
 
-    if(state->pos.col + width >= state->cols) {
+    if(state->pos.col + width >= THISROWWIDTH(state)) {
       if(state->mode.autowrap)
         state->at_phantom = 1;
     }
@@ -338,12 +401,12 @@ static int on_control(unsigned char control, void *user)
     break;
 
   case 0x8d: // RI - ECMA-48 8.3.104
-    if(state->pos.row == state->scrollregion_start) {
+    if(state->pos.row == state->scrollregion_top) {
       VTermRect rect = {
-        .start_row = state->scrollregion_start,
-        .end_row   = SCROLLREGION_END(state),
-        .start_col = 0,
-        .end_col   = state->cols,
+        .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);
@@ -352,6 +415,14 @@ static int on_control(unsigned char control, void *user)
         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:
     return 0;
   }
@@ -375,7 +446,7 @@ static void output_mouse(VTermState *state, int code, int 
pressed, int modifiers
     if(!pressed)
       code = 3;
 
-    vterm_push_output_sprintf(state->vt, "\e[M%c%c%c",
+    vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "M%c%c%c",
         (code | modifiers) + 0x20, col + 0x21, row + 0x21);
     break;
 
@@ -389,13 +460,14 @@ static void output_mouse(VTermState *state, int code, int 
pressed, int modifiers
       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(state->vt, "\e[M%s", utf8);
+      vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "M%s", utf8);
     }
     break;
 
   case MOUSE_SGR:
-    vterm_push_output_sprintf(state->vt, "\e[<%d;%d;%d%c",
+    vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "<%d;%d;%d%c",
         code | modifiers, col + 1, row + 1, pressed ? 'M' : 'm');
     break;
 
@@ -403,7 +475,7 @@ static void output_mouse(VTermState *state, int code, int 
pressed, int modifiers
     if(!pressed)
       code = 3;
 
-    vterm_push_output_sprintf(state->vt, "\e[%d;%d;%dM",
+    vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%d;%d;%dM",
         code | modifiers, col + 1, row + 1);
     break;
   }
@@ -452,67 +524,24 @@ static void mousefunc(int col, int row, int button, int 
pressed, int modifiers,
 
 static int settermprop_bool(VTermState *state, VTermProp prop, int v)
 {
-  VTermValue val;
-  val.boolean = v;
-
-#ifdef DEBUG
-  if(VTERM_VALUETYPE_BOOL != vterm_get_prop_type(prop)) {
-    fprintf(stderr, "Cannot set prop %d as it has type %d, not type BOOL\n",
-        prop, vterm_get_prop_type(prop));
-    return -1;
-  }
-#endif
-
-  if(state->callbacks && state->callbacks->settermprop)
-    if((*state->callbacks->settermprop)(prop, &val, state->cbdata))
-      return 1;
-
-  return 0;
+  VTermValue val = { .boolean = v };
+  return vterm_state_set_termprop(state, prop, &val);
 }
 
 static int settermprop_int(VTermState *state, VTermProp prop, int v)
 {
-  VTermValue val;
-  val.number = v;
-
-#ifdef DEBUG
-  if(VTERM_VALUETYPE_INT != vterm_get_prop_type(prop)) {
-    fprintf(stderr, "Cannot set prop %d as it has type %d, not type int\n",
-        prop, vterm_get_prop_type(prop));
-    return -1;
-  }
-#endif
-
-  if(state->callbacks && state->callbacks->settermprop)
-    if((*state->callbacks->settermprop)(prop, &val, state->cbdata))
-      return 1;
-
-  return 0;
+  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];
-  VTermValue val;
-
   strncpy(strvalue, str, len);
   strvalue[len] = 0;
 
-  val.string = strvalue;
-
-#ifdef DEBUG
-  if(VTERM_VALUETYPE_STRING != vterm_get_prop_type(prop)) {
-    fprintf(stderr, "Cannot set prop %d as it has type %d, not type STRING\n",
-        prop, vterm_get_prop_type(prop));
-    return -1;
-  }
-#endif
-
-  if(state->callbacks && state->callbacks->settermprop)
-    if((*state->callbacks->settermprop)(prop, &val, state->cbdata))
-      return 1;
-
-  return 0;
+  VTermValue val = { .string = strvalue };
+  return vterm_state_set_termprop(state, prop, &val);
 }
 
 static void savecursor(VTermState *state, int save)
@@ -529,13 +558,10 @@ static void savecursor(VTermState *state, int save)
     VTermPos oldpos = state->pos;
 
     state->pos = state->saved.pos;
-    state->mode.cursor_visible = state->saved.mode.cursor_visible;
-    state->mode.cursor_blink   = state->saved.mode.cursor_blink;
-    state->mode.cursor_shape   = state->saved.mode.cursor_shape;
 
-    settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, 
state->mode.cursor_visible);
-    settermprop_bool(state, VTERM_PROP_CURSORBLINK,   
state->mode.cursor_blink);
-    settermprop_int (state, VTERM_PROP_CURSORSHAPE,   
state->mode.cursor_shape);
+    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);
 
@@ -543,25 +569,6 @@ static void savecursor(VTermState *state, int save)
   }
 }
 
-static void altscreen(VTermState *state, int alt)
-{
-  /* Only store that we're on the alternate screen if the usercode said it
-   * switched */
-  if(!settermprop_bool(state, VTERM_PROP_ALTSCREEN, alt))
-    return;
-
-  state->mode.alt_screen = alt;
-  if(alt) {
-    VTermRect rect = {
-      .start_row = 0,
-      .start_col = 0,
-      .end_row = state->rows,
-      .end_col = state->cols,
-    };
-    erase(state, rect);
-  }
-}
-
 static int on_escape(const char *bytes, size_t len, void *user)
 {
   VTermState *state = user;
@@ -570,17 +577,59 @@ static int on_escape(const char *bytes, size_t len, void 
*user)
    * 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 < state->cols; pos.col++)
+          for(pos.col = 0; pos.col < ROWWIDTH(state, pos.row); pos.col++)
             putglyph(state, E, 1, pos);
         break;
       }
@@ -616,6 +665,9 @@ static int on_escape(const char *bytes, size_t len, void 
*user)
     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;
@@ -641,6 +693,18 @@ static int on_escape(const char *bytes, size_t len, void 
*user)
     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;
   }
@@ -670,7 +734,7 @@ static void set_dec_mode(VTermState *state, int num, int 
val)
     state->mode.cursor = val;
     break;
 
-  case 5:
+  case 5: // DECSCNM - screen mode
     settermprop_bool(state, VTERM_PROP_REVERSE, val);
     break;
 
@@ -678,8 +742,8 @@ static void set_dec_mode(VTermState *state, int num, int 
val)
     {
       VTermPos oldpos = state->pos;
       state->mode.origin = val;
-      state->pos.row = state->mode.origin ? state->scrollregion_start : 0;
-      state->pos.col = 0;
+      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;
@@ -689,15 +753,24 @@ static void set_dec_mode(VTermState *state, int num, int 
val)
     break;
 
   case 12:
-    state->mode.cursor_blink = val;
     settermprop_bool(state, VTERM_PROP_CURSORBLINK, val);
     break;
 
   case 25:
-    state->mode.cursor_visible = val;
     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:
@@ -706,7 +779,7 @@ static void set_dec_mode(VTermState *state, int num, int 
val)
       state->mouse_row     = 0;
       state->mouse_buttons = 0;
 
-      state->mouse_flags = 0;
+      state->mouse_flags = MOUSE_WANT_CLICK;
       state->mouse_protocol = MOUSE_X10;
 
       if(num == 1002)
@@ -714,6 +787,9 @@ static void set_dec_mode(VTermState *state, int num, int 
val)
       if(num == 1003)
         state->mouse_flags |= MOUSE_WANT_MOVE;
     }
+    else {
+      state->mouse_flags = 0;
+    }
 
     if(state->callbacks && state->callbacks->setmousefunc)
       (*state->callbacks->setmousefunc)(val ? mousefunc : NULL, state, 
state->cbdata);
@@ -733,7 +809,7 @@ static void set_dec_mode(VTermState *state, int num, int 
val)
     break;
 
   case 1047:
-    altscreen(state, val);
+    settermprop_bool(state, VTERM_PROP_ALTSCREEN, val);
     break;
 
   case 1048:
@@ -741,7 +817,7 @@ static void set_dec_mode(VTermState *state, int num, int 
val)
     break;
 

[ *** diff truncated: 1159 lines dropped *** ]


############################################################################

Revision:    hrev47669
Commit:      6a545a8eb1e3dd624c7a473fe9965a4c08e5a54f
URL:         http://cgit.haiku-os.org/haiku/commit/?id=6a545a8
Author:      Adrien Destugues <pulkomandy@xxxxxxxxxxxxx>
Date:        Mon Aug 11 21:13:04 2014 UTC

libvterm: C89 backport.

* As a separate commit so next time the update will be easier...

----------------------------------------------------------------------------


Other related posts: