[master] 5e84ab6 Bite the bullet and import Ed Schouten's terminal emulation "Teken"

Poul-Henning Kamp phk at FreeBSD.org
Wed Apr 4 21:49:10 UTC 2018


commit 5e84ab6e75f59f46df7497cc6afd2f5cc5fac48d
Author: Poul-Henning Kamp <phk at FreeBSD.org>
Date:   Wed Apr 4 21:37:01 2018 +0000

    Bite the bullet and import Ed Schouten's terminal emulation "Teken"
    
    This allows us to use TERM=xterm.

diff --git a/bin/varnishtest/Makefile.am b/bin/varnishtest/Makefile.am
index c2c81d7..06f2259 100644
--- a/bin/varnishtest/Makefile.am
+++ b/bin/varnishtest/Makefile.am
@@ -30,6 +30,12 @@ varnishtest_SOURCES = \
 		cmds.h \
 		vmods.h \
 		vtc.h \
+		teken.c \
+		teken.h \
+		teken_scs.h \
+		teken_subr.h \
+		teken_subr_compat.h \
+		teken_wcwidth.h \
 		vtc.c \
 		vtc_barrier.c \
 		vtc_client.c \
@@ -51,7 +57,6 @@ varnishtest_SOURCES = \
 		vtc_proxy.c \
 		vtc_server.c \
 		vtc_subr.c \
-		vtc_term.c \
 		vtc_varnish.c
 
 varnishtest_LDADD = \
@@ -67,4 +72,16 @@ varnishtest_CFLAGS = \
 		-DTOP_BUILDDIR='"${top_builddir}"'
 
 EXTRA_DIST = $(top_srcdir)/bin/varnishtest/tests/*.vtc \
-	$(top_srcdir)/bin/varnishtest/tests/README
+	$(top_srcdir)/bin/varnishtest/tests/README \
+	$(top_srcdir)/bin/varnishtest/gensequences \
+	$(top_srcdir)/bin/varnishtest/sequences \
+	$(top_srcdir)/bin/varnishtest/teken.3
+
+teken_state.h:	$(srcdir)/sequences $(srcdir)/gensequences
+	awk -f $(srcdir)/gensequences $(srcdir)/sequences \
+	    > $(builddir)/teken_state.h
+
+BUILT_SOURCES = teken_state.h
+
+CLEANFILES = $(BUILT_SOURCES)
+
diff --git a/bin/varnishtest/flint.lnt b/bin/varnishtest/flint.lnt
index 25eace2..fa51cc8 100644
--- a/bin/varnishtest/flint.lnt
+++ b/bin/varnishtest/flint.lnt
@@ -1,4 +1,6 @@
 
++libh(teken/teken.h)
+
 // Tell FlexeLint when these don't return
 -function(exit, vtc_fatal)
 -function(__assert(1), vtc_log(2))
@@ -8,6 +10,8 @@
 -emacro({779}, ENC)		// String constant in comparison operator '!='
 -emacro({506}, CHKFRAME)	// Constant value Boolean
 
+-esym(522, teken_subr_*)
+
 -esym(850, av)
 
 -esym(534, snprintf)		// Only for varnishtest, and not really nice
diff --git a/bin/varnishtest/flint.sh b/bin/varnishtest/flint.sh
index 49f5e2d..7cc4f26 100755
--- a/bin/varnishtest/flint.sh
+++ b/bin/varnishtest/flint.sh
@@ -4,6 +4,7 @@ FLOPS='
 	-DTOP_BUILDDIR="foo"
 	-I../../lib/libvgz
 	*.c
+	teken/teken.c
 '
 
 . ../../tools/flint_skel.sh
diff --git a/bin/varnishtest/gensequences b/bin/varnishtest/gensequences
new file mode 100644
index 0000000..83a3d10
--- /dev/null
+++ b/bin/varnishtest/gensequences
@@ -0,0 +1,157 @@
+#!/usr/bin/awk -f
+
+#-
+# Copyright (c) 2008-2009 Ed Schouten <ed at FreeBSD.org>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# $FreeBSD: head/sys/teken/gensequences 223574 2011-06-26 18:25:10Z ed $
+
+function die(msg) {
+	print msg;
+	exit 1;
+}
+
+function cchar(str) {
+	if (str == "^[")
+		return "\\x1B";
+
+	return str;
+}
+
+BEGIN {
+FS = "\t+"
+
+while (getline > 0) {
+	if (NF == 0 || $1 ~ /^#/)
+		continue;
+
+	if (NF != 3 && NF != 4)
+		die("Invalid line layout: " NF " columns");
+
+	split($3, sequence, " +");
+	nsequences = 0;
+	for (s in sequence)
+		nsequences++;
+
+	prefix = "";
+	l_prefix_name[""] = "teken_state_init";
+	for (i = 1; i < nsequences; i++) {
+		n = prefix sequence[i];
+		l_prefix_parent[n] = prefix;
+		l_prefix_suffix[n] = sequence[i];
+		if (!l_prefix_name[n])
+			l_prefix_name[n] = "teken_state_" ++npr;
+		prefix = n;
+	}
+
+	suffix = sequence[nsequences];
+	cmd = prefix suffix;
+
+	# Fill lists
+	if (l_cmd_name[cmd] != "")
+		die(cmd " already exists");
+	l_cmd_prefix[cmd] = prefix;
+	l_cmd_suffix[cmd] = suffix;
+	l_cmd_args[cmd] = $4;
+	l_cmd_abbr[cmd] = $1;
+	l_cmd_name[cmd] = $2;
+	l_cmd_c_name[cmd] = "teken_subr_" tolower($2);
+	gsub(" ", "_", l_cmd_c_name[cmd]);
+
+	if ($4 != "")
+		l_prefix_numbercmds[prefix]++;
+}
+
+print "/* Generated file. Do not edit. */";
+print "";
+
+for (p in l_prefix_name) {
+	if (l_prefix_name[p] != "teken_state_init")
+		print "static teken_state_t	" l_prefix_name[p] ";";
+}
+
+for (p in l_prefix_name) {
+	print "";
+	print "/* '" p "' */";
+	print "static void";
+	print l_prefix_name[p] "(teken_t *t, teken_char_t c)";
+	print "{";
+
+	if (l_prefix_numbercmds[p] > 0) {
+		print "";
+		print "\tif (teken_state_numbers(t, c))";
+		print "\t\treturn;";
+	}
+
+	print "";
+	print "\tswitch (c) {";
+	for (c in l_cmd_prefix) {
+		if (l_cmd_prefix[c] != p)
+			continue;
+
+		print "\tcase '" cchar(l_cmd_suffix[c]) "': /* " l_cmd_abbr[c] ": " l_cmd_name[c] " */";
+
+		if (l_cmd_args[c] == "v") {
+			print "\t\t" l_cmd_c_name[c] "(t, t->t_curnum, t->t_nums);";
+		} else {
+			printf "\t\t%s(t", l_cmd_c_name[c];
+			split(l_cmd_args[c], args, " ");
+			for (a = 1; args[a] != ""; a++) {
+				if (args[a] == "n")
+					printf ", (t->t_curnum < %d || t->t_nums[%d] == 0) ? 1 : t->t_nums[%d]", a, (a - 1), (a - 1);
+				else if (args[a] == "r")
+					printf ", t->t_curnum < %d ? 0 : t->t_nums[%d]", a, (a - 1);
+				else
+					die("Invalid argument type: " args[a]);
+			}
+			print ");";
+		}
+		print "\t\tbreak;";
+	}
+	for (pc in l_prefix_parent) {
+		if (l_prefix_parent[pc] != p)
+			continue;
+		print "\tcase '" cchar(l_prefix_suffix[pc]) "':";
+		print "\t\tteken_state_switch(t, " l_prefix_name[pc] ");";
+		print "\t\treturn;";
+	}
+
+	print "\tdefault:";
+	if (l_prefix_name[p] == "teken_state_init") {
+		print "\t\tteken_subr_regular_character(t, c);";
+	} else {
+		print "\t\tteken_printf(\"Unsupported sequence in " l_prefix_name[p] ": %u\\n\", (unsigned int)c);";
+	}
+	print "\t\tbreak;";
+
+	print "\t}";
+
+	if (l_prefix_name[p] != "teken_state_init") {
+		print "";
+		print "\tteken_state_switch(t, teken_state_init);";
+	}
+	print "}";
+}
+
+}
diff --git a/bin/varnishtest/sequences b/bin/varnishtest/sequences
new file mode 100644
index 0000000..af92df0
--- /dev/null
+++ b/bin/varnishtest/sequences
@@ -0,0 +1,115 @@
+#-
+# Copyright (c) 2008-2009 Ed Schouten <ed at FreeBSD.org>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# $FreeBSD: head/sys/teken/sequences 322662 2017-08-18 15:40:40Z bde $
+
+# File format is as follows:
+#	Abbr		Abbreviation of sequence name
+#	Name		Sequence name (will be converted to C function name)
+#	Sequence	Bytes that form the sequence
+#	Arguments	Standard value of arguments passed to this sequence
+#			- `n' non-zero number (0 gets converted to 1)
+#			- `r' regular numeric argument
+#			- `v' means a variable number of arguments
+
+# Abbr	Name					Sequence	Arguments
+CBT	Cursor Backward Tabulation		^[ [ Z		n
+CHT	Cursor Forward Tabulation		^[ [ I		n
+CNL	Cursor Next Line			^[ [ E		n
+CPL	Cursor Previous Line			^[ [ F		n
+CPR	Cursor Position Report			^[ [ n		r
+CUB	Cursor Backward				^[ [ D		n
+CUD	Cursor Down				^[ [ B		n
+CUD	Cursor Down				^[ [ e		n
+CUF	Cursor Forward				^[ [ C		n
+CUF	Cursor Forward				^[ [ a		n
+CUP	Cursor Position				^[ [ H		n n
+CUP	Cursor Position				^[ [ f		n n
+CUU	Cursor Up				^[ [ A		n
+DA1	Primary Device Attributes		^[ [ c		r
+DA2	Secondary Device Attributes		^[ [ > c	r
+DC	Delete character			^[ [ P		n
+DCS	Device Control String			^[ P
+DECALN	Alignment test				^[ # 8
+DECDHL	Double Height Double Width Line Top	^[ # 3
+DECDHL	Double Height Double Width Line Bottom	^[ # 4
+DECDWL	Single Height Double Width Line		^[ # 6
+DECKPAM	Keypad application mode			^[ =
+DECKPNM	Keypad numeric mode			^[ >
+DECRC	Restore cursor				^[ 8
+DECRC	Restore cursor				^[ [ u
+DECRM	Reset DEC mode				^[ [ ? l	r
+DECSC	Save cursor				^[ 7
+DECSC	Save cursor				^[ [ s
+DECSM	Set DEC mode				^[ [ ? h	r
+DECSTBM	Set top and bottom margins		^[ [ r		r r
+DECSWL	Single Height Single Width Line		^[ # 5
+DL	Delete line				^[ [ M		n
+DSR	Device Status Report			^[ [ ? n	r
+ECH	Erase character				^[ [ X		n
+ED	Erase display				^[ [ J		r
+EL	Erase line				^[ [ K		r
+G0SCS0	G0 SCS Special Graphics			^[ ( 0
+G0SCS1	G0 SCS US ASCII				^[ ( 1
+G0SCS2	G0 SCS Special Graphics			^[ ( 2
+G0SCSA	G0 SCS UK National			^[ ( A
+G0SCSB	G0 SCS US ASCII				^[ ( B
+G1SCS0	G1 SCS Special Graphics			^[ ) 0
+G1SCS1	G1 SCS US ASCII				^[ ) 1
+G1SCS2	G1 SCS Special Graphics			^[ ) 2
+G1SCSA	G1 SCS UK National			^[ ) A
+G1SCSB	G1 SCS US ASCII				^[ ) B
+HPA	Horizontal Position Absolute		^[ [ G		n
+HPA	Horizontal Position Absolute		^[ [ `		n
+HTS	Horizontal Tab Set			^[ H
+ICH	Insert character			^[ [ @		n
+IL	Insert line				^[ [ L		n
+IND	Index					^[ D
+NEL	Next line				^[ E
+OSC	Operating System Command		^[ ]
+RI	Reverse index				^[ M
+RIS	Reset to Initial State			^[ c
+RM	Reset Mode				^[ [ l		r
+SD	Pan Up					^[ [ T		n
+SGR	Set Graphic Rendition			^[ [ m		v
+SM	Set Mode				^[ [ h		r
+ST	String Terminator			^[ \\
+SU	Pan Down				^[ [ S		n
+TBC	Tab Clear				^[ [ g		r
+VPA	Vertical Position Absolute		^[ [ d		n
+
+# Cons25 compatibility sequences
+C25BLPD	Cons25 set bell pitch duration		^[ [ = B	r r
+C25BORD	Cons25 set border			^[ [ = A	r
+C25DBG	Cons25 set default background		^[ [ = G	r
+C25DFG	Cons25 set default foreground		^[ [ = F	r
+C25GCS	Cons25 set global cursor shape		^[ [ = C	v
+C25LCT	Cons25 set local cursor type		^[ [ = S	r
+C25MODE	Cons25 set terminal mode		^[ [ = T	r
+C25SGR	Cons25 set graphic rendition		^[ [ x		r r
+C25VTSW	Cons25 switch virtual terminal		^[ [ z		r
+
+# VT52 compatibility
+#DECID	VT52 DECID				^[ Z
diff --git a/bin/varnishtest/teken.3 b/bin/varnishtest/teken.3
new file mode 100644
index 0000000..c1ece85
--- /dev/null
+++ b/bin/varnishtest/teken.3
@@ -0,0 +1,234 @@
+.\" Copyright (c) 2011 Ed Schouten <ed at FreeBSD.org>
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD: head/sys/teken/libteken/teken.3 315418 2017-03-16 16:40:54Z bde $
+.\"
+.Dd Mar 13, 2017
+.Dt TEKEN 3
+.Os
+.Sh NAME
+.Nm teken
+.Nd xterm-like terminal emulation interface
+.Sh LIBRARY
+.Lb libteken
+.Sh SYNOPSIS
+.In teken.h
+.Ft void
+.Fn teken_init "teken_t *t" "const teken_funcs_t *funcs" "void *thunk"
+.Ft void
+.Fn teken_input "teken_t *t" "const void *buf" "size_t nbytes"
+.Ft const teken_pos_t *
+.Fn teken_get_winsize "teken_t *t"
+.Ft void
+.Fn teken_set_winsize "teken_t *t" "const teken_pos_t *size"
+.Ft const teken_pos_t *
+.Fn teken_get_cursor "teken_t *t"
+.Ft void
+.Fn teken_set_cursor "teken_t *t" "const teken_pos_t *pos"
+.Ft const teken_attr_t *
+.Fn teken_get_curattr "teken_t *t"
+.Ft void
+.Fn teken_set_curattr "teken_t *t" "const teken_attr_t *attr"
+.Ft const teken_attr_t *
+.Fn teken_get_defattr "teken_t *t"
+.Ft void
+.Fn teken_set_defattr "teken_t *t" "const teken_attr_t *attr"
+.Ft const char *
+.Fn teken_get_sequence "teken_t *t" "unsigned int id"
+.Ft teken_color_t
+.Fn teken_256to16 "teken_color_t color"
+.Ft teken_color_t
+.Fn teken_256to8 "teken_color_t color"
+.Ft void
+.Fn teken_get_defattr_cons25 "teken_t *t" "int *fg" "int *bg"
+.Ft void
+.Fn teken_set_8bit "teken_t *t"
+.Ft void
+.Fn teken_set_cons25 "teken_t *t"
+.Sh DESCRIPTION
+The
+.Nm
+library implements the input parser of a 256-color xterm-like terminal.
+It converts a stream of UTF-8 encoded characters into a series of
+primitive drawing instructions that can be used by a console driver or
+terminal emulator to render a terminal application.
+.Pp
+The
+.Fn teken_init
+function is used to initialize terminal state object
+.Fa t ,
+having type
+.Vt teken_t .
+The supplied
+.Vt teken_funcs_t
+structure
+.Fa funcs
+contains a set of callback functions, which are called when supplying
+data to
+.Fn teken_input .
+The
+.Fa thunk
+argument stores an arbitrary pointer, which is passed to each invocation
+of the callback functions.
+.Pp
+The
+.Vt teken_funcs_t
+structure stores the following callbacks:
+.Bd -literal -offset indent
+typedef struct {
+	tf_bell_t     *tf_bell;     /* Audible/visible bell. */
+	tf_cursor_t   *tf_cursor;   /* Move cursor to x/y. */
+	tf_putchar_t  *tf_putchar;  /* Put Unicode character at x/y. */
+	tf_fill_t     *tf_fill;     /* Fill rectangle with character. */
+	tf_copy_t     *tf_copy;     /* Copy rectangle to new location. */
+	tf_param_t    *tf_param;    /* Miscellaneous options. */
+	tf_respond_t  *tf_respond;  /* Send response string to user. */
+} teken_funcs_t;
+.Ed
+.Pp
+All callbacks must be provided, though unimplemented callbacks may some
+times be sufficient.
+The actual types of these callbacks can be found in
+.In teken.h .
+.Pp
+By default,
+.Fn teken_init
+initializes the
+.Vt teken_t
+structure to emulate a terminal having 24 rows and 80 columns.
+The
+.Fn teken_get_winsize
+and
+.Fn teken_set_winsize
+functions can be used to obtain and modify the dimensions of the
+terminal.
+.Pp
+Even though the cursor position is normally controlled by input of data
+through
+.Fn teken_input
+and returned by the
+.Fn tf_cursor
+callback, it can be obtained and modified manually using the
+.Fn teken_get_cursor
+and
+.Fn teken_set_cursor
+functions.
+The same holds for
+.Fn teken_get_curattr
+and
+.Fn teken_set_curattr ,
+which can be used to change the currently selected font attributes and
+foreground and background color.
+.Pp
+By default,
+.Nm
+emulates a white-on-black terminal, which means the default foreground
+color is white, while the background color is black.
+These defaults can be modified using
+.Fn teken_get_defattr
+and
+.Fn teken_set_defattr .
+.Pp
+The
+.Fn teken_get_sequence
+function is a utility function that can be used to obtain escape
+sequences of special keyboard keys, generated by user input.
+The
+.Fa id
+parameter must be one of the
+.Dv TKEY_*
+parameters listed in
+.In teken.h .
+.Sh LEGACY FEATURES
+This library also provides a set of functions that shouldn't be used in
+any modern applications.
+.Pp
+The
+.Fn teken_256to16
+function converts an xterm-256 256-color code to an xterm 16-color code
+whose color with default palettes is as similar as possible (not very
+similar).
+The lower 3 bits of the result are the ANSI color and the next lowest
+bit is brightness.
+Other layers (hardare and software) that only support 16 colors can use
+this to avoid knowing the details of 256-color codes.
+.Pp
+The
+.Fn teken_256to8
+function is similar to
+.Fn teken_256to16
+except it converts to an ANSI 8-color code.
+This is more accurate than discarding the brigtness bit in the result of
+.Fn teken_256to16 .
+.Pp
+The
+.Fn teken_get_defattr_cons25
+function obtains the default terminal attributes as a pair of foreground
+and background colors, using ANSI color numbering.
+.Pp
+The
+.Fn teken_set_8bit
+function disables UTF-8 processing and switches to 8-bit character mode,
+which can be used to support character sets like CP437 and ISO-8859-1.
+.Pp
+The
+.Fn teken_set_cons25
+function switches terminal emulation to
+.Dv cons25 ,
+which is used by versions of
+.Fx
+prior to 9.0.
+.Sh SEE ALSO
+.Xr ncurses 3 ,
+.Xr termcap 3 ,
+.Xr syscons 4
+.Sh HISTORY
+The
+.Nm
+library appeared in
+.Fx 8.0 ,
+though it was only available and used inside the kernel.
+In
+.Fx 9.0 ,
+the
+.Nm
+library appeared in userspace.
+.Sh AUTHORS
+.An Ed Schouten Aq ed at FreeBSD.org
+.Sh SECURITY CONSIDERATIONS
+The
+.Fn tf_respond
+callback is used to respond to device status requests commands generated
+by an application.
+In the past, there have been various security issues, where a malicious
+application sends a device status request before termination, causing
+the generated response to be interpreted by applications such as
+.Xr sh 1 .
+.Pp
+.Nm
+only implements a small subset of responses which are unlikely to cause
+any harm.
+Still, it is advised to leave
+.Fn tf_respond
+unimplemented.
diff --git a/bin/varnishtest/teken.c b/bin/varnishtest/teken.c
new file mode 100644
index 0000000..8f9076d
--- /dev/null
+++ b/bin/varnishtest/teken.c
@@ -0,0 +1,719 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2008-2009 Ed Schouten <ed at FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: head/sys/teken/teken.c 326272 2017-11-27 15:23:17Z pfg $
+ */
+
+#include "config.h"
+
+#include <sys/types.h>
+#include <limits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "vdef.h"
+#include "vas.h"
+
+#define	teken_assert(x)		assert(x)
+
+/* debug messages */
+#define	teken_printf(x,...)
+
+/* Private flags for t_stateflags. */
+#define	TS_FIRSTDIGIT	0x0001	/* First numeric digit in escape sequence. */
+#define	TS_INSERT	0x0002	/* Insert mode. */
+#define	TS_AUTOWRAP	0x0004	/* Autowrap. */
+#define	TS_ORIGIN	0x0008	/* Origin mode. */
+#define	TS_WRAPPED	0x0010	/* Next character should be printed on col 0. */
+#define	TS_8BIT		0x0020	/* UTF-8 disabled. */
+#define	TS_CONS25	0x0040	/* cons25 emulation. */
+#define	TS_INSTRING	0x0080	/* Inside string. */
+#define	TS_CURSORKEYS	0x0100	/* Cursor keys mode. */
+
+/* Character that blanks a cell. */
+#define	BLANK	' '
+
+#include "teken.h"
+#include "teken_wcwidth.h"
+#include "teken_scs.h"
+
+static teken_state_t	teken_state_init;
+
+/*
+ * Wrappers for hooks.
+ */
+
+static inline void
+teken_funcs_bell(const teken_t *t)
+{
+
+	if (t->t_funcs->tf_bell != NULL)
+		t->t_funcs->tf_bell(t->t_softc);
+}
+
+static inline void
+teken_funcs_cursor(const teken_t *t)
+{
+
+	teken_assert(t->t_cursor.tp_row < t->t_winsize.tp_row);
+	teken_assert(t->t_cursor.tp_col < t->t_winsize.tp_col);
+
+	teken_assert(t->t_funcs->tf_cursor != NULL);
+	t->t_funcs->tf_cursor(t->t_softc, &t->t_cursor);
+}
+
+static inline void
+teken_funcs_putchar(const teken_t *t, const teken_pos_t *p, teken_char_t c,
+    const teken_attr_t *a)
+{
+
+	teken_assert(p->tp_row < t->t_winsize.tp_row);
+	teken_assert(p->tp_col < t->t_winsize.tp_col);
+
+	teken_assert(t->t_funcs->tf_putchar != NULL);
+	t->t_funcs->tf_putchar(t->t_softc, p, c, a);
+}
+
+static inline void
+teken_funcs_fill(const teken_t *t, const teken_rect_t *r,
+    const teken_char_t c, const teken_attr_t *a)
+{
+
+	teken_assert(r->tr_end.tp_row > r->tr_begin.tp_row);
+	teken_assert(r->tr_end.tp_row <= t->t_winsize.tp_row);
+	teken_assert(r->tr_end.tp_col > r->tr_begin.tp_col);
+	teken_assert(r->tr_end.tp_col <= t->t_winsize.tp_col);
+
+	teken_assert(t->t_funcs->tf_fill != NULL);
+	t->t_funcs->tf_fill(t->t_softc, r, c, a);
+}
+
+static inline void
+teken_funcs_copy(const teken_t *t, const teken_rect_t *r, const teken_pos_t *p)
+{
+
+	teken_assert(r->tr_end.tp_row > r->tr_begin.tp_row);
+	teken_assert(r->tr_end.tp_row <= t->t_winsize.tp_row);
+	teken_assert(r->tr_end.tp_col > r->tr_begin.tp_col);
+	teken_assert(r->tr_end.tp_col <= t->t_winsize.tp_col);
+	teken_assert(p->tp_row + (r->tr_end.tp_row - r->tr_begin.tp_row) <= t->t_winsize.tp_row);
+	teken_assert(p->tp_col + (r->tr_end.tp_col - r->tr_begin.tp_col) <= t->t_winsize.tp_col);
+
+	teken_assert(t->t_funcs->tf_copy != NULL);
+	t->t_funcs->tf_copy(t->t_softc, r, p);
+}
+
+static inline void
+teken_funcs_param(const teken_t *t, int cmd, unsigned int value)
+{
+
+	if (t->t_funcs->tf_param != NULL)
+		t->t_funcs->tf_param(t->t_softc, cmd, value);
+}
+
+static inline void
+teken_funcs_respond(const teken_t *t, const void *buf, size_t len)
+{
+
+	if (t->t_funcs->tf_respond != NULL)
+		t->t_funcs->tf_respond(t->t_softc, buf, len);
+}
+
+#include "teken_subr.h"
+#include "teken_subr_compat.h"
+
+/*
+ * Programming interface.
+ */
+
+void
+teken_init(teken_t *t, const teken_funcs_t *tf, void *softc)
+{
+	teken_pos_t tp = { .tp_row = 24, .tp_col = 80 };
+
+	t->t_funcs = tf;
+	t->t_softc = softc;
+
+	t->t_nextstate = teken_state_init;
+	t->t_stateflags = 0;
+	t->t_utf8_left = 0;
+
+	t->t_defattr.ta_format = 0;
+	t->t_defattr.ta_fgcolor = TC_WHITE;
+	t->t_defattr.ta_bgcolor = TC_BLACK;
+	teken_subr_do_reset(t);
+
+	teken_set_winsize(t, &tp);
+}
+
+static void
+teken_input_char(teken_t *t, teken_char_t c)
+{
+
+	/*
+	 * There is no support for DCS and OSC.  Just discard strings
+	 * until we receive characters that may indicate string
+	 * termination.
+	 */
+	if (t->t_stateflags & TS_INSTRING) {
+		switch (c) {
+		case '\x1B':
+			t->t_stateflags &= ~TS_INSTRING;
+			break;
+		case '\a':
+			t->t_stateflags &= ~TS_INSTRING;
+			return;
+		default:
+			return;
+		}
+	}
+
+	switch (c) {
+	case '\0':
+		break;
+	case '\a':
+		teken_subr_bell(t);
+		break;
+	case '\b':
+		teken_subr_backspace(t);
+		break;
+	case '\n':
+	case '\x0B':
+		teken_subr_newline(t);
+		break;
+	case '\x0C':
+		teken_subr_newpage(t);
+		break;
+	case '\x0E':
+		if (t->t_stateflags & TS_CONS25)
+			t->t_nextstate(t, c);
+		else
+			t->t_curscs = 1;
+		break;
+	case '\x0F':
+		if (t->t_stateflags & TS_CONS25)
+			t->t_nextstate(t, c);
+		else
+			t->t_curscs = 0;
+		break;
+	case '\r':
+		teken_subr_carriage_return(t);
+		break;
+	case '\t':
+		teken_subr_horizontal_tab(t);
+		break;
+	default:
+		t->t_nextstate(t, c);
+		break;
+	}
+
+	/* Post-processing assertions. */
+	teken_assert(t->t_cursor.tp_row >= t->t_originreg.ts_begin);
+	teken_assert(t->t_cursor.tp_row < t->t_originreg.ts_end);
+	teken_assert(t->t_cursor.tp_row < t->t_winsize.tp_row);
+	teken_assert(t->t_cursor.tp_col < t->t_winsize.tp_col);
+	teken_assert(t->t_saved_cursor.tp_row < t->t_winsize.tp_row);
+	teken_assert(t->t_saved_cursor.tp_col < t->t_winsize.tp_col);
+	teken_assert(t->t_scrollreg.ts_end <= t->t_winsize.tp_row);
+	teken_assert(t->t_scrollreg.ts_begin < t->t_scrollreg.ts_end);
+	/* Origin region has to be window size or the same as scrollreg. */
+	teken_assert((t->t_originreg.ts_begin == t->t_scrollreg.ts_begin &&
+	    t->t_originreg.ts_end == t->t_scrollreg.ts_end) ||
+	    (t->t_originreg.ts_begin == 0 &&
+	    t->t_originreg.ts_end == t->t_winsize.tp_row));
+}
+
+static void
+teken_input_byte(teken_t *t, unsigned char c)
+{
+
+	/*
+	 * UTF-8 handling.
+	 */
+	if ((c & 0x80) == 0x00 || t->t_stateflags & TS_8BIT) {
+		/* One-byte sequence. */
+		t->t_utf8_left = 0;
+		teken_input_char(t, c);
+	} else if ((c & 0xe0) == 0xc0) {
+		/* Two-byte sequence. */
+		t->t_utf8_left = 1;
+		t->t_utf8_partial = c & 0x1f;
+	} else if ((c & 0xf0) == 0xe0) {
+		/* Three-byte sequence. */
+		t->t_utf8_left = 2;
+		t->t_utf8_partial = c & 0x0f;
+	} else if ((c & 0xf8) == 0xf0) {
+		/* Four-byte sequence. */
+		t->t_utf8_left = 3;
+		t->t_utf8_partial = c & 0x07;
+	} else if ((c & 0xc0) == 0x80) {
+		if (t->t_utf8_left == 0)
+			return;
+		t->t_utf8_left--;
+		t->t_utf8_partial = (t->t_utf8_partial << 6) | (c & 0x3f);
+		if (t->t_utf8_left == 0) {
+			teken_printf("Got UTF-8 char %x\n", t->t_utf8_partial);
+			teken_input_char(t, t->t_utf8_partial);
+		}
+	}
+}
+
+void
+teken_input(teken_t *t, const void *buf, size_t len)
+{
+	const char *c = buf;
+
+	while (len-- > 0)
+		teken_input_byte(t, *c++);
+}
+
+const teken_pos_t *
+teken_get_cursor(const teken_t *t)
+{
+
+	return (&t->t_cursor);
+}
+
+void
+teken_set_cursor(teken_t *t, const teken_pos_t *p)
+{
+
+	/* XXX: bounds checking with originreg! */
+	teken_assert(p->tp_row < t->t_winsize.tp_row);
+	teken_assert(p->tp_col < t->t_winsize.tp_col);
+
+	t->t_cursor = *p;
+}
+
+const teken_attr_t *
+teken_get_curattr(const teken_t *t)
+{
+
+	return (&t->t_curattr);
+}
+
+void
+teken_set_curattr(teken_t *t, const teken_attr_t *a)
+{
+
+	t->t_curattr = *a;
+}
+
+const teken_attr_t *
+teken_get_defattr(const teken_t *t)
+{
+
+	return (&t->t_defattr);
+}
+
+void
+teken_set_defattr(teken_t *t, const teken_attr_t *a)
+{
+
+	t->t_curattr = t->t_saved_curattr = t->t_defattr = *a;
+}
+
+const teken_pos_t *
+teken_get_winsize(const teken_t *t)
+{
+
+	return (&t->t_winsize);
+}
+
+static void
+teken_trim_cursor_pos(teken_t *t, const teken_pos_t *new)
+{
+	const teken_pos_t *cur;
+
+	cur = &t->t_winsize;
+
+	if (cur->tp_row < new->tp_row || cur->tp_col < new->tp_col)
+		return;
+	if (t->t_cursor.tp_row >= new->tp_row)
+		t->t_cursor.tp_row = new->tp_row - 1;
+	if (t->t_cursor.tp_col >= new->tp_col)
+		t->t_cursor.tp_col = new->tp_col - 1;
+}
+
+void
+teken_set_winsize(teken_t *t, const teken_pos_t *p)
+{
+
+	teken_trim_cursor_pos(t, p);
+	t->t_winsize = *p;
+	teken_subr_do_reset(t);
+}
+
+void
+teken_set_winsize_noreset(teken_t *t, const teken_pos_t *p)
+{
+
+	teken_trim_cursor_pos(t, p);
+	t->t_winsize = *p;
+	teken_subr_do_resize(t);
+}
+
+void
+teken_set_8bit(teken_t *t)
+{
+
+	t->t_stateflags |= TS_8BIT;
+}
+
+void
+teken_set_cons25(teken_t *t)
+{
+
+	t->t_stateflags |= TS_CONS25;
+}
+
+/*
+ * State machine.
+ */
+
+static void
+teken_state_switch(teken_t *t, teken_state_t *s)
+{
+
+	t->t_nextstate = s;
+	t->t_curnum = 0;
+	t->t_stateflags |= TS_FIRSTDIGIT;
+}
+
+static int
+teken_state_numbers(teken_t *t, teken_char_t c)
+{
+
+	teken_assert(t->t_curnum < T_NUMSIZE);
+
+	if (c >= '0' && c <= '9') {
+		if (t->t_stateflags & TS_FIRSTDIGIT) {
+			/* First digit. */
+			t->t_stateflags &= ~TS_FIRSTDIGIT;
+			t->t_nums[t->t_curnum] = c - '0';
+		} else if (t->t_nums[t->t_curnum] < UINT_MAX / 100) {
+			/*
+			 * There is no need to continue parsing input
+			 * once the value exceeds the size of the
+			 * terminal. It would only allow for integer
+			 * overflows when performing arithmetic on the
+			 * cursor position.
+			 *
+			 * Ignore any further digits if the value is
+			 * already UINT_MAX / 100.
+			 */
+			t->t_nums[t->t_curnum] =
+			    t->t_nums[t->t_curnum] * 10 + c - '0';
+		}
+		return (1);
+	} else if (c == ';') {
+		if (t->t_stateflags & TS_FIRSTDIGIT)
+			t->t_nums[t->t_curnum] = 0;
+
+		/* Only allow a limited set of arguments. */
+		if (++t->t_curnum == T_NUMSIZE) {
+			teken_state_switch(t, teken_state_init);
+			return (1);
+		}
+
+		t->t_stateflags |= TS_FIRSTDIGIT;
+		return (1);
+	} else {
+		if (t->t_stateflags & TS_FIRSTDIGIT && t->t_curnum > 0) {
+			/* Finish off the last empty argument. */
+			t->t_nums[t->t_curnum] = 0;
+			t->t_curnum++;
+		} else if ((t->t_stateflags & TS_FIRSTDIGIT) == 0) {
+			/* Also count the last argument. */
+			t->t_curnum++;
+		}
+	}
+
+	return (0);
+}
+
+#define	k	TC_BLACK
+#define	b	TC_BLUE
+#define	y	TC_BROWN
+#define	c	TC_CYAN
+#define	g	TC_GREEN
+#define	m	TC_MAGENTA
+#define	r	TC_RED
+#define	w	TC_WHITE
+#define	K	(TC_BLACK | TC_LIGHT)
+#define	B	(TC_BLUE | TC_LIGHT)
+#define	Y	(TC_BROWN | TC_LIGHT)
+#define	C	(TC_CYAN | TC_LIGHT)
+#define	G	(TC_GREEN | TC_LIGHT)
+#define	M	(TC_MAGENTA | TC_LIGHT)
+#define	R	(TC_RED | TC_LIGHT)
+#define	W	(TC_WHITE | TC_LIGHT)
+
+/**
+ * The xterm-256 color map has steps of 0x28 (in the range 0-0xff), except
+ * for the first step which is 0x5f.  Scale to the range 0-6 by dividing
+ * by 0x28 and rounding down.  The range of 0-5 cannot represent the
+ * larger first step.
+ *
+ * This table is generated by the follow rules:
+ * - if all components are equal, the result is black for (0, 0, 0) and
+ *   (2, 2, 2), else white; otherwise:
+ * - subtract the smallest component from all components
+ * - if this gives only one nonzero component, then that is the color
+ * - else if one component is 2 or more larger than the other nonzero one,
+ *   then that component gives the color
+ * - else there are 2 nonzero components.  The color is that of a small
+ *   equal mixture of these components (cyan, yellow or magenta).  E.g.,
+ *   (0, 5, 6) (Turquoise2) is a much purer cyan than (0, 2, 3)
+ *   (DeepSkyBlue4), but we map both to cyan since we can't represent
+ *   delicate shades of either blue or cyan and blue would be worse.
+ *   Here it is important that components of 1 never occur.  Blue would
+ *   be twice as large as green in (0, 1, 2).
+ */
+static const teken_color_t teken_256to8tab[] = {
+	/* xterm normal colors: */
+	k, r, g, y, b, m, c, w,
+
+	/* xterm bright colors: */
+	k, r, g, y, b, m, c, w,
+
+	/* Red0 submap. */
+	k, b, b, b, b, b,
+	g, c, c, b, b, b,
+	g, c, c, c, b, b,
+	g, g, c, c, c, b,
+	g, g, g, c, c, c,
+	g, g, g, g, c, c,
+
+	/* Red2 submap. */
+	r, m, m, b, b, b,
+	y, k, b, b, b, b,
+	y, g, c, c, b, b,
+	g, g, c, c, c, b,
+	g, g, g, c, c, c,
+	g, g, g, g, c, c,
+
+	/* Red3 submap. */
+	r, m, m, m, b, b,
+	y, r, m, m, b, b,
+	y, y, w, b, b, b,
+	y, y, g, c, c, b,
+	g, g, g, c, c, c,
+	g, g, g, g, c, c,
+
+	/* Red4 submap. */
+	r, r, m, m, m, b,
+	r, r, m, m, m, b,
+	y, y, r, m, m, b,
+	y, y, y, w, b, b,
+	y, y, y, g, c, c,
+	g, g, g, g, c, c,
+
+	/* Red5 submap. */
+	r, r, r, m, m, m,
+	r, r, r, m, m, m,
+	r, r, r, m, m, m,
+	y, y, y, r, m, m,
+	y, y, y, y, w, b,
+	y, y, y, y, g, c,
+
+	/* Red6 submap. */
+	r, r, r, r, m, m,
+	r, r, r, r, m, m,
+	r, r, r, r, m, m,
+	r, r, r, r, m, m,
+	y, y, y, y, r, m,
+	y, y, y, y, y, w,
+
+	/* Grey submap. */
+	k, k, k, k, k, k,
+	k, k, k, k, k, k,
+	w, w, w, w, w, w,
+	w, w, w, w, w, w,
+};
+
+/*
+ * This table is generated from the previous one by setting TC_LIGHT for
+ * entries whose luminosity in the xterm256 color map is 60% or larger.
+ * Thus the previous table is currently not really needed.  It will be
+ * used for different fine tuning of the tables.
+ */
+static const teken_color_t teken_256to16tab[] = {
+	/* xterm normal colors: */
+	k, r, g, y, b, m, c, w,
+
+	/* xterm bright colors: */
+	K, R, G, Y, B, M, C, W,
+
+	/* Red0 submap. */
+	k, b, b, b, b, b,
+	g, c, c, b, b, b,
+	g, c, c, c, b, b,
+	g, g, c, c, c, b,
+	g, g, g, c, c, c,
+	g, g, g, g, c, c,
+
+	/* Red2 submap. */
+	r, m, m, b, b, b,
+	y, K, b, b, B, B,
+	y, g, c, c, B, B,
+	g, g, c, c, C, B,
+	g, G, G, C, C, C,
+	g, G, G, G, C, C,
+
+	/* Red3 submap. */
+	r, m, m, m, b, b,
+	y, r, m, m, B, B,
+	y, y, w, B, B, B,
+	y, y, G, C, C, B,
+	g, G, G, C, C, C,
+	g, G, G, G, C, C,
+
+	/* Red4 submap. */
+	r, r, m, m, m, b,
+	r, r, m, m, M, B,
+	y, y, R, M, M, B,
+	y, y, Y, W, B, B,
+	y, Y, Y, G, C, C,
+	g, G, G, G, C, C,
+
+	/* Red5 submap. */
+	r, r, r, m, m, m,
+	r, R, R, M, M, M,
+	r, R, R, M, M, M,
+	y, Y, Y, R, M, M,
+	y, Y, Y, Y, W, B,
+	y, Y, Y, Y, G, C,
+
+	/* Red6 submap. */
+	r, r, r, r, m, m,
+	r, R, R, R, M, M,
+	r, R, R, R, M, M,
+	r, R, R, R, M, M,
+	y, Y, Y, Y, R, M,
+	y, Y, Y, Y, Y, W,
+
+	/* Grey submap. */
+	k, k, k, k, k, k,
+	K, K, K, K, K, K,
+	w, w, w, w, w, w,
+	W, W, W, W, W, W,
+};
+
+#undef	k
+#undef	b
+#undef	y
+#undef	c
+#undef	g
+#undef	m
+#undef	r
+#undef	w
+#undef	K
+#undef	B
+#undef	Y
+#undef	C
+#undef	G
+#undef	M
+#undef	R
+#undef	W
+
+teken_color_t
+teken_256to8(teken_color_t c)
+{
+
+	return (teken_256to8tab[c % 256]);
+}
+
+teken_color_t
+teken_256to16(teken_color_t c)
+{
+
+	return (teken_256to16tab[c % 256]);
+}
+
+static const char * const special_strings_cons25[] = {
+	[TKEY_UP] = "\x1B[A",		[TKEY_DOWN] = "\x1B[B",
+	[TKEY_LEFT] = "\x1B[D",		[TKEY_RIGHT] = "\x1B[C",
+
+	[TKEY_HOME] = "\x1B[H",		[TKEY_END] = "\x1B[F",
+	[TKEY_INSERT] = "\x1B[L",	[TKEY_DELETE] = "\x7F",
+	[TKEY_PAGE_UP] = "\x1B[I",	[TKEY_PAGE_DOWN] = "\x1B[G",
+
+	[TKEY_F1] = "\x1B[M",		[TKEY_F2] = "\x1B[N",
+	[TKEY_F3] = "\x1B[O",		[TKEY_F4] = "\x1B[P",
+	[TKEY_F5] = "\x1B[Q",		[TKEY_F6] = "\x1B[R",
+	[TKEY_F7] = "\x1B[S",		[TKEY_F8] = "\x1B[T",
+	[TKEY_F9] = "\x1B[U",		[TKEY_F10] = "\x1B[V",
+	[TKEY_F11] = "\x1B[W",		[TKEY_F12] = "\x1B[X",
+};
+
+static const char * const special_strings_ckeys[] = {
+	[TKEY_UP] = "\x1BOA",		[TKEY_DOWN] = "\x1BOB",
+	[TKEY_LEFT] = "\x1BOD",		[TKEY_RIGHT] = "\x1BOC",
+
+	[TKEY_HOME] = "\x1BOH",		[TKEY_END] = "\x1BOF",
+};
+
+static const char * const special_strings_normal[] = {
+	[TKEY_UP] = "\x1B[A",		[TKEY_DOWN] = "\x1B[B",
+	[TKEY_LEFT] = "\x1B[D",		[TKEY_RIGHT] = "\x1B[C",
+
+	[TKEY_HOME] = "\x1B[H",		[TKEY_END] = "\x1B[F",
+	[TKEY_INSERT] = "\x1B[2~",	[TKEY_DELETE] = "\x1B[3~",
+	[TKEY_PAGE_UP] = "\x1B[5~",	[TKEY_PAGE_DOWN] = "\x1B[6~",
+
+	[TKEY_F1] = "\x1BOP",		[TKEY_F2] = "\x1BOQ",
+	[TKEY_F3] = "\x1BOR",		[TKEY_F4] = "\x1BOS",
+	[TKEY_F5] = "\x1B[15~",		[TKEY_F6] = "\x1B[17~",
+	[TKEY_F7] = "\x1B[18~",		[TKEY_F8] = "\x1B[19~",
+	[TKEY_F9] = "\x1B[20~",		[TKEY_F10] = "\x1B[21~",
+	[TKEY_F11] = "\x1B[23~",	[TKEY_F12] = "\x1B[24~",
+};
+
+const char *
+teken_get_sequence(const teken_t *t, unsigned int k)
+{
+
+	/* Cons25 mode. */
+	if (t->t_stateflags & TS_CONS25 &&
+	    k < sizeof special_strings_cons25 / sizeof(char *))
+		return (special_strings_cons25[k]);
+
+	/* Cursor keys mode. */
+	if (t->t_stateflags & TS_CURSORKEYS &&
+	    k < sizeof special_strings_ckeys / sizeof(char *))
+		return (special_strings_ckeys[k]);
+
+	/* Default xterm sequences. */
+	if (k < sizeof special_strings_normal / sizeof(char *))
+		return (special_strings_normal[k]);
+
+	return (NULL);
+}
+
+#include "teken_state.h"
diff --git a/bin/varnishtest/teken.h b/bin/varnishtest/teken.h
new file mode 100644
index 0000000..986c3bd
--- /dev/null
+++ b/bin/varnishtest/teken.h
@@ -0,0 +1,215 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2008-2009 Ed Schouten <ed at FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: head/sys/teken/teken.h 326272 2017-11-27 15:23:17Z pfg $
+ */
+
+#ifndef _TEKEN_H_
+#define	_TEKEN_H_
+
+#include <sys/types.h>
+
+/*
+ * libteken: terminal emulation library.
+ *
+ * This library converts an UTF-8 stream of bytes to terminal drawing
+ * commands.
+ */
+
+typedef uint32_t teken_char_t;
+typedef unsigned short teken_unit_t;
+typedef unsigned char teken_format_t;
+#define	TF_BOLD		0x01	/* Bold character. */
+#define	TF_UNDERLINE	0x02	/* Underline character. */
+#define	TF_BLINK	0x04	/* Blinking character. */
+#define	TF_REVERSE	0x08	/* Reverse rendered character. */
+#define	TF_CJK_RIGHT	0x10	/* Right-hand side of CJK character. */
+typedef unsigned char teken_color_t;
+#define	TC_BLACK	0
+#define	TC_RED		1
+#define	TC_GREEN	2
+#define	TC_BROWN	3
+#define	TC_BLUE		4
+#define	TC_MAGENTA	5
+#define	TC_CYAN		6
+#define	TC_WHITE	7
+#define	TC_NCOLORS	8
+#define	TC_LIGHT	8	/* ORed with the others. */
+
+typedef struct {
+	teken_unit_t	tp_row;
+	teken_unit_t	tp_col;
+} teken_pos_t;
+typedef struct {
+	teken_pos_t	tr_begin;
+	teken_pos_t	tr_end;
+} teken_rect_t;
+typedef struct {
+	teken_format_t	ta_format;
+	teken_color_t	ta_fgcolor;
+	teken_color_t	ta_bgcolor;
+} teken_attr_t;
+typedef struct {
+	teken_unit_t	ts_begin;
+	teken_unit_t	ts_end;
+} teken_span_t;
+
+typedef struct __teken teken_t;
+
+typedef void teken_state_t(teken_t *, teken_char_t);
+
+/*
+ * Drawing routines supplied by the user.
+ */
+
+typedef void tf_bell_t(void *);
+typedef void tf_cursor_t(void *, const teken_pos_t *);
+typedef void tf_putchar_t(void *, const teken_pos_t *, teken_char_t,
+    const teken_attr_t *);
+typedef void tf_fill_t(void *, const teken_rect_t *, teken_char_t,
+    const teken_attr_t *);
+typedef void tf_copy_t(void *, const teken_rect_t *, const teken_pos_t *);
+typedef void tf_param_t(void *, int, unsigned int);
+#define	TP_SHOWCURSOR	0
+#define	TP_KEYPADAPP	1
+#define	TP_AUTOREPEAT	2
+#define	TP_SWITCHVT	3
+#define	TP_132COLS	4
+#define	TP_SETBELLPD	5
+#define	TP_SETBELLPD_PITCH(pd)		((pd) >> 16)
+#define	TP_SETBELLPD_DURATION(pd)	((pd) & 0xffff)
+#define	TP_MOUSE	6
+#define	TP_SETBORDER	7
+#define	TP_SETLOCALCURSOR	8
+#define	TP_SETGLOBALCURSOR	9
+typedef void tf_respond_t(void *, const void *, size_t);
+
+typedef struct {
+	tf_bell_t	*tf_bell;
+	tf_cursor_t	*tf_cursor;
+	tf_putchar_t	*tf_putchar;
+	tf_fill_t	*tf_fill;
+	tf_copy_t	*tf_copy;
+	tf_param_t	*tf_param;
+	tf_respond_t	*tf_respond;
+} teken_funcs_t;
+
+typedef teken_char_t teken_scs_t(const teken_t *, teken_char_t);
+
+/*
+ * Terminal state.
+ */
+
+struct __teken {
+	const teken_funcs_t *t_funcs;
+	void		*t_softc;
+
+	teken_state_t	*t_nextstate;
+	unsigned int	 t_stateflags;
+
+#define T_NUMSIZE	8
+	unsigned int	 t_nums[T_NUMSIZE];
+	unsigned int	 t_curnum;
+
+	teken_pos_t	 t_cursor;
+	teken_attr_t	 t_curattr;
+	teken_pos_t	 t_saved_cursor;
+	teken_attr_t	 t_saved_curattr;
+
+	teken_attr_t	 t_defattr;
+	teken_pos_t	 t_winsize;
+
+	/* For DECSTBM. */
+	teken_span_t	 t_scrollreg;
+	/* For DECOM. */
+	teken_span_t	 t_originreg;
+
+#define	T_NUMCOL	160
+	unsigned int	 t_tabstops[T_NUMCOL / (sizeof(unsigned int) * 8)];
+
+	unsigned int	 t_utf8_left;
+	teken_char_t	 t_utf8_partial;
+
+	unsigned int	 t_curscs;
+	teken_scs_t	*t_saved_curscs;
+	teken_scs_t	*t_scs[2];
+};
+
+/* Initialize teken structure. */
+void	teken_init(teken_t *, const teken_funcs_t *, void *);
+
+/* Deliver character input. */
+void	teken_input(teken_t *, const void *, size_t);
+
+/* Get/set teken attributes. */
+const teken_pos_t *teken_get_cursor(const teken_t *);
+const teken_attr_t *teken_get_curattr(const teken_t *);
+const teken_attr_t *teken_get_defattr(const teken_t *);
+void	teken_get_defattr_cons25(const teken_t *, int *, int *);
+const teken_pos_t *teken_get_winsize(const teken_t *);
+void	teken_set_cursor(teken_t *, const teken_pos_t *);
+void	teken_set_curattr(teken_t *, const teken_attr_t *);
+void	teken_set_defattr(teken_t *, const teken_attr_t *);
+void	teken_set_winsize(teken_t *, const teken_pos_t *);
+void	teken_set_winsize_noreset(teken_t *, const teken_pos_t *);
+
+/* Key input escape sequences. */
+#define	TKEY_UP		0x00
+#define	TKEY_DOWN	0x01
+#define	TKEY_LEFT	0x02
+#define	TKEY_RIGHT	0x03
+
+#define	TKEY_HOME	0x04
+#define	TKEY_END	0x05
+#define	TKEY_INSERT	0x06
+#define	TKEY_DELETE	0x07
+#define	TKEY_PAGE_UP	0x08
+#define	TKEY_PAGE_DOWN	0x09
+
+#define	TKEY_F1		0x0a
+#define	TKEY_F2		0x0b
+#define	TKEY_F3		0x0c
+#define	TKEY_F4		0x0d
+#define	TKEY_F5		0x0e
+#define	TKEY_F6		0x0f
+#define	TKEY_F7		0x10
+#define	TKEY_F8		0x11
+#define	TKEY_F9		0x12
+#define	TKEY_F10	0x13
+#define	TKEY_F11	0x14
+#define	TKEY_F12	0x15
+const char *teken_get_sequence(const teken_t *, unsigned int);
+
+/* Legacy features. */
+void	teken_set_8bit(teken_t *);
+void	teken_set_cons25(teken_t *);
+
+/* Color conversion. */
+teken_color_t teken_256to16(teken_color_t);
+teken_color_t teken_256to8(teken_color_t);
+
+#endif /* !_TEKEN_H_ */
diff --git a/bin/varnishtest/teken_scs.h b/bin/varnishtest/teken_scs.h
new file mode 100644
index 0000000..fd99de1
--- /dev/null
+++ b/bin/varnishtest/teken_scs.h
@@ -0,0 +1,83 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2009 Ed Schouten <ed at FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: head/sys/teken/teken_scs.h 326272 2017-11-27 15:23:17Z pfg $
+ */
+
+static inline teken_char_t
+teken_scs_process(const teken_t *t, teken_char_t c)
+{
+
+	return (t->t_scs[t->t_curscs](t, c));
+}
+
+/* Unicode points for VT100 box drawing. */
+static const uint16_t teken_boxdrawing_unicode[31] = {
+    0x25c6, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0, 0x00b1,
+    0x2424, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c, 0x23ba,
+    0x23bb, 0x2500, 0x23bc, 0x23bd, 0x251c, 0x2524, 0x2534, 0x252c,
+    0x2502, 0x2264, 0x2265, 0x03c0, 0x2260, 0x00a3, 0x00b7
+};
+
+/* ASCII points for VT100 box drawing. */
+static const uint8_t teken_boxdrawing_8bit[31] = {
+    '?', '?', 'H', 'F', 'C', 'L', '?', '?',
+    'N', 'V', '+', '+', '+', '+', '+', '-',
+    '-', '-', '-', '-', '+', '+', '+', '+',
+    '|', '?', '?', '?', '?', '?', '?',
+};
+
+static teken_char_t
+teken_scs_special_graphics(const teken_t *t, teken_char_t c)
+{
+
+	/* Box drawing. */
+	if (c >= '`' && c <= '~')
+		return (t->t_stateflags & TS_8BIT ?
+		    teken_boxdrawing_8bit[c - '`'] :
+		    teken_boxdrawing_unicode[c - '`']);
+	return (c);
+}
+
+static teken_char_t
+teken_scs_uk_national(const teken_t *t, teken_char_t c)
+{
+
+	/* Pound sign. */
+	if (c == '#')
+		return (t->t_stateflags & TS_8BIT ? 0x9c : 0xa3);
+	return (c);
+}
+
+static teken_char_t
+teken_scs_us_ascii(const teken_t *t, teken_char_t c)
+{
+
+	/* No processing. */
+	(void)t;
+	return (c);
+}
diff --git a/bin/varnishtest/teken_subr.h b/bin/varnishtest/teken_subr.h
new file mode 100644
index 0000000..b67ef5c
--- /dev/null
+++ b/bin/varnishtest/teken_subr.h
@@ -0,0 +1,1315 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2008-2009 Ed Schouten <ed at FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: head/sys/teken/teken_subr.h 326272 2017-11-27 15:23:17Z pfg $
+ */
+
+static void teken_subr_cursor_up(teken_t *, unsigned int);
+static void teken_subr_erase_line(const teken_t *, unsigned int);
+static void teken_subr_regular_character(teken_t *, teken_char_t);
+static void teken_subr_reset_to_initial_state(teken_t *);
+static void teken_subr_save_cursor(teken_t *);
+
+static inline int
+teken_tab_isset(const teken_t *t, unsigned int col)
+{
+	unsigned int b, o;
+
+	if (col >= T_NUMCOL)
+		return ((col % 8) == 0);
+
+	b = col / (sizeof(unsigned int) * 8);
+	o = col % (sizeof(unsigned int) * 8);
+
+	return (t->t_tabstops[b] & (1U << o));
+}
+
+static inline void
+teken_tab_clear(teken_t *t, unsigned int col)
+{
+	unsigned int b, o;
+
+	if (col >= T_NUMCOL)
+		return;
+
+	b = col / (sizeof(unsigned int) * 8);
+	o = col % (sizeof(unsigned int) * 8);
+
+	t->t_tabstops[b] &= ~(1U << o);
+}
+
+static inline void
+teken_tab_set(teken_t *t, unsigned int col)
+{
+	unsigned int b, o;
+
+	if (col >= T_NUMCOL)
+		return;
+
+	b = col / (sizeof(unsigned int) * 8);
+	o = col % (sizeof(unsigned int) * 8);
+
+	t->t_tabstops[b] |= 1U << o;
+}
+
+static void
+teken_tab_default(teken_t *t)
+{
+	unsigned int i;
+
+	memset(t->t_tabstops, 0, T_NUMCOL / 8);
+
+	for (i = 8; i < T_NUMCOL; i += 8)
+		teken_tab_set(t, i);
+}
+
+static void
+teken_subr_do_scroll(const teken_t *t, int amount)
+{
+	teken_rect_t tr;
+	teken_pos_t tp;
+
+	teken_assert(t->t_cursor.tp_row <= t->t_winsize.tp_row);
+	teken_assert(t->t_scrollreg.ts_end <= t->t_winsize.tp_row);
+	teken_assert(amount != 0);
+
+	/* Copy existing data 1 line up. */
+	if (amount > 0) {
+		/* Scroll down. */
+
+		/* Copy existing data up. */
+		if (t->t_scrollreg.ts_begin + amount < t->t_scrollreg.ts_end) {
+			tr.tr_begin.tp_row = t->t_scrollreg.ts_begin + amount;
+			tr.tr_begin.tp_col = 0;
+			tr.tr_end.tp_row = t->t_scrollreg.ts_end;
+			tr.tr_end.tp_col = t->t_winsize.tp_col;
+			tp.tp_row = t->t_scrollreg.ts_begin;
+			tp.tp_col = 0;
+			teken_funcs_copy(t, &tr, &tp);
+
+			tr.tr_begin.tp_row = t->t_scrollreg.ts_end - amount;
+		} else {
+			tr.tr_begin.tp_row = t->t_scrollreg.ts_begin;
+		}
+
+		/* Clear the last lines. */
+		tr.tr_begin.tp_col = 0;
+		tr.tr_end.tp_row = t->t_scrollreg.ts_end;
+		tr.tr_end.tp_col = t->t_winsize.tp_col;
+		teken_funcs_fill(t, &tr, BLANK, &t->t_curattr);
+	} else {
+		/* Scroll up. */
+		amount = -amount;
+
+		/* Copy existing data down. */
+		if (t->t_scrollreg.ts_begin + amount < t->t_scrollreg.ts_end) {
+			tr.tr_begin.tp_row = t->t_scrollreg.ts_begin;
+			tr.tr_begin.tp_col = 0;
+			tr.tr_end.tp_row = t->t_scrollreg.ts_end - amount;
+			tr.tr_end.tp_col = t->t_winsize.tp_col;
+			tp.tp_row = t->t_scrollreg.ts_begin + amount;
+			tp.tp_col = 0;
+			teken_funcs_copy(t, &tr, &tp);
+
+			tr.tr_end.tp_row = t->t_scrollreg.ts_begin + amount;
+		} else {
+			tr.tr_end.tp_row = t->t_scrollreg.ts_end;
+		}
+
+		/* Clear the first lines. */
+		tr.tr_begin.tp_row = t->t_scrollreg.ts_begin;
+		tr.tr_begin.tp_col = 0;
+		tr.tr_end.tp_col = t->t_winsize.tp_col;
+		teken_funcs_fill(t, &tr, BLANK, &t->t_curattr);
+	}
+}
+
+static ssize_t
+teken_subr_do_cpr(const teken_t *t, unsigned int cmd, char response[16])
+{
+
+	switch (cmd) {
+	case 5: /* Operating status. */
+		strcpy(response, "0n");
+		return (2);
+	case 6: { /* Cursor position. */
+		int len;
+
+		len = snprintf(response, 16, "%u;%uR",
+		    (t->t_cursor.tp_row - t->t_originreg.ts_begin) + 1,
+		    t->t_cursor.tp_col + 1);
+
+		if (len >= 16)
+			return (-1);
+		return (len);
+	}
+	case 15: /* Printer status. */
+		strcpy(response, "13n");
+		return (3);
+	case 25: /* UDK status. */
+		strcpy(response, "20n");
+		return (3);
+	case 26: /* Keyboard status. */
+		strcpy(response, "27;1n");
+		return (5);
+	default:
+		teken_printf("Unknown DSR\n");
+		return (-1);
+	}
+}
+
+static void
+teken_subr_alignment_test(teken_t *t)
+{
+	teken_rect_t tr;
+
+	t->t_cursor.tp_row = t->t_cursor.tp_col = 0;
+	t->t_scrollreg.ts_begin = 0;
+	t->t_scrollreg.ts_end = t->t_winsize.tp_row;
+	t->t_originreg = t->t_scrollreg;
+	t->t_stateflags &= ~(TS_WRAPPED|TS_ORIGIN);
+	teken_funcs_cursor(t);
+
+	tr.tr_begin.tp_row = 0;
+	tr.tr_begin.tp_col = 0;
+	tr.tr_end = t->t_winsize;
+	teken_funcs_fill(t, &tr, 'E', &t->t_defattr);
+}
+
+static void
+teken_subr_backspace(teken_t *t)
+{
+
+	if (t->t_stateflags & TS_CONS25) {
+		if (t->t_cursor.tp_col == 0) {
+			if (t->t_cursor.tp_row == t->t_originreg.ts_begin)
+				return;
+			t->t_cursor.tp_row--;
+			t->t_cursor.tp_col = t->t_winsize.tp_col - 1;
+		} else {
+			t->t_cursor.tp_col--;
+		}
+	} else {
+		if (t->t_cursor.tp_col == 0)
+			return;
+
+		t->t_cursor.tp_col--;
+		t->t_stateflags &= ~TS_WRAPPED;
+	}
+
+	teken_funcs_cursor(t);
+}
+
+static void
+teken_subr_bell(const teken_t *t)
+{
+
+	teken_funcs_bell(t);
+}
+
+static void
+teken_subr_carriage_return(teken_t *t)
+{
+
+	t->t_cursor.tp_col = 0;
+	t->t_stateflags &= ~TS_WRAPPED;
+	teken_funcs_cursor(t);
+}
+
+static void
+teken_subr_cursor_backward(teken_t *t, unsigned int ncols)
+{
+
+	if (ncols > t->t_cursor.tp_col)
+		t->t_cursor.tp_col = 0;
+	else
+		t->t_cursor.tp_col -= ncols;
+	t->t_stateflags &= ~TS_WRAPPED;
+	teken_funcs_cursor(t);
+}
+
+static void
+teken_subr_cursor_backward_tabulation(teken_t *t, unsigned int ntabs)
+{
+
+	do {
+		/* Stop when we've reached the beginning of the line. */
+		if (t->t_cursor.tp_col == 0)
+			break;
+
+		t->t_cursor.tp_col--;
+
+		/* Tab marker set. */
+		if (teken_tab_isset(t, t->t_cursor.tp_col))
+			ntabs--;
+	} while (ntabs > 0);
+
+	teken_funcs_cursor(t);
+}
+
+static void
+teken_subr_cursor_down(teken_t *t, unsigned int nrows)
+{
+
+	if (t->t_cursor.tp_row + nrows >= t->t_scrollreg.ts_end)
+		t->t_cursor.tp_row = t->t_scrollreg.ts_end - 1;
+	else
+		t->t_cursor.tp_row += nrows;
+	t->t_stateflags &= ~TS_WRAPPED;
+	teken_funcs_cursor(t);
+}
+
+static void
+teken_subr_cursor_forward(teken_t *t, unsigned int ncols)
+{
+
+	if (t->t_cursor.tp_col + ncols >= t->t_winsize.tp_col)
+		t->t_cursor.tp_col = t->t_winsize.tp_col - 1;
+	else
+		t->t_cursor.tp_col += ncols;
+	t->t_stateflags &= ~TS_WRAPPED;
+	teken_funcs_cursor(t);
+}
+
+static void
+teken_subr_cursor_forward_tabulation(teken_t *t, unsigned int ntabs)
+{
+
+	do {
+		/* Stop when we've reached the end of the line. */
+		if (t->t_cursor.tp_col == t->t_winsize.tp_col - 1)
+			break;
+
+		t->t_cursor.tp_col++;
+
+		/* Tab marker set. */
+		if (teken_tab_isset(t, t->t_cursor.tp_col))
+			ntabs--;
+	} while (ntabs > 0);
+
+	teken_funcs_cursor(t);
+}
+
+static void
+teken_subr_cursor_next_line(teken_t *t, unsigned int ncols)
+{
+
+	t->t_cursor.tp_col = 0;
+	teken_subr_cursor_down(t, ncols);
+}
+
+static void
+teken_subr_cursor_position(teken_t *t, unsigned int row, unsigned int col)
+{
+
+	row = (row - 1) + t->t_originreg.ts_begin;
+	t->t_cursor.tp_row = row < t->t_originreg.ts_end ?
+	    row : t->t_originreg.ts_end - 1;
+
+	col--;
+	t->t_cursor.tp_col = col < t->t_winsize.tp_col ?
+	    col : t->t_winsize.tp_col - 1;
+
+	t->t_stateflags &= ~TS_WRAPPED;
+	teken_funcs_cursor(t);
+}
+
+static void
+teken_subr_cursor_position_report(const teken_t *t, unsigned int cmd)
+{
+	char response[18] = "\x1B[";
+	ssize_t len;
+
+	len = teken_subr_do_cpr(t, cmd, response + 2);
+	if (len < 0)
+		return;
+
+	teken_funcs_respond(t, response, len + 2);
+}
+
+static void
+teken_subr_cursor_previous_line(teken_t *t, unsigned int ncols)
+{
+
+	t->t_cursor.tp_col = 0;
+	teken_subr_cursor_up(t, ncols);
+}
+
+static void
+teken_subr_cursor_up(teken_t *t, unsigned int nrows)
+{
+
+	if (t->t_scrollreg.ts_begin + nrows >= t->t_cursor.tp_row)
+		t->t_cursor.tp_row = t->t_scrollreg.ts_begin;
+	else
+		t->t_cursor.tp_row -= nrows;
+	t->t_stateflags &= ~TS_WRAPPED;
+	teken_funcs_cursor(t);
+}
+
+static void
+teken_subr_delete_character(const teken_t *t, unsigned int ncols)
+{
+	teken_rect_t tr;
+
+	tr.tr_begin.tp_row = t->t_cursor.tp_row;
+	tr.tr_end.tp_row = t->t_cursor.tp_row + 1;
+	tr.tr_end.tp_col = t->t_winsize.tp_col;
+
+	if (t->t_cursor.tp_col + ncols >= t->t_winsize.tp_col) {
+		tr.tr_begin.tp_col = t->t_cursor.tp_col;
+	} else {
+		/* Copy characters to the left. */
+		tr.tr_begin.tp_col = t->t_cursor.tp_col + ncols;
+		teken_funcs_copy(t, &tr, &t->t_cursor);
+
+		tr.tr_begin.tp_col = t->t_winsize.tp_col - ncols;
+	}
+
+	/* Blank trailing columns. */
+	teken_funcs_fill(t, &tr, BLANK, &t->t_curattr);
+}
+
+static void
+teken_subr_delete_line(const teken_t *t, unsigned int nrows)
+{
+	teken_rect_t tr;
+
+	/* Ignore if outside scrolling region. */
+	if (t->t_cursor.tp_row < t->t_scrollreg.ts_begin ||
+	    t->t_cursor.tp_row >= t->t_scrollreg.ts_end)
+		return;
+
+	tr.tr_begin.tp_col = 0;
+	tr.tr_end.tp_row = t->t_scrollreg.ts_end;
+	tr.tr_end.tp_col = t->t_winsize.tp_col;
+
+	if (t->t_cursor.tp_row + nrows >= t->t_scrollreg.ts_end) {
+		tr.tr_begin.tp_row = t->t_cursor.tp_row;
+	} else {
+		teken_pos_t tp;
+
+		/* Copy rows up. */
+		tr.tr_begin.tp_row = t->t_cursor.tp_row + nrows;
+		tp.tp_row = t->t_cursor.tp_row;
+		tp.tp_col = 0;
+		teken_funcs_copy(t, &tr, &tp);
+
+		tr.tr_begin.tp_row = t->t_scrollreg.ts_end - nrows;
+	}
+
+	/* Blank trailing rows. */
+	teken_funcs_fill(t, &tr, BLANK, &t->t_curattr);
+}
+
+static void
+teken_subr_device_control_string(teken_t *t)
+{
+
+	teken_printf("Unsupported device control string\n");
+	t->t_stateflags |= TS_INSTRING;
+}
+
+static void
+teken_subr_device_status_report(const teken_t *t, unsigned int cmd)
+{
+	char response[19] = "\x1B[?";
+	ssize_t len;
+
+	len = teken_subr_do_cpr(t, cmd, response + 3);
+	if (len < 0)
+		return;
+
+	teken_funcs_respond(t, response, len + 3);
+}
+
+static void
+teken_subr_double_height_double_width_line_top(const teken_t *t)
+{
+
+	(void)t;
+	teken_printf("double height double width top\n");
+}
+
+static void
+teken_subr_double_height_double_width_line_bottom(const teken_t *t)
+{
+
+	(void)t;
+	teken_printf("double height double width bottom\n");
+}
+
+static void
+teken_subr_erase_character(const teken_t *t, unsigned int ncols)
+{
+	teken_rect_t tr;
+
+	tr.tr_begin = t->t_cursor;
+	tr.tr_end.tp_row = t->t_cursor.tp_row + 1;
+
+	if (t->t_cursor.tp_col + ncols >= t->t_winsize.tp_col)
+		tr.tr_end.tp_col = t->t_winsize.tp_col;
+	else
+		tr.tr_end.tp_col = t->t_cursor.tp_col + ncols;
+
+	teken_funcs_fill(t, &tr, BLANK, &t->t_curattr);
+}
+
+static void
+teken_subr_erase_display(const teken_t *t, unsigned int mode)
+{
+	teken_rect_t r;
+
+	r.tr_begin.tp_col = 0;
+	r.tr_end.tp_col = t->t_winsize.tp_col;
+
+	switch (mode) {
+	case 1: /* Erase from the top to the cursor. */
+		teken_subr_erase_line(t, 1);
+
+		/* Erase lines above. */
+		if (t->t_cursor.tp_row == 0)
+			return;
+		r.tr_begin.tp_row = 0;
+		r.tr_end.tp_row = t->t_cursor.tp_row;
+		break;
+	case 2: /* Erase entire display. */
+		r.tr_begin.tp_row = 0;
+		r.tr_end.tp_row = t->t_winsize.tp_row;
+		break;
+	default: /* Erase from cursor to the bottom. */
+		teken_subr_erase_line(t, 0);
+
+		/* Erase lines below. */
+		if (t->t_cursor.tp_row == t->t_winsize.tp_row - 1)
+			return;
+		r.tr_begin.tp_row = t->t_cursor.tp_row + 1;
+		r.tr_end.tp_row = t->t_winsize.tp_row;
+		break;
+	}
+
+	teken_funcs_fill(t, &r, BLANK, &t->t_curattr);
+}
+
+static void
+teken_subr_erase_line(const teken_t *t, unsigned int mode)
+{
+	teken_rect_t r;
+
+	r.tr_begin.tp_row = t->t_cursor.tp_row;
+	r.tr_end.tp_row = t->t_cursor.tp_row + 1;
+
+	switch (mode) {
+	case 1: /* Erase from the beginning of the line to the cursor. */
+		r.tr_begin.tp_col = 0;
+		r.tr_end.tp_col = t->t_cursor.tp_col + 1;
+		break;
+	case 2: /* Erase entire line. */
+		r.tr_begin.tp_col = 0;
+		r.tr_end.tp_col = t->t_winsize.tp_col;
+		break;
+	default: /* Erase from cursor to the end of the line. */
+		r.tr_begin.tp_col = t->t_cursor.tp_col;
+		r.tr_end.tp_col = t->t_winsize.tp_col;
+		break;
+	}
+
+	teken_funcs_fill(t, &r, BLANK, &t->t_curattr);
+}
+
+static void
+teken_subr_g0_scs_special_graphics(teken_t *t)
+{
+
+	t->t_scs[0] = teken_scs_special_graphics;
+}
+
+static void
+teken_subr_g0_scs_uk_national(teken_t *t)
+{
+
+	t->t_scs[0] = teken_scs_uk_national;
+}
+
+static void
+teken_subr_g0_scs_us_ascii(teken_t *t)
+{
+
+	t->t_scs[0] = teken_scs_us_ascii;
+}
+
+static void
+teken_subr_g1_scs_special_graphics(teken_t *t)
+{
+
+	t->t_scs[1] = teken_scs_special_graphics;
+}
+
+static void
+teken_subr_g1_scs_uk_national(teken_t *t)
+{
+
+	t->t_scs[1] = teken_scs_uk_national;
+}
+
+static void
+teken_subr_g1_scs_us_ascii(teken_t *t)
+{
+
+	t->t_scs[1] = teken_scs_us_ascii;
+}
+
+static void
+teken_subr_horizontal_position_absolute(teken_t *t, unsigned int col)
+{
+
+	col--;
+	t->t_cursor.tp_col = col < t->t_winsize.tp_col ?
+	    col : t->t_winsize.tp_col - 1;
+
+	t->t_stateflags &= ~TS_WRAPPED;
+	teken_funcs_cursor(t);
+}
+
+static void
+teken_subr_horizontal_tab(teken_t *t)
+{
+
+	teken_subr_cursor_forward_tabulation(t, 1);
+}
+
+static void
+teken_subr_horizontal_tab_set(teken_t *t)
+{
+
+	teken_tab_set(t, t->t_cursor.tp_col);
+}
+
+static void
+teken_subr_index(teken_t *t)
+{
+
+	if (t->t_cursor.tp_row < t->t_scrollreg.ts_end - 1) {
+		t->t_cursor.tp_row++;
+		t->t_stateflags &= ~TS_WRAPPED;
+		teken_funcs_cursor(t);
+	} else {
+		teken_subr_do_scroll(t, 1);
+	}
+}
+
+static void
+teken_subr_insert_character(const teken_t *t, unsigned int ncols)
+{
+	teken_rect_t tr;
+
+	tr.tr_begin = t->t_cursor;
+	tr.tr_end.tp_row = t->t_cursor.tp_row + 1;
+
+	if (t->t_cursor.tp_col + ncols >= t->t_winsize.tp_col) {
+		tr.tr_end.tp_col = t->t_winsize.tp_col;
+	} else {
+		teken_pos_t tp;
+
+		/* Copy characters to the right. */
+		tr.tr_end.tp_col = t->t_winsize.tp_col - ncols;
+		tp.tp_row = t->t_cursor.tp_row;
+		tp.tp_col = t->t_cursor.tp_col + ncols;
+		teken_funcs_copy(t, &tr, &tp);
+
+		tr.tr_end.tp_col = t->t_cursor.tp_col + ncols;
+	}
+
+	/* Blank current location. */
+	teken_funcs_fill(t, &tr, BLANK, &t->t_curattr);
+}
+
+static void
+teken_subr_insert_line(const teken_t *t, unsigned int nrows)
+{
+	teken_rect_t tr;
+
+	/* Ignore if outside scrolling region. */
+	if (t->t_cursor.tp_row < t->t_scrollreg.ts_begin ||
+	    t->t_cursor.tp_row >= t->t_scrollreg.ts_end)
+		return;
+
+	tr.tr_begin.tp_row = t->t_cursor.tp_row;
+	tr.tr_begin.tp_col = 0;
+	tr.tr_end.tp_col = t->t_winsize.tp_col;
+
+	if (t->t_cursor.tp_row + nrows >= t->t_scrollreg.ts_end) {
+		tr.tr_end.tp_row = t->t_scrollreg.ts_end;
+	} else {
+		teken_pos_t tp;
+
+		/* Copy lines down. */
+		tr.tr_end.tp_row = t->t_scrollreg.ts_end - nrows;
+		tp.tp_row = t->t_cursor.tp_row + nrows;
+		tp.tp_col = 0;
+		teken_funcs_copy(t, &tr, &tp);
+
+		tr.tr_end.tp_row = t->t_cursor.tp_row + nrows;
+	}
+
+	/* Blank current location. */
+	teken_funcs_fill(t, &tr, BLANK, &t->t_curattr);
+}
+
+static void
+teken_subr_keypad_application_mode(const teken_t *t)
+{
+
+	teken_funcs_param(t, TP_KEYPADAPP, 1);
+}
+
+static void
+teken_subr_keypad_numeric_mode(const teken_t *t)
+{
+
+	teken_funcs_param(t, TP_KEYPADAPP, 0);
+}
+
+static void
+teken_subr_newline(teken_t *t)
+{
+
+	t->t_cursor.tp_row++;
+
+	if (t->t_cursor.tp_row >= t->t_scrollreg.ts_end) {
+		teken_subr_do_scroll(t, 1);
+		t->t_cursor.tp_row = t->t_scrollreg.ts_end - 1;
+	}
+
+	t->t_stateflags &= ~TS_WRAPPED;
+	teken_funcs_cursor(t);
+}
+
+static void
+teken_subr_newpage(teken_t *t)
+{
+
+	if (t->t_stateflags & TS_CONS25) {
+		teken_rect_t tr;
+
+		/* Clear screen. */
+		tr.tr_begin.tp_row = t->t_originreg.ts_begin;
+		tr.tr_begin.tp_col = 0;
+		tr.tr_end.tp_row = t->t_originreg.ts_end;
+		tr.tr_end.tp_col = t->t_winsize.tp_col;
+		teken_funcs_fill(t, &tr, BLANK, &t->t_curattr);
+
+		/* Cursor at top left. */
+		t->t_cursor.tp_row = t->t_originreg.ts_begin;
+		t->t_cursor.tp_col = 0;
+		t->t_stateflags &= ~TS_WRAPPED;
+		teken_funcs_cursor(t);
+	} else {
+		teken_subr_newline(t);
+	}
+}
+
+static void
+teken_subr_next_line(teken_t *t)
+{
+
+	t->t_cursor.tp_col = 0;
+	teken_subr_newline(t);
+}
+
+static void
+teken_subr_operating_system_command(teken_t *t)
+{
+
+	teken_printf("Unsupported operating system command\n");
+	t->t_stateflags |= TS_INSTRING;
+}
+
+static void
+teken_subr_pan_down(const teken_t *t, unsigned int nrows)
+{
+
+	teken_subr_do_scroll(t, (int)nrows);
+}
+
+static void
+teken_subr_pan_up(const teken_t *t, unsigned int nrows)
+{
+
+	teken_subr_do_scroll(t, -(int)nrows);
+}
+
+static void
+teken_subr_primary_device_attributes(const teken_t *t, unsigned int request)
+{
+
+	if (request == 0) {
+		const char response[] = "\x1B[?1;2c";
+
+		teken_funcs_respond(t, response, sizeof response - 1);
+	} else {
+		teken_printf("Unknown DA1\n");
+	}
+}
+
+static void
+teken_subr_do_putchar(const teken_t *t, const teken_pos_t *tp, teken_char_t c,
+    int width)
+{
+
+	if (t->t_stateflags & TS_INSERT &&
+	    tp->tp_col < t->t_winsize.tp_col - width) {
+		teken_rect_t ctr;
+		teken_pos_t ctp;
+
+		/* Insert mode. Move existing characters to the right. */
+		ctr.tr_begin = *tp;
+		ctr.tr_end.tp_row = tp->tp_row + 1;
+		ctr.tr_end.tp_col = t->t_winsize.tp_col - width;
+		ctp.tp_row = tp->tp_row;
+		ctp.tp_col = tp->tp_col + width;
+		teken_funcs_copy(t, &ctr, &ctp);
+	}
+
+	teken_funcs_putchar(t, tp, c, &t->t_curattr);
+
+	if (width == 2 && tp->tp_col + 1 < t->t_winsize.tp_col) {
+		teken_pos_t tp2;
+		teken_attr_t attr;
+
+		/* Print second half of CJK fullwidth character. */
+		tp2.tp_row = tp->tp_row;
+		tp2.tp_col = tp->tp_col + 1;
+		attr = t->t_curattr;
+		attr.ta_format |= TF_CJK_RIGHT;
+		teken_funcs_putchar(t, &tp2, c, &attr);
+	}
+}
+
+static void
+teken_subr_regular_character(teken_t *t, teken_char_t c)
+{
+	int width;
+
+	if (t->t_stateflags & TS_8BIT) {
+		if (!(t->t_stateflags & TS_CONS25) && (c <= 0x1b || c == 0x7f))
+			return;
+		c = teken_scs_process(t, c);
+		width = 1;
+	} else {
+		c = teken_scs_process(t, c);
+		width = teken_wcwidth(c);
+		/* XXX: Don't process zero-width characters yet. */
+		if (width <= 0)
+			return;
+	}
+
+	if (t->t_stateflags & TS_CONS25) {
+		teken_subr_do_putchar(t, &t->t_cursor, c, width);
+		t->t_cursor.tp_col += width;
+
+		if (t->t_cursor.tp_col >= t->t_winsize.tp_col) {
+			if (t->t_cursor.tp_row == t->t_scrollreg.ts_end - 1) {
+				/* Perform scrolling. */
+				teken_subr_do_scroll(t, 1);
+			} else {
+				/* No scrolling needed. */
+				if (t->t_cursor.tp_row <
+				    t->t_winsize.tp_row - 1)
+					t->t_cursor.tp_row++;
+			}
+			t->t_cursor.tp_col = 0;
+		}
+	} else if (t->t_stateflags & TS_AUTOWRAP &&
+	    ((t->t_stateflags & TS_WRAPPED &&
+	    t->t_cursor.tp_col + 1 == t->t_winsize.tp_col) ||
+	    t->t_cursor.tp_col + width > t->t_winsize.tp_col)) {
+		teken_pos_t tp;
+
+		/*
+		 * Perform line wrapping, if:
+		 * - Autowrapping is enabled, and
+		 *   - We're in the wrapped state at the last column, or
+		 *   - The character to be printed does not fit anymore.
+		 */
+		if (t->t_cursor.tp_row == t->t_scrollreg.ts_end - 1) {
+			/* Perform scrolling. */
+			teken_subr_do_scroll(t, 1);
+			tp.tp_row = t->t_scrollreg.ts_end - 1;
+		} else {
+			/* No scrolling needed. */
+			tp.tp_row = t->t_cursor.tp_row + 1;
+			if (tp.tp_row == t->t_winsize.tp_row) {
+				/*
+				 * Corner case: regular character
+				 * outside scrolling region, but at the
+				 * bottom of the screen.
+				 */
+				teken_subr_do_putchar(t, &t->t_cursor,
+				    c, width);
+				return;
+			}
+		}
+
+		tp.tp_col = 0;
+		teken_subr_do_putchar(t, &tp, c, width);
+
+		t->t_cursor.tp_row = tp.tp_row;
+		t->t_cursor.tp_col = width;
+		t->t_stateflags &= ~TS_WRAPPED;
+	} else {
+		/* No line wrapping needed. */
+		teken_subr_do_putchar(t, &t->t_cursor, c, width);
+		t->t_cursor.tp_col += width;
+
+		if (t->t_cursor.tp_col >= t->t_winsize.tp_col) {
+			t->t_stateflags |= TS_WRAPPED;
+			t->t_cursor.tp_col = t->t_winsize.tp_col - 1;
+		} else {
+			t->t_stateflags &= ~TS_WRAPPED;
+		}
+	}
+
+	teken_funcs_cursor(t);
+}
+
+static void
+teken_subr_reset_dec_mode(teken_t *t, unsigned int cmd)
+{
+
+	switch (cmd) {
+	case 1: /* Cursor keys mode. */
+		t->t_stateflags &= ~TS_CURSORKEYS;
+		break;
+	case 2: /* DECANM: ANSI/VT52 mode. */
+		teken_printf("DECRST VT52\n");
+		break;
+	case 3: /* 132 column mode. */
+		teken_funcs_param(t, TP_132COLS, 0);
+		teken_subr_reset_to_initial_state(t);
+		break;
+	case 5: /* Inverse video. */
+		teken_printf("DECRST inverse video\n");
+		break;
+	case 6: /* Origin mode. */
+		t->t_stateflags &= ~TS_ORIGIN;
+		t->t_originreg.ts_begin = 0;
+		t->t_originreg.ts_end = t->t_winsize.tp_row;
+		t->t_cursor.tp_row = t->t_cursor.tp_col = 0;
+		t->t_stateflags &= ~TS_WRAPPED;
+		teken_funcs_cursor(t);
+		break;
+	case 7: /* Autowrap mode. */
+		t->t_stateflags &= ~TS_AUTOWRAP;
+		break;
+	case 8: /* Autorepeat mode. */
+		teken_funcs_param(t, TP_AUTOREPEAT, 0);
+		break;
+	case 25: /* Hide cursor. */
+		teken_funcs_param(t, TP_SHOWCURSOR, 0);
+		break;
+	case 40: /* Disallow 132 columns. */
+		teken_printf("DECRST allow 132\n");
+		break;
+	case 45: /* Disable reverse wraparound. */
+		teken_printf("DECRST reverse wraparound\n");
+		break;
+	case 47: /* Switch to alternate buffer. */
+		teken_printf("Switch to alternate buffer\n");
+		break;
+	case 1000: /* Mouse input. */
+		teken_funcs_param(t, TP_MOUSE, 0);
+		break;
+	default:
+		teken_printf("Unknown DECRST: %u\n", cmd);
+	}
+}
+
+static void
+teken_subr_reset_mode(teken_t *t, unsigned int cmd)
+{
+
+	switch (cmd) {
+	case 4:
+		t->t_stateflags &= ~TS_INSERT;
+		break;
+	default:
+		teken_printf("Unknown reset mode: %u\n", cmd);
+	}
+}
+
+static void
+teken_subr_do_resize(teken_t *t)
+{
+
+	t->t_scrollreg.ts_begin = 0;
+	t->t_scrollreg.ts_end = t->t_winsize.tp_row;
+	t->t_originreg = t->t_scrollreg;
+}
+
+static void
+teken_subr_do_reset(teken_t *t)
+{
+
+	t->t_curattr = t->t_defattr;
+	t->t_cursor.tp_row = t->t_cursor.tp_col = 0;
+	t->t_scrollreg.ts_begin = 0;
+	t->t_scrollreg.ts_end = t->t_winsize.tp_row;
+	t->t_originreg = t->t_scrollreg;
+	t->t_stateflags &= TS_8BIT|TS_CONS25;
+	t->t_stateflags |= TS_AUTOWRAP;
+
+	t->t_scs[0] = teken_scs_us_ascii;
+	t->t_scs[1] = teken_scs_us_ascii;
+	t->t_curscs = 0;
+
+	teken_subr_save_cursor(t);
+	teken_tab_default(t);
+}
+
+static void
+teken_subr_reset_to_initial_state(teken_t *t)
+{
+
+	teken_subr_do_reset(t);
+	teken_subr_erase_display(t, 2);
+	teken_funcs_param(t, TP_SHOWCURSOR, 1);
+	teken_funcs_cursor(t);
+}
+
+static void
+teken_subr_restore_cursor(teken_t *t)
+{
+
+	t->t_cursor = t->t_saved_cursor;
+	t->t_curattr = t->t_saved_curattr;
+	t->t_scs[t->t_curscs] = t->t_saved_curscs;
+	t->t_stateflags &= ~TS_WRAPPED;
+
+	/* Get out of origin mode when the cursor is moved outside. */
+	if (t->t_cursor.tp_row < t->t_originreg.ts_begin ||
+	    t->t_cursor.tp_row >= t->t_originreg.ts_end) {
+		t->t_stateflags &= ~TS_ORIGIN;
+		t->t_originreg.ts_begin = 0;
+		t->t_originreg.ts_end = t->t_winsize.tp_row;
+	}
+
+	teken_funcs_cursor(t);
+}
+
+static void
+teken_subr_reverse_index(teken_t *t)
+{
+
+	if (t->t_cursor.tp_row > t->t_scrollreg.ts_begin) {
+		t->t_cursor.tp_row--;
+		t->t_stateflags &= ~TS_WRAPPED;
+		teken_funcs_cursor(t);
+	} else {
+		teken_subr_do_scroll(t, -1);
+	}
+}
+
+static void
+teken_subr_save_cursor(teken_t *t)
+{
+
+	t->t_saved_cursor = t->t_cursor;
+	t->t_saved_curattr = t->t_curattr;
+	t->t_saved_curscs = t->t_scs[t->t_curscs];
+}
+
+static void
+teken_subr_secondary_device_attributes(const teken_t *t, unsigned int request)
+{
+
+	if (request == 0) {
+		const char response[] = "\x1B[>0;10;0c";
+		teken_funcs_respond(t, response, sizeof response - 1);
+	} else {
+		teken_printf("Unknown DA2\n");
+	}
+}
+
+static void
+teken_subr_set_dec_mode(teken_t *t, unsigned int cmd)
+{
+
+	switch (cmd) {
+	case 1: /* Cursor keys mode. */
+		t->t_stateflags |= TS_CURSORKEYS;
+		break;
+	case 2: /* DECANM: ANSI/VT52 mode. */
+		teken_printf("DECSET VT52\n");
+		break;
+	case 3: /* 132 column mode. */
+		teken_funcs_param(t, TP_132COLS, 1);
+		teken_subr_reset_to_initial_state(t);
+		break;
+	case 5: /* Inverse video. */
+		teken_printf("DECSET inverse video\n");
+		break;
+	case 6: /* Origin mode. */
+		t->t_stateflags |= TS_ORIGIN;
+		t->t_originreg = t->t_scrollreg;
+		t->t_cursor.tp_row = t->t_scrollreg.ts_begin;
+		t->t_cursor.tp_col = 0;
+		t->t_stateflags &= ~TS_WRAPPED;
+		teken_funcs_cursor(t);
+		break;
+	case 7: /* Autowrap mode. */
+		t->t_stateflags |= TS_AUTOWRAP;
+		break;
+	case 8: /* Autorepeat mode. */
+		teken_funcs_param(t, TP_AUTOREPEAT, 1);
+		break;
+	case 25: /* Display cursor. */
+		teken_funcs_param(t, TP_SHOWCURSOR, 1);
+		break;
+	case 40: /* Allow 132 columns. */
+		teken_printf("DECSET allow 132\n");
+		break;
+	case 45: /* Enable reverse wraparound. */
+		teken_printf("DECSET reverse wraparound\n");
+		break;
+	case 47: /* Switch to alternate buffer. */
+		teken_printf("Switch away from alternate buffer\n");
+		break;
+	case 1000: /* Mouse input. */
+		teken_funcs_param(t, TP_MOUSE, 1);
+		break;
+	default:
+		teken_printf("Unknown DECSET: %u\n", cmd);
+	}
+}
+
+static void
+teken_subr_set_mode(teken_t *t, unsigned int cmd)
+{
+
+	switch (cmd) {
+	case 4:
+		teken_printf("Insert mode\n");
+		t->t_stateflags |= TS_INSERT;
+		break;
+	default:
+		teken_printf("Unknown set mode: %u\n", cmd);
+	}
+}
+
+static void
+teken_subr_set_graphic_rendition(teken_t *t, unsigned int ncmds,
+    const unsigned int cmds[])
+{
+	unsigned int i, n;
+
+	/* No attributes means reset. */
+	if (ncmds == 0) {
+		t->t_curattr = t->t_defattr;
+		return;
+	}
+
+	for (i = 0; i < ncmds; i++) {
+		n = cmds[i];
+
+		switch (n) {
+		case 0: /* Reset. */
+			t->t_curattr = t->t_defattr;
+			break;
+		case 1: /* Bold. */
+			t->t_curattr.ta_format |= TF_BOLD;
+			break;
+		case 4: /* Underline. */
+			t->t_curattr.ta_format |= TF_UNDERLINE;
+			break;
+		case 5: /* Blink. */
+			t->t_curattr.ta_format |= TF_BLINK;
+			break;
+		case 7: /* Reverse. */
+			t->t_curattr.ta_format |= TF_REVERSE;
+			break;
+		case 22: /* Remove bold. */
+			t->t_curattr.ta_format &= ~TF_BOLD;
+			break;
+		case 24: /* Remove underline. */
+			t->t_curattr.ta_format &= ~TF_UNDERLINE;
+			break;
+		case 25: /* Remove blink. */
+			t->t_curattr.ta_format &= ~TF_BLINK;
+			break;
+		case 27: /* Remove reverse. */
+			t->t_curattr.ta_format &= ~TF_REVERSE;
+			break;
+		case 30: /* Set foreground color: black */
+		case 31: /* Set foreground color: red */
+		case 32: /* Set foreground color: green */
+		case 33: /* Set foreground color: brown */
+		case 34: /* Set foreground color: blue */
+		case 35: /* Set foreground color: magenta */
+		case 36: /* Set foreground color: cyan */
+		case 37: /* Set foreground color: white */
+			t->t_curattr.ta_fgcolor = n - 30;
+			break;
+		case 38: /* Set foreground color: 256 color mode */
+			if (i + 2 >= ncmds || cmds[i + 1] != 5)
+				continue;
+			t->t_curattr.ta_fgcolor = cmds[i + 2];
+			i += 2;
+			break;
+		case 39: /* Set default foreground color. */
+			t->t_curattr.ta_fgcolor = t->t_defattr.ta_fgcolor;
+			break;
+		case 40: /* Set background color: black */
+		case 41: /* Set background color: red */
+		case 42: /* Set background color: green */
+		case 43: /* Set background color: brown */
+		case 44: /* Set background color: blue */
+		case 45: /* Set background color: magenta */
+		case 46: /* Set background color: cyan */
+		case 47: /* Set background color: white */
+			t->t_curattr.ta_bgcolor = n - 40;
+			break;
+		case 48: /* Set background color: 256 color mode */
+			if (i + 2 >= ncmds || cmds[i + 1] != 5)
+				continue;
+			t->t_curattr.ta_bgcolor = cmds[i + 2];
+			i += 2;
+			break;
+		case 49: /* Set default background color. */
+			t->t_curattr.ta_bgcolor = t->t_defattr.ta_bgcolor;
+			break;
+		case 90: /* Set bright foreground color: black */
+		case 91: /* Set bright foreground color: red */
+		case 92: /* Set bright foreground color: green */
+		case 93: /* Set bright foreground color: brown */
+		case 94: /* Set bright foreground color: blue */
+		case 95: /* Set bright foreground color: magenta */
+		case 96: /* Set bright foreground color: cyan */
+		case 97: /* Set bright foreground color: white */
+			t->t_curattr.ta_fgcolor = (n - 90) + 8;
+			break;
+		case 100: /* Set bright background color: black */
+		case 101: /* Set bright background color: red */
+		case 102: /* Set bright background color: green */
+		case 103: /* Set bright background color: brown */
+		case 104: /* Set bright background color: blue */
+		case 105: /* Set bright background color: magenta */
+		case 106: /* Set bright background color: cyan */
+		case 107: /* Set bright background color: white */
+			t->t_curattr.ta_bgcolor = (n - 100) + 8;
+			break;
+		default:
+			teken_printf("unsupported attribute %u\n", n);
+		}
+	}
+}
+
+static void
+teken_subr_set_top_and_bottom_margins(teken_t *t, unsigned int top,
+    unsigned int bottom)
+{
+
+	/* Adjust top row number. */
+	if (top > 0)
+		top--;
+	/* Adjust bottom row number. */
+	if (bottom == 0 || bottom > t->t_winsize.tp_row)
+		bottom = t->t_winsize.tp_row;
+
+	/* Invalid arguments. */
+	if (top >= bottom - 1) {
+		top = 0;
+		bottom = t->t_winsize.tp_row;
+	}
+
+	/* Apply scrolling region. */
+	t->t_scrollreg.ts_begin = top;
+	t->t_scrollreg.ts_end = bottom;
+	if (t->t_stateflags & TS_ORIGIN)
+		t->t_originreg = t->t_scrollreg;
+
+	/* Home cursor to the top left of the scrolling region. */
+	t->t_cursor.tp_row = t->t_originreg.ts_begin;
+	t->t_cursor.tp_col = 0;
+	t->t_stateflags &= ~TS_WRAPPED;
+	teken_funcs_cursor(t);
+}
+
+static void
+teken_subr_single_height_double_width_line(const teken_t *t)
+{
+
+	(void)t;
+	teken_printf("single height double width???\n");
+}
+
+static void
+teken_subr_single_height_single_width_line(const teken_t *t)
+{
+
+	(void)t;
+	teken_printf("single height single width???\n");
+}
+
+static void
+teken_subr_string_terminator(const teken_t *t)
+{
+
+	(void)t;
+	/*
+	 * Strings are already terminated in teken_input_char() when ^[
+	 * is inserted.
+	 */
+}
+
+static void
+teken_subr_tab_clear(teken_t *t, unsigned int cmd)
+{
+
+	switch (cmd) {
+	case 0:
+		teken_tab_clear(t, t->t_cursor.tp_col);
+		break;
+	case 3:
+		memset(t->t_tabstops, 0, T_NUMCOL / 8);
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+teken_subr_vertical_position_absolute(teken_t *t, unsigned int row)
+{
+
+	row = (row - 1) + t->t_originreg.ts_begin;
+	t->t_cursor.tp_row = row < t->t_originreg.ts_end ?
+	    row : t->t_originreg.ts_end - 1;
+
+	t->t_stateflags &= ~TS_WRAPPED;
+	teken_funcs_cursor(t);
+}
diff --git a/bin/varnishtest/teken_subr_compat.h b/bin/varnishtest/teken_subr_compat.h
new file mode 100644
index 0000000..9c84f13
--- /dev/null
+++ b/bin/varnishtest/teken_subr_compat.h
@@ -0,0 +1,153 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2008-2009 Ed Schouten <ed at FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: head/sys/teken/teken_subr_compat.h 326272 2017-11-27 15:23:17Z pfg $
+ */
+
+static void
+teken_subr_cons25_set_border(const teken_t *t, unsigned int c)
+{
+
+	teken_funcs_param(t, TP_SETBORDER, c);
+}
+
+static void
+teken_subr_cons25_set_global_cursor_shape(const teken_t *t, unsigned int ncmds,
+    const unsigned int cmds[])
+{
+	unsigned int code, i;
+
+	/*
+	 * Pack the args to work around API deficiencies.  This requires
+	 * knowing too much about the low level to be fully compatible.
+	 * Returning when ncmds > 3 is necessary and happens to be
+	 * compatible.  Discarding high bits is necessary and happens to
+	 * be incompatible only for invalid args when ncmds == 3.
+	 */
+	if (ncmds > 3)
+		return;
+	code = 0;
+	for (i = ncmds; i > 0; i--)
+		code = (code << 8) | (cmds[i - 1] & 0xff);
+	code = (code << 8) | ncmds;
+	teken_funcs_param(t, TP_SETGLOBALCURSOR, code);
+}
+
+static void
+teken_subr_cons25_set_local_cursor_type(const teken_t *t, unsigned int type)
+{
+
+	teken_funcs_param(t, TP_SETLOCALCURSOR, type);
+}
+
+static const teken_color_t cons25_colors[8] = { TC_BLACK, TC_BLUE,
+    TC_GREEN, TC_CYAN, TC_RED, TC_MAGENTA, TC_BROWN, TC_WHITE };
+
+static void
+teken_subr_cons25_set_default_background(teken_t *t, unsigned int c)
+{
+
+	t->t_defattr.ta_bgcolor = cons25_colors[c % 8] | (c & 8);
+	t->t_curattr.ta_bgcolor = cons25_colors[c % 8] | (c & 8);
+}
+
+static void
+teken_subr_cons25_set_default_foreground(teken_t *t, unsigned int c)
+{
+
+	t->t_defattr.ta_fgcolor = cons25_colors[c % 8] | (c & 8);
+	t->t_curattr.ta_fgcolor = cons25_colors[c % 8] | (c & 8);
+}
+
+static const teken_color_t cons25_revcolors[8] = { 0, 4, 2, 6, 1, 5, 3, 7 };
+
+void
+teken_get_defattr_cons25(const teken_t *t, int *fg, int *bg)
+{
+
+	*fg = cons25_revcolors[teken_256to8(t->t_defattr.ta_fgcolor)];
+	if (t->t_defattr.ta_format & TF_BOLD)
+		*fg += 8;
+	*bg = cons25_revcolors[teken_256to8(t->t_defattr.ta_bgcolor)];
+}
+
+static void
+teken_subr_cons25_switch_virtual_terminal(const teken_t *t, unsigned int vt)
+{
+
+	teken_funcs_param(t, TP_SWITCHVT, vt);
+}
+
+static void
+teken_subr_cons25_set_bell_pitch_duration(const teken_t *t, unsigned int pitch,
+    unsigned int duration)
+{
+
+	teken_funcs_param(t, TP_SETBELLPD, (pitch << 16) |
+	    (duration & 0xffff));
+}
+
+static void
+teken_subr_cons25_set_graphic_rendition(teken_t *t, unsigned int cmd,
+    unsigned int param)
+{
+
+	(void)param;
+	switch (cmd) {
+	case 0: /* Reset. */
+		t->t_curattr = t->t_defattr;
+		break;
+	default:
+		teken_printf("unsupported attribute %u\n", cmd);
+	}
+}
+
+static void
+teken_subr_cons25_set_terminal_mode(teken_t *t, unsigned int mode)
+{
+
+	switch (mode) {
+	case 0:	/* Switch terminal to xterm. */
+		t->t_stateflags &= ~TS_CONS25;
+		break;
+	case 1: /* Switch terminal to cons25. */
+		t->t_stateflags |= TS_CONS25;
+		break;
+	default:
+		break;
+	}
+}
+
+#if 0
+static void
+teken_subr_vt52_decid(teken_t *t)
+{
+	const char response[] = "\x1B/Z";
+
+	teken_funcs_respond(t, response, sizeof response - 1);
+}
+#endif
diff --git a/bin/varnishtest/teken_wcwidth.h b/bin/varnishtest/teken_wcwidth.h
new file mode 100644
index 0000000..6482305
--- /dev/null
+++ b/bin/varnishtest/teken_wcwidth.h
@@ -0,0 +1,120 @@
+/*
+ * 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
+ *
+ * $FreeBSD: head/sys/teken/teken_wcwidth.h 186681 2009-01-01 13:26:53Z ed $
+ */
+
+struct interval {
+  teken_char_t first;
+  teken_char_t last;
+};
+
+/* auxiliary function for binary search in interval table */
+static int bisearch(teken_char_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;
+}
+
+static int teken_wcwidth(teken_char_t ucs)
+{
+  /* 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 }
+  };
+
+  /* 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)));
+}
diff --git a/bin/varnishtest/tests/u00008.vtc b/bin/varnishtest/tests/u00008.vtc
index 5b6922b..8a88ad7 100644
--- a/bin/varnishtest/tests/u00008.vtc
+++ b/bin/varnishtest/tests/u00008.vtc
@@ -36,5 +36,5 @@ process p1 -need-bytes 5000 -screen_dump
 
 process p1 -winsz 25 132
 
-process p1 -need-bytes 7000 -screen_dump -write {q} -wait
+process p1 -need-bytes 4000 -screen_dump -write {q} -wait
 
diff --git a/bin/varnishtest/vtc.h b/bin/varnishtest/vtc.h
index 94dbf14..6776e88 100644
--- a/bin/varnishtest/vtc.h
+++ b/bin/varnishtest/vtc.h
@@ -135,9 +135,3 @@ void vtc_expect(struct vtclog *, const char *, const char *, const char *,
     const char *, const char *);
 void vtc_wait4(struct vtclog *, long, int, int, int);
 void *vtc_record(struct vtclog *, int, struct vsb *);
-
-/* vtc_term.c */
-struct term *Term_New(struct vtclog *, int, int);
-void Term_Feed(struct term *, const char *, const char *);
-void Term_Dump(const struct term *);
-void Term_SetSize(struct term *, int, int);
diff --git a/bin/varnishtest/vtc_process.c b/bin/varnishtest/vtc_process.c
index 7c37103..fadfb14 100644
--- a/bin/varnishtest/vtc_process.c
+++ b/bin/varnishtest/vtc_process.c
@@ -54,6 +54,8 @@
 #include "vsb.h"
 #include "vsub.h"
 
+#include "teken.h"
+
 struct process {
 	unsigned		magic;
 #define PROCESS_MAGIC		0x1617b43e
@@ -84,15 +86,137 @@ struct process {
 	pthread_t		tp;
 	unsigned		hasthread;
 
-	struct term		*term;
-	int			lin;
-	int			col;
+	int			nlin;
+	int			ncol;
+	char			**vram;
+	teken_t			tek[1];
 };
 
 static VTAILQ_HEAD(, process)	processes =
     VTAILQ_HEAD_INITIALIZER(processes);
 
 /**********************************************************************
+ * Terminal emulation
+ */
+
+static void
+term_cursor(void *priv, const teken_pos_t *pos)
+{
+	(void)priv;
+	(void)pos;
+}
+
+static void
+term_putchar(void *priv, const teken_pos_t *pos, teken_char_t ch,
+    const teken_attr_t *at)
+{
+	struct process *pp;
+
+	CAST_OBJ_NOTNULL(pp, priv, PROCESS_MAGIC);
+	(void)at;
+	if (ch > 126 || ch < 32)
+		ch = '?';
+	assert(pos->tp_row < pp->nlin);
+	assert(pos->tp_col < pp->ncol);
+	pp->vram[pos->tp_row][pos->tp_col] = ch;
+}
+
+static void
+term_fill(void *priv, const teken_rect_t *r, teken_char_t c,
+    const teken_attr_t *a)
+{
+	teken_pos_t p;
+
+	/* Braindead implementation of fill() - just call putchar(). */
+	for (p.tp_row = r->tr_begin.tp_row;
+	    p.tp_row < r->tr_end.tp_row; p.tp_row++)
+		for (p.tp_col = r->tr_begin.tp_col;
+		    p.tp_col < r->tr_end.tp_col; p.tp_col++)
+			term_putchar(priv, &p, c, a);
+}
+
+static void
+term_copy(void *priv, const teken_rect_t *r, const teken_pos_t *p)
+{
+	struct process *pp;
+	int nrow, ncol, y; /* Has to be signed - >= 0 comparison */
+
+	/*
+	 * Copying is a little tricky. We must make sure we do it in
+	 * correct order, to make sure we don't overwrite our own data.
+	 */
+	CAST_OBJ_NOTNULL(pp, priv, PROCESS_MAGIC);
+
+	nrow = r->tr_end.tp_row - r->tr_begin.tp_row;
+	ncol = r->tr_end.tp_col - r->tr_begin.tp_col;
+
+	if (p->tp_row < r->tr_begin.tp_row) {
+		/* Copy from top to bottom. */
+		for (y = 0; y < nrow; y++)
+			memmove(&pp->vram[p->tp_row + y][p->tp_col],
+			    &pp->vram[r->tr_begin.tp_row + y][r->tr_begin.tp_col], ncol);
+	} else {
+		/* Copy from bottom to top. */
+		for (y = nrow - 1; y >= 0; y--)
+			memmove(&pp->vram[p->tp_row + y][p->tp_col],
+			    &pp->vram[r->tr_begin.tp_row + y][r->tr_begin.tp_col], ncol);
+	}
+}
+
+static const teken_funcs_t process_teken_func = {
+	.tf_cursor	=	term_cursor,
+	.tf_putchar	=	term_putchar,
+	.tf_fill	=	term_fill,
+	.tf_copy	=	term_copy,
+};
+
+static void
+term_screen_dump(const struct process *pp)
+{
+	int i;
+
+	for (i = 0; i < pp->nlin; i++)
+		vtc_dump(pp->vl, 3, "screen", pp->vram[i], pp->ncol);
+}
+
+static void
+term_resize(struct process *pp, int lin, int col)
+{
+	teken_pos_t pos;
+	char **vram;
+	int i, j;
+
+	vram = calloc(lin, sizeof *pp->vram);
+	AN(vram);
+	for (i = 0; i < lin; i++) {
+		vram[i] = malloc(col + 1L);
+		AN(vram[i]);
+		memset(vram[i], ' ', col);
+		vram[i][col] = '\0';
+	}
+	if (pp->vram != NULL) {
+		for (i = 0; i < lin; i++) {
+			if (i >= pp->nlin)
+				break;
+			j = col;
+			if (j > pp->ncol)
+				j = pp->ncol;
+			memcpy(vram[i], pp->vram[i], j);
+		}
+		for (i = 0; i < pp->nlin; i++)
+			free(pp->vram[i]);
+		free(pp->vram);
+	}
+	pp->vram = vram;
+	pp->nlin = lin;
+	pp->ncol = col;
+
+	pos.tp_row = lin;
+	pos.tp_col = col;
+	teken_set_winsize(pp->tek, &pos);
+}
+
+/**********************************************************************
  * Allocate and initialize a process
  */
 
@@ -131,10 +255,8 @@ process_new(const char *name)
 	p->fd_term = -1;
 
 	VTAILQ_INSERT_TAIL(&processes, p, list);
-	p->lin = 25;
-	p->col = 80;
-	p->term = Term_New(p->vl, p->lin, p->col);
-	AN(p->term);
+	teken_init(p->tek, &process_teken_func, p);
+	term_resize(p, 25, 80);
 	return (p);
 }
 
@@ -214,7 +336,7 @@ process_stdout(const struct vev *ev, int what)
 	else if (p->log == 3)
 		vtc_hexdump(p->vl, 4, "stdout", buf, i);
 	(void)write(p->f_stdout, buf, i);
-	Term_Feed(p->term, buf, buf + i);
+	teken_input(p->tek, buf, i);
 	return (0);
 }
 
@@ -311,14 +433,14 @@ process_thread(void *priv)
 }
 
 static void
-process_winsz(struct process *p, int fd, int lin, int col)
+process_winsz(struct process *p, int fd)
 {
 	struct winsize ws;
 	int i;
 
 	memset(&ws, 0, sizeof ws);
-	ws.ws_row = (short)lin;
-	ws.ws_col = (short)col;
+	ws.ws_row = (short)p->nlin;
+	ws.ws_col = (short)p->ncol;
 	i = ioctl(fd, TIOCSWINSZ, &ws);
 	if (i)
 		vtc_log(p->vl, 4, "TIOCWINSZ %d %s", i, strerror(errno));
@@ -330,7 +452,7 @@ process_init_term(struct process *p, int fd)
 	struct termios tt;
 	int i;
 
-	process_winsz(p, fd, p->lin, p->col);
+	process_winsz(p, fd);
 
 	memset(&tt, 0, sizeof tt);
 	tt.c_cflag = CREAD | CS8 | HUPCL;
@@ -407,7 +529,7 @@ process_start(struct process *p)
 		VSUB_closefrom(STDERR_FILENO + 1);
 		process_init_term(p, slave);
 
-		AZ(setenv("TERM", "ansi.sys", 1));
+		AZ(setenv("TERM", "xterm", 1));
 		AZ(unsetenv("TERMCAP"));
 		// Not using NULL because GCC is now even more demented...
 		assert(write(STDERR_FILENO, "+", 1) == 1);
@@ -510,6 +632,23 @@ process_write(const struct process *p, const char *text)
 }
 
 static void
+process_write_hex(const struct process *p, const char *text)
+{
+	struct vsb *vsb;
+	int j;
+
+	if (!p->hasthread)
+		vtc_fatal(p->vl, "Cannot write to a non-running process");
+
+	vsb = vtc_hex_to_bin(p->vl, text);
+	assert(VSB_len(vsb) >= 0);
+	vtc_hexdump(p->vl, 4, "sendhex", VSB_data(vsb), VSB_len(vsb));
+	j = write(p->fd_term, VSB_data(vsb), VSB_len(vsb));
+	assert(j == VSB_len(vsb));
+	VSB_destroy(&vsb);
+}
+
+static void
 process_close(struct process *p)
 {
 
@@ -614,6 +753,7 @@ cmd_process(CMD_ARGS)
 {
 	struct process *p, *p2;
 	uintmax_t u, v;
+	unsigned lin,col;
 
 	(void)priv;
 	(void)cmd;
@@ -713,7 +853,7 @@ cmd_process(CMD_ARGS)
 			continue;
 		}
 		if (!strcmp(*av, "-screen_dump")) {
-			Term_Dump(p->term);
+			term_screen_dump(p);
 			continue;
 		}
 		if (!strcmp(*av, "-start")) {
@@ -730,19 +870,24 @@ cmd_process(CMD_ARGS)
 			continue;
 		}
 		if (!strcmp(*av, "-winsz")) {
-			p->lin = atoi(av[1]);
-			assert(p->lin > 1);
-			p->col = atoi(av[2]);
-			assert(p->col > 1);
+			lin = atoi(av[1]);
+			assert(lin > 1);
+			col = atoi(av[2]);
+			assert(col > 1);
 			av += 2;
-			Term_SetSize(p->term, p->lin, p->col);
-			process_winsz(p, p->fd_term, p->lin, p->col);
+			term_resize(p, lin, col);
+			process_winsz(p, p->fd_term);
 		}
 		if (!strcmp(*av, "-write")) {
 			process_write(p, av[1]);
 			av++;
 			continue;
 		}
+		if (!strcmp(*av, "-writehex")) {
+			process_write_hex(p, av[1]);
+			av++;
+			continue;
+		}
 		if (!strcmp(*av, "-writeln")) {
 			process_write(p, av[1]);
 			process_write(p, "\n");
diff --git a/bin/varnishtest/vtc_term.c b/bin/varnishtest/vtc_term.c
deleted file mode 100644
index ee9426d..0000000
--- a/bin/varnishtest/vtc_term.c
+++ /dev/null
@@ -1,327 +0,0 @@
-/*-
- * Copyright (c) 2018 Varnish Software AS
- * All rights reserved.
- *
- * Author: Poul-Henning Kamp <phk at FreeBSD.org>
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- *
- * A trivial ANSI terminal emulation
- */
-
-#include "config.h"
-
-#include <ctype.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "vtc.h"
-
-struct term {
-	unsigned		magic;
-#define TERM_MAGIC		0x1c258f0f
-
-	struct vtclog		*vl;
-	unsigned		state;
-#define NTERMARG		10
-	int			arg[NTERMARG];
-	int			*argp;
-	int			nlin;
-	int			ncol;
-	char			**vram;
-	int			col;
-	int			line;
-};
-
-static void
-term_clear(char * const *vram, int lin, int col)
-{
-	int i;
-
-	for (i = 0; i < lin; i++) {
-		memset(vram[i], ' ', col);
-		vram[i][col] = '\0';
-	}
-}
-
-static void
-term_scroll(const struct term *tp)
-{
-	int i;
-	char *l;
-
-	l = tp->vram[0];
-	for (i = 0; i < tp->nlin -1; i++)
-		tp->vram[i] = tp->vram[i + 1];
-	tp->vram[i] = l;
-	memset(l, ' ', tp->ncol);
-}
-
-void
-Term_Dump(const struct term *tp)
-{
-	int i;
-
-	for (i = 0; i < tp->nlin; i++)
-		vtc_dump(tp->vl, 3, "screen", tp->vram[i], tp->ncol);
-}
-
-static void
-term_escape(struct term *tp, int c, int n)
-{
-	int i;
-
-	switch (c) {
-	case 'A':
-		// CUU - Cursor up
-		if (tp->arg[0] == -1) tp->arg[0] = 1;
-		tp->line -= tp->arg[0];
-		if (tp->line < 0)
-			vtc_fatal(tp->vl, "ANSI A[%d] outside vram",
-			    tp->arg[0]);
-		break;
-	case 'B':
-		// CUD - Cursor down
-		if (tp->arg[0] == -1) tp->arg[0] = 1;
-		if (tp->arg[0] > tp->nlin)
-			vtc_fatal(tp->vl, "ANSI B[%d] outside vram",
-			    tp->arg[0]);
-		tp->line += tp->arg[0];
-		while (tp->line >= tp->nlin) {
-			term_scroll(tp);
-			tp->line--;
-		}
-		break;
-	case 'C':
-		// CUF - Cursor forward
-		if (tp->arg[0] == -1) tp->arg[0] = 1;
-		tp->col += tp->arg[0];
-		if (tp->col >= tp->ncol)
-			tp->col = tp->ncol - 1;
-		break;
-	case 'D':
-		// CUB - Cursor backward
-		if (tp->arg[0] == -1) tp->arg[0] = 1;
-		tp->col -= tp->arg[0];
-		if (tp->col < 0)
-			tp->col = 0;
-		break;
-	case 'h':
-		// SM - Set Mode (mostly ignored XXX?)
-		tp->col = 0;
-		tp->line = 0;
-		break;
-	case 'H':
-		// CUP - Cursor Position
-		if (tp->arg[0] == -1) tp->arg[0] = 1;
-		if (tp->arg[1] == -1) tp->arg[1] = 1;
-		if (tp->arg[0] > tp->nlin || tp->arg[1] > tp->ncol)
-			vtc_fatal(tp->vl, "ANSI H[%d,%d] outside vram",
-			    tp->arg[0], tp->arg[1]);
-		tp->line = tp->arg[0] - 1;
-		tp->col = tp->arg[1] - 1;
-		break;
-	case 'J':
-		// ED - Erase in Display (0=below, 1=above, 2=all)
-		switch (tp->arg[0]) {
-		case 2:
-			term_clear(tp->vram, tp->nlin, tp->ncol);
-			break;
-		default:
-			vtc_fatal(tp->vl, "ANSI J[%d]", tp->arg[0]);
-		}
-		break;
-	case 'K':
-		// EL - Erase in line (0=right, 1=left, 2=full line)
-		if (tp->arg[0] == -1) tp->arg[0] = 0;
-		switch (tp->arg[0]) {
-		case 0:
-			for (i = tp->col; i < tp->ncol; i++)
-				tp->vram[tp->line][i] = ' ';
-			break;
-		case 1:
-			for (i = 0; i < tp->col; i++)
-				tp->vram[tp->line][i] = ' ';
-			break;
-		case 2:
-			for (i = 0; i < tp->ncol; i++)
-				tp->vram[tp->line][i] = ' ';
-			break;
-		default:
-			vtc_fatal(tp->vl, "ANSI K[%d]", tp->arg[0]);
-		}
-		break;
-	case 'm':
-		// SGG - Character Attributes (ignored)
-		break;
-	default:
-		for (i = 0; i < n; i++)
-			vtc_log(tp->vl, 4, "ANSI arg %d", tp->arg[i]);
-		vtc_fatal(tp->vl, "ANSI unknown (%c)", c);
-		break;
-	}
-}
-
-static void
-term_char(struct term *tp, char c)
-{
-	assert(tp->col < tp->ncol);
-	assert(tp->line < tp->nlin);
-	assert(tp->state <= 3);
-	switch (c) {
-	case 0x00:
-		break;
-	case '\b':
-		if (tp->col > 0)
-			tp->col--;
-		break;
-	case '\t':
-		while (++tp->col % 8)
-			continue;
-		if (tp->col >= tp->ncol) {
-			tp->col = 0;
-			term_char(tp, '\n');
-		}
-		break;
-	case '\n':
-		if (tp->line == tp->nlin - 1)
-			term_scroll(tp);
-		else
-			tp->line++;
-		break;
-	case '\r':
-		tp->col = 0;
-		break;
-	default:
-		if (c < ' ' || c > '~')
-			c = '?';
-		tp->vram[tp->line][tp->col++] = c;
-		if (tp->col >= tp->ncol) {
-			tp->col = 0;
-			term_char(tp, '\n');
-		}
-	}
-}
-
-void
-Term_Feed(struct term *tp, const char *b, const char *e)
-{
-	int i;
-
-	while (b < e) {
-		assert(tp->col < tp->ncol);
-		assert(tp->line < tp->nlin);
-		assert(tp->state <= 3);
-		switch (tp->state) {
-		case 0:
-			if (*b == '\x1b')
-				tp->state = 1;
-			else if (*(const uint8_t*)b == 0x9b)
-				tp->state = 2;
-			else
-				term_char(tp, *b);
-			b++;
-			break;
-		case 1:
-			if (*b++ != '[')
-				vtc_fatal(tp->vl, "ANSI not '[' (0x%x)",
-				    b[-1] & 0xff);
-			tp->state = 2;
-			break;
-		case 2:
-			tp->argp = tp->arg;
-			for (i=0; i < NTERMARG; i++)
-				tp->arg[i] = -1;
-			tp->state = 3;
-			if (*b == '?')
-				b++;
-			break;
-		case 3:
-			if (tp->argp - tp->arg >= NTERMARG)
-				vtc_fatal(tp->vl, "ANSI too many args");
-
-			if (isdigit(*b)) {
-				if (*tp->argp == -1)
-					*tp->argp = 0;
-				*tp->argp *= 10;
-				*tp->argp += *b++ - '0';
-				continue;
-			}
-			if (*b == ';') {
-				tp->argp++;
-				tp->state = 3;
-				b++;
-				continue;
-			}
-			term_escape(tp, *b++, tp->argp -  tp->arg);
-			tp->state = 0;
-			break;
-		default:
-			WRONG("Wrong ansi state");
-		}
-	}
-}
-
-void
-Term_SetSize(struct term *tp, int lin, int col)
-{
-	char **vram;
-	int i, j;
-
-	vram = calloc(lin, sizeof *tp->vram);
-	AN(vram);
-	for (i = 0; i < lin; i++) {
-		vram[i] = malloc(col + 1L);
-		AN(vram[i]);
-	}
-	term_clear(vram, lin, col);
-	if (tp->vram != NULL) {
-		for (i = 0; i < lin; i++) {
-			if (i >= tp->nlin)
-				break;
-			j = col;
-			if (j > tp->ncol)
-				j = tp->ncol;
-			memcpy(vram[i], tp->vram[i], j);
-		}
-		for (i = 0; i < tp->nlin; i++)
-			free(tp->vram[i]);
-		free(tp->vram);
-	}
-	tp->vram = vram;
-	tp->nlin = lin;
-	tp->ncol = col;
-}
-
-struct term *
-Term_New(struct vtclog *vl, int lin, int col)
-{
-	struct term *tp;
-
-	ALLOC_OBJ(tp, TERM_MAGIC);
-	AN(tp);
-	tp->vl = vl;
-	Term_SetSize(tp, lin, col);
-	tp->line = tp->nlin - 1;
-	return (tp);
-}
-


More information about the varnish-commit mailing list