@@ -655,6 +655,8 @@ X_PRE_LIBS
X_CFLAGS
XMKMF
xmkmfpath
TERM_OBJ
TERM_SRC
CHANNEL_OBJ
CHANNEL_SRC
NETBEANS_OBJ
@@ -814,6 +816,7 @@ enable_cscope
enable_workshop
enable_netbeans
enable_channel
enable_terminal
enable_multibyte
enable_hangulinput
enable_xim
@@ -1491,6 +1494,7 @@ Optional Features:
--enable-workshop Include Sun Visual Workshop support.
--disable-netbeans Disable NetBeans integration support.
--disable-channel Disable process communication support.
--enable-terminal Disable terminal emulation support.
--enable-multibyte Include multibyte editing support.
--enable-hangulinput Include Hangul input support.
--enable-xim Include XIM input support.
@@ -7464,6 +7468,35 @@ if test "$enable_channel" = "yes"; then

fi

{ $as_echo "$as_me:${as_lineno-$LINENO}: checking --enable-terminal argument" >&5
$as_echo_n "checking --enable-terminal argument... " >&6; }
# Check whether --enable-terminal was given.
if test "${enable_terminal+set}" = set; then :
enableval=$enable_terminal; enable_terminal="yes"
fi

if test "$enable_terminal" = "yes"; then
if test "x$features" = "xtiny" -o "x$features" = "xsmall"; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: cannot use terminal emulator with tiny or small features" >&5
$as_echo "cannot use terminal emulator with tiny or small features" >&6; }
enable_terminal="no"
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
fi
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
fi
if test "$enable_terminal" = "yes"; then
$as_echo "#define FEAT_TERMINAL 1" >>confdefs.h

TERM_SRC="libvterm/src/encoding.c libvterm/src/keyboard.c libvterm/src/mouse.c libvterm/src/parser.c libvterm/src/pen.c libvterm/src/screen.c libvterm/src/state.c libvterm/src/unicode.c libvterm/src/vterm.c"

TERM_OBJ="objects/term_encoding.o objects/term_keyboard.o objects/term_mouse.o objects/term_parser.o objects/term_pen.o objects/term_screen.o objects/term_state.o objects/term_unicode.o objects/term_vterm.o"

fi

{ $as_echo "$as_me:${as_lineno-$LINENO}: checking --enable-multibyte argument" >&5
$as_echo_n "checking --enable-multibyte argument... " >&6; }
# Check whether --enable-multibyte was given.
@@ -431,6 +431,9 @@
/* Define if you want to include process communication. */
#undef FEAT_JOB_CHANNEL

/* Define if you want to include terminal emulator support. */
#undef FEAT_TERMINAL

/* Define default global runtime path */
#undef RUNTIME_GLOBAL

@@ -91,6 +91,8 @@ NETBEANS_SRC = @NETBEANS_SRC@
NETBEANS_OBJ = @NETBEANS_OBJ@
CHANNEL_SRC = @CHANNEL_SRC@
CHANNEL_OBJ = @CHANNEL_OBJ@
TERM_SRC = @TERM_SRC@
TERM_OBJ = @TERM_OBJ@

RUBY = @vi_cv_path_ruby@
RUBY_SRC = @RUBY_SRC@
@@ -2028,6 +2028,28 @@ if test "$enable_channel" = "yes"; then
AC_SUBST(CHANNEL_OBJ)
fi

AC_MSG_CHECKING(--enable-terminal argument)
AC_ARG_ENABLE(terminal,
[ --enable-terminal Disable terminal emulation support.],
[enable_terminal="yes"], )
if test "$enable_terminal" = "yes"; then
if test "x$features" = "xtiny" -o "x$features" = "xsmall"; then
AC_MSG_RESULT([cannot use terminal emulator with tiny or small features])
enable_terminal="no"
else
AC_MSG_RESULT(yes)
fi
else
AC_MSG_RESULT(no)
fi
if test "$enable_terminal" = "yes"; then
AC_DEFINE(FEAT_TERMINAL)
TERM_SRC="libvterm/src/encoding.c libvterm/src/keyboard.c libvterm/src/mouse.c libvterm/src/parser.c libvterm/src/pen.c libvterm/src/screen.c libvterm/src/state.c libvterm/src/unicode.c libvterm/src/vterm.c"
AC_SUBST(TERM_SRC)
TERM_OBJ="objects/term_encoding.o objects/term_keyboard.o objects/term_mouse.o objects/term_parser.o objects/term_pen.o objects/term_screen.o objects/term_state.o objects/term_unicode.o objects/term_vterm.o"
AC_SUBST(TERM_OBJ)
fi

AC_MSG_CHECKING(--enable-multibyte argument)
AC_ARG_ENABLE(multibyte,
[ --enable-multibyte Include multibyte editing support.], ,
@@ -5870,6 +5870,9 @@ f_has(typval_T *argvars, typval_T *rettv)
#ifdef FEAT_TERMGUICOLORS
"termguicolors",
#endif
#ifdef FEAT_TERMINAL
"terminal",
#endif
#ifdef TERMINFO
"terminfo",
#endif
@@ -25,12 +25,12 @@ static const unsigned short cmdidxs1[26] =
/* r */ 351,
/* s */ 370,
/* t */ 437,
/* u */ 472,
/* v */ 483,
/* w */ 501,
/* x */ 516,
/* y */ 525,
/* z */ 526
/* u */ 473,
/* v */ 484,
/* w */ 502,
/* x */ 517,
/* y */ 526,
/* z */ 527
};

/*
@@ -60,7 +60,7 @@ static const unsigned char cmdidxs2[26][26] =
/* q */ { 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
/* r */ { 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 18, 0, 0, 0, 0 },
/* s */ { 2, 6, 15, 0, 18, 22, 0, 24, 25, 0, 0, 28, 30, 34, 38, 40, 0, 48, 0, 49, 0, 61, 62, 0, 63, 0 },
/* t */ { 2, 0, 19, 0, 22, 23, 0, 24, 0, 25, 0, 26, 27, 28, 29, 30, 0, 31, 33, 0, 34, 0, 0, 0, 0, 0 },
/* t */ { 2, 0, 19, 0, 22, 24, 0, 25, 0, 26, 0, 27, 28, 29, 30, 31, 0, 32, 34, 0, 35, 0, 0, 0, 0, 0 },
/* u */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
/* v */ { 0, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 9, 12, 0, 0, 0, 0, 15, 0, 16, 0, 0, 0, 0, 0 },
/* w */ { 2, 0, 0, 0, 0, 0, 0, 3, 4, 0, 0, 0, 0, 8, 0, 9, 10, 0, 12, 0, 13, 14, 0, 0, 0, 0 },
@@ -69,4 +69,4 @@ static const unsigned char cmdidxs2[26][26] =
/* z */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
};

static const int command_count = 539;
static const int command_count = 540;
@@ -488,6 +488,9 @@ static void ex_folddo(exarg_T *eap);
#ifndef FEAT_PROFILE
# define ex_profile ex_ni
#endif
#ifndef FEAT_TERMINAL
# define ex_terminal ex_ni
#endif

/*
* Declare cmdnames[].
@@ -1267,6 +1267,13 @@
# undef FEAT_JOB_CHANNEL
#endif

/*
* +terminal ":terminal" command. Runs a terminal in a window.
*/
#if !defined(FEAT_JOB_CHANNEL) && defined(FEAT_TERMINAL)
# undef FEAT_TERMINAL
#endif

/*
* +signs Allow signs to be displayed to the left of text lines.
* Adds the ":sign" command.
@@ -0,0 +1,13 @@
.libs
*.lo
*.la

bin/*
!bin/*.c

pangoterm
t/test
t/suites.h
t/externs.h
t/harness
src/encoding/*.inc
@@ -0,0 +1,18 @@
*~
*.swp

tags
src/*.o
src/*.lo
src/encoding/*.inc

libvterm.la
bin/unterm
bin/vterm-ctrl
bin/vterm-dump

t/harness
t/harness.lo
t/harness.o

.libs/
@@ -0,0 +1,23 @@


The MIT License

Copyright (c) 2008 Paul Evans <leonerd@leonerd.org.uk>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
@@ -0,0 +1,145 @@
ifeq ($(shell uname),Darwin)
LIBTOOL ?= glibtool
else
LIBTOOL ?= libtool
endif

ifneq ($(VERBOSE),1)
LIBTOOL +=--quiet
endif

# override CFLAGS +=-Wall -Iinclude -std=c99 -DINLINE="static inline" -DUSE_INLINE
override CFLAGS +=-Wall -Iinclude -std=c90 -Wpedantic -DINLINE=""

ifeq ($(shell uname),SunOS)
override CFLAGS +=-D__EXTENSIONS__ -D_XPG6 -D__XOPEN_OR_POSIX
endif

ifeq ($(DEBUG),1)
override CFLAGS +=-ggdb -DDEBUG
endif

ifeq ($(PROFILE),1)
override CFLAGS +=-pg
override LDFLAGS+=-pg
endif

CFILES=$(sort $(wildcard src/*.c))
HFILES=$(sort $(wildcard include/*.h))
OBJECTS=$(CFILES:.c=.lo)
LIBRARY=libvterm.la

BINFILES_SRC=$(sort $(wildcard bin/*.c))
BINFILES=$(BINFILES_SRC:.c=)

TBLFILES=$(sort $(wildcard src/encoding/*.tbl))
INCFILES=$(TBLFILES:.tbl=.inc)

HFILES_INT=$(sort $(wildcard src/*.h)) $(HFILES)

VERSION_MAJOR=0
VERSION_MINOR=0

VERSION_CURRENT=0
VERSION_REVISION=0
VERSION_AGE=0

VERSION=0

PREFIX=/usr/local
BINDIR=$(PREFIX)/bin
LIBDIR=$(PREFIX)/lib
INCDIR=$(PREFIX)/include
MANDIR=$(PREFIX)/share/man
MAN3DIR=$(MANDIR)/man3

all: $(LIBRARY) $(BINFILES)

$(LIBRARY): $(OBJECTS)
@echo LINK $@
@$(LIBTOOL) --mode=link --tag=CC $(CC) -rpath $(LIBDIR) -version-info $(VERSION_CURRENT):$(VERSION_REVISION):$(VERSION_AGE) -o $@ $^ $(LDFLAGS)

src/%.lo: src/%.c $(HFILES_INT)
@echo CC $<
@$(LIBTOOL) --mode=compile --tag=CC $(CC) $(CFLAGS) -o $@ -c $<

src/encoding/%.inc: src/encoding/%.tbl
@echo TBL $<
@perl -CSD tbl2inc_c.pl $< >$@

src/encoding.lo: $(INCFILES)

bin/%: bin/%.c $(LIBRARY)
@echo CC $<
@$(LIBTOOL) --mode=link --tag=CC $(CC) $(CFLAGS) -o $@ $< -lvterm $(LDFLAGS)

t/harness.lo: t/harness.c $(HFILES)
@echo CC $<
@$(LIBTOOL) --mode=compile --tag=CC $(CC) $(CFLAGS) -o $@ -c $<

t/harness: t/harness.lo $(LIBRARY)
@echo LINK $@
@$(LIBTOOL) --mode=link --tag=CC $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)

.PHONY: test
test: $(LIBRARY) t/harness
for T in `ls t/[0-9]*.test`; do echo "** $$T **"; perl t/run-test.pl $$T $(if $(VALGRIND),--valgrind) || exit 1; done

.PHONY: clean
clean:
$(LIBTOOL) --mode=clean rm -f $(OBJECTS) $(INCFILES)
$(LIBTOOL) --mode=clean rm -f t/harness.lo t/harness
$(LIBTOOL) --mode=clean rm -f $(LIBRARY) $(BINFILES)

.PHONY: install
install: install-inc install-lib install-bin

install-inc:
install -d $(DESTDIR)$(INCDIR)
install -m644 $(HFILES) $(DESTDIR)$(INCDIR)
install -d $(DESTDIR)$(LIBDIR)/pkgconfig
sed -e "s,@PREFIX@,$(PREFIX)," -e "s,@LIBDIR@,$(LIBDIR)," -e "s,@VERSION@,$(VERSION)," <vterm.pc.in >$(DESTDIR)$(LIBDIR)/pkgconfig/vterm.pc

install-lib: $(LIBRARY)
install -d $(DESTDIR)$(LIBDIR)
$(LIBTOOL) --mode=install install $(LIBRARY) $(DESTDIR)$(LIBDIR)/$(LIBRARY)
$(LIBTOOL) --mode=finish $(DESTDIR)$(LIBDIR)

install-bin: $(BINFILES)
install -d $(DESTDIR)$(BINDIR)
$(LIBTOOL) --mode=install install $(BINFILES) $(DESTDIR)$(BINDIR)/

# DIST CUT

VERSION=$(VERSION_MAJOR).$(VERSION_MINOR)

DISTDIR=libvterm-$(VERSION)

distdir: $(INCFILES)
mkdir __distdir
cp LICENSE __distdir
mkdir __distdir/src
cp src/*.c src/*.h __distdir/src
mkdir __distdir/src/encoding
cp src/encoding/*.inc __distdir/src/encoding
mkdir __distdir/include
cp include/*.h __distdir/include
mkdir __distdir/bin
cp bin/*.c __distdir/bin
mkdir __distdir/t
cp t/*.test t/harness.c t/run-test.pl __distdir/t
sed "s,@VERSION@,$(VERSION)," <vterm.pc.in >__distdir/vterm.pc.in
sed "/^# DIST CUT/Q" <Makefile >__distdir/Makefile
mv __distdir $(DISTDIR)

TARBALL=$(DISTDIR).tar.gz

dist: distdir
tar -czf $(TARBALL) $(DISTDIR)
rm -rf $(DISTDIR)

dist+bzr:
$(MAKE) dist VERSION=$(VERSION)+bzr`bzr revno`

distdir+bzr:
$(MAKE) distdir VERSION=$(VERSION)+bzr`bzr revno`
@@ -0,0 +1,13 @@
This is a MODIFIED version of libvterm.

The original can be found:
On the original site (tar archive and Bazaar repository):
http://www.leonerd.org.uk/code/libvterm/
Cloned on Github:
https://github.com/neovim/libvterm

Modifications:
- Add a .gitignore file.
- Convert from C99 to C90.
- Other changes to support embedding in Vim.
-
@@ -0,0 +1,287 @@
#include <stdio.h>
#include <string.h>

#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <unistd.h>

#include "vterm.h"

#define DEFINE_INLINES
#include "../src/utf8.h" /* fill_utf8 */

#define streq(a,b) (!strcmp(a,b))

static VTerm *vt;
static VTermScreen *vts;

static int cols;
static int rows;

static enum {
FORMAT_PLAIN,
FORMAT_SGR
} format = FORMAT_PLAIN;

static int col2index(VTermColor target)
{
int index;

for(index = 0; index < 256; index++) {
VTermColor col;
vterm_state_get_palette_color(NULL, index, &col);
if(col.red == target.red && col.green == target.green && col.blue == target.blue)
return index;
}
return -1;
}

static void dump_cell(const VTermScreenCell *cell, const VTermScreenCell *prevcell)
{
switch(format) {
case FORMAT_PLAIN:
break;
case FORMAT_SGR:
{
/* If all 7 attributes change, that means 7 SGRs max */
/* Each colour could consume up to 3 */
int sgr[7 + 2*3]; int sgri = 0;

if(!prevcell->attrs.bold && cell->attrs.bold)
sgr[sgri++] = 1;
if(prevcell->attrs.bold && !cell->attrs.bold)
sgr[sgri++] = 22;

if(!prevcell->attrs.underline && cell->attrs.underline)
sgr[sgri++] = 4;
if(prevcell->attrs.underline && !cell->attrs.underline)
sgr[sgri++] = 24;

if(!prevcell->attrs.italic && cell->attrs.italic)
sgr[sgri++] = 3;
if(prevcell->attrs.italic && !cell->attrs.italic)
sgr[sgri++] = 23;

if(!prevcell->attrs.blink && cell->attrs.blink)
sgr[sgri++] = 5;
if(prevcell->attrs.blink && !cell->attrs.blink)
sgr[sgri++] = 25;

if(!prevcell->attrs.reverse && cell->attrs.reverse)
sgr[sgri++] = 7;
if(prevcell->attrs.reverse && !cell->attrs.reverse)
sgr[sgri++] = 27;

if(!prevcell->attrs.strike && cell->attrs.strike)
sgr[sgri++] = 9;
if(prevcell->attrs.strike && !cell->attrs.strike)
sgr[sgri++] = 29;

if(!prevcell->attrs.font && cell->attrs.font)
sgr[sgri++] = 10 + cell->attrs.font;
if(prevcell->attrs.font && !cell->attrs.font)
sgr[sgri++] = 10;

if(prevcell->fg.red != cell->fg.red ||
prevcell->fg.green != cell->fg.green ||
prevcell->fg.blue != cell->fg.blue) {
int index = col2index(cell->fg);
if(index == -1)
sgr[sgri++] = 39;
else if(index < 8)
sgr[sgri++] = 30 + index;
else if(index < 16)
sgr[sgri++] = 90 + (index - 8);
else {
sgr[sgri++] = 38;
sgr[sgri++] = 5 | (1<<31);
sgr[sgri++] = index | (1<<31);
}
}

if(prevcell->bg.red != cell->bg.red ||
prevcell->bg.green != cell->bg.green ||
prevcell->bg.blue != cell->bg.blue) {
int index = col2index(cell->bg);
if(index == -1)
sgr[sgri++] = 49;
else if(index < 8)
sgr[sgri++] = 40 + index;
else if(index < 16)
sgr[sgri++] = 100 + (index - 8);
else {
sgr[sgri++] = 48;
sgr[sgri++] = 5 | (1<<31);
sgr[sgri++] = index | (1<<31);
}
}

if(!sgri)
break;

printf("\x1b[");
{
int i;
for(i = 0; i < sgri; i++)
printf(!i ? "%d" :
sgr[i] & (1<<31) ? ":%d" :
";%d",
sgr[i] & ~(1<<31));
}
printf("m");
}
break;
}

{
int i;
for(i = 0; i < VTERM_MAX_CHARS_PER_CELL && cell->chars[i]; i++) {
char bytes[6];
bytes[fill_utf8(cell->chars[i], bytes)] = 0;
printf("%s", bytes);
}
}
}

static void dump_eol(const VTermScreenCell *prevcell)
{
switch(format) {
case FORMAT_PLAIN:
break;
case FORMAT_SGR:
if(prevcell->attrs.bold || prevcell->attrs.underline || prevcell->attrs.italic ||
prevcell->attrs.blink || prevcell->attrs.reverse || prevcell->attrs.strike ||
prevcell->attrs.font)
printf("\x1b[m");
break;
}

printf("\n");
}

void dump_row(int row)
{
VTermPos pos;
VTermScreenCell prevcell;
pos.row = row;
pos.col = 0;
memset(&prevcell, 0, sizeof(prevcell));
vterm_state_get_default_colors(vterm_obtain_state(vt), &prevcell.fg, &prevcell.bg);

while(pos.col < cols) {
VTermScreenCell cell;
vterm_screen_get_cell(vts, pos, &cell);

dump_cell(&cell, &prevcell);

pos.col += cell.width;
prevcell = cell;
}

dump_eol(&prevcell);
}

static int screen_sb_pushline(int cols, const VTermScreenCell *cells, void *user)
{
VTermScreenCell prevcell;
int col;

memset(&prevcell, 0, sizeof(prevcell));
vterm_state_get_default_colors(vterm_obtain_state(vt), &prevcell.fg, &prevcell.bg);

for(col = 0; col < cols; col++) {
dump_cell(cells + col, &prevcell);
prevcell = cells[col];
}

dump_eol(&prevcell);

return 1;
}

static int screen_resize(int new_rows, int new_cols, void *user)
{
rows = new_rows;
cols = new_cols;
return 1;
}

static VTermScreenCallbacks cb_screen = {
NULL, /* damage */
NULL, /* moverect */
NULL, /* movecursor */
NULL, /* settermprop */
NULL, /* bell */
&screen_resize, /* resize */
&screen_sb_pushline, /* sb_pushline */
NULL, /* popline */
};

int main(int argc, char *argv[])
{
int opt;
const char *file;
int fd;
int len;
char buffer[1024];
int row;

rows = 25;
cols = 80;

while((opt = getopt(argc, argv, "f:l:c:")) != -1) {
switch(opt) {
case 'f':
if(streq(optarg, "plain"))
format = FORMAT_PLAIN;
else if(streq(optarg, "sgr"))
format = FORMAT_SGR;
else {
fprintf(stderr, "Unrecognised format '%s'\n", optarg);
exit(1);
}
break;

case 'l':
rows = atoi(optarg);
if(!rows)
rows = 25;
break;

case 'c':
cols = atoi(optarg);
if(!cols)
cols = 80;
break;
}
}

file = argv[optind++];
fd = open(file, O_RDONLY);
if(fd == -1) {
fprintf(stderr, "Cannot open %s - %s\n", file, strerror(errno));
exit(1);
}

vt = vterm_new(rows, cols);
vterm_set_utf8(vt, true);

vts = vterm_obtain_screen(vt);
vterm_screen_set_callbacks(vts, &cb_screen, NULL);

vterm_screen_reset(vts, 1);

while((len = read(fd, buffer, sizeof(buffer))) > 0) {
vterm_input_write(vt, buffer, len);
}

for(row = 0; row < rows; row++) {
dump_row(row);
}

close(fd);

vterm_free(vt);
return 0;
}
@@ -0,0 +1,362 @@
#define _XOPEN_SOURCE 500 /* strdup */

#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define streq(a,b) (strcmp(a,b)==0)

#include <termios.h>

static char *getvalue(int *argip, int argc, char *argv[])
{
if(*argip >= argc) {
fprintf(stderr, "Expected an option value\n");
exit(1);
}

return argv[(*argip)++];
}

static int getchoice(int *argip, int argc, char *argv[], const char *options[])
{
const char *arg = getvalue(argip, argc, argv);

int value = -1;
while(options[++value])
if(streq(arg, options[value]))
return value;

fprintf(stderr, "Unrecognised option value %s\n", arg);
exit(1);
}

typedef enum {
OFF,
ON,
QUERY
} BoolQuery;

static BoolQuery getboolq(int *argip, int argc, char *argv[])
{
const char *choices[] = {"off", "on", "query", NULL};
return getchoice(argip, argc, argv, choices);
}

static char *helptext[] = {
"reset",
"s8c1t [off|on]",
"keypad [app|num]",
"screen [off|on|query]",
"cursor [off|on|query]",
"curblink [off|on|query]",
"curshape [block|under|bar|query]",
"mouse [off|click|clickdrag|motion]",
"altscreen [off|on|query]",
"bracketpaste [off|on|query]",
"icontitle [STR]",
"icon [STR]",
"title [STR]",
NULL
};

static bool seticanon(bool icanon, bool echo)
{
struct termios termios;

tcgetattr(0, &termios);

bool ret = (termios.c_lflag & ICANON);

if(icanon) termios.c_lflag |= ICANON;
else termios.c_lflag &= ~ICANON;

if(echo) termios.c_lflag |= ECHO;
else termios.c_lflag &= ~ECHO;

tcsetattr(0, TCSANOW, &termios);

return ret;
}

static void await_c1(int c1)
{
int c;

/* await CSI - 8bit or 2byte 7bit form */
bool in_esc = false;
while((c = getchar())) {
if(c == c1)
break;
if(in_esc && c == (char)(c1 - 0x40))
break;
if(!in_esc && c == 0x1b)
in_esc = true;
else
in_esc = false;
}
}

static char *read_csi()
{
unsigned char csi[32];
int i = 0;

await_c1(0x9B); /* CSI */

/* TODO: This really should be a more robust CSI parser
*/
for(; i < sizeof(csi)-1; i++) {
int c = csi[i] = getchar();
if(c >= 0x40 && c <= 0x7e)
break;
}
csi[++i] = 0;

/* TODO: returns longer than 32? */

return strdup((char *)csi);
}

static char *read_dcs()
{
unsigned char dcs[32];
bool in_esc = false;
int i;

await_c1(0x90);

for(i = 0; i < sizeof(dcs)-1; ) {
char c = getchar();
if(c == 0x9c) /* ST */
break;
if(in_esc && c == 0x5c)
break;
if(!in_esc && c == 0x1b)
in_esc = true;
else {
dcs[i++] = c;
in_esc = false;
}
}
dcs[++i] = 0;

return strdup((char *)dcs);
}

static void usage(int exitcode)
{
char **p;

fprintf(stderr, "Control a libvterm-based terminal\n"
"\n"
"Options:\n");

for(p = helptext; *p; p++)
fprintf(stderr, " %s\n", *p);

exit(exitcode);
}

static bool query_dec_mode(int mode)
{
char *s = NULL;

printf("\x1b[?%d$p", mode);

do {
int reply_mode, reply_value;
char reply_cmd;

if(s)
free(s);
s = read_csi();

/* expect "?" mode ";" value "$y" */

/* If the sscanf format string ends in a literal, we can't tell from
* its return value if it matches. Hence we'll %c the cmd and check it
* explicitly
*/
if(sscanf(s, "?%d;%d$%c", &reply_mode, &reply_value, &reply_cmd) < 3)
continue;
if(reply_cmd != 'y')
continue;

if(reply_mode != mode)
continue;

free(s);

if(reply_value == 1 || reply_value == 3)
return true;
if(reply_value == 2 || reply_value == 4)
return false;

printf("Unrecognised reply to DECRQM: %d\n", reply_value);
return false;
} while(1);
}

static void do_dec_mode(int mode, BoolQuery val, const char *name)
{
switch(val) {
case OFF:
case ON:
printf("\x1b[?%d%c", mode, val == ON ? 'h' : 'l');
break;

case QUERY:
if(query_dec_mode(mode))
printf("%s on\n", name);
else
printf("%s off\n", name);
break;
}
}

static int query_rqss_numeric(char *cmd)
{
char *s = NULL;

printf("\x1bP$q%s\x1b\\", cmd);

do {
int num;

if(s)
free(s);
s = read_dcs();

if(!s)
return -1;
if(strlen(s) < strlen(cmd))
return -1;
if(strcmp(s + strlen(s) - strlen(cmd), cmd) != 0) {
printf("No match\n");
continue;
}

if(s[0] != '1' || s[1] != '$' || s[2] != 'r')
return -1;

if(sscanf(s + 3, "%d", &num) != 1)
return -1;

return num;
} while(1);
}

bool wasicanon;

void restoreicanon(void)
{
seticanon(wasicanon, true);
}

int main(int argc, char *argv[])
{
int argi = 1;

if(argc == 1)
usage(0);

wasicanon = seticanon(false, false);
atexit(restoreicanon);

while(argi < argc) {
const char *arg = argv[argi++];

if(streq(arg, "reset")) {
printf("\x1b" "c");
}
else if(streq(arg, "s8c1t")) {
const char *choices[] = {"off", "on", NULL};
switch(getchoice(&argi, argc, argv, choices)) {
case 0:
printf("\x1b F"); break;
case 1:
printf("\x1b G"); break;
}
}
else if(streq(arg, "keypad")) {
const char *choices[] = {"app", "num", NULL};
switch(getchoice(&argi, argc, argv, choices)) {
case 0:
printf("\x1b="); break;
case 1:
printf("\x1b>"); break;
}
}
else if(streq(arg, "screen")) {
do_dec_mode(5, getboolq(&argi, argc, argv), "screen");
}
else if(streq(arg, "cursor")) {
do_dec_mode(25, getboolq(&argi, argc, argv), "cursor");
}
else if(streq(arg, "curblink")) {
do_dec_mode(12, getboolq(&argi, argc, argv), "curblink");
}
else if(streq(arg, "curshape")) {
/* TODO: This ought to query the current value of DECSCUSR because it */
/* may need blinking on or off */
const char *choices[] = {"block", "under", "bar", "query", NULL};
int shape = getchoice(&argi, argc, argv, choices);
switch(shape) {
case 3: /* query */
shape = query_rqss_numeric(" q");
switch(shape) {
case 1: case 2:
printf("curshape block\n");
break;
case 3: case 4:
printf("curshape under\n");
break;
case 5: case 6:
printf("curshape bar\n");
break;
}
break;

case 0:
case 1:
case 2:
printf("\x1b[%d q", 1 + (shape * 2));
break;
}
}
else if(streq(arg, "mouse")) {
const char *choices[] = {"off", "click", "clickdrag", "motion", NULL};
switch(getchoice(&argi, argc, argv, choices)) {
case 0:
printf("\x1b[?1000l"); break;
case 1:
printf("\x1b[?1000h"); break;
case 2:
printf("\x1b[?1002h"); break;
case 3:
printf("\x1b[?1003h"); break;
}
}
else if(streq(arg, "altscreen")) {
do_dec_mode(1049, getboolq(&argi, argc, argv), "altscreen");
}
else if(streq(arg, "bracketpaste")) {
do_dec_mode(2004, getboolq(&argi, argc, argv), "bracketpaste");
}
else if(streq(arg, "icontitle")) {
printf("\x1b]0;%s\a", getvalue(&argi, argc, argv));
}
else if(streq(arg, "icon")) {
printf("\x1b]1;%s\a", getvalue(&argi, argc, argv));
}
else if(streq(arg, "title")) {
printf("\x1b]2;%s\a", getvalue(&argi, argc, argv));
}
else {
fprintf(stderr, "Unrecognised command %s\n", arg);
exit(1);
}
}
return 0;
}
@@ -0,0 +1,231 @@
/* Require getopt(3) */
#define _XOPEN_SOURCE

#include <stdio.h>
#include <string.h>
#define streq(a,b) (strcmp(a,b)==0)

#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include "vterm.h"

static const char *special_begin = "{";
static const char *special_end = "}";

static int parser_text(const char bytes[], size_t len, void *user)
{
unsigned char *b = (unsigned char *)bytes;

int i;
for(i = 0; i < len; /* none */) {
if(b[i] < 0x20) /* C0 */
break;
else if(b[i] < 0x80) /* ASCII */
i++;
else if(b[i] < 0xa0) /* C1 */
break;
else if(b[i] < 0xc0) /* UTF-8 continuation */
break;
else if(b[i] < 0xe0) { /* UTF-8 2-byte */
/* 2-byte UTF-8 */
if(len < i+2) break;
i += 2;
}
else if(b[i] < 0xf0) { /* UTF-8 3-byte */
if(len < i+3) break;
i += 3;
}
else if(b[i] < 0xf8) { /* UTF-8 4-byte */
if(len < i+4) break;
i += 4;
}
else /* otherwise invalid */
break;
}

printf("%.*s", i, b);
return i;
}

/* 0 1 2 3 4 5 6 7 8 9 A B C D E F */
static const char *name_c0[] = {
"NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BEL", "BS", "HT", "LF", "VT", "FF", "CR", "LS0", "LS1",
"DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB", "CAN", "EM", "SUB", "ESC", "FS", "GS", "RS", "US",
};
static const char *name_c1[] = {
NULL, NULL, "BPH", "NBH", NULL, "NEL", "SSA", "ESA", "HTS", "HTJ", "VTS", "PLD", "PLU", "RI", "SS2", "SS3",
"DCS", "PU1", "PU2", "STS", "CCH", "MW", "SPA", "EPA", "SOS", NULL, "SCI", "CSI", "ST", "OSC", "PM", "APC",
};

static int parser_control(unsigned char control, void *user)
{
if(control < 0x20)
printf("%s%s%s", special_begin, name_c0[control], special_end);
else if(control >= 0x80 && control < 0xa0 && name_c1[control - 0x80])
printf("%s%s%s", special_begin, name_c1[control - 0x80], special_end);
else
printf("%sCONTROL 0x%02x%s", special_begin, control, special_end);

if(control == 0x0a)
printf("\n");
return 1;
}

static int parser_escape(const char bytes[], size_t len, void *user)
{
if(bytes[0] >= 0x20 && bytes[0] < 0x30) {
if(len < 2)
return -1;
len = 2;
}
else {
len = 1;
}

printf("%sESC %.*s%s", special_begin, (int)len, bytes, special_end);

return len;
}

/* 0 1 2 3 4 5 6 7 8 9 A B C D E F */
static const char *name_csi_plain[] = {
"ICH", "CUU", "CUD", "CUF", "CUB", "CNL", "CPL", "CHA", "CUP", "CHT", "ED", "EL", "IL", "DL", "EF", "EA",
"DCH", "SSE", "CPR", "SU", "SD", "NP", "PP", "CTC", "ECH", "CVT", "CBT", "SRS", "PTX", "SDS", "SIMD",NULL,
"HPA", "HPR", "REP", "DA", "VPA", "VPR", "HVP", "TBC", "SM", "MC", "HPB", "VPB", "RM", "SGR", "DSR", "DAQ",
};

/*0 4 8 B */
static const int newline_csi_plain[] = {
0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0,
};

static int parser_csi(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user)
{
const char *name = NULL;
if(!leader && !intermed && command < 0x70)
name = name_csi_plain[command - 0x40];
else if(leader && streq(leader, "?") && !intermed) {
/* DEC */
switch(command) {
case 'h': name = "DECSM"; break;
case 'l': name = "DECRM"; break;
}
if(name)
leader = NULL;
}

if(!leader && !intermed && command < 0x70 && newline_csi_plain[command - 0x40])
printf("\n");

if(name)
printf("%s%s", special_begin, name);
else
printf("%sCSI", special_begin);

if(leader && leader[0])
printf(" %s", leader);

{
int i;
for(i = 0; i < argcount; i++) {
printf(i ? "," : " ");
}

if(args[i] == CSI_ARG_MISSING)
printf("*");
else {
while(CSI_ARG_HAS_MORE(args[i]))
printf("%ld+", CSI_ARG(args[i++]));
printf("%ld", CSI_ARG(args[i]));
}
}

if(intermed && intermed[0])
printf(" %s", intermed);

if(name)
printf("%s", special_end);
else
printf(" %c%s", command, special_end);

return 1;
}

static int parser_osc(const char *command, size_t cmdlen, void *user)
{
printf("%sOSC %.*s%s", special_begin, (int)cmdlen, command, special_end);

return 1;
}

static int parser_dcs(const char *command, size_t cmdlen, void *user)
{
printf("%sDCS %.*s%s", special_begin, (int)cmdlen, command, special_end);

return 1;
}

static VTermParserCallbacks parser_cbs = {
&parser_text, /* text */
&parser_control, /* control */
&parser_escape, /* escape */
&parser_csi, /* csi */
&parser_osc, /* osc */
&parser_dcs, /* dcs */
NULL /* resize */
};

int main(int argc, char *argv[])
{
int use_colour = isatty(1);
const char *file;
int fd;
VTerm *vt;
int len;
char buffer[1024];

int opt;
while((opt = getopt(argc, argv, "c")) != -1) {
switch(opt) {
case 'c': use_colour = 1; break;
}
}

file = argv[optind++];

if(!file || streq(file, "-"))
fd = 0; /* stdin */
else {
fd = open(file, O_RDONLY);
if(fd == -1) {
fprintf(stderr, "Cannot open %s - %s\n", file, strerror(errno));
exit(1);
}
}

if(use_colour) {
special_begin = "\x1b[7m{";
special_end = "}\x1b[m";
}

/* Size matters not for the parser */
vt = vterm_new(25, 80);
vterm_set_utf8(vt, 1);
vterm_parser_set_callbacks(vt, &parser_cbs, NULL);

while((len = read(fd, buffer, sizeof(buffer))) > 0) {
vterm_input_write(vt, buffer, len);
}

printf("\n");

close(fd);
vterm_free(vt);
return 0;
}
@@ -0,0 +1,11 @@
ECMA-48:
http://www.ecma-international.org/publications/standards/Ecma-048.htm

Xterm Control Sequences:
http://invisible-island.net/xterm/ctlseqs/ctlseqs.html

Digital VT100 User Guide:
http://vt100.net/docs/vt100-ug/

Digital VT220 Programmer Reference Manual
http://vt100.net/docs/vt220-rm/
@@ -0,0 +1,226 @@
Sequences documented in parens are implicit ones from parser.c, which move
between states.

1 = VT100
2 = VT220
3 = VT320

C0 controls

123 0x00 = NUL
123 0x07 = BEL
123 0x08 = BS
123 0x09 = HT
123 0x0A = LF
123 0x0B = VT
123 0x0C = FF
123 0x0D = CR
123 0x0E = LS1
123 0x0F = LS0
(0x18 = CAN)
(0x1A = SUB)
(0x1B = ESC)

123 0x7f = DEL (ignored)

C1 controls

123 0x84 = IND
123 0x85 = NEL
123 0x88 = HTS
123 0x8D = RI
23 0x8e = SS2
23 0x8f = SS3
(0x90 = DCS)
(0x9B = CSI)
(0x9C = ST)
(0x9D = OSC)

Escape sequences
- excluding sequences that are C1 aliases

123 ESC () = SCS, select character set (G0, G1)
23 ESC *+ = SCS, select character set (G2, G3)
123 ESC 7 = DECSC - save cursor
123 ESC 8 = DECRC - restore cursor
123 ESC # 3 = DECDHL, double-height line (top half)
123 ESC # 4 = DECDHL, double-height line (bottom half)
123 ESC # 5 = DECSWL, single-width single-height line
123 ESC # 6 = DECDWL, double-width single-height line
123 ESC # 8 = DECALN
123 ESC < = Ignored (used by VT100 to exit VT52 mode)
123 ESC = = DECKPAM, keypad application mode
123 ESC > = DECKPNM, keypad numeric mode
23 ESC Sp F = S7C1T
23 ESC Sp G = S8C1T
(ESC P = DCS)
(ESC [ = CSI)
(ESC \ = ST)
(ESC ] = OSC)
123 ESC c = RIS, reset initial state
3 ESC n = LS2
3 ESC o = LS3
3 ESC ~ = LS1R
3 ESC } = LS2R
3 ESC | = LS3R

DCSes

3 DCS $ q ST = DECRQSS
3 m = Request SGR
Sp q = Request DECSCUSR
3 " q = Request DECSCA
3 r = Request DECSTBM
s = Request DECSLRM

CSIs
23 CSI @ = ICH
123 CSI A = CUU
123 CSI B = CUD
123 CSI C = CUF
123 CSI D = CUB
CSI E = CNL
CSI F = CPL
CSI G = CHA
123 CSI H = CUP
CSI I = CHT
123 CSI J = ED
23 CSI ? J = DECSED, selective erase in display
123 CSI K = EL
23 CSI ? K = DECSEL, selective erase in line
23 CSI L = IL
23 CSI M = DL
23 CSI P = DCH
CSI S = SU
CSI T = SD
23 CSI X = ECH
CSI Z = CBT
CSI ` = HPA
CSI a = HPR
123 CSI c = DA, device attributes
123 0 = DA
23 CSI > c = DECSDA
23 0 = SDA
CSI d = VPA
CSI e = VPR
123 CSI f = HVP
123 CSI g = TBC
123 CSI h = SM, Set mode
123 CSI ? h = DECSM, DEC set mode
CSI j = HPB
CSI k = VPB
123 CSI l = RM, Reset mode
123 CSI ? l = DECRM, DEC reset mode
123 CSI m = SGR, Set Graphic Rendition
123 CSI n = DSR, Device Status Report
23 5 = operating status
23 6 = CPR = cursor position
23 CSI ? n = DECDSR; behaves as DSR but uses CSI ? instead of CSI to respond
23 CSI ! p = DECSTR, soft terminal reset
3 CSI ? $ p = DECRQM, request mode
CSI Sp q = DECSCUSR (odd numbers blink, even numbers solid)
1 or 2 = block
3 or 4 = underline
5 or 6 = I-beam to left
23 CSI " q = DECSCA, select character attributes
123 CSI r = DECSTBM
CSI s = DECSLRM
CSI ' } = DECIC
CSI ' ~ = DECDC

OSCs

OSC 0; = Set icon name and title
OSC 1; = Set icon name
OSC 2; = Set title

Standard modes

23 SM 4 = IRM
123 SM 20 = NLM, linefeed/newline

DEC modes

123 DECSM 1 = DECCKM, cursor keys
123 DECSM 5 = DECSCNM, screen
123 DECSM 6 = DECOM, origin
123 DECSM 7 = DECAWM, autowrap
DECSM 12 = Cursor blink
23 DECSM 25 = DECTCEM, text cursor enable
DECSM 69 = DECVSSM, vertical screen split
DECSM 1000 = Mouse click/release tracking
DECSM 1002 = Mouse click/release/drag tracking
DECSM 1003 = Mouse all movements tracking
DECSM 1005 = Mouse protocol extended (UTF-8) - not recommended
DECSM 1006 = Mouse protocol SGR
DECSM 1015 = Mouse protocol rxvt
DECSM 1047 = Altscreen
DECSM 1048 = Save cursor
DECSM 1049 = 1047 + 1048
DECSM 2004 = Bracketed paste

Graphic Renditions

123 SGR 0 = Reset
123 SGR 1 = Bold on
SGR 3 = Italic on
123 SGR 4 = Underline single
123 SGR 5 = Blink on
123 SGR 7 = Reverse on
SGR 9 = Strikethrough on
SGR 10-19 = Select font
SGR 21 = Underline double
23 SGR 22 = Bold off
SGR 23 = Italic off
23 SGR 24 = Underline off
23 SGR 25 = Blink off
23 SGR 27 = Reverse off
SGR 29 = Strikethrough off
SGR 30-37 = Foreground ANSI
SGR 38 = Foreground alternative palette
SGR 39 = Foreground default
SGR 40-47 = Background ANSI
SGR 48 = Background alternative palette
SGR 49 = Background default
SGR 90-97 = Foreground ANSI high-intensity
SGR 100-107 = Background ANSI high-intensity

The state storage used by ESC 7 and DECSM 1048/1049 is shared.

Unimplemented sequences:

The following sequences are not recognised by libvterm.

123 0x05 = ENQ
3 0x11 = DC1 (XON)
3 0x13 = DC3 (XOFF)
12 ESC Z = DECID, identify terminal
DCS $ q = [DECRQSS]
3 " p = Request DECSCL
3 $ } = Request DECSASD
3 $ ~ = Request DECSSDT
23 DCS { = DECDLD, down-line-loadable character set
23 DCS | = DECUDK, user-defined key
23 CSI i = DEC printer control
23 CSI " p = DECSCL, set compatibility level
1 CSI q = DECLL, load LEDs
3 CSI $ u = DECRQTSR, request terminal state report
3 1 = terminal state report
3 CSI & u = DECRQUPSS, request user-preferred supplemental set
3 CSI $ w = DECRQPSR, request presentation state report
3 1 = cursor information report
3 2 = tab stop report
1 CSI x = DECREQTPARM, request terminal parameters
123 CSI y = DECTST, invoke confidence test
3 CSI $ } = DECSASD, select active status display
3 CSI $ ~ = DECSSDT, select status line type
23 SM 2 = KAM, keyboard action
123 SM 12 = SRM, send/receive
123 DECSM 2 = DECANM, ANSI/VT52
123 DECSM 3 = DECCOLM, 132 column
123 DECSM 4 = DECSCLM, scrolling
123 DECSM 8 = DECARM, auto-repeat
12 DECSM 9 = DECINLM, interlace
23 DECSM 18 = DECPFF, print form feed
23 DECSM 19 = DECPEX, print extent
23 DECSM 42 = DECNRCM, national/multinational character
@@ -0,0 +1,370 @@
/*
* NOTE: This is a MODIFIED version of libvterm, see the README file.
*/
#ifndef __VTERM_H__
#define __VTERM_H__

#ifdef __cplusplus
extern "C" {
#endif

#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>

#include "vterm_keycodes.h"

typedef struct VTerm VTerm;
typedef struct VTermState VTermState;
typedef struct VTermScreen VTermScreen;

/* Specifies a screen point. */
typedef struct {
int row;
int col;
} VTermPos;

/*
* Some small utility functions; we can just keep these static here.
*/

/*
* Order points by on-screen flow order:
* Return < 0 if "a" is before "b"
* Return 0 if "a" and "b" are equal
* Return > 0 if "a" is after "b".
*/
int vterm_pos_cmp(VTermPos a, VTermPos b);

#if defined(DEFINE_INLINES) || USE_INLINE
INLINE int vterm_pos_cmp(VTermPos a, VTermPos b)
{
return (a.row == b.row) ? a.col - b.col : a.row - b.row;
}
#endif

/* Specifies a rectangular screen area. */
typedef struct {
int start_row;
int end_row;
int start_col;
int end_col;
} VTermRect;

/* Return true if the rect "r" contains the point "p". */
int vterm_rect_contains(VTermRect r, VTermPos p);

#if defined(DEFINE_INLINES) || USE_INLINE
INLINE int vterm_rect_contains(VTermRect r, VTermPos p)
{
return p.row >= r.start_row && p.row < r.end_row &&
p.col >= r.start_col && p.col < r.end_col;
}
#endif

/* Move "rect" "row_delta" down and "col_delta" right.
* Does not check boundaries. */
void vterm_rect_move(VTermRect *rect, int row_delta, int col_delta);

#if defined(DEFINE_INLINES) || USE_INLINE
INLINE void vterm_rect_move(VTermRect *rect, int row_delta, int col_delta)
{
rect->start_row += row_delta; rect->end_row += row_delta;
rect->start_col += col_delta; rect->end_col += col_delta;
}
#endif

typedef struct {
uint8_t red, green, blue;
} VTermColor;

typedef enum {
/* VTERM_VALUETYPE_NONE = 0 */
VTERM_VALUETYPE_BOOL = 1,
VTERM_VALUETYPE_INT,
VTERM_VALUETYPE_STRING,
VTERM_VALUETYPE_COLOR
} VTermValueType;

typedef union {
int boolean;
int number;
char *string;
VTermColor color;
} VTermValue;

typedef enum {
/* VTERM_ATTR_NONE = 0 */
VTERM_ATTR_BOLD = 1, /* bool: 1, 22 */
VTERM_ATTR_UNDERLINE, /* number: 4, 21, 24 */
VTERM_ATTR_ITALIC, /* bool: 3, 23 */
VTERM_ATTR_BLINK, /* bool: 5, 25 */
VTERM_ATTR_REVERSE, /* bool: 7, 27 */
VTERM_ATTR_STRIKE, /* bool: 9, 29 */
VTERM_ATTR_FONT, /* number: 10-19 */
VTERM_ATTR_FOREGROUND, /* color: 30-39 90-97 */
VTERM_ATTR_BACKGROUND /* color: 40-49 100-107 */
} VTermAttr;

typedef enum {
/* VTERM_PROP_NONE = 0 */
VTERM_PROP_CURSORVISIBLE = 1, /* bool */
VTERM_PROP_CURSORBLINK, /* bool */
VTERM_PROP_ALTSCREEN, /* bool */
VTERM_PROP_TITLE, /* string */
VTERM_PROP_ICONNAME, /* string */
VTERM_PROP_REVERSE, /* bool */
VTERM_PROP_CURSORSHAPE, /* number */
VTERM_PROP_MOUSE /* number */
} VTermProp;

enum {
VTERM_PROP_CURSORSHAPE_BLOCK = 1,
VTERM_PROP_CURSORSHAPE_UNDERLINE,
VTERM_PROP_CURSORSHAPE_BAR_LEFT
};

enum {
VTERM_PROP_MOUSE_NONE = 0,
VTERM_PROP_MOUSE_CLICK,
VTERM_PROP_MOUSE_DRAG,
VTERM_PROP_MOUSE_MOVE
};

typedef struct {
const uint32_t *chars;
int width;
unsigned int protected_cell:1; /* DECSCA-protected against DECSEL/DECSED */
unsigned int dwl:1; /* DECDWL or DECDHL double-width line */
unsigned int dhl:2; /* DECDHL double-height line (1=top 2=bottom) */
} VTermGlyphInfo;

typedef struct {
unsigned int doublewidth:1; /* DECDWL or DECDHL line */
unsigned int doubleheight:2; /* DECDHL line (1=top 2=bottom) */
} VTermLineInfo;

typedef struct {
/* libvterm relies on the allocated memory to be zeroed out before it is
* returned by the allocator. */
void *(*malloc)(size_t size, void *allocdata);
void (*free)(void *ptr, void *allocdata);
} VTermAllocatorFunctions;

/* Allocate and initialize a new terminal with default allocators. */
VTerm *vterm_new(int rows, int cols);

/* Allocate and initialize a new terminal with specified allocators. */
VTerm *vterm_new_with_allocator(int rows, int cols, VTermAllocatorFunctions *funcs, void *allocdata);

/* Free and cleanup a terminal and all its data. */
void vterm_free(VTerm* vt);

void vterm_get_size(const VTerm *vt, int *rowsp, int *colsp);
void vterm_set_size(VTerm *vt, int rows, int cols);

int vterm_get_utf8(const VTerm *vt);
void vterm_set_utf8(VTerm *vt, int is_utf8);

size_t vterm_input_write(VTerm *vt, const char *bytes, size_t len);

size_t vterm_output_get_buffer_size(const VTerm *vt);
size_t vterm_output_get_buffer_current(const VTerm *vt);
size_t vterm_output_get_buffer_remaining(const VTerm *vt);

size_t vterm_output_read(VTerm *vt, char *buffer, size_t len);

void vterm_keyboard_unichar(VTerm *vt, uint32_t c, VTermModifier mod);
void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod);

void vterm_keyboard_start_paste(VTerm *vt);
void vterm_keyboard_end_paste(VTerm *vt);

void vterm_mouse_move(VTerm *vt, int row, int col, VTermModifier mod);
void vterm_mouse_button(VTerm *vt, int button, bool pressed, VTermModifier mod);

/* ------------
* Parser layer
* ------------ */

/* Flag to indicate non-final subparameters in a single CSI parameter.
* Consider
* CSI 1;2:3:4;5a
* 1 4 and 5 are final.
* 2 and 3 are non-final and will have this bit set
*
* Don't confuse this with the final byte of the CSI escape; 'a' in this case.
*/
#define CSI_ARG_FLAG_MORE (1<<31)
#define CSI_ARG_MASK (~(1<<31))

#define CSI_ARG_HAS_MORE(a) ((a) & CSI_ARG_FLAG_MORE)
#define CSI_ARG(a) ((a) & CSI_ARG_MASK)

/* Can't use -1 to indicate a missing argument; use this instead */
#define CSI_ARG_MISSING ((1UL<<31)-1)

#define CSI_ARG_IS_MISSING(a) (CSI_ARG(a) == CSI_ARG_MISSING)
#define CSI_ARG_OR(a,def) (CSI_ARG(a) == CSI_ARG_MISSING ? (def) : CSI_ARG(a))
#define CSI_ARG_COUNT(a) (CSI_ARG(a) == CSI_ARG_MISSING || CSI_ARG(a) == 0 ? 1 : CSI_ARG(a))

typedef struct {
int (*text)(const char *bytes, size_t len, void *user);
int (*control)(unsigned char control, void *user);
int (*escape)(const char *bytes, size_t len, void *user);
int (*csi)(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user);
int (*osc)(const char *command, size_t cmdlen, void *user);
int (*dcs)(const char *command, size_t cmdlen, void *user);
int (*resize)(int rows, int cols, void *user);
} VTermParserCallbacks;

void vterm_parser_set_callbacks(VTerm *vt, const VTermParserCallbacks *callbacks, void *user);
void *vterm_parser_get_cbdata(VTerm *vt);

/* -----------
* State layer
* ----------- */

typedef struct {
int (*putglyph)(VTermGlyphInfo *info, VTermPos pos, void *user);
int (*movecursor)(VTermPos pos, VTermPos oldpos, int visible, void *user);
int (*scrollrect)(VTermRect rect, int downward, int rightward, void *user);
int (*moverect)(VTermRect dest, VTermRect src, void *user);
int (*erase)(VTermRect rect, int selective, void *user);
int (*initpen)(void *user);
int (*setpenattr)(VTermAttr attr, VTermValue *val, void *user);
int (*settermprop)(VTermProp prop, VTermValue *val, void *user);
int (*bell)(void *user);
int (*resize)(int rows, int cols, VTermPos *delta, void *user);
int (*setlineinfo)(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user);
} VTermStateCallbacks;

VTermState *vterm_obtain_state(VTerm *vt);

void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user);
void *vterm_state_get_cbdata(VTermState *state);

/* Only invokes control, csi, osc, dcs */
void vterm_state_set_unrecognised_fallbacks(VTermState *state, const VTermParserCallbacks *fallbacks, void *user);
void *vterm_state_get_unrecognised_fbdata(VTermState *state);

void vterm_state_reset(VTermState *state, int hard);
void vterm_state_get_cursorpos(const VTermState *state, VTermPos *cursorpos);
void vterm_state_get_default_colors(const VTermState *state, VTermColor *default_fg, VTermColor *default_bg);
void vterm_state_get_palette_color(const VTermState *state, int index, VTermColor *col);
void vterm_state_set_default_colors(VTermState *state, const VTermColor *default_fg, const VTermColor *default_bg);
void vterm_state_set_palette_color(VTermState *state, int index, const VTermColor *col);
void vterm_state_set_bold_highbright(VTermState *state, int bold_is_highbright);
int vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTermValue *val);
int vterm_state_set_termprop(VTermState *state, VTermProp prop, VTermValue *val);
const VTermLineInfo *vterm_state_get_lineinfo(const VTermState *state, int row);

/* ------------
* Screen layer
* ------------ */

typedef struct {
unsigned int bold : 1;
unsigned int underline : 2;
unsigned int italic : 1;
unsigned int blink : 1;
unsigned int reverse : 1;
unsigned int strike : 1;
unsigned int font : 4; /* 0 to 9 */
unsigned int dwl : 1; /* On a DECDWL or DECDHL line */
unsigned int dhl : 2; /* On a DECDHL line (1=top 2=bottom) */
} VTermScreenCellAttrs;

typedef struct {
#define VTERM_MAX_CHARS_PER_CELL 6
uint32_t chars[VTERM_MAX_CHARS_PER_CELL];
char width;
VTermScreenCellAttrs attrs;
VTermColor fg, bg;
} VTermScreenCell;

/* All fields are optional, NULL when not used. */
typedef struct {
int (*damage)(VTermRect rect, void *user);
int (*moverect)(VTermRect dest, VTermRect src, void *user);
int (*movecursor)(VTermPos pos, VTermPos oldpos, int visible, void *user);
int (*settermprop)(VTermProp prop, VTermValue *val, void *user);
int (*bell)(void *user);
int (*resize)(int rows, int cols, void *user);
int (*sb_pushline)(int cols, const VTermScreenCell *cells, void *user);
int (*sb_popline)(int cols, VTermScreenCell *cells, void *user);
} VTermScreenCallbacks;

VTermScreen *vterm_obtain_screen(VTerm *vt);

/*
* Install screen callbacks. These are invoked when the screen contents is
* changed. "user" is passed into to the callback.
*/
void vterm_screen_set_callbacks(VTermScreen *screen, const VTermScreenCallbacks *callbacks, void *user);
void *vterm_screen_get_cbdata(VTermScreen *screen);

/* Only invokes control, csi, osc, dcs */
void vterm_screen_set_unrecognised_fallbacks(VTermScreen *screen, const VTermParserCallbacks *fallbacks, void *user);
void *vterm_screen_get_unrecognised_fbdata(VTermScreen *screen);

void vterm_screen_enable_altscreen(VTermScreen *screen, int altscreen);

typedef enum {
VTERM_DAMAGE_CELL, /* every cell */
VTERM_DAMAGE_ROW, /* entire rows */
VTERM_DAMAGE_SCREEN, /* entire screen */
VTERM_DAMAGE_SCROLL /* entire screen + scrollrect */
} VTermDamageSize;

void vterm_screen_flush_damage(VTermScreen *screen);
void vterm_screen_set_damage_merge(VTermScreen *screen, VTermDamageSize size);

void vterm_screen_reset(VTermScreen *screen, int hard);

/* Neither of these functions NUL-terminate the buffer */
size_t vterm_screen_get_chars(const VTermScreen *screen, uint32_t *chars, size_t len, const VTermRect rect);
size_t vterm_screen_get_text(const VTermScreen *screen, char *str, size_t len, const VTermRect rect);

typedef enum {
VTERM_ATTR_BOLD_MASK = 1 << 0,
VTERM_ATTR_UNDERLINE_MASK = 1 << 1,
VTERM_ATTR_ITALIC_MASK = 1 << 2,
VTERM_ATTR_BLINK_MASK = 1 << 3,
VTERM_ATTR_REVERSE_MASK = 1 << 4,
VTERM_ATTR_STRIKE_MASK = 1 << 5,
VTERM_ATTR_FONT_MASK = 1 << 6,
VTERM_ATTR_FOREGROUND_MASK = 1 << 7,
VTERM_ATTR_BACKGROUND_MASK = 1 << 8
} VTermAttrMask;

int vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect *extent, VTermPos pos, VTermAttrMask attrs);

int vterm_screen_get_cell(const VTermScreen *screen, VTermPos pos, VTermScreenCell *cell);

int vterm_screen_is_eol(const VTermScreen *screen, VTermPos pos);

/* ---------
* Utilities
* --------- */

VTermValueType vterm_get_attr_type(VTermAttr attr);
VTermValueType vterm_get_prop_type(VTermProp prop);

void vterm_scroll_rect(VTermRect rect,
int downward,
int rightward,
int (*moverect)(VTermRect src, VTermRect dest, void *user),
int (*eraserect)(VTermRect rect, int selective, void *user),
void *user);

void vterm_copy_cells(VTermRect dest,
VTermRect src,
void (*copycell)(VTermPos dest, VTermPos src, void *user),
void *user);

#ifdef __cplusplus
}
#endif

#endif
@@ -0,0 +1,58 @@
#ifndef __VTERM_INPUT_H__
#define __VTERM_INPUT_H__

typedef enum {
VTERM_MOD_NONE = 0x00,
VTERM_MOD_SHIFT = 0x01,
VTERM_MOD_ALT = 0x02,
VTERM_MOD_CTRL = 0x04
} VTermModifier;

typedef enum {
VTERM_KEY_NONE,

VTERM_KEY_ENTER,
VTERM_KEY_TAB,
VTERM_KEY_BACKSPACE,
VTERM_KEY_ESCAPE,

VTERM_KEY_UP,
VTERM_KEY_DOWN,
VTERM_KEY_LEFT,
VTERM_KEY_RIGHT,

VTERM_KEY_INS,
VTERM_KEY_DEL,
VTERM_KEY_HOME,
VTERM_KEY_END,
VTERM_KEY_PAGEUP,
VTERM_KEY_PAGEDOWN,

VTERM_KEY_FUNCTION_0 = 256,
VTERM_KEY_FUNCTION_MAX = VTERM_KEY_FUNCTION_0 + 255,

VTERM_KEY_KP_0,
VTERM_KEY_KP_1,
VTERM_KEY_KP_2,
VTERM_KEY_KP_3,
VTERM_KEY_KP_4,
VTERM_KEY_KP_5,
VTERM_KEY_KP_6,
VTERM_KEY_KP_7,
VTERM_KEY_KP_8,
VTERM_KEY_KP_9,
VTERM_KEY_KP_MULT,
VTERM_KEY_KP_PLUS,
VTERM_KEY_KP_COMMA,
VTERM_KEY_KP_MINUS,
VTERM_KEY_KP_PERIOD,
VTERM_KEY_KP_DIVIDE,
VTERM_KEY_KP_ENTER,
VTERM_KEY_KP_EQUAL,

VTERM_KEY_MAX /* Must be last */
} VTermKey;

#define VTERM_KEY_FUNCTION(n) (VTERM_KEY_FUNCTION_0+(n))

#endif
@@ -0,0 +1,232 @@
#include "vterm_internal.h"

#define UNICODE_INVALID 0xFFFD

#if defined(DEBUG) && DEBUG > 1
# define DEBUG_PRINT_UTF8
#endif

struct UTF8DecoderData {
/* number of bytes remaining in this codepoint */
int bytes_remaining;

/* number of bytes total in this codepoint once it's finished
(for detecting overlongs) */
int bytes_total;

int this_cp;
};

static void init_utf8(VTermEncoding *enc UNUSED, void *data_)
{
struct UTF8DecoderData *data = data_;

data->bytes_remaining = 0;
data->bytes_total = 0;
}

static void decode_utf8(VTermEncoding *enc UNUSED, void *data_,
uint32_t cp[], int *cpi, int cplen,
const char bytes[], size_t *pos, size_t bytelen)
{
struct UTF8DecoderData *data = data_;

#ifdef DEBUG_PRINT_UTF8
printf("BEGIN UTF-8\n");
#endif

for(; *pos < bytelen && *cpi < cplen; (*pos)++) {
unsigned char c = bytes[*pos];

#ifdef DEBUG_PRINT_UTF8
printf(" pos=%zd c=%02x rem=%d\n", *pos, c, data->bytes_remaining);
#endif

if(c < 0x20) /* C0 */
return;

else if(c >= 0x20 && c < 0x7f) {
if(data->bytes_remaining)
cp[(*cpi)++] = UNICODE_INVALID;

cp[(*cpi)++] = c;
#ifdef DEBUG_PRINT_UTF8
printf(" UTF-8 char: U+%04x\n", c);
#endif
data->bytes_remaining = 0;
}

else if(c == 0x7f) /* DEL */
return;

else if(c >= 0x80 && c < 0xc0) {
if(!data->bytes_remaining) {
cp[(*cpi)++] = UNICODE_INVALID;
continue;
}

data->this_cp <<= 6;
data->this_cp |= c & 0x3f;
data->bytes_remaining--;

if(!data->bytes_remaining) {
#ifdef DEBUG_PRINT_UTF8
printf(" UTF-8 raw char U+%04x bytelen=%d ", data->this_cp, data->bytes_total);
#endif
/* Check for overlong sequences */
switch(data->bytes_total) {
case 2:
if(data->this_cp < 0x0080) data->this_cp = UNICODE_INVALID;
break;
case 3:
if(data->this_cp < 0x0800) data->this_cp = UNICODE_INVALID;
break;
case 4:
if(data->this_cp < 0x10000) data->this_cp = UNICODE_INVALID;
break;
case 5:
if(data->this_cp < 0x200000) data->this_cp = UNICODE_INVALID;
break;
case 6:
if(data->this_cp < 0x4000000) data->this_cp = UNICODE_INVALID;
break;
}
/* Now look for plain invalid ones */
if((data->this_cp >= 0xD800 && data->this_cp <= 0xDFFF) ||
data->this_cp == 0xFFFE ||
data->this_cp == 0xFFFF)
data->this_cp = UNICODE_INVALID;
#ifdef DEBUG_PRINT_UTF8
printf(" char: U+%04x\n", data->this_cp);
#endif
cp[(*cpi)++] = data->this_cp;
}
}

else if(c >= 0xc0 && c < 0xe0) {
if(data->bytes_remaining)
cp[(*cpi)++] = UNICODE_INVALID;

data->this_cp = c & 0x1f;
data->bytes_total = 2;
data->bytes_remaining = 1;
}

else if(c >= 0xe0 && c < 0xf0) {
if(data->bytes_remaining)
cp[(*cpi)++] = UNICODE_INVALID;

data->this_cp = c & 0x0f;
data->bytes_total = 3;
data->bytes_remaining = 2;
}

else if(c >= 0xf0 && c < 0xf8) {
if(data->bytes_remaining)
cp[(*cpi)++] = UNICODE_INVALID;

data->this_cp = c & 0x07;
data->bytes_total = 4;
data->bytes_remaining = 3;
}

else if(c >= 0xf8 && c < 0xfc) {
if(data->bytes_remaining)
cp[(*cpi)++] = UNICODE_INVALID;

data->this_cp = c & 0x03;
data->bytes_total = 5;
data->bytes_remaining = 4;
}

else if(c >= 0xfc && c < 0xfe) {
if(data->bytes_remaining)
cp[(*cpi)++] = UNICODE_INVALID;

data->this_cp = c & 0x01;
data->bytes_total = 6;
data->bytes_remaining = 5;
}

else {
cp[(*cpi)++] = UNICODE_INVALID;
}
}
}

static VTermEncoding encoding_utf8 = {
&init_utf8, /* init */
&decode_utf8 /* decode */
};

static void decode_usascii(VTermEncoding *enc UNUSED, void *data UNUSED,
uint32_t cp[], int *cpi, int cplen,
const char bytes[], size_t *pos, size_t bytelen)
{
int is_gr = bytes[*pos] & 0x80;

for(; *pos < bytelen && *cpi < cplen; (*pos)++) {
unsigned char c = bytes[*pos] ^ is_gr;

if(c < 0x20 || c == 0x7f || c >= 0x80)
return;

cp[(*cpi)++] = c;
}
}

static VTermEncoding encoding_usascii = {
NULL, /* init */
&decode_usascii /* decode */
};

struct StaticTableEncoding {
const VTermEncoding enc;
const uint32_t chars[128];
};

static void decode_table(VTermEncoding *enc, void *data UNUSED,
uint32_t cp[], int *cpi, int cplen,
const char bytes[], size_t *pos, size_t bytelen)
{
struct StaticTableEncoding *table = (struct StaticTableEncoding *)enc;
int is_gr = bytes[*pos] & 0x80;

for(; *pos < bytelen && *cpi < cplen; (*pos)++) {
unsigned char c = bytes[*pos] ^ is_gr;

if(c < 0x20 || c == 0x7f || c >= 0x80)
return;

if(table->chars[c])
cp[(*cpi)++] = table->chars[c];
else
cp[(*cpi)++] = c;
}
}

#include "encoding/DECdrawing.inc"
#include "encoding/uk.inc"

static struct {
VTermEncodingType type;
char designation;
VTermEncoding *enc;
}
encodings[] = {
{ ENC_UTF8, 'u', &encoding_utf8 },
{ ENC_SINGLE_94, '0', (VTermEncoding*)&encoding_DECdrawing },
{ ENC_SINGLE_94, 'A', (VTermEncoding*)&encoding_uk },
{ ENC_SINGLE_94, 'B', &encoding_usascii },
{ 0 },
};

/* This ought to be INTERNAL but isn't because it's used by unit testing */
VTermEncoding *vterm_lookup_encoding(VTermEncodingType type, char designation)
{
int i;
for(i = 0; encodings[i].designation; i++)
if(encodings[i].type == type && encodings[i].designation == designation)
return encodings[i].enc;
return NULL;
}
@@ -0,0 +1,31 @@
6/0 = U+25C6 # BLACK DIAMOND
6/1 = U+2592 # MEDIUM SHADE (checkerboard)
6/2 = U+2409 # SYMBOL FOR HORIZONTAL TAB
6/3 = U+240C # SYMBOL FOR FORM FEED
6/4 = U+240D # SYMBOL FOR CARRIAGE RETURN
6/5 = U+240A # SYMBOL FOR LINE FEED
6/6 = U+00B0 # DEGREE SIGN
6/7 = U+00B1 # PLUS-MINUS SIGN (plus or minus)
6/8 = U+2424 # SYMBOL FOR NEW LINE
6/9 = U+240B # SYMBOL FOR VERTICAL TAB
6/10 = U+2518 # BOX DRAWINGS LIGHT UP AND LEFT (bottom-right corner)
6/11 = U+2510 # BOX DRAWINGS LIGHT DOWN AND LEFT (top-right corner)
6/12 = U+250C # BOX DRAWINGS LIGHT DOWN AND RIGHT (top-left corner)
6/13 = U+2514 # BOX DRAWINGS LIGHT UP AND RIGHT (bottom-left corner)
6/14 = U+253C # BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL (crossing lines)
6/15 = U+23BA # HORIZONTAL SCAN LINE-1
7/0 = U+23BB # HORIZONTAL SCAN LINE-3
7/1 = U+2500 # BOX DRAWINGS LIGHT HORIZONTAL
7/2 = U+23BC # HORIZONTAL SCAN LINE-7
7/3 = U+23BD # HORIZONTAL SCAN LINE-9
7/4 = U+251C # BOX DRAWINGS LIGHT VERTICAL AND RIGHT
7/5 = U+2524 # BOX DRAWINGS LIGHT VERTICAL AND LEFT
7/6 = U+2534 # BOX DRAWINGS LIGHT UP AND HORIZONTAL
7/7 = U+252C # BOX DRAWINGS LIGHT DOWN AND HORIZONTAL
7/8 = U+2502 # BOX DRAWINGS LIGHT VERTICAL
7/9 = U+2A7D # LESS-THAN OR SLANTED EQUAL-TO
7/10 = U+2A7E # GREATER-THAN OR SLANTED EQUAL-TO
7/11 = U+03C0 # GREEK SMALL LETTER PI
7/12 = U+2260 # NOT EQUAL TO
7/13 = U+00A3 # POUND SIGN
7/14 = U+00B7 # MIDDLE DOT
@@ -0,0 +1 @@
2/3 = "£"
@@ -0,0 +1,228 @@
#include "vterm_internal.h"

#include <stdio.h>

#include "utf8.h"

void vterm_keyboard_unichar(VTerm *vt, uint32_t c, VTermModifier mod)
{
int needs_CSIu;

/* The shift modifier is never important for Unicode characters
* apart from Space
*/
if(c != ' ')
mod &= ~VTERM_MOD_SHIFT;

if(mod == 0) {
/* Normal text - ignore just shift */
char str[6];
int seqlen = fill_utf8(c, str);
vterm_push_output_bytes(vt, str, seqlen);
return;
}

switch(c) {
/* Special Ctrl- letters that can't be represented elsewise */
case 'i': case 'j': case 'm': case '[':
needs_CSIu = 1;
break;
/* Ctrl-\ ] ^ _ don't need CSUu */
case '\\': case ']': case '^': case '_':
needs_CSIu = 0;
break;
/* Shift-space needs CSIu */
case ' ':
needs_CSIu = !!(mod & VTERM_MOD_SHIFT);
break;
/* All other characters needs CSIu except for letters a-z */
default:
needs_CSIu = (c < 'a' || c > 'z');
}

/* ALT we can just prefix with ESC; anything else requires CSI u */
if(needs_CSIu && (mod & ~VTERM_MOD_ALT)) {
vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", c, mod+1);
return;
}

if(mod & VTERM_MOD_CTRL)
c &= 0x1f;

vterm_push_output_sprintf(vt, "%s%c", mod & VTERM_MOD_ALT ? ESC_S : "", c);
}

typedef struct {
enum {
KEYCODE_NONE,
KEYCODE_LITERAL,
KEYCODE_TAB,
KEYCODE_ENTER,
KEYCODE_SS3,
KEYCODE_CSI,
KEYCODE_CSI_CURSOR,
KEYCODE_CSINUM,
KEYCODE_KEYPAD
} type;
char literal;
int csinum;
} keycodes_s;

static keycodes_s keycodes[] = {
{ KEYCODE_NONE, 0, 0 }, /* NONE */

{ KEYCODE_ENTER, '\r', 0 }, /* ENTER */
{ KEYCODE_TAB, '\t', 0 }, /* TAB */
{ KEYCODE_LITERAL, '\x7f', 0 }, /* BACKSPACE == ASCII DEL */
{ KEYCODE_LITERAL, '\x1b', 0 }, /* ESCAPE */

{ KEYCODE_CSI_CURSOR, 'A', 0 }, /* UP */
{ KEYCODE_CSI_CURSOR, 'B', 0 }, /* DOWN */
{ KEYCODE_CSI_CURSOR, 'D', 0 }, /* LEFT */
{ KEYCODE_CSI_CURSOR, 'C', 0 }, /* RIGHT */

{ KEYCODE_CSINUM, '~', 2 }, /* INS */
{ KEYCODE_CSINUM, '~', 3 }, /* DEL */
{ KEYCODE_CSI_CURSOR, 'H', 0 }, /* HOME */
{ KEYCODE_CSI_CURSOR, 'F', 0 }, /* END */
{ KEYCODE_CSINUM, '~', 5 }, /* PAGEUP */
{ KEYCODE_CSINUM, '~', 6 }, /* PAGEDOWN */
};

static keycodes_s keycodes_fn[] = {
{ KEYCODE_NONE, 0, 0 }, /* F0 - shouldn't happen */
{ KEYCODE_CSI_CURSOR, 'P', 0 }, /* F1 */
{ KEYCODE_CSI_CURSOR, 'Q', 0 }, /* F2 */
{ KEYCODE_CSI_CURSOR, 'R', 0 }, /* F3 */
{ KEYCODE_CSI_CURSOR, 'S', 0 }, /* F4 */
{ KEYCODE_CSINUM, '~', 15 }, /* F5 */
{ KEYCODE_CSINUM, '~', 17 }, /* F6 */
{ KEYCODE_CSINUM, '~', 18 }, /* F7 */
{ KEYCODE_CSINUM, '~', 19 }, /* F8 */
{ KEYCODE_CSINUM, '~', 20 }, /* F9 */
{ KEYCODE_CSINUM, '~', 21 }, /* F10 */
{ KEYCODE_CSINUM, '~', 23 }, /* F11 */
{ KEYCODE_CSINUM, '~', 24 }, /* F12 */
};

static keycodes_s keycodes_kp[] = {
{ KEYCODE_KEYPAD, '0', 'p' }, /* KP_0 */
{ KEYCODE_KEYPAD, '1', 'q' }, /* KP_1 */
{ KEYCODE_KEYPAD, '2', 'r' }, /* KP_2 */
{ KEYCODE_KEYPAD, '3', 's' }, /* KP_3 */
{ KEYCODE_KEYPAD, '4', 't' }, /* KP_4 */
{ KEYCODE_KEYPAD, '5', 'u' }, /* KP_5 */
{ KEYCODE_KEYPAD, '6', 'v' }, /* KP_6 */
{ KEYCODE_KEYPAD, '7', 'w' }, /* KP_7 */
{ KEYCODE_KEYPAD, '8', 'x' }, /* KP_8 */
{ KEYCODE_KEYPAD, '9', 'y' }, /* KP_9 */
{ KEYCODE_KEYPAD, '*', 'j' }, /* KP_MULT */
{ KEYCODE_KEYPAD, '+', 'k' }, /* KP_PLUS */
{ KEYCODE_KEYPAD, ',', 'l' }, /* KP_COMMA */
{ KEYCODE_KEYPAD, '-', 'm' }, /* KP_MINUS */
{ KEYCODE_KEYPAD, '.', 'n' }, /* KP_PERIOD */
{ KEYCODE_KEYPAD, '/', 'o' }, /* KP_DIVIDE */
{ KEYCODE_KEYPAD, '\n', 'M' }, /* KP_ENTER */
{ KEYCODE_KEYPAD, '=', 'X' }, /* KP_EQUAL */
};

void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod)
{
keycodes_s k;

if(key == VTERM_KEY_NONE)
return;

if(key < VTERM_KEY_FUNCTION_0) {
if(key >= sizeof(keycodes)/sizeof(keycodes[0]))
return;
k = keycodes[key];
}
else if(key >= VTERM_KEY_FUNCTION_0 && key <= VTERM_KEY_FUNCTION_MAX) {
if((key - VTERM_KEY_FUNCTION_0) >= sizeof(keycodes_fn)/sizeof(keycodes_fn[0]))
return;
k = keycodes_fn[key - VTERM_KEY_FUNCTION_0];
}
else if(key >= VTERM_KEY_KP_0) {
if((key - VTERM_KEY_KP_0) >= sizeof(keycodes_kp)/sizeof(keycodes_kp[0]))
return;
k = keycodes_kp[key - VTERM_KEY_KP_0];
}

switch(k.type) {
case KEYCODE_NONE:
break;

case KEYCODE_TAB:
/* Shift-Tab is CSI Z but plain Tab is 0x09 */
if(mod == VTERM_MOD_SHIFT)
vterm_push_output_sprintf_ctrl(vt, C1_CSI, "Z");
else if(mod & VTERM_MOD_SHIFT)
vterm_push_output_sprintf_ctrl(vt, C1_CSI, "1;%dZ", mod+1);
else
goto case_LITERAL;
break;

case KEYCODE_ENTER:
/* Enter is CRLF in newline mode, but just LF in linefeed */
if(vt->state->mode.newline)
vterm_push_output_sprintf(vt, "\r\n");
else
goto case_LITERAL;
break;

case KEYCODE_LITERAL: case_LITERAL:
if(mod & (VTERM_MOD_SHIFT|VTERM_MOD_CTRL))
vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", k.literal, mod+1);
else
vterm_push_output_sprintf(vt, mod & VTERM_MOD_ALT ? ESC_S "%c" : "%c", k.literal);
break;

case KEYCODE_SS3: case_SS3:
if(mod == 0)
vterm_push_output_sprintf_ctrl(vt, C1_SS3, "%c", k.literal);
else
goto case_CSI;
break;

case KEYCODE_CSI: case_CSI:
if(mod == 0)
vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%c", k.literal);
else
vterm_push_output_sprintf_ctrl(vt, C1_CSI, "1;%d%c", mod + 1, k.literal);
break;

case KEYCODE_CSINUM:
if(mod == 0)
vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d%c", k.csinum, k.literal);
else
vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%d%c", k.csinum, mod + 1, k.literal);
break;

case KEYCODE_CSI_CURSOR:
if(vt->state->mode.cursor)
goto case_SS3;
else
goto case_CSI;

case KEYCODE_KEYPAD:
if(vt->state->mode.keypad) {
k.literal = k.csinum;
goto case_SS3;
}
else
goto case_LITERAL;
}
}

void vterm_keyboard_start_paste(VTerm *vt)
{
if(vt->state->mode.bracketpaste)
vterm_push_output_sprintf_ctrl(vt, C1_CSI, "200~");
}

void vterm_keyboard_end_paste(VTerm *vt)
{
if(vt->state->mode.bracketpaste)
vterm_push_output_sprintf_ctrl(vt, C1_CSI, "201~");
}
@@ -0,0 +1,96 @@
#include "vterm_internal.h"

#include "utf8.h"

static void output_mouse(VTermState *state, int code, int pressed, int modifiers, int col, int row)
{
modifiers <<= 2;

switch(state->mouse_protocol) {
case MOUSE_X10:
if(col + 0x21 > 0xff)
col = 0xff - 0x21;
if(row + 0x21 > 0xff)
row = 0xff - 0x21;

if(!pressed)
code = 3;

vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "M%c%c%c",
(code | modifiers) + 0x20, col + 0x21, row + 0x21);
break;

case MOUSE_UTF8:
{
char utf8[18]; size_t len = 0;

if(!pressed)
code = 3;

len += fill_utf8((code | modifiers) + 0x20, utf8 + len);
len += fill_utf8(col + 0x21, utf8 + len);
len += fill_utf8(row + 0x21, utf8 + len);
utf8[len] = 0;

vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "M%s", utf8);
}
break;

case MOUSE_SGR:
vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "<%d;%d;%d%c",
code | modifiers, col + 1, row + 1, pressed ? 'M' : 'm');
break;

case MOUSE_RXVT:
if(!pressed)
code = 3;

vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%d;%d;%dM",
code | modifiers, col + 1, row + 1);
break;
}
}

void vterm_mouse_move(VTerm *vt, int row, int col, VTermModifier mod)
{
VTermState *state = vt->state;

if(col == state->mouse_col && row == state->mouse_row)
return;

state->mouse_col = col;
state->mouse_row = row;

if((state->mouse_flags & MOUSE_WANT_DRAG && state->mouse_buttons) ||
(state->mouse_flags & MOUSE_WANT_MOVE)) {
int button = state->mouse_buttons & 0x01 ? 1 :
state->mouse_buttons & 0x02 ? 2 :
state->mouse_buttons & 0x04 ? 3 : 4;
output_mouse(state, button-1 + 0x20, 1, mod, col, row);
}
}

void vterm_mouse_button(VTerm *vt, int button, bool pressed, VTermModifier mod)
{
VTermState *state = vt->state;

int old_buttons = state->mouse_buttons;

if(button > 0 && button <= 3) {
if(pressed)
state->mouse_buttons |= (1 << (button-1));
else
state->mouse_buttons &= ~(1 << (button-1));
}

/* Most of the time we don't get button releases from 4/5 */
if(state->mouse_buttons == old_buttons && button < 4)
return;

if(button < 4) {
output_mouse(state, button-1, pressed, mod, state->mouse_col, state->mouse_row);
}
else if(button < 6) {
output_mouse(state, button-4 + 0x40, pressed, mod, state->mouse_col, state->mouse_row);
}
}
@@ -0,0 +1,346 @@
#include "vterm_internal.h"

#include <stdio.h>
#include <string.h>

#define CSI_ARGS_MAX 16
#define CSI_LEADER_MAX 16
#define CSI_INTERMED_MAX 16

static void do_control(VTerm *vt, unsigned char control)
{
if(vt->parser_callbacks && vt->parser_callbacks->control)
if((*vt->parser_callbacks->control)(control, vt->cbdata))
return;

DEBUG_LOG1("libvterm: Unhandled control 0x%02x\n", control);
}

static void do_string_csi(VTerm *vt, const char *args, size_t arglen, char command)
{
int i = 0;

int leaderlen = 0;
char leader[CSI_LEADER_MAX];
int argcount = 1; /* Always at least 1 arg */
long csi_args[CSI_ARGS_MAX];
int argi;
int intermedlen = 0;
char intermed[CSI_INTERMED_MAX];

/* Extract leader bytes 0x3c to 0x3f */
for( ; i < (int)arglen; i++) {
if(args[i] < 0x3c || args[i] > 0x3f)
break;
if(leaderlen < CSI_LEADER_MAX-1)
leader[leaderlen++] = args[i];
}

leader[leaderlen] = 0;

for( ; i < (int)arglen; i++)
if(args[i] == 0x3b || args[i] == 0x3a) /* ; or : */
argcount++;

/* TODO: Consider if these buffers should live in the VTerm struct itself */
if(argcount > CSI_ARGS_MAX)
argcount = CSI_ARGS_MAX;

for(argi = 0; argi < argcount; argi++)
csi_args[argi] = CSI_ARG_MISSING;

argi = 0;
for(i = leaderlen; i < (int)arglen && argi < argcount; i++) {
switch(args[i]) {
case 0x30: case 0x31: case 0x32: case 0x33: case 0x34:
case 0x35: case 0x36: case 0x37: case 0x38: case 0x39:
if(csi_args[argi] == CSI_ARG_MISSING)
csi_args[argi] = 0;
csi_args[argi] *= 10;
csi_args[argi] += args[i] - '0';
break;
case 0x3a:
csi_args[argi] |= CSI_ARG_FLAG_MORE;
/* FALLTHROUGH */
case 0x3b:
argi++;
break;
default:
goto done_leader;
}
}
done_leader: ;

for( ; i < (int)arglen; i++) {
if((args[i] & 0xf0) != 0x20)
break;

if(intermedlen < CSI_INTERMED_MAX-1)
intermed[intermedlen++] = args[i];
}

intermed[intermedlen] = 0;

if(i < (int)arglen) {
DEBUG_LOG2("libvterm: TODO unhandled CSI bytes \"%.*s\"\n", (int)(arglen - i), args + i);
}

#if 0
printf("Parsed CSI args %.*s as:\n", arglen, args);
printf(" leader: %s\n", leader);
for(argi = 0; argi < argcount; argi++) {
printf(" %lu", CSI_ARG(csi_args[argi]));
if(!CSI_ARG_HAS_MORE(csi_args[argi]))
printf("\n");
printf(" intermed: %s\n", intermed);
}
#endif

if(vt->parser_callbacks && vt->parser_callbacks->csi)
if((*vt->parser_callbacks->csi)(leaderlen ? leader : NULL, csi_args, argcount, intermedlen ? intermed : NULL, command, vt->cbdata))
return;

DEBUG_LOG3("libvterm: Unhandled CSI %.*s %c\n", (int)arglen, args, command);
}

static void append_strbuffer(VTerm *vt, const char *str, size_t len)
{
if(len > vt->strbuffer_len - vt->strbuffer_cur) {
len = vt->strbuffer_len - vt->strbuffer_cur;
DEBUG_LOG1("Truncating strbuffer preserve to %zd bytes\n", len);
}

if(len > 0) {
strncpy(vt->strbuffer + vt->strbuffer_cur, str, len);
vt->strbuffer_cur += len;
}
}

static size_t do_string(VTerm *vt, const char *str_frag, size_t len)
{
size_t eaten;

if(vt->strbuffer_cur) {
if(str_frag)
append_strbuffer(vt, str_frag, len);

str_frag = vt->strbuffer;
len = vt->strbuffer_cur;
}
else if(!str_frag) {
DEBUG_LOG("parser.c: TODO: No strbuffer _and_ no final fragment???\n");
len = 0;
}

vt->strbuffer_cur = 0;

switch(vt->parser_state) {
case NORMAL:
if(vt->parser_callbacks && vt->parser_callbacks->text)
if((eaten = (*vt->parser_callbacks->text)(str_frag, len, vt->cbdata)))
return eaten;

DEBUG_LOG1("libvterm: Unhandled text (%zu chars)\n", len);
return 0;

case ESC:
if(len == 1 && str_frag[0] >= 0x40 && str_frag[0] < 0x60) {
/* C1 emulations using 7bit clean */
/* ESC 0x40 == 0x80 */
do_control(vt, str_frag[0] + 0x40);
return 0;
}

if(vt->parser_callbacks && vt->parser_callbacks->escape)
if((*vt->parser_callbacks->escape)(str_frag, len, vt->cbdata))
return 0;

DEBUG_LOG1("libvterm: Unhandled escape ESC 0x%02x\n", str_frag[len-1]);
return 0;

case CSI:
do_string_csi(vt, str_frag, len - 1, str_frag[len - 1]);
return 0;

case OSC:
if(vt->parser_callbacks && vt->parser_callbacks->osc)
if((*vt->parser_callbacks->osc)(str_frag, len, vt->cbdata))
return 0;

DEBUG_LOG2("libvterm: Unhandled OSC %.*s\n", (int)len, str_frag);
return 0;

case DCS:
if(vt->parser_callbacks && vt->parser_callbacks->dcs)
if((*vt->parser_callbacks->dcs)(str_frag, len, vt->cbdata))
return 0;

DEBUG_LOG2("libvterm: Unhandled DCS %.*s\n", (int)len, str_frag);
return 0;

case ESC_IN_OSC:
case ESC_IN_DCS:
DEBUG_LOG("libvterm: ARGH! Should never do_string() in ESC_IN_{OSC,DCS}\n");
return 0;
}

return 0;
}

size_t vterm_input_write(VTerm *vt, const char *bytes, size_t len)
{
size_t pos = 0;
const char *string_start;

switch(vt->parser_state) {
case NORMAL:
string_start = NULL;
break;
case ESC:
case ESC_IN_OSC:
case ESC_IN_DCS:
case CSI:
case OSC:
case DCS:
string_start = bytes;
break;
}

#define ENTER_STRING_STATE(st) do { vt->parser_state = st; string_start = bytes + pos + 1; } while(0)
#define ENTER_NORMAL_STATE() do { vt->parser_state = NORMAL; string_start = NULL; } while(0)

for( ; pos < len; pos++) {
unsigned char c = bytes[pos];

if(c == 0x00 || c == 0x7f) { /* NUL, DEL */
if(vt->parser_state != NORMAL) {
append_strbuffer(vt, string_start, bytes + pos - string_start);
string_start = bytes + pos + 1;
}
continue;
}
if(c == 0x18 || c == 0x1a) { /* CAN, SUB */
ENTER_NORMAL_STATE();
continue;
}
else if(c == 0x1b) { /* ESC */
if(vt->parser_state == OSC)
vt->parser_state = ESC_IN_OSC;
else if(vt->parser_state == DCS)
vt->parser_state = ESC_IN_DCS;
else
ENTER_STRING_STATE(ESC);
continue;
}
else if(c == 0x07 && /* BEL, can stand for ST in OSC or DCS state */
(vt->parser_state == OSC || vt->parser_state == DCS)) {
/* fallthrough */
}
else if(c < 0x20) { /* other C0 */
if(vt->parser_state != NORMAL)
append_strbuffer(vt, string_start, bytes + pos - string_start);
do_control(vt, c);
if(vt->parser_state != NORMAL)
string_start = bytes + pos + 1;
continue;
}
/* else fallthrough */

switch(vt->parser_state) {
case ESC_IN_OSC:
case ESC_IN_DCS:
if(c == 0x5c) { /* ST */
switch(vt->parser_state) {
case ESC_IN_OSC: vt->parser_state = OSC; break;
case ESC_IN_DCS: vt->parser_state = DCS; break;
default: break;
}
do_string(vt, string_start, bytes + pos - string_start - 1);
ENTER_NORMAL_STATE();
break;
}
vt->parser_state = ESC;
string_start = bytes + pos;
/* else fallthrough */

case ESC:
switch(c) {
case 0x50: /* DCS */
ENTER_STRING_STATE(DCS);
break;
case 0x5b: /* CSI */
ENTER_STRING_STATE(CSI);
break;
case 0x5d: /* OSC */
ENTER_STRING_STATE(OSC);
break;
default:
if(c >= 0x30 && c < 0x7f) {
/* +1 to pos because we want to include this command byte as well */
do_string(vt, string_start, bytes + pos - string_start + 1);
ENTER_NORMAL_STATE();
}
else if(c >= 0x20 && c < 0x30) {
/* intermediate byte */
}
else {
DEBUG_LOG1("TODO: Unhandled byte %02x in Escape\n", c);
}
}
break;

case CSI:
if(c >= 0x40 && c <= 0x7f) {
/* +1 to pos because we want to include this command byte as well */
do_string(vt, string_start, bytes + pos - string_start + 1);
ENTER_NORMAL_STATE();
}
break;

case OSC:
case DCS:
if(c == 0x07 || (c == 0x9c && !vt->mode.utf8)) {
do_string(vt, string_start, bytes + pos - string_start);
ENTER_NORMAL_STATE();
}
break;

case NORMAL:
if(c >= 0x80 && c < 0xa0 && !vt->mode.utf8) {
switch(c) {
case 0x90: /* DCS */
ENTER_STRING_STATE(DCS);
break;
case 0x9b: /* CSI */
ENTER_STRING_STATE(CSI);
break;
case 0x9d: /* OSC */
ENTER_STRING_STATE(OSC);
break;
default:
do_control(vt, c);
break;
}
}
else {
size_t text_eaten = do_string(vt, bytes + pos, len - pos);

if(text_eaten == 0) {
string_start = bytes + pos;
goto pause;
}

pos += (text_eaten - 1); /* we'll ++ it again in a moment */
}
break;
}
}

pause:
if(string_start && string_start < len + bytes) {
size_t remaining = len - (string_start - bytes);
append_strbuffer(vt, string_start, remaining);
}

return len;
}