about summary refs log tree commit diff stats
path: root/cpp
diff options
context:
space:
mode:
Diffstat (limited to 'cpp')
-rw-r--r--cpp/070display63
-rw-r--r--cpp/display.mu5
-rw-r--r--cpp/makefile8
-rw-r--r--cpp/termbox/COPYING19
-rw-r--r--cpp/termbox/README2
-rw-r--r--cpp/termbox/bytebuffer.inl78
-rw-r--r--cpp/termbox/input.inl132
-rw-r--r--cpp/termbox/makefile7
-rw-r--r--cpp/termbox/term.inl302
-rw-r--r--cpp/termbox/termbox.c679
-rw-r--r--cpp/termbox/termbox.h303
-rw-r--r--cpp/termbox/utf8.c79
12 files changed, 1651 insertions, 26 deletions
diff --git a/cpp/070display b/cpp/070display
index a64aa550..feb16fe1 100644
--- a/cpp/070display
+++ b/cpp/070display
@@ -1,21 +1,18 @@
-//: Text-mode cursor primitives. Currently thin wrappers around ncurses calls.
-//: Mu starts out at the 'console' where lines wrap and scrolling is
-//: automatic, where keys aren't read until pressing <enter>.
-//: This file provides mechanisms for opening a 'display' and taking raw
-//: charge of the cursor and keyboard.
-
-:(before "End Includes")
-#include<ncurses.h>
+//: Take charge of the text-mode display and keyboard.
 
 //:: Display management
 
+:(before "End Globals")
+size_t Display_row = 0, Display_column = 0;
+
 :(before "End Primitive Recipe Declarations")
 SWITCH_TO_DISPLAY,
 :(before "End Primitive Recipe Numbers")
 Recipe_number["switch-to-display"] = SWITCH_TO_DISPLAY;
 :(before "End Primitive Recipe Implementations")
 case SWITCH_TO_DISPLAY: {
-  initscr();
+  tb_init();
+  Display_row = Display_column = 0;
   break;
 }
 
@@ -25,7 +22,7 @@ RETURN_TO_CONSOLE,
 Recipe_number["return-to-console"] = RETURN_TO_CONSOLE;
 :(before "End Primitive Recipe Implementations")
 case RETURN_TO_CONSOLE: {
-  endwin();
+  tb_shutdown();
   break;
 }
 
@@ -35,7 +32,8 @@ CLEAR_DISPLAY,
 Recipe_number["clear-display"] = CLEAR_DISPLAY;
 :(before "End Primitive Recipe Implementations")
 case CLEAR_DISPLAY: {
-  clear();
+  tb_clear();
+  Display_row = Display_column = 0;
   break;
 }
 
@@ -45,7 +43,12 @@ CLEAR_LINE_ON_DISPLAY,
 Recipe_number["clear-line-on-display"] = CLEAR_LINE_ON_DISPLAY;
 :(before "End Primitive Recipe Implementations")
 case CLEAR_LINE_ON_DISPLAY: {
-  clrtoeol();
+  size_t width = tb_width();
+  for (size_t x = Display_column; x < width; ++x) {
+    tb_change_cell(x, Display_row, ' ', TB_WHITE, TB_DEFAULT);
+  }
+  tb_set_cursor(Display_column, Display_row);
+  tb_present();
   break;
 }
 
@@ -56,7 +59,24 @@ Recipe_number["print-character-to-display"] = PRINT_CHARACTER_TO_DISPLAY;
 :(before "End Primitive Recipe Implementations")
 case PRINT_CHARACTER_TO_DISPLAY: {
   vector<int> arg = read_memory(instructions[pc].ingredients[0]);
-  addch(arg[0]);
+  int h=tb_height(), w=tb_width();
+  size_t height = (h >= 0) ? h : 0;
+  size_t width = (w >= 0) ? w : 0;
+  if (arg[0] == '\n') {
+    if (Display_row < height) {
+      Display_column = 0;
+      ++Display_row;
+      tb_set_cursor(Display_column, Display_row);
+      tb_present();
+    }
+    break;
+  }
+  tb_change_cell(Display_column, Display_row, arg[0], TB_WHITE, TB_DEFAULT);
+  if (Display_column < width) {
+    Display_column++;
+    tb_set_cursor(Display_column, Display_row);
+  }
+  tb_present();
   break;
 }
 
@@ -66,13 +86,11 @@ CURSOR_POSITION_ON_DISPLAY,
 Recipe_number["cursor-position-on-display"] = CURSOR_POSITION_ON_DISPLAY;
 :(before "End Primitive Recipe Implementations")
 case CURSOR_POSITION_ON_DISPLAY: {
-  size_t cursor_row = 0, cursor_column = 0;
-  getyx(stdscr, cursor_row, cursor_column);
   vector<int> row;
-  row.push_back(cursor_row);
+  row.push_back(Display_row);
   write_memory(instructions[pc].products[0], row);
   vector<int> column;
-  column.push_back(cursor_column);
+  column.push_back(Display_column);
   write_memory(instructions[pc].products[1], column);
   break;
 }
@@ -85,7 +103,10 @@ Recipe_number["move-cursor-on-display"] = MOVE_CURSOR_ON_DISPLAY;
 case MOVE_CURSOR_ON_DISPLAY: {
   vector<int> row = read_memory(instructions[pc].ingredients[0]);
   vector<int> column = read_memory(instructions[pc].ingredients[1]);
-  move(row[0], column[0]);
+  Display_row = row[0];
+  Display_column = column[0];
+  tb_set_cursor(Display_column, Display_row);
+  tb_present();
   break;
 }
 
@@ -97,6 +118,10 @@ WAIT_FOR_KEY_FROM_KEYBOARD,
 Recipe_number["wait-for-key-from-keyboard"] = WAIT_FOR_KEY_FROM_KEYBOARD;
 :(before "End Primitive Recipe Implementations")
 case WAIT_FOR_KEY_FROM_KEYBOARD: {
-  getch();
+  struct tb_event event;
+  tb_poll_event(&event);
   break;
 }
+
+:(before "End Includes")
+#include"termbox/termbox.h"
diff --git a/cpp/display.mu b/cpp/display.mu
index fa5f3b85..1b472e7e 100644
--- a/cpp/display.mu
+++ b/cpp/display.mu
@@ -2,11 +2,6 @@ recipe main [
   switch-to-display
   print-character-to-display 97:literal
   1:integer/raw, 2:integer/raw <- cursor-position-on-display
-  $print 1:integer/raw
-  $print [, ]
-  $print 2:integer/raw
-  $print [
-]
   wait-for-key-from-keyboard
   clear-display
   move-cursor-on-display 0:literal, 4:literal
diff --git a/cpp/makefile b/cpp/makefile
index 5563cd8a..a2c90f5c 100644
--- a/cpp/makefile
+++ b/cpp/makefile
@@ -1,6 +1,6 @@
-mu: makefile enumerate/enumerate tangle/tangle mu.cc
+mu: makefile enumerate/enumerate tangle/tangle mu.cc termbox/libtermbox.a
 	@make autogenerated_lists >/dev/null
-	g++ -g -Wall -Wextra -fno-strict-aliasing mu.cc -lncurses -o mu
+	g++ -g -Wall -Wextra -fno-strict-aliasing mu.cc termbox/libtermbox.a -o mu
 
 # To see what the program looks like after all layers have been applied, read
 # mu.cc
@@ -14,6 +14,9 @@ enumerate/enumerate:
 tangle/tangle:
 	cd tangle && make && ./tangle test
 
+termbox/libtermbox.a:
+	cd termbox && make
+
 # auto-generated files; by convention they end in '_list'.
 .PHONY: autogenerated_lists test clena 
 autogenerated_lists: mu.cc function_list test_list
@@ -34,4 +37,5 @@ clena: clean
 clean:
 	cd enumerate && make clean
 	cd tangle && make clean
+	cd termbox && make clean
 	rm -rf mu.cc core.mu mu *_list
diff --git a/cpp/termbox/COPYING b/cpp/termbox/COPYING
new file mode 100644
index 00000000..e9bb4eac
--- /dev/null
+++ b/cpp/termbox/COPYING
@@ -0,0 +1,19 @@
+Copyright (C) 2010-2013 nsf <no.smile.face@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/cpp/termbox/README b/cpp/termbox/README
new file mode 100644
index 00000000..eccbee72
--- /dev/null
+++ b/cpp/termbox/README
@@ -0,0 +1,2 @@
+Fork of https://github.com/nsf/termbox as of 2015-04-22
+git hash 7c154d98a7d9207d768ee0a8e519ede74c0105cf
diff --git a/cpp/termbox/bytebuffer.inl b/cpp/termbox/bytebuffer.inl
new file mode 100644
index 00000000..c476742d
--- /dev/null
+++ b/cpp/termbox/bytebuffer.inl
@@ -0,0 +1,78 @@
+struct bytebuffer {
+	char *buf;
+	int len;
+	int cap;
+};
+
+static void bytebuffer_reserve(struct bytebuffer *b, int cap) {
+	if (b->cap >= cap) {
+		return;
+	}
+
+	// prefer doubling capacity
+	if (b->cap * 2 >= cap) {
+		cap = b->cap * 2;
+	}
+
+	char *newbuf = malloc(cap);
+	if (b->len > 0) {
+		// copy what was there, b->len > 0 assumes b->buf != null
+		memcpy(newbuf, b->buf, b->len);
+	}
+	if (b->buf) {
+		// in case there was an allocated buffer, free it
+		free(b->buf);
+	}
+	b->buf = newbuf;
+	b->cap = cap;
+}
+
+static void bytebuffer_init(struct bytebuffer *b, int cap) {
+	b->cap = 0;
+	b->len = 0;
+	b->buf = 0;
+
+	if (cap > 0) {
+		b->cap = cap;
+		b->buf = malloc(cap); // just assume malloc works always
+	}
+}
+
+static void bytebuffer_free(struct bytebuffer *b) {
+	if (b->buf)
+		free(b->buf);
+}
+
+static void bytebuffer_clear(struct bytebuffer *b) {
+	b->len = 0;
+}
+
+static void bytebuffer_append(struct bytebuffer *b, const char *data, int len) {
+	bytebuffer_reserve(b, b->len + len);
+	memcpy(b->buf + b->len, data, len);
+	b->len += len;
+}
+
+static void bytebuffer_puts(struct bytebuffer *b, const char *str) {
+	bytebuffer_append(b, str, strlen(str));
+}
+
+static void bytebuffer_resize(struct bytebuffer *b, int len) {
+	bytebuffer_reserve(b, len);
+	b->len = len;
+}
+
+static void bytebuffer_flush(struct bytebuffer *b, int fd) {
+	write(fd, b->buf, b->len);
+	bytebuffer_clear(b);
+}
+
+static void bytebuffer_truncate(struct bytebuffer *b, int n) {
+	if (n <= 0)
+		return;
+	if (n > b->len)
+		n = b->len;
+	const int nmove = b->len - n;
+	memmove(b->buf, b->buf+n, nmove);
+	b->len -= n;
+}
diff --git a/cpp/termbox/input.inl b/cpp/termbox/input.inl
new file mode 100644
index 00000000..8c555114
--- /dev/null
+++ b/cpp/termbox/input.inl
@@ -0,0 +1,132 @@
+// if s1 starts with s2 returns true, else false
+// len is the length of s1
+// s2 should be null-terminated
+static bool starts_with(const char *s1, int len, const char *s2)
+{
+	int n = 0;
+	while (*s2 && n < len) {
+		if (*s1++ != *s2++)
+			return false;
+		n++;
+	}
+	return *s2 == 0;
+}
+
+// convert escape sequence to event, and return consumed bytes on success (failure == 0)
+static int parse_escape_seq(struct tb_event *event, const char *buf, int len)
+{
+	if (len >= 6 && starts_with(buf, len, "\033[M")) {
+
+		switch (buf[3] & 3) {
+		case 0:
+			if (buf[3] == 0x60)
+				event->key = TB_KEY_MOUSE_WHEEL_UP;
+			else
+				event->key = TB_KEY_MOUSE_LEFT;
+			break;
+		case 1:
+			if (buf[3] == 0x61)
+				event->key = TB_KEY_MOUSE_WHEEL_DOWN;
+			else
+				event->key = TB_KEY_MOUSE_MIDDLE;
+			break;
+		case 2:
+			event->key = TB_KEY_MOUSE_RIGHT;
+			break;
+		case 3:
+			event->key = TB_KEY_MOUSE_RELEASE;
+			break;
+		default:
+			return -6;
+		}
+		event->type = TB_EVENT_MOUSE; // TB_EVENT_KEY by default
+
+		// the coord is 1,1 for upper left
+		event->x = buf[4] - 1 - 32;
+		event->y = buf[5] - 1 - 32;
+
+		return 6;
+	}
+
+	// it's pretty simple here, find 'starts_with' match and return
+	// success, else return failure
+	int i;
+	for (i = 0; keys[i]; i++) {
+		if (starts_with(buf, len, keys[i])) {
+			event->ch = 0;
+			event->key = 0xFFFF-i;
+			return strlen(keys[i]);
+		}
+	}
+	return 0;
+}
+
+static bool extract_event(struct tb_event *event, struct bytebuffer *inbuf, int inputmode)
+{
+	const char *buf = inbuf->buf;
+	const int len = inbuf->len;
+	if (len == 0)
+		return false;
+
+	if (buf[0] == '\033') {
+		int n = parse_escape_seq(event, buf, len);
+		if (n != 0) {
+			bool success = true;
+			if (n < 0) {
+				success = false;
+				n = -n;
+			}
+			bytebuffer_truncate(inbuf, n);
+			return success;
+		} else {
+			// it's not escape sequence, then it's ALT or ESC,
+			// check inputmode
+			if (inputmode&TB_INPUT_ESC) {
+				// if we're in escape mode, fill ESC event, pop
+				// buffer, return success
+				event->ch = 0;
+				event->key = TB_KEY_ESC;
+				event->mod = 0;
+				bytebuffer_truncate(inbuf, 1);
+				return true;
+			}
+			if (inputmode&TB_INPUT_ALT) {
+				// if we're in alt mode, set ALT modifier to
+				// event and redo parsing
+				event->mod = TB_MOD_ALT;
+				bytebuffer_truncate(inbuf, 1);
+				return extract_event(event, inbuf, inputmode);
+			}
+			assert(!"never got here");
+		}
+	}
+
+	// if we're here, this is not an escape sequence and not an alt sequence
+	// so, it's a FUNCTIONAL KEY or a UNICODE character
+
+	// first of all check if it's a functional key
+	if ((unsigned char)buf[0] <= TB_KEY_SPACE ||
+	    (unsigned char)buf[0] == TB_KEY_BACKSPACE2)
+	{
+		// fill event, pop buffer, return success */
+		event->ch = 0;
+		event->key = (uint16_t)buf[0];
+		bytebuffer_truncate(inbuf, 1);
+		return true;
+	}
+
+	// feh... we got utf8 here
+
+	// check if there is all bytes
+	if (len >= tb_utf8_char_length(buf[0])) {
+		/* everything ok, fill event, pop buffer, return success */
+		tb_utf8_char_to_unicode(&event->ch, buf);
+		event->key = 0;
+		bytebuffer_truncate(inbuf, tb_utf8_char_length(buf[0]));
+		return true;
+	}
+
+	// event isn't recognized, perhaps there is not enough bytes in utf8
+	// sequence
+	return false;
+}
diff --git a/cpp/termbox/makefile b/cpp/termbox/makefile
new file mode 100644
index 00000000..58d76b21
--- /dev/null
+++ b/cpp/termbox/makefile
@@ -0,0 +1,7 @@
+CFLAGS=-O3 -Wall -Wextra -D_XOPEN_SOURCE
+
+libtermbox.a: utf8.o termbox.o
+	ar rcs libtermbox.a *.o
+
+clean:
+	rm *.o libtermbox.a
diff --git a/cpp/termbox/term.inl b/cpp/termbox/term.inl
new file mode 100644
index 00000000..80812b40
--- /dev/null
+++ b/cpp/termbox/term.inl
@@ -0,0 +1,302 @@
+enum {
+	T_ENTER_CA,
+	T_EXIT_CA,
+	T_SHOW_CURSOR,
+	T_HIDE_CURSOR,
+	T_CLEAR_SCREEN,
+	T_SGR0,
+	T_UNDERLINE,
+	T_BOLD,
+	T_BLINK,
+	T_REVERSE,
+	T_ENTER_KEYPAD,
+	T_EXIT_KEYPAD,
+	T_ENTER_MOUSE,
+	T_EXIT_MOUSE,
+	T_FUNCS_NUM,
+};
+
+#define EUNSUPPORTED_TERM -1
+
+// rxvt-256color
+static const char *rxvt_256color_keys[] = {
+	"\033[11~","\033[12~","\033[13~","\033[14~","\033[15~","\033[17~","\033[18~","\033[19~","\033[20~","\033[21~","\033[23~","\033[24~","\033[2~","\033[3~","\033[7~","\033[8~","\033[5~","\033[6~","\033[A","\033[B","\033[D","\033[C", 0
+};
+static const char *rxvt_256color_funcs[] = {
+	"\0337\033[?47h", "\033[2J\033[?47l\0338", "\033[?25h", "\033[?25l", "\033[H\033[2J", "\033[m", "\033[4m", "\033[1m", "\033[5m", "\033[7m", "\033=", "\033>", "\033[?1000h", "\033[?1000l",
+};
+
+// Eterm
+static const char *eterm_keys[] = {
+	"\033[11~","\033[12~","\033[13~","\033[14~","\033[15~","\033[17~","\033[18~","\033[19~","\033[20~","\033[21~","\033[23~","\033[24~","\033[2~","\033[3~","\033[7~","\033[8~","\033[5~","\033[6~","\033[A","\033[B","\033[D","\033[C", 0
+};
+static const char *eterm_funcs[] = {
+	"\0337\033[?47h", "\033[2J\033[?47l\0338", "\033[?25h", "\033[?25l", "\033[H\033[2J", "\033[m", "\033[4m", "\033[1m", "\033[5m", "\033[7m", "", "", "", "",
+};
+
+// screen
+static const char *screen_keys[] = {
+	"\033OP","\033OQ","\033OR","\033OS","\033[15~","\033[17~","\033[18~","\033[19~","\033[20~","\033[21~","\033[23~","\033[24~","\033[2~","\033[3~","\033[1~","\033[4~","\033[5~","\033[6~","\033OA","\033OB","\033OD","\033OC", 0
+};
+static const char *screen_funcs[] = {
+	"\033[?1049h", "\033[?1049l", "\033[34h\033[?25h", "\033[?25l", "\033[H\033[J", "\033[m", "\033[4m", "\033[1m", "\033[5m", "\033[7m", "\033[?1h\033=", "\033[?1l\033>", "\033[?1000h", "\033[?1000l",
+};
+
+// rxvt-unicode
+static const char *rxvt_unicode_keys[] = {
+	"\033[11~","\033[12~","\033[13~","\033[14~","\033[15~","\033[17~","\033[18~","\033[19~","\033[20~","\033[21~","\033[23~","\033[24~","\033[2~","\033[3~","\033[7~","\033[8~","\033[5~","\033[6~","\033[A","\033[B","\033[D","\033[C", 0
+};
+static const char *rxvt_unicode_funcs[] = {
+	"\033[?1049h", "\033[r\033[?1049l", "\033[?25h", "\033[?25l", "\033[H\033[2J", "\033[m\033(B", "\033[4m", "\033[1m", "\033[5m", "\033[7m", "\033=", "\033>", "\033[?1000h", "\033[?1000l",
+};
+
+// linux
+static const char *linux_keys[] = {
+	"\033[[A","\033[[B","\033[[C","\033[[D","\033[[E","\033[17~","\033[18~","\033[19~","\033[20~","\033[21~","\033[23~","\033[24~","\033[2~","\033[3~","\033[1~","\033[4~","\033[5~","\033[6~","\033[A","\033[B","\033[D","\033[C", 0
+};
+static const char *linux_funcs[] = {
+	"", "", "\033[?25h\033[?0c", "\033[?25l\033[?1c", "\033[H\033[J", "\033[0;10m", "\033[4m", "\033[1m", "\033[5m", "\033[7m", "", "", "", "",
+};
+
+// xterm
+static const char *xterm_keys[] = {
+	"\033OP","\033OQ","\033OR","\033OS","\033[15~","\033[17~","\033[18~","\033[19~","\033[20~","\033[21~","\033[23~","\033[24~","\033[2~","\033[3~","\033OH","\033OF","\033[5~","\033[6~","\033OA","\033OB","\033OD","\033OC", 0
+};
+static const char *xterm_funcs[] = {
+	"\033[?1049h", "\033[?1049l", "\033[?12l\033[?25h", "\033[?25l", "\033[H\033[2J", "\033(B\033[m", "\033[4m", "\033[1m", "\033[5m", "\033[7m", "\033[?1h\033=", "\033[?1l\033>", "\033[?1000h", "\033[?1000l",
+};
+
+static struct term {
+	const char *name;
+	const char **keys;
+	const char **funcs;
+} terms[] = {
+	{"rxvt-256color", rxvt_256color_keys, rxvt_256color_funcs},
+	{"Eterm", eterm_keys, eterm_funcs},
+	{"screen", screen_keys, screen_funcs},
+	{"rxvt-unicode", rxvt_unicode_keys, rxvt_unicode_funcs},
+	{"linux", linux_keys, linux_funcs},
+	{"xterm", xterm_keys, xterm_funcs},
+	{0, 0, 0},
+};
+
+static bool init_from_terminfo = false;
+static const char **keys;
+static const char **funcs;
+
+static int try_compatible(const char *term, const char *name,
+			  const char **tkeys, const char **tfuncs)
+{
+	if (strstr(term, name)) {
+		keys = tkeys;
+		funcs = tfuncs;
+		return 0;
+	}
+
+	return EUNSUPPORTED_TERM;
+}
+
+static int init_term_builtin(void)
+{
+	int i;
+	const char *term = getenv("TERM");
+
+	if (term) {
+		for (i = 0; terms[i].name; i++) {
+			if (!strcmp(terms[i].name, term)) {
+				keys = terms[i].keys;
+				funcs = terms[i].funcs;
+				return 0;
+			}
+		}
+
+		/* let's do some heuristic, maybe it's a compatible terminal */
+		if (try_compatible(term, "xterm", xterm_keys, xterm_funcs) == 0)
+			return 0;
+		if (try_compatible(term, "rxvt", rxvt_unicode_keys, rxvt_unicode_funcs) == 0)
+			return 0;
+		if (try_compatible(term, "linux", linux_keys, linux_funcs) == 0)
+			return 0;
+		if (try_compatible(term, "Eterm", eterm_keys, eterm_funcs) == 0)
+			return 0;
+		if (try_compatible(term, "screen", screen_keys, screen_funcs) == 0)
+			return 0;
+		/* let's assume that 'cygwin' is xterm compatible */
+		if (try_compatible(term, "cygwin", xterm_keys, xterm_funcs) == 0)
+			return 0;
+	}
+
+	return EUNSUPPORTED_TERM;
+}
+
+//----------------------------------------------------------------------
+// terminfo
+//----------------------------------------------------------------------
+
+static char *read_file(const char *file) {
+	FILE *f = fopen(file, "rb");
+	if (!f)
+		return 0;
+
+	struct stat st;
+	if (fstat(fileno(f), &st) != 0) {
+		fclose(f);
+		return 0;
+	}
+
+	char *data = malloc(st.st_size);
+	if (!data) {
+		fclose(f);
+		return 0;
+	}
+
+	if (fread(data, 1, st.st_size, f) != (size_t)st.st_size) {
+		fclose(f);
+		free(data);
+		return 0;
+	}
+
+	fclose(f);
+	return data;
+}
+
+static char *terminfo_try_path(const char *path, const char *term) {
+	char tmp[4096];
+	snprintf(tmp, sizeof(tmp), "%s/%c/%s", path, term[0], term);
+	tmp[sizeof(tmp)-1] = '\0';
+	char *data = read_file(tmp);
+	if (data) {
+		return data;
+	}
+
+	// fallback to darwin specific dirs structure
+	snprintf(tmp, sizeof(tmp), "%s/%x/%s", path, term[0], term);
+	tmp[sizeof(tmp)-1] = '\0';
+	return read_file(tmp);
+}
+
+static char *load_terminfo(void) {
+	char tmp[4096];
+	const char *term = getenv("TERM");
+	if (!term) {
+		return 0;
+	}
+
+	// if TERMINFO is set, no other directory should be searched
+	const char *terminfo = getenv("TERMINFO");
+	if (terminfo) {
+		return terminfo_try_path(terminfo, term);
+	}
+
+	// next, consider ~/.terminfo
+	const char *home = getenv("HOME");
+	if (home) {
+		snprintf(tmp, sizeof(tmp), "%s/.terminfo", home);
+		tmp[sizeof(tmp)-1] = '\0';
+		char *data = terminfo_try_path(tmp, term);
+		if (data)
+			return data;
+	}
+
+	// next, TERMINFO_DIRS
+	const char *dirs = getenv("TERMINFO_DIRS");
+	if (dirs) {
+		snprintf(tmp, sizeof(tmp), "%s", dirs);
+		tmp[sizeof(tmp)-1] = '\0';
+		char *dir = strtok(tmp, ":");
+		while (dir) {
+			const char *cdir = dir;
+			if (strcmp(cdir, "") == 0) {
+				cdir = "/usr/share/terminfo";
+			}
+			char *data = terminfo_try_path(cdir, term);
+			if (data)
+				return data;
+			dir = strtok(0, ":");
+		}
+	}
+
+	// fallback to /usr/share/terminfo
+	return terminfo_try_path("/usr/share/terminfo", term);
+}
+
+#define TI_MAGIC 0432
+#define TI_HEADER_LENGTH 12
+#define TB_KEYS_NUM 22
+
+static const char *terminfo_copy_string(char *data, int str, int table) {
+	const int16_t off = *(int16_t*)(data + str);
+	const char *src = data + table + off;
+	int len = strlen(src);
+	char *dst = malloc(len+1);
+	strcpy(dst, src);
+	return dst;
+}
+
+static const int16_t ti_funcs[] = {
+	28, 40, 16, 13, 5, 39, 36, 27, 26, 34, 89, 88,
+};
+
+static const int16_t ti_keys[] = {
+	66, 68 /* apparently not a typo; 67 is F10 for whatever reason */, 69,
+	70, 71, 72, 73, 74, 75, 67, 216, 217, 77, 59, 76, 164, 82, 81, 87, 61,
+	79, 83,
+};
+
+static int init_term(void) {
+	int i;
+	char *data = load_terminfo();
+	if (!data) {
+		init_from_terminfo = false;
+		return init_term_builtin();
+	}
+
+	int16_t *header = (int16_t*)data;
+	if ((header[1] + header[2]) % 2) {
+		// old quirk to align everything on word boundaries
+		header[2] += 1;
+	}
+
+	const int str_offset = TI_HEADER_LENGTH +
+		header[1] + header[2] +	2 * header[3];
+	const int table_offset = str_offset + 2 * header[4];
+
+	keys = malloc(sizeof(const char*) * (TB_KEYS_NUM+1));
+	for (i = 0; i < TB_KEYS_NUM; i++) {
+		keys[i] = terminfo_copy_string(data,
+			str_offset + 2 * ti_keys[i], table_offset);
+	}
+	keys[TB_KEYS_NUM] = 0;
+
+	funcs = malloc(sizeof(const char*) * T_FUNCS_NUM);
+	// the last two entries are reserved for mouse. because the table offset is
+	// not there, the two entries have to fill in manually
+	for (i = 0; i < T_FUNCS_NUM-2; i++) {
+		funcs[i] = terminfo_copy_string(data,
+			str_offset + 2 * ti_funcs[i], table_offset);
+	}
+
+	funcs[T_FUNCS_NUM-2] = "\033[?1000h";
+	funcs[T_FUNCS_NUM-1] = "\033[?1000l";
+
+	init_from_terminfo = true;
+	free(data);
+	return 0;
+}
+
+static void shutdown_term(void) {
+	if (init_from_terminfo) {
+		int i;
+		for (i = 0; i < TB_KEYS_NUM; i++) {
+			free((void*)keys[i]);
+		}
+		// the last two entries are reserved for mouse. because the table offset
+		// is not there, the two entries have to fill in manually and do not
+		// need to be freed.
+		for (i = 0; i < T_FUNCS_NUM-2; i++) {
+			free((void*)funcs[i]);
+		}
+		free(keys);
+		free(funcs);
+	}
+}
diff --git a/cpp/termbox/termbox.c b/cpp/termbox/termbox.c
new file mode 100644
index 00000000..c9a7ae5e
--- /dev/null
+++ b/cpp/termbox/termbox.c
@@ -0,0 +1,679 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <termios.h>
+#include <unistd.h>
+#include <wchar.h>
+
+#include "termbox.h"
+
+#include "bytebuffer.inl"
+#include "term.inl"
+#include "input.inl"
+
+struct cellbuf {
+	int width;
+	int height;
+	struct tb_cell *cells;
+};
+
+#define CELL(buf, x, y) (buf)->cells[(y) * (buf)->width + (x)]
+#define IS_CURSOR_HIDDEN(cx, cy) (cx == -1 || cy == -1)
+#define LAST_COORD_INIT -1
+
+static struct termios orig_tios;
+
+static struct cellbuf back_buffer;
+static struct cellbuf front_buffer;
+static struct bytebuffer output_buffer;
+static struct bytebuffer input_buffer;
+
+static int termw = -1;
+static int termh = -1;
+
+static int inputmode = TB_INPUT_ESC;
+static int outputmode = TB_OUTPUT_NORMAL;
+
+static int inout;
+static int winch_fds[2];
+
+static int lastx = LAST_COORD_INIT;
+static int lasty = LAST_COORD_INIT;
+static int cursor_x = -1;
+static int cursor_y = -1;
+
+static uint16_t background = TB_DEFAULT;
+static uint16_t foreground = TB_DEFAULT;
+
+static void write_cursor(int x, int y);
+static void write_sgr_fg(uint16_t fg);
+static void write_sgr_bg(uint16_t bg);
+static void write_sgr(uint16_t fg, uint16_t bg);
+
+static void cellbuf_init(struct cellbuf *buf, int width, int height);
+static void cellbuf_resize(struct cellbuf *buf, int width, int height);
+static void cellbuf_clear(struct cellbuf *buf);
+static void cellbuf_free(struct cellbuf *buf);
+
+static void update_size(void);
+static void update_term_size(void);
+static void send_attr(uint16_t fg, uint16_t bg);
+static void send_char(int x, int y, uint32_t c);
+static void send_clear(void);
+static void sigwinch_handler(int xxx);
+static int wait_fill_event(struct tb_event *event, struct timeval *timeout);
+
+/* may happen in a different thread */
+static volatile int buffer_size_change_request;
+
+/* -------------------------------------------------------- */
+
+int tb_init(void)
+{
+	inout = open("/dev/tty", O_RDWR);
+	if (inout == -1) {
+		return TB_EFAILED_TO_OPEN_TTY;
+	}
+
+	if (init_term() < 0) {
+		close(inout);
+		return TB_EUNSUPPORTED_TERMINAL;
+	}
+
+	if (pipe(winch_fds) < 0) {
+		close(inout);
+		return TB_EPIPE_TRAP_ERROR;
+	}
+
+	struct sigaction sa;
+	memset(&sa, 0, sizeof(sa));
+	sa.sa_handler = sigwinch_handler;
+	sa.sa_flags = 0;
+	sigaction(SIGWINCH, &sa, 0);
+
+	tcgetattr(inout, &orig_tios);
+
+	struct termios tios;
+	memcpy(&tios, &orig_tios, sizeof(tios));
+
+	tios.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
+                           | INLCR | IGNCR | ICRNL | IXON);
+	tios.c_oflag &= ~OPOST;
+	tios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
+	tios.c_cflag &= ~(CSIZE | PARENB);
+	tios.c_cflag |= CS8;
+	tios.c_cc[VMIN] = 0;
+	tios.c_cc[VTIME] = 0;
+	tcsetattr(inout, TCSAFLUSH, &tios);
+
+	bytebuffer_init(&input_buffer, 128);
+	bytebuffer_init(&output_buffer, 32 * 1024);
+
+	bytebuffer_puts(&output_buffer, funcs[T_ENTER_CA]);
+	bytebuffer_puts(&output_buffer, funcs[T_ENTER_KEYPAD]);
+	bytebuffer_puts(&output_buffer, funcs[T_HIDE_CURSOR]);
+	send_clear();
+
+	update_term_size();
+	cellbuf_init(&back_buffer, termw, termh);
+	cellbuf_init(&front_buffer, termw, termh);
+	cellbuf_clear(&back_buffer);
+	cellbuf_clear(&front_buffer);
+
+	return 0;
+}
+
+void tb_shutdown(void)
+{
+	if (termw == -1) {
+		fputs("tb_shutdown() should not be called twice.", stderr);
+		abort();
+	}
+
+	bytebuffer_puts(&output_buffer, funcs[T_SHOW_CURSOR]);
+	bytebuffer_puts(&output_buffer, funcs[T_SGR0]);
+	bytebuffer_puts(&output_buffer, funcs[T_CLEAR_SCREEN]);
+	bytebuffer_puts(&output_buffer, funcs[T_EXIT_CA]);
+	bytebuffer_puts(&output_buffer, funcs[T_EXIT_KEYPAD]);
+	bytebuffer_puts(&output_buffer, funcs[T_EXIT_MOUSE]);
+	bytebuffer_flush(&output_buffer, inout);
+	tcsetattr(inout, TCSAFLUSH, &orig_tios);
+
+	shutdown_term();
+	close(inout);
+	close(winch_fds[0]);
+	close(winch_fds[1]);
+
+	cellbuf_free(&back_buffer);
+	cellbuf_free(&front_buffer);
+	bytebuffer_free(&output_buffer);
+	bytebuffer_free(&input_buffer);
+	termw = termh = -1;
+}
+
+void tb_present(void)
+{
+	int x,y,w,i;
+	struct tb_cell *back, *front;
+
+	/* invalidate cursor position */
+	lastx = LAST_COORD_INIT;
+	lasty = LAST_COORD_INIT;
+
+	if (buffer_size_change_request) {
+		update_size();
+		buffer_size_change_request = 0;
+	}
+
+	for (y = 0; y < front_buffer.height; ++y) {
+		for (x = 0; x < front_buffer.width; ) {
+			back = &CELL(&back_buffer, x, y);
+			front = &CELL(&front_buffer, x, y);
+			w = wcwidth(back->ch);
+			if (w < 1) w = 1;
+			if (memcmp(back, front, sizeof(struct tb_cell)) == 0) {
+				x += w;
+				continue;
+			}
+			memcpy(front, back, sizeof(struct tb_cell));
+			send_attr(back->fg, back->bg);
+			if (w > 1 && x >= front_buffer.width - (w - 1)) {
+				// Not enough room for wide ch, so send spaces
+				for (i = x; i < front_buffer.width; ++i) {
+					send_char(i, y, ' ');
+				}
+			} else {
+				send_char(x, y, back->ch);
+				for (i = 1; i < w; ++i) {
+					front = &CELL(&front_buffer, x + i, y);
+					front->ch = 0;
+					front->fg = back->fg;
+					front->bg = back->bg;
+				}
+			}
+			x += w;
+		}
+	}
+	if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y))
+		write_cursor(cursor_x, cursor_y);
+	bytebuffer_flush(&output_buffer, inout);
+}
+
+void tb_set_cursor(int cx, int cy)
+{
+	if (IS_CURSOR_HIDDEN(cursor_x, cursor_y) && !IS_CURSOR_HIDDEN(cx, cy))
+		bytebuffer_puts(&output_buffer, funcs[T_SHOW_CURSOR]);
+
+	if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y) && IS_CURSOR_HIDDEN(cx, cy))
+		bytebuffer_puts(&output_buffer, funcs[T_HIDE_CURSOR]);
+
+	cursor_x = cx;
+	cursor_y = cy;
+	if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y))
+		write_cursor(cursor_x, cursor_y);
+}
+
+void tb_put_cell(int x, int y, const struct tb_cell *cell)
+{
+	if ((unsigned)x >= (unsigned)back_buffer.width)
+		return;
+	if ((unsigned)y >= (unsigned)back_buffer.height)
+		return;
+	CELL(&back_buffer, x, y) = *cell;
+}
+
+void tb_change_cell(int x, int y, uint32_t ch, uint16_t fg, uint16_t bg)
+{
+	struct tb_cell c = {ch, fg, bg};
+	tb_put_cell(x, y, &c);
+}
+
+void tb_blit(int x, int y, int w, int h, const struct tb_cell *cells)
+{
+	if (x + w < 0 || x >= back_buffer.width)
+		return;
+	if (y + h < 0 || y >= back_buffer.height)
+		return;
+	int xo = 0, yo = 0, ww = w, hh = h;
+	if (x < 0) {
+		xo = -x;
+		ww -= xo;
+		x = 0;
+	}
+	if (y < 0) {
+		yo = -y;
+		hh -= yo;
+		y = 0;
+	}
+	if (ww > back_buffer.width - x)
+		ww = back_buffer.width - x;
+	if (hh > back_buffer.height - y)
+		hh = back_buffer.height - y;
+
+	int sy;
+	struct tb_cell *dst = &CELL(&back_buffer, x, y);
+	const struct tb_cell *src = cells + yo * w + xo;
+	size_t size = sizeof(struct tb_cell) * ww;
+
+	for (sy = 0; sy < hh; ++sy) {
+		memcpy(dst, src, size);
+		dst += back_buffer.width;
+		src += w;
+	}
+}
+
+struct tb_cell *tb_cell_buffer()
+{
+	return back_buffer.cells;
+}
+
+int tb_poll_event(struct tb_event *event)
+{
+	return wait_fill_event(event, 0);
+}
+
+int tb_peek_event(struct tb_event *event, int timeout)
+{
+	struct timeval tv;
+	tv.tv_sec = timeout / 1000;
+	tv.tv_usec = (timeout - (tv.tv_sec * 1000)) * 1000;
+	return wait_fill_event(event, &tv);
+}
+
+int tb_width(void)
+{
+	return termw;
+}
+
+int tb_height(void)
+{
+	return termh;
+}
+
+void tb_clear(void)
+{
+	if (buffer_size_change_request) {
+		update_size();
+		buffer_size_change_request = 0;
+	}
+	cellbuf_clear(&back_buffer);
+}
+
+int tb_select_input_mode(int mode)
+{
+	if (mode) {
+		inputmode = mode;
+		if (mode&TB_INPUT_MOUSE) {
+			bytebuffer_puts(&output_buffer, funcs[T_ENTER_MOUSE]);
+			bytebuffer_flush(&output_buffer, inout);
+		} else {
+			bytebuffer_puts(&output_buffer, funcs[T_EXIT_MOUSE]);
+			bytebuffer_flush(&output_buffer, inout);
+		}
+	}
+	return inputmode;
+}
+
+int tb_select_output_mode(int mode)
+{
+	if (mode)
+		outputmode = mode;
+	return outputmode;
+}
+
+void tb_set_clear_attributes(uint16_t fg, uint16_t bg)
+{
+	foreground = fg;
+	background = bg;
+}
+
+/* -------------------------------------------------------- */
+
+static int convertnum(uint32_t num, char* buf) {
+	int i, l = 0;
+	int ch;
+	do {
+		buf[l++] = '0' + (num % 10);
+		num /= 10;
+	} while (num);
+	for(i = 0; i < l / 2; i++) {
+		ch = buf[i];
+		buf[i] = buf[l - 1 - i];
+		buf[l - 1 - i] = ch;
+	}
+	return l;
+}
+
+#define WRITE_LITERAL(X) bytebuffer_append(&output_buffer, (X), sizeof(X)-1)
+#define WRITE_INT(X) bytebuffer_append(&output_buffer, buf, convertnum((X), buf))
+
+static void write_cursor(int x, int y) {
+	char buf[32];
+	WRITE_LITERAL("\033[");
+	WRITE_INT(y+1);
+	WRITE_LITERAL(";");
+	WRITE_INT(x+1);
+	WRITE_LITERAL("H");
+}
+
+// can only be called in NORMAL output mode
+static void write_sgr_fg(uint16_t fg) {
+	char buf[32];
+
+	WRITE_LITERAL("\033[3");
+	WRITE_INT(fg-1);
+	WRITE_LITERAL("m");
+}
+
+// can only be called in NORMAL output mode
+static void write_sgr_bg(uint16_t bg) {
+	char buf[32];
+
+	WRITE_LITERAL("\033[4");
+	WRITE_INT(bg-1);
+	WRITE_LITERAL("m");
+}
+
+static void write_sgr(uint16_t fg, uint16_t bg) {
+	char buf[32];
+
+	switch (outputmode) {
+	case TB_OUTPUT_256:
+	case TB_OUTPUT_216:
+	case TB_OUTPUT_GRAYSCALE:
+		WRITE_LITERAL("\033[38;5;");
+		WRITE_INT(fg);
+		WRITE_LITERAL("m");
+		WRITE_LITERAL("\033[48;5;");
+		WRITE_INT(bg);
+		WRITE_LITERAL("m");
+		break;
+	case TB_OUTPUT_NORMAL:
+	default:
+		WRITE_LITERAL("\033[3");
+		WRITE_INT(fg-1);
+		WRITE_LITERAL(";4");
+		WRITE_INT(bg-1);
+		WRITE_LITERAL("m");
+	}
+}
+
+static void cellbuf_init(struct cellbuf *buf, int width, int height)
+{
+	buf->cells = (struct tb_cell*)malloc(sizeof(struct tb_cell) * width * height);
+	assert(buf->cells);
+	buf->width = width;
+	buf->height = height;
+}
+
+static void cellbuf_resize(struct cellbuf *buf, int width, int height)
+{
+	if (buf->width == width && buf->height == height)
+		return;
+
+	int oldw = buf->width;
+	int oldh = buf->height;
+	struct tb_cell *oldcells = buf->cells;
+
+	cellbuf_init(buf, width, height);
+	cellbuf_clear(buf);
+
+	int minw = (width < oldw) ? width : oldw;
+	int minh = (height < oldh) ? height : oldh;
+	int i;
+
+	for (i = 0; i < minh; ++i) {
+		struct tb_cell *csrc = oldcells + (i * oldw);
+		struct tb_cell *cdst = buf->cells + (i * width);
+		memcpy(cdst, csrc, sizeof(struct tb_cell) * minw);
+	}
+
+	free(oldcells);
+}
+
+static void cellbuf_clear(struct cellbuf *buf)
+{
+	int i;
+	int ncells = buf->width * buf->height;
+
+	for (i = 0; i < ncells; ++i) {
+		buf->cells[i].ch = ' ';
+		buf->cells[i].fg = foreground;
+		buf->cells[i].bg = background;
+	}
+}
+
+static void cellbuf_free(struct cellbuf *buf)
+{
+	free(buf->cells);
+}
+
+static void get_term_size(int *w, int *h)
+{
+	struct winsize sz;
+	memset(&sz, 0, sizeof(sz));
+
+	ioctl(inout, TIOCGWINSZ, &sz);
+
+	if (w) *w = sz.ws_col;
+	if (h) *h = sz.ws_row;
+}
+
+static void update_term_size(void)
+{
+	struct winsize sz;
+	memset(&sz, 0, sizeof(sz));
+
+	ioctl(inout, TIOCGWINSZ, &sz);
+
+	termw = sz.ws_col;
+	termh = sz.ws_row;
+}
+
+static void send_attr(uint16_t fg, uint16_t bg)
+{
+#define LAST_ATTR_INIT 0xFFFF
+	static uint16_t lastfg = LAST_ATTR_INIT, lastbg = LAST_ATTR_INIT;
+	if (fg != lastfg || bg != lastbg) {
+		bytebuffer_puts(&output_buffer, funcs[T_SGR0]);
+
+		uint16_t fgcol;
+		uint16_t bgcol;
+
+		switch (outputmode) {
+		case TB_OUTPUT_256:
+			fgcol = fg & 0xFF;
+			bgcol = bg & 0xFF;
+			break;
+
+		case TB_OUTPUT_216:
+			fgcol = fg & 0xFF; if (fgcol > 215) fgcol = 7;
+			bgcol = bg & 0xFF; if (bgcol > 215) bgcol = 0;
+			fgcol += 0x10;
+			bgcol += 0x10;
+			break;
+
+		case TB_OUTPUT_GRAYSCALE:
+			fgcol = fg & 0xFF; if (fgcol > 23) fgcol = 23;
+			bgcol = bg & 0xFF; if (bgcol > 23) bgcol = 0;
+			fgcol += 0xe8;
+			bgcol += 0xe8;
+			break;
+
+		case TB_OUTPUT_NORMAL:
+		default:
+			fgcol = fg & 0x0F;
+			bgcol = bg & 0x0F;
+		}
+
+		if (fg & TB_BOLD)
+			bytebuffer_puts(&output_buffer, funcs[T_BOLD]);
+		if (bg & TB_BOLD)
+			bytebuffer_puts(&output_buffer, funcs[T_BLINK]);
+		if (fg & TB_UNDERLINE)
+			bytebuffer_puts(&output_buffer, funcs[T_UNDERLINE]);
+		if ((fg & TB_REVERSE) || (bg & TB_REVERSE))
+			bytebuffer_puts(&output_buffer, funcs[T_REVERSE]);
+
+		switch (outputmode) {
+		case TB_OUTPUT_256:
+		case TB_OUTPUT_216:
+		case TB_OUTPUT_GRAYSCALE:
+			write_sgr(fgcol, bgcol);
+			break;
+
+		case TB_OUTPUT_NORMAL:
+		default:
+			if (fgcol != TB_DEFAULT) {
+				if (bgcol != TB_DEFAULT)
+					write_sgr(fgcol, bgcol);
+				else
+					write_sgr_fg(fgcol);
+			} else if (bgcol != TB_DEFAULT) {
+				write_sgr_bg(bgcol);
+			}
+		}
+
+		lastfg = fg;
+		lastbg = bg;
+	}
+}
+
+static void send_char(int x, int y, uint32_t c)
+{
+	char buf[7];
+	int bw = tb_utf8_unicode_to_char(buf, c);
+	buf[bw] = '\0';
+	if (x-1 != lastx || y != lasty)
+		write_cursor(x, y);
+	lastx = x; lasty = y;
+	if(!c) buf[0] = ' '; // replace 0 with whitespace
+	bytebuffer_puts(&output_buffer, buf);
+}
+
+static void send_clear(void)
+{
+	send_attr(foreground, background);
+	bytebuffer_puts(&output_buffer, funcs[T_CLEAR_SCREEN]);
+	if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y))
+		write_cursor(cursor_x, cursor_y);
+	bytebuffer_flush(&output_buffer, inout);
+
+	/* we need to invalidate cursor position too and these two vars are
+	 * used only for simple cursor positioning optimization, cursor
+	 * actually may be in the correct place, but we simply discard
+	 * optimization once and it gives us simple solution for the case when
+	 * cursor moved */
+	lastx = LAST_COORD_INIT;
+	lasty = LAST_COORD_INIT;
+}
+
+static void sigwinch_handler(int xxx)
+{
+	(void) xxx;
+	const int zzz = 1;
+	write(winch_fds[1], &zzz, sizeof(int));
+}
+
+static void update_size(void)
+{
+	update_term_size();
+	cellbuf_resize(&back_buffer, termw, termh);
+	cellbuf_resize(&front_buffer, termw, termh);
+	cellbuf_clear(&front_buffer);
+	send_clear();
+}
+
+static int read_up_to(int n) {
+	assert(n > 0);
+	const int prevlen = input_buffer.len;
+	bytebuffer_resize(&input_buffer, prevlen + n);
+
+	int read_n = 0;
+	while (read_n <= n) {
+		ssize_t r = 0;
+		if (read_n < n) {
+			r = read(inout, input_buffer.buf + prevlen + read_n, n - read_n);
+		}
+#ifdef __CYGWIN__
+		// While linux man for tty says when VMIN == 0 && VTIME == 0, read
+		// should return 0 when there is nothing to read, cygwin's read returns
+		// -1. Not sure why and if it's correct to ignore it, but let's pretend
+		// it's zero.
+		if (r < 0) r = 0;
+#endif
+		if (r < 0) {
+			// EAGAIN / EWOULDBLOCK shouldn't occur here
+			assert(errno != EAGAIN && errno != EWOULDBLOCK);
+			return -1;
+		} else if (r > 0) {
+			read_n += r;
+		} else {
+			bytebuffer_resize(&input_buffer, prevlen + read_n);
+			return read_n;
+		}
+	}
+	assert(!"unreachable");
+	return 0;
+}
+
+static int wait_fill_event(struct tb_event *event, struct timeval *timeout)
+{
+	// ;-)
+#define ENOUGH_DATA_FOR_PARSING 64
+	fd_set events;
+	memset(event, 0, sizeof(struct tb_event));
+
+	// try to extract event from input buffer, return on success
+	event->type = TB_EVENT_KEY;
+	if (extract_event(event, &input_buffer, inputmode))
+		return event->type;
+
+	// it looks like input buffer is incomplete, let's try the short path,
+	// but first make sure there is enough space
+	int n = read_up_to(ENOUGH_DATA_FOR_PARSING);
+	if (n < 0)
+		return -1;
+	if (n > 0 && extract_event(event, &input_buffer, inputmode))
+		return event->type;
+
+	// n == 0, or not enough data, let's go to select
+	while (1) {
+		FD_ZERO(&events);
+		FD_SET(inout, &events);
+		FD_SET(winch_fds[0], &events);
+		int maxfd = (winch_fds[0] > inout) ? winch_fds[0] : inout;
+		int result = select(maxfd+1, &events, 0, 0, timeout);
+		if (!result)
+			return 0;
+
+		if (FD_ISSET(inout, &events)) {
+			event->type = TB_EVENT_KEY;
+			n = read_up_to(ENOUGH_DATA_FOR_PARSING);
+			if (n < 0)
+				return -1;
+
+			if (n == 0)
+				continue;
+
+			if (extract_event(event, &input_buffer, inputmode))
+				return event->type;
+		}
+		if (FD_ISSET(winch_fds[0], &events)) {
+			event->type = TB_EVENT_RESIZE;
+			int zzz = 0;
+			read(winch_fds[0], &zzz, sizeof(int));
+			buffer_size_change_request = 1;
+			get_term_size(&event->w, &event->h);
+			return TB_EVENT_RESIZE;
+		}
+	}
+}
diff --git a/cpp/termbox/termbox.h b/cpp/termbox/termbox.h
new file mode 100644
index 00000000..85a8cf12
--- /dev/null
+++ b/cpp/termbox/termbox.h
@@ -0,0 +1,303 @@
+#pragma once
+
+#include <stdint.h>
+
+/* for shared objects */
+#if __GNUC__ >= 4
+ #define SO_IMPORT __attribute__((visibility("default")))
+#else
+ #define SO_IMPORT
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Key constants. See also struct tb_event's key field.
+ *
+ * These are a safe subset of terminfo keys, which exist on all popular
+ * terminals. Termbox uses only them to stay truly portable.
+ */
+#define TB_KEY_F1               (0xFFFF-0)
+#define TB_KEY_F2               (0xFFFF-1)
+#define TB_KEY_F3               (0xFFFF-2)
+#define TB_KEY_F4               (0xFFFF-3)
+#define TB_KEY_F5               (0xFFFF-4)
+#define TB_KEY_F6               (0xFFFF-5)
+#define TB_KEY_F7               (0xFFFF-6)
+#define TB_KEY_F8               (0xFFFF-7)
+#define TB_KEY_F9               (0xFFFF-8)
+#define TB_KEY_F10              (0xFFFF-9)
+#define TB_KEY_F11              (0xFFFF-10)
+#define TB_KEY_F12              (0xFFFF-11)
+#define TB_KEY_INSERT           (0xFFFF-12)
+#define TB_KEY_DELETE           (0xFFFF-13)
+#define TB_KEY_HOME             (0xFFFF-14)
+#define TB_KEY_END              (0xFFFF-15)
+#define TB_KEY_PGUP             (0xFFFF-16)
+#define TB_KEY_PGDN             (0xFFFF-17)
+#define TB_KEY_ARROW_UP         (0xFFFF-18)
+#define TB_KEY_ARROW_DOWN       (0xFFFF-19)
+#define TB_KEY_ARROW_LEFT       (0xFFFF-20)
+#define TB_KEY_ARROW_RIGHT      (0xFFFF-21)
+#define TB_KEY_MOUSE_LEFT       (0xFFFF-22)
+#define TB_KEY_MOUSE_RIGHT      (0xFFFF-23)
+#define TB_KEY_MOUSE_MIDDLE     (0xFFFF-24)
+#define TB_KEY_MOUSE_RELEASE    (0xFFFF-25)
+#define TB_KEY_MOUSE_WHEEL_UP   (0xFFFF-26)
+#define TB_KEY_MOUSE_WHEEL_DOWN (0xFFFF-27)
+
+/* These are all ASCII code points below SPACE character and a BACKSPACE key. */
+#define TB_KEY_CTRL_TILDE       0x00
+#define TB_KEY_CTRL_2           0x00 /* clash with 'CTRL_TILDE' */
+#define TB_KEY_CTRL_A           0x01
+#define TB_KEY_CTRL_B           0x02
+#define TB_KEY_CTRL_C           0x03
+#define TB_KEY_CTRL_D           0x04
+#define TB_KEY_CTRL_E           0x05
+#define TB_KEY_CTRL_F           0x06
+#define TB_KEY_CTRL_G           0x07
+#define TB_KEY_BACKSPACE        0x08
+#define TB_KEY_CTRL_H           0x08 /* clash with 'CTRL_BACKSPACE' */
+#define TB_KEY_TAB              0x09
+#define TB_KEY_CTRL_I           0x09 /* clash with 'TAB' */
+#define TB_KEY_CTRL_J           0x0A
+#define TB_KEY_CTRL_K           0x0B
+#define TB_KEY_CTRL_L           0x0C
+#define TB_KEY_ENTER            0x0D
+#define TB_KEY_CTRL_M           0x0D /* clash with 'ENTER' */
+#define TB_KEY_CTRL_N           0x0E
+#define TB_KEY_CTRL_O           0x0F
+#define TB_KEY_CTRL_P           0x10
+#define TB_KEY_CTRL_Q           0x11
+#define TB_KEY_CTRL_R           0x12
+#define TB_KEY_CTRL_S           0x13
+#define TB_KEY_CTRL_T           0x14
+#define TB_KEY_CTRL_U           0x15
+#define TB_KEY_CTRL_V           0x16
+#define TB_KEY_CTRL_W           0x17
+#define TB_KEY_CTRL_X           0x18
+#define TB_KEY_CTRL_Y           0x19
+#define TB_KEY_CTRL_Z           0x1A
+#define TB_KEY_ESC              0x1B
+#define TB_KEY_CTRL_LSQ_BRACKET 0x1B /* clash with 'ESC' */
+#define TB_KEY_CTRL_3           0x1B /* clash with 'ESC' */
+#define TB_KEY_CTRL_4           0x1C
+#define TB_KEY_CTRL_BACKSLASH   0x1C /* clash with 'CTRL_4' */
+#define TB_KEY_CTRL_5           0x1D
+#define TB_KEY_CTRL_RSQ_BRACKET 0x1D /* clash with 'CTRL_5' */
+#define TB_KEY_CTRL_6           0x1E
+#define TB_KEY_CTRL_7           0x1F
+#define TB_KEY_CTRL_SLASH       0x1F /* clash with 'CTRL_7' */
+#define TB_KEY_CTRL_UNDERSCORE  0x1F /* clash with 'CTRL_7' */
+#define TB_KEY_SPACE            0x20
+#define TB_KEY_BACKSPACE2       0x7F
+#define TB_KEY_CTRL_8           0x7F /* clash with 'DELETE' */
+
+/* These are non-existing ones.
+ *
+ * #define TB_KEY_CTRL_1 clash with '1'
+ * #define TB_KEY_CTRL_9 clash with '9'
+ * #define TB_KEY_CTRL_0 clash with '0'
+ */
+
+/* Currently there is only one modifier. See also struct tb_event's mod
+ * field.
+ */
+#define TB_MOD_ALT 0x01
+
+/* Colors (see struct tb_cell's fg and bg fields). */
+#define TB_DEFAULT 0x00
+#define TB_BLACK   0x01
+#define TB_RED     0x02
+#define TB_GREEN   0x03
+#define TB_YELLOW  0x04
+#define TB_BLUE    0x05
+#define TB_MAGENTA 0x06
+#define TB_CYAN    0x07
+#define TB_WHITE   0x08
+
+/* Attributes, it is possible to use multiple attributes by combining them
+ * using bitwise OR ('|'). Although, colors cannot be combined. But you can
+ * combine attributes and a single color. See also struct tb_cell's fg and bg
+ * fields.
+ */
+#define TB_BOLD      0x0100
+#define TB_UNDERLINE 0x0200
+#define TB_REVERSE   0x0400
+
+/* A cell, single conceptual entity on the terminal screen. The terminal screen
+ * is basically a 2d array of cells. It has the following fields:
+ *  - 'ch' is a unicode character
+ *  - 'fg' foreground color and attributes
+ *  - 'bg' background color and attributes
+ */
+struct tb_cell {
+	uint32_t ch;
+	uint16_t fg;
+	uint16_t bg;
+};
+
+#define TB_EVENT_KEY    1
+#define TB_EVENT_RESIZE 2
+#define TB_EVENT_MOUSE  3
+
+/* This struct represents a termbox event. The 'mod', 'key' and 'ch' fields are
+ * valid if 'type' is TB_EVENT_KEY. The 'w' and 'h' fields are valid if 'type'
+ * is TB_EVENT_RESIZE. The 'x' and 'y' fields are valid if 'type' is
+ * TB_EVENT_MOUSE.
+ */
+struct tb_event {
+	uint8_t type;
+	uint8_t mod;
+	uint16_t key;
+	uint32_t ch;
+	int32_t w;
+	int32_t h;
+	int32_t x;
+	int32_t y;
+};
+
+/* Error codes returned by tb_init(). All of them are self-explanatory, except
+ * the pipe trap error. Termbox uses unix pipes in order to deliver a message
+ * from a signal handler (SIGWINCH) to the main event reading loop. Honestly in
+ * most cases you should just check the returned code as < 0.
+ */
+#define TB_EUNSUPPORTED_TERMINAL -1
+#define TB_EFAILED_TO_OPEN_TTY   -2
+#define TB_EPIPE_TRAP_ERROR      -3
+
+/* Initializes the termbox library. This function should be called before any
+ * other functions. After successful initialization, the library must be
+ * finalized using the tb_shutdown() function.
+ */
+SO_IMPORT int tb_init(void);
+SO_IMPORT void tb_shutdown(void);
+
+/* Returns the size of the internal back buffer (which is the same as
+ * terminal's window size in characters). The internal buffer can be resized
+ * after tb_clear() or tb_present() function calls. Both dimensions have an
+ * unspecified negative value when called before tb_init() or after
+ * tb_shutdown().
+ */
+SO_IMPORT int tb_width(void);
+SO_IMPORT int tb_height(void);
+
+/* Clears the internal back buffer using TB_DEFAULT color or the
+ * color/attributes set by tb_set_clear_attributes() function.
+ */
+SO_IMPORT void tb_clear(void);
+SO_IMPORT void tb_set_clear_attributes(uint16_t fg, uint16_t bg);
+
+/* Synchronizes the internal back buffer with the terminal. */
+SO_IMPORT void tb_present(void);
+
+#define TB_HIDE_CURSOR -1
+
+/* Sets the position of the cursor. Upper-left character is (0, 0). If you pass
+ * TB_HIDE_CURSOR as both coordinates, then the cursor will be hidden. Cursor
+ * is hidden by default.
+ */
+SO_IMPORT void tb_set_cursor(int cx, int cy);
+
+/* Changes cell's parameters in the internal back buffer at the specified
+ * position.
+ */
+SO_IMPORT void tb_put_cell(int x, int y, const struct tb_cell *cell);
+SO_IMPORT void tb_change_cell(int x, int y, uint32_t ch, uint16_t fg, uint16_t bg);
+
+/* Copies the buffer from 'cells' at the specified position, assuming the
+ * buffer is a two-dimensional array of size ('w' x 'h'), represented as a
+ * one-dimensional buffer containing lines of cells starting from the top.
+ *
+ * (DEPRECATED: use tb_cell_buffer() instead and copy memory on your own)
+ */
+SO_IMPORT void tb_blit(int x, int y, int w, int h, const struct tb_cell *cells);
+
+/* Returns a pointer to internal cell back buffer. You can get its dimensions
+ * using tb_width() and tb_height() functions. The pointer stays valid as long
+ * as no tb_clear() and tb_present() calls are made. The buffer is
+ * one-dimensional buffer containing lines of cells starting from the top.
+ */
+SO_IMPORT struct tb_cell *tb_cell_buffer();
+
+#define TB_INPUT_CURRENT 0 /* 000 */
+#define TB_INPUT_ESC     1 /* 001 */
+#define TB_INPUT_ALT     2 /* 010 */
+#define TB_INPUT_MOUSE   4 /* 100 */
+
+/* Sets the termbox input mode. Termbox has two input modes:
+ * 1. Esc input mode.
+ *    When ESC sequence is in the buffer and it doesn't match any known
+ *    ESC sequence => ESC means TB_KEY_ESC.
+ * 2. Alt input mode.
+ *    When ESC sequence is in the buffer and it doesn't match any known
+ *    sequence => ESC enables TB_MOD_ALT modifier for the next keyboard event.
+ *
+ * If 'mode' is TB_INPUT_CURRENT, it returns the current input mode.
+ */
+SO_IMPORT int tb_select_input_mode(int mode);
+
+#define TB_OUTPUT_CURRENT   0
+#define TB_OUTPUT_NORMAL    1
+#define TB_OUTPUT_256       2
+#define TB_OUTPUT_216       3
+#define TB_OUTPUT_GRAYSCALE 4
+
+/* Sets the termbox output mode. Termbox has three output options:
+ * 1. TB_OUTPUT_NORMAL     => [1..8]
+ *    This mode provides 8 different colors:
+ *      black, red, green, yellow, blue, magenta, cyan, white
+ *    Shortcut: TB_BLACK, TB_RED, ...
+ *    Attributes: TB_BOLD, TB_UNDERLINE, TB_REVERSE
+ *
+ *    Example usage:
+ *        tb_change_cell(x, y, '@', TB_BLACK | TB_BOLD, TB_RED);
+ *
+ * 2. TB_OUTPUT_256        => [0..256]
+ *    In this mode you can leverage the 256 terminal mode:
+ *    0x00 - 0x07: the 8 colors as in TB_OUTPUT_NORMAL
+ *    0x08 - 0x0f: TB_* | TB_BOLD
+ *    0x10 - 0xe7: 216 different colors
+ *    0xe8 - 0xff: 24 different shades of grey
+ *
+ *    Example usage:
+ *        tb_change_cell(x, y, '@', 184, 240);
+ *        tb_change_cell(x, y, '@', 0xb8, 0xf0);
+ *
+ * 2. TB_OUTPUT_216        => [0..216]
+ *    This mode supports the 3rd range of the 256 mode only.
+ *    But you don't need to provide an offset.
+ *
+ * 3. TB_OUTPUT_GRAYSCALE  => [0..23]
+ *    This mode supports the 4th range of the 256 mode only.
+ *    But you dont need to provide an offset.
+ *
+ * Execute build/src/demo/output to see its impact on your terminal.
+ *
+ * If 'mode' is TB_OUTPUT_CURRENT, it returns the current output mode.
+ */
+SO_IMPORT int tb_select_output_mode(int mode);
+
+/* Wait for an event up to 'timeout' milliseconds and fill the 'event'
+ * structure with it, when the event is available. Returns the type of the
+ * event (one of TB_EVENT_* constants) or -1 if there was an error or 0 in case
+ * there were no event during 'timeout' period.
+ */
+SO_IMPORT int tb_peek_event(struct tb_event *event, int timeout);
+
+/* Wait for an event forever and fill the 'event' structure with it, when the
+ * event is available. Returns the type of the event (one of TB_EVENT_*
+ * constants) or -1 if there was an error.
+ */
+SO_IMPORT int tb_poll_event(struct tb_event *event);
+
+/* Utility utf8 functions. */
+#define TB_EOF -1
+SO_IMPORT int tb_utf8_char_length(char c);
+SO_IMPORT int tb_utf8_char_to_unicode(uint32_t *out, const char *c);
+SO_IMPORT int tb_utf8_unicode_to_char(char *out, uint32_t c);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/cpp/termbox/utf8.c b/cpp/termbox/utf8.c
new file mode 100644
index 00000000..0c37dae5
--- /dev/null
+++ b/cpp/termbox/utf8.c
@@ -0,0 +1,79 @@
+#include "termbox.h"
+
+static const unsigned char utf8_length[256] = {
+  1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,1,1
+};
+
+static const unsigned char utf8_mask[6] = {
+	0x7F,
+	0x1F,
+	0x0F,
+	0x07,
+	0x03,
+	0x01
+};
+
+int tb_utf8_char_length(char c)
+{
+	return utf8_length[(unsigned char)c];
+}
+
+int tb_utf8_char_to_unicode(uint32_t *out, const char *c)
+{
+	if (*c == 0)
+		return TB_EOF;
+
+	int i;
+	unsigned char len = tb_utf8_char_length(*c);
+	unsigned char mask = utf8_mask[len-1];
+	uint32_t result = c[0] & mask;
+	for (i = 1; i < len; ++i) {
+		result <<= 6;
+		result |= c[i] & 0x3f;
+	}
+
+	*out = result;
+	return (int)len;
+}
+
+int tb_utf8_unicode_to_char(char *out, uint32_t c)
+{
+	int len = 0;
+	int first;
+	int i;
+
+	if (c < 0x80) {
+		first = 0;
+		len = 1;
+	} else if (c < 0x800) {
+		first = 0xc0;
+		len = 2;
+	} else if (c < 0x10000) {
+		first = 0xe0;
+		len = 3;
+	} else if (c < 0x200000) {
+		first = 0xf0;
+		len = 4;
+	} else if (c < 0x4000000) {
+		first = 0xf8;
+		len = 5;
+	} else {
+		first = 0xfc;
+		len = 6;
+	}
+
+	for (i = len - 1; i > 0; --i) {
+		out[i] = (c & 0x3f) | 0x80;
+		c >>= 6;
+	}
+	out[0] = c | first;
+
+	return len;
+}