packages feed

termbox (empty) → 0.1.0

raw patch · 12 files changed

+2964/−0 lines, 12 filesdep +arraydep +basedep +termboxsetup-changed

Dependencies added: array, base, termbox

Files

+ .travis.yml view
@@ -0,0 +1,94 @@+# This Travis job script has been generated by a script via+#+#   runghc make_travis_yml_2.hs 'cabal.project'+#+# For more information, see https://github.com/hvr/multi-ghc-travis+#+language: c+sudo: false++git:+  submodules: false  # whether to recursively clone submodules++cache:+  directories:+    - $HOME/.cabal/packages+    - $HOME/.cabal/store++before_cache:+  - rm -fv $HOME/.cabal/packages/hackage.haskell.org/build-reports.log+  # remove files that are regenerated by 'cabal update'+  - rm -fv $HOME/.cabal/packages/hackage.haskell.org/00-index.*+  - rm -fv $HOME/.cabal/packages/hackage.haskell.org/*.json+  - rm -fv $HOME/.cabal/packages/hackage.haskell.org/01-index.cache+  - rm -fv $HOME/.cabal/packages/hackage.haskell.org/01-index.tar+  - rm -fv $HOME/.cabal/packages/hackage.haskell.org/01-index.tar.idx++  - rm -rfv $HOME/.cabal/packages/head.hackage++matrix:+  include:+    - compiler: "ghc-8.0.2"+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-head,ghc-8.0.2], sources: [hvr-ghc]}}+    - compiler: "ghc-8.2.2"+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-head,ghc-8.2.2], sources: [hvr-ghc]}}+    - compiler: "ghc-8.4.3"+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-head,ghc-8.4.3], sources: [hvr-ghc]}}++before_install:+  - HC=${CC}+  - HCPKG=${HC/ghc/ghc-pkg}+  - unset CC+  - ROOTDIR=$(pwd)+  - mkdir -p $HOME/.local/bin+  - "PATH=/opt/ghc/bin:/opt/ghc-ppa-tools/bin:$HOME/local/bin:$PATH"+  - HCNUMVER=$(( $(${HC} --numeric-version|sed -E 's/([0-9]+)\.([0-9]+)\.([0-9]+).*/\1 * 10000 + \2 * 100 + \3/') ))+  - echo $HCNUMVER++install:+  - cabal --version+  - echo "$(${HC} --version) [$(${HC} --print-project-git-commit-id 2> /dev/null || echo '?')]"+  - BENCH=${BENCH---enable-benchmarks}+  - TEST=${TEST---enable-tests}+  - HADDOCK=${HADDOCK-true}+  - INSTALLED=${INSTALLED-true}+  - GHCHEAD=${GHCHEAD-false}+  - travis_retry cabal update -v+  - "sed -i.bak 's/^jobs:/-- jobs:/' ${HOME}/.cabal/config"+  - rm -fv cabal.project cabal.project.local+  - grep -Ev -- '^\s*--' ${HOME}/.cabal/config | grep -Ev '^\s*$'+  - "printf 'packages: \".\"\\n' > cabal.project"+  - cat cabal.project+  - if [ -f "./configure.ac" ]; then+      (cd "." && autoreconf -i);+    fi+  - rm -f cabal.project.freeze+  - cabal new-build -w ${HC} ${TEST} ${BENCH} --project-file="cabal.project" --dep -j2+  - cabal new-build -w ${HC} --disable-tests --disable-benchmarks --project-file="cabal.project" --dep -j2+  - rm -rf .ghc.environment.* "."/dist+  - DISTDIR=$(mktemp -d /tmp/dist-test.XXXX)++# Here starts the actual work to be performed for the package under test;+# any command which exits with a non-zero exit code causes the build to fail.+script:+  # test that source-distributions can be generated+  - (cd "." && cabal sdist)+  - mv "."/dist/termbox-*.tar.gz ${DISTDIR}/+  - cd ${DISTDIR} || false+  - find . -maxdepth 1 -name '*.tar.gz' -exec tar -xvf '{}' \;+  - "printf 'packages: termbox-*/*.cabal\\n' > cabal.project"+  - cat cabal.project+  # this builds all libraries and executables (without tests/benchmarks)+  - cabal new-build -w ${HC} --disable-tests --disable-benchmarks all++  # Build with installed constraints for packages in global-db+  - if $INSTALLED; then echo cabal new-build -w ${HC} --disable-tests --disable-benchmarks $(${HCPKG} list --global --simple-output --names-only | sed 's/\([a-zA-Z0-9-]\{1,\}\) */--constraint="\1 installed" /g') all | sh; else echo "Not building with installed constraints"; fi++  # build & run tests, build benchmarks+  - cabal new-build -w ${HC} ${TEST} ${BENCH} all++  # cabal check+  - (cd termbox-* && cabal check)++# REGENDATA ["cabal.project"]+# EOF
+ CHANGELOG.md view
@@ -0,0 +1,11 @@+# Changelog++All notable changes to this project will be documented in this file.++The format is based on [Keep a Changelog](http://keepachangelog.com/)+and this project adheres to the [Haskell Package Versioning Policy](https://pvp.haskell.org/).++## [0.1.0] - 2018-07-18++### Added+- Initial release
+ LICENSE view
@@ -0,0 +1,11 @@+Copyright 2018 Mitchell Rosen++Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:++1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.++2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.++3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ cbits/LICENSE view
@@ -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.
+ cbits/termbox.c view
@@ -0,0 +1,1284 @@+#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/select.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"++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 = realloc(b->buf, cap);+	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;+}++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 ENTER_MOUSE_SEQ "\x1b[?1000h\x1b[?1002h\x1b[?1015h\x1b[?1006h"+#define EXIT_MOUSE_SEQ "\x1b[?1006l\x1b[?1015l\x1b[?1002l\x1b[?1000l"++#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>", ENTER_MOUSE_SEQ, EXIT_MOUSE_SEQ,+};++// 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>", ENTER_MOUSE_SEQ, EXIT_MOUSE_SEQ,+};++// 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>", ENTER_MOUSE_SEQ, EXIT_MOUSE_SEQ,+};++// 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>", ENTER_MOUSE_SEQ, EXIT_MOUSE_SEQ,+};++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_ALT_MAGIC 542+#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;++	const int number_sec_len = header[0] == TI_ALT_MAGIC ? 4 : 2;++	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] +	number_sec_len * 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] = ENTER_MOUSE_SEQ;+	funcs[T_FUNCS_NUM-1] = EXIT_MOUSE_SEQ;++	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);+	}+}++// 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;+}++static int parse_mouse_event(struct tb_event *event, const char *buf, int len) {+	if (len >= 6 && starts_with(buf, len, "\033[M")) {+		// X10 mouse encoding, the simplest one+		// \033 [ M Cb Cx Cy+		int b = buf[3] - 32;+		switch (b & 3) {+		case 0:+			if ((b & 64) != 0)+				event->key = TB_KEY_MOUSE_WHEEL_UP;+			else+				event->key = TB_KEY_MOUSE_LEFT;+			break;+		case 1:+			if ((b & 64) != 0)+				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+		if ((b & 32) != 0)+			event->mod |= TB_MOD_MOTION;++		// the coord is 1,1 for upper left+		event->x = (uint8_t)buf[4] - 1 - 32;+		event->y = (uint8_t)buf[5] - 1 - 32;++		return 6;+	} else if (starts_with(buf, len, "\033[<") || starts_with(buf, len, "\033[")) {+		// xterm 1006 extended mode or urxvt 1015 extended mode+		// xterm: \033 [ < Cb ; Cx ; Cy (M or m)+		// urxvt: \033 [ Cb ; Cx ; Cy M+		int i, mi = -1, starti = -1;+		int isM, isU, s1 = -1, s2 = -1;+		int n1 = 0, n2 = 0, n3 = 0;++		for (i = 0; i < len; i++) {+			// We search the first (s1) and the last (s2) ';'+			if (buf[i] == ';') {+				if (s1 == -1)+					s1 = i;+				s2 = i;+			}++			// We search for the first 'm' or 'M'+			if ((buf[i] == 'm' || buf[i] == 'M') && mi == -1) {+				mi = i;+				break;+			}+		}+		if (mi == -1)+			return 0;++		// whether it's a capital M or not+		isM = (buf[mi] == 'M');++		if (buf[2] == '<') {+			isU = 0;+			starti = 3;+		} else {+			isU = 1;+			starti = 2;+		}++		if (s1 == -1 || s2 == -1 || s1 == s2)+			return 0;++		n1 = strtoul(&buf[starti], NULL, 10);+		n2 = strtoul(&buf[s1 + 1], NULL, 10);+		n3 = strtoul(&buf[s2 + 1], NULL, 10);++		if (isU)+			n1 -= 32;++		switch (n1 & 3) {+		case 0:+			if ((n1&64) != 0) {+				event->key = TB_KEY_MOUSE_WHEEL_UP;+			} else {+				event->key = TB_KEY_MOUSE_LEFT;+			}+			break;+		case 1:+			if ((n1&64) != 0) {+				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 mi + 1;+		}++		if (!isM) {+			// on xterm mouse release is signaled by lowercase m+			event->key = TB_KEY_MOUSE_RELEASE;+		}++		event->type = TB_EVENT_MOUSE; // TB_EVENT_KEY by default+		if ((n1&32) != 0)+			event->mod |= TB_MOD_MOTION;++		event->x = (uint8_t)n2 - 1;+		event->y = (uint8_t)n3 - 1;++		return mi + 1;+	}++	return 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)+{+	int mouse_parsed = parse_mouse_event(event, buf, len);++	if (mouse_parsed != 0)+		return mouse_parsed;++	// 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;+			} else 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;+}++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(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_fd(int inout_)+{+	inout = inout_;+	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;+}++int tb_init_file(const char* name){+	return tb_init_fd(open(name, O_RDWR));+}++int tb_init(void)+{+	return tb_init_file("/dev/tty");+}++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(void)+{+	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) {+		if ((mode & (TB_INPUT_ESC | TB_INPUT_ALT)) == 0)+			mode |= TB_INPUT_ESC;++		/* technically termbox can handle that, but let's be nice and show here+		   what mode is actually used */+		if ((mode & (TB_INPUT_ESC | TB_INPUT_ALT)) == (TB_INPUT_ESC | TB_INPUT_ALT))+			mode &= ~TB_INPUT_ALT;++		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");+}++static void write_sgr(uint16_t fg, uint16_t bg) {+	char buf[32];++	if (fg == TB_DEFAULT && bg == TB_DEFAULT)+		return;++	switch (outputmode) {+	case TB_OUTPUT_256:+	case TB_OUTPUT_216:+	case TB_OUTPUT_GRAYSCALE:+		WRITE_LITERAL("\033[");+		if (fg != TB_DEFAULT) {+			WRITE_LITERAL("38;5;");+			WRITE_INT(fg);+			if (bg != TB_DEFAULT) {+				WRITE_LITERAL(";");+			}+		}+		if (bg != TB_DEFAULT) {+			WRITE_LITERAL("48;5;");+			WRITE_INT(bg);+		}+		WRITE_LITERAL("m");+		break;+	case TB_OUTPUT_NORMAL:+	default:+		WRITE_LITERAL("\033[");+		if (fg != TB_DEFAULT) {+			WRITE_LITERAL("3");+			WRITE_INT(fg - 1);+			if (bg != TB_DEFAULT) {+				WRITE_LITERAL(";");+			}+		}+		if (bg != TB_DEFAULT) {+			WRITE_LITERAL("4");+			WRITE_INT(bg - 1);+		}+		WRITE_LITERAL("m");+		break;+	}+}++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]);++		write_sgr(fgcol, 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);+	if (x-1 != lastx || y != lasty)+		write_cursor(x, y);+	lastx = x; lasty = y;+	if(!c) buf[0] = ' '; // replace 0 with whitespace+	bytebuffer_append(&output_buffer, buf, bw);+}++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;+		}+	}+}
+ cbits/utf8.c view
@@ -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;+}
+ examples/Colors.hs view
@@ -0,0 +1,108 @@+-- | Something like https://github.com/nsf/termbox/blob/8b72969ff4bba120d8b8e4a29bae07102ed71055/src/demo/output.c++{-# language LambdaCase #-}++import Control.Monad+import Data.Foldable++import qualified Termbox as Tb++main :: IO ()+main = do+  Tb.main $ do+    clear++    do+      let+        rectangles :: [(Int, Int, Int, Int)]+        rectangles = do+          y0 <- [0, 4 ..]+          x0 <- [0, 8 .. 24]+          pure (x0, y0, x0+7, y0+3)++      let+        colors :: [(String, Tb.Attr, Tb.Attr)]+        colors =+          [ ("black", Tb.black, Tb.white)+          , ("red", Tb.red, Tb.black)+          , ("green", Tb.green, Tb.black)+          , ("yellow", Tb.yellow, Tb.black)+          , ("blue", Tb.blue, Tb.black)+          , ("magenta", Tb.magenta, Tb.black)+          , ("cyan", Tb.cyan, Tb.black)+          , ("white", Tb.white, Tb.black)+          ]++      zipWithM_+        (\(x0, y0, x1, y1) (name, bg, fg) -> do+          rectangle x0 y0 x1 y1 (Tb.Cell ' ' mempty bg)+          string x0 y0 fg bg name)+        rectangles+        colors++    Tb.flush+    _ <- Tb.poll++    clear+    Tb.setOutputMode Tb.OutputModeGrayscale++    do+      let+        rectangles :: [(Int, Int, Int, Int)]+        rectangles = do+          y0 <- [0, 4 ..]+          x0 <- [0, 8 .. 40]+          pure (x0, y0, x0+7, y0+3)++      zipWithM_+        (\(x0, y0, x1, y1) n -> do+          rectangle x0 y0 x1 y1 (Tb.Cell ' ' mempty (fromInteger n))+          string x0 y0 12 (fromInteger n) (show n))+        rectangles+        [1..23]++    Tb.flush+    _ <- Tb.poll++    clear+    Tb.setOutputMode Tb.OutputMode216++    do+      let+        rectangles :: [(Int, Int, Int, Int)]+        rectangles = do+          y0 <- [0, 2..]+          x0 <- [0, 4 .. 40]+          pure (x0, y0, x0+3, y0+1)++      zipWithM_+        (\(x0, y0, x1, y1) n -> do+          rectangle x0 y0 x1 y1 (Tb.Cell ' ' mempty (fromInteger n))+          string x0 y0 2 (fromInteger n) (show n))+        rectangles+        [1..216]++    Tb.flush+    _ <- Tb.poll++    pure ()++clear :: IO ()+clear = do+  mode <- Tb.getOutputMode+  Tb.setOutputMode Tb.OutputModeNormal+  Tb.clear mempty mempty+  Tb.flush+  Tb.setOutputMode mode++string :: Int -> Int -> Tb.Attr -> Tb.Attr -> [Char] -> IO ()+string x0 y fg bg cs =+  zipWithM_+    (\x c -> Tb.set x y (Tb.Cell c fg bg))+    [x0..]+    cs++rectangle :: Int -> Int -> Int -> Int -> Tb.Cell -> IO ()+rectangle x0 y0 x1 y1 c =+  for_ ((,) <$> [x0..x1] <*> [y0..y1]) $ \(x, y) ->+    Tb.set x y c
+ include/termbox.h view
@@ -0,0 +1,320 @@+#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 'BACKSPACE2' */++/* 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'+ */++/*+ * Alt modifier constant, see tb_event.mod field and tb_select_input_mode function.+ * Mouse-motion modifier+ */+#define TB_MOD_ALT    0x01+#define TB_MOD_MOTION 0x02++/* 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++/* An event, single interaction from the user. The 'mod' 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. The 'key' field is valid if 'type' is either TB_EVENT_KEY+ * or TB_EVENT_MOUSE. The fields 'key' and 'ch' are mutually exclusive; only+ * one of them can be non-zero at a time.+ */+struct tb_event {+	uint8_t type;+	uint8_t mod; /* modifiers to either 'key' or 'ch' below */+	uint16_t key; /* one of the TB_KEY_* constants */+	uint32_t ch; /* unicode character */+	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. Function tb_init is same as tb_init_file("/dev/tty").+ * After successful initialization, the library must be+ * finalized using the tb_shutdown() function.+ */+SO_IMPORT int tb_init(void);+SO_IMPORT int tb_init_file(const char* name);+SO_IMPORT int tb_init_fd(int inout);+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(void);++#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.+ *+ * You can also apply TB_INPUT_MOUSE via bitwise OR operation to either of the+ * modes (e.g. TB_INPUT_ESC | TB_INPUT_MOUSE). If none of the main two modes+ * were set, but the mouse mode was, TB_INPUT_ESC mode is used. If for some+ * reason you've decided to use (TB_INPUT_ESC | TB_INPUT_ALT) combination, it+ * will behave as if only TB_INPUT_ESC was selected.+ *+ * If 'mode' is TB_INPUT_CURRENT, it returns the current input mode.+ *+ * Default termbox input mode is TB_INPUT_ESC.+ */+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);+ *+ * 3. 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.+ *+ * 4. 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.+ *+ * Default termbox output mode is TB_OUTPUT_NORMAL.+ */+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
+ src/Termbox.hs view
@@ -0,0 +1,645 @@+{-# language InstanceSigs        #-}+{-# language LambdaCase          #-}+{-# language RankNTypes          #-}+{-# language RecordWildCards     #-}+{-# language ScopedTypeVariables #-}+{-# language TypeFamilies        #-}+{-# language UnicodeSyntax       #-}++module Termbox+  ( -- $intro++    -- * Initialization+    main+  , InitError(..)+    -- * Terminal contents+  , Cell(..)+  , set+  , buffer+  , clear+  , flush+    -- * Terminal size+  , size+    -- * Cursor manipulation+  , setCursor+  , hideCursor+    -- * Event handling+  , Event(..)+  , Key(..)+  , Mouse(..)+  , poll+  , PollError(..)+    -- * Attributes+  , Attr+  , black+  , red+  , green+  , yellow+  , blue+  , magenta+  , cyan+  , white+  , bold+  , underline+  , reverse+    -- * Terminal modes+  , InputMode(..)+  , MouseMode(..)+  , getInputMode+  , setInputMode+  , OutputMode(..)+  , getOutputMode+  , setOutputMode+  ) where++import Prelude hiding (mod, reverse)++import qualified Termbox.Internal as Tb++import Control.Exception+import Control.Monad (join)+import Data.Array.Storable+import Data.Bits ((.|.), (.&.))+import Data.Functor (void)+import Data.Semigroup (Semigroup(..))+import Data.Word+import Foreign (ForeignPtr, Ptr, newForeignPtr_)+import Foreign.Marshal.Alloc (alloca)+import Foreign.Storable++import qualified Data.Array.Storable.Internals as Array++-- $intro+-- This module is intended to be imported qualified.+--+-- @+-- import qualified Termbox+-- @++--------------------------------------------------------------------------------+-- Initialization+--------------------------------------------------------------------------------++-- | Initialization errors that can be thrown by 'main'.+data InitError+  = FailedToOpenTTY+  | PipeTrapError+  | UnsupportedTerminal+  deriving (Show)++instance Exception InitError++-- | Run a @termbox@ program and restore the terminal state afterwards. May+-- throw an 'InitError' exception.+main :: IO a -> IO a+main =+  bracket_+    (Tb.init >>= \case+      Tb.InitOk -> pure ()+      Tb.FailedToOpenTTY -> throwIO FailedToOpenTTY+      Tb.PipeTrapError -> throwIO PipeTrapError+      Tb.UnsupportedTerminal -> throwIO UnsupportedTerminal)+    Tb.shutdown++--------------------------------------------------------------------------------+-- Terminal size+--------------------------------------------------------------------------------++-- | Get the terminal width and height.+size :: (width ~ Int, height ~ Int) => IO (width, height)+size =+  (,) <$> Tb.width <*> Tb.height++--------------------------------------------------------------------------------+-- Cursor+--------------------------------------------------------------------------------++-- | Set the cursor coordinates.+setCursor :: (col ~ Int, row ~ Int) => col -> row -> IO ()+setCursor =+  Tb.setCursor++-- | Hide the cursor.+hideCursor :: IO ()+hideCursor =+  Tb.setCursor Tb._HIDE_CURSOR Tb._HIDE_CURSOR++--------------------------------------------------------------------------------+-- Terminal contents+--------------------------------------------------------------------------------++-- | A 'Cell' contains a character, foreground attribute, and background+-- attribute.+data Cell+  = Cell !Char !Attr !Attr+  deriving (Eq)++instance Show Cell where+  show (Cell ch fg bg) =+    "Cell " ++ show ch ++ " " ++ show (attrToWord fg) ++ " " +++      show (attrToWord bg)++instance Storable Cell where+  sizeOf :: Cell -> Int+  sizeOf _ =+    Tb.sizeofCell++  alignment :: Cell -> Int+  alignment _ =+    Tb.alignofCell++  peek :: Ptr Cell -> IO Cell+  peek ptr =+    Cell+      <$> Tb.getCellCh ptr+      <*> (wordToAttr <$>  Tb.getCellFg ptr)+      <*> (wordToAttr <$> Tb.getCellBg ptr)++  poke :: Ptr Cell -> Cell -> IO ()+  poke ptr (Cell ch fg bg) = do+    Tb.setCellCh ptr ch+    Tb.setCellFg ptr (attrToWord fg)+    Tb.setCellBg ptr (attrToWord bg)++-- | Set the 'Cell' at the given coordinates.+set :: (col ~ Int, row ~ Int) => col -> row -> Cell -> IO ()+set x y (Cell ch fg bg) =+  Tb.changeCell x y ch (attrToWord fg) (attrToWord bg)++-- | Get the terminal's internal back buffer as a two-dimensional array of+-- 'Cell's indexed by their coordinates.+--+-- /Warning/: the data is only valid until the next call to 'clear' or+-- 'flush'.+buffer :: (row ~ Int, col ~ Int) => IO (StorableArray (row, col) Cell)+buffer =+  join+    (mkbuffer+      <$> (tb_cell_buffer >>= newForeignPtr_)+      <*> Tb.width+      <*> Tb.height)+ where+  mkbuffer+    :: ForeignPtr Cell+    -> Int+    -> Int+    -> IO (StorableArray (Int, Int) Cell)+  mkbuffer buff w h =+    Array.unsafeForeignPtrToStorableArray buff ((0, 0), (h-1, w-1))++-- | Clear the back buffer with the given foreground and background attributes.+clear :: (fg ~ Attr, bg ~ Attr) => fg -> bg -> IO ()+clear fg bg = do+  Tb.setClearAttributes (attrToWord fg) (attrToWord bg)+  Tb.clear++-- | Synchronize the internal back buffer with the terminal.+flush :: IO ()+flush =+  Tb.present++--------------------------------------------------------------------------------+-- Terminal mode+--------------------------------------------------------------------------------++-- | The input modes.+--+-- * __Esc__. When ESC sequence is in the buffer and it doesn't match any known+-- sequence, ESC means 'KeyEsc'.+--+-- * __Alt__. When ESC sequence is in the buffer and it doesn't match any known+-- sequence, ESC enables the /alt/ modifier for the next keyboard event.+data InputMode+  = InputModeEsc MouseMode -- ^ Default.+  | InputModeAlt MouseMode+  deriving (Eq, Ord, Show)++-- | The mouse mode.+--+-- * __No__. Don't handle mouse events.+--+-- * __Yes__. Handle mouse events.+data MouseMode+  = MouseModeNo -- ^ Default.+  | MouseModeYes+  deriving (Eq, Ord, Show)++-- | Get the current input mode.+getInputMode :: IO InputMode+getInputMode =+  f <$> Tb.selectInputMode Tb._INPUT_CURRENT+ where+  f :: Int -> InputMode+  f = \case+    1 -> InputModeEsc MouseModeNo+    2 -> InputModeAlt MouseModeNo+    5 -> InputModeEsc MouseModeYes+    6 -> InputModeAlt MouseModeYes+    n -> error ("getInputMode: " ++ show n)++-- | Set the input mode.+setInputMode :: InputMode -> IO ()+setInputMode =+  void . Tb.selectInputMode . f+ where+  f :: InputMode -> Int+  f = \case+    InputModeEsc MouseModeNo -> Tb._INPUT_ESC+    InputModeEsc MouseModeYes -> Tb._INPUT_ESC .|. Tb._INPUT_MOUSE+    InputModeAlt MouseModeNo -> Tb._INPUT_ALT+    InputModeAlt MouseModeYes -> Tb._INPUT_ALT .|. Tb._INPUT_MOUSE++-- | The output modes.+--+-- * __Normal__. Supports colors /0..8/, which includes all named color+-- attributes exported by this library, e.g. 'red'.+--+-- * __Grayscale__. Supports colors /0..23/.+--+-- * __216__. Supports colors /0..216/.+--+-- * __256__. Supports colors /0..255/.+data OutputMode+  = OutputModeNormal -- ^ Default.+  | OutputModeGrayscale+  | OutputMode216+  | OutputMode256+  deriving (Eq, Ord, Show)++-- | Get the current output mode.+getOutputMode :: IO OutputMode+getOutputMode =+  f <$> Tb.selectOutputMode Tb.OutputModeCurrent+ where+  f :: Tb.OutputMode -> OutputMode+  f = \case+    Tb.OutputModeNormal -> OutputModeNormal+    Tb.OutputMode256 -> OutputMode256+    Tb.OutputMode216 -> OutputMode216+    Tb.OutputModeGrayscale -> OutputModeGrayscale+    Tb.OutputModeCurrent -> error "getOutputMode: OutputModeCurrent"++-- | Set the output mode.+setOutputMode :: OutputMode -> IO ()+setOutputMode =+  void . Tb.selectOutputMode . f+ where+  f :: OutputMode -> Tb.OutputMode+  f = \case+    OutputModeNormal -> Tb.OutputModeNormal+    OutputMode256 -> Tb.OutputMode256+    OutputMode216 -> Tb.OutputMode216+    OutputModeGrayscale -> Tb.OutputModeGrayscale++--------------------------------------------------------------------------------+-- Event handling+--------------------------------------------------------------------------------++-- | A input event.+data Event+  = EventKey !Key !Bool -- ^ Key event. The bool indicates the /alt/ modifier.+  | EventResize !Int !Int -- ^ Resize event (width, then height)+  | EventMouse !Mouse !Int !Int -- ^ Mouse event (column, then row)+  deriving (Eq, Show)++-- | A key event.+data Key+  = KeyChar Char+  | KeyArrowDown+  | KeyArrowLeft+  | KeyArrowRight+  | KeyArrowUp+  | KeyBackspace+  | KeyBackspace2+  | KeyCtrl2+  | KeyCtrl3+  | KeyCtrl4+  | KeyCtrl5+  | KeyCtrl6+  | KeyCtrl7+  | KeyCtrl8+  | KeyCtrlA+  | KeyCtrlB+  | KeyCtrlBackslash+  | KeyCtrlC+  | KeyCtrlD+  | KeyCtrlE+  | KeyCtrlF+  | KeyCtrlG+  | KeyCtrlH+  | KeyCtrlI+  | KeyCtrlJ+  | KeyCtrlK+  | KeyCtrlL+  | KeyCtrlLsqBracket+  | KeyCtrlM+  | KeyCtrlN+  | KeyCtrlO+  | KeyCtrlP+  | KeyCtrlQ+  | KeyCtrlR+  | KeyCtrlRsqBracket+  | KeyCtrlS+  | KeyCtrlSlash+  | KeyCtrlT+  | KeyCtrlTilde+  | KeyCtrlU+  | KeyCtrlUnderscore+  | KeyCtrlV+  | KeyCtrlW+  | KeyCtrlX+  | KeyCtrlY+  | KeyCtrlZ+  | KeyDelete+  | KeyEnd+  | KeyEnter+  | KeyEsc+  | KeyF1+  | KeyF10+  | KeyF11+  | KeyF12+  | KeyF2+  | KeyF3+  | KeyF4+  | KeyF5+  | KeyF6+  | KeyF7+  | KeyF8+  | KeyF9+  | KeyHome+  | KeyInsert+  | KeyPageDn+  | KeyPageUp+  | KeySpace+  | KeyTab+  deriving (Eq, Ord, Show)++-- | A mouse event.+data Mouse+  = MouseLeft+  | MouseMiddle+  | MouseRelease+  | MouseRight+  | MouseWheelDown+  | MouseWheelUp+  deriving (Eq, Ord, Show)++-- | Block until an 'Event' arrives.+--+-- /Note/: @termbox v1.1.2@ does not properly handle OS signals that interrupt+-- the underlying @select@ system call, so unfortunately the familiar @Ctrl-C@+-- will not be able to stop a program stuck in 'pollEvent'.+--+-- You can work around this issue by polling in a background thread using the+-- @threaded@ runtime, or simply writing event-handling code that is responsive+-- to intuitive "quit" keys like @q@ and @Esc@.+--+-- This function may throw a 'PollError' exception under mysterious+-- circumstances that are not well-documented in the original C codebase.+poll :: IO Event+poll =+  alloca $ \ptr ->+    Tb.pollEvent ptr >>= \case+      -1 ->+        throwIO PollError+      _ ->+        parseEvent <$> peek ptr++-- | An error occurred when 'poll'ing.+data PollError+  = PollError+  deriving Show++instance Exception PollError++-- | Parse an 'Event' from a 'Tb.Event'.+parseEvent :: Tb.Event -> Event+parseEvent = \case+  Tb.Event Tb.EventKey mod key ch _ _ _ _ ->+    parseEventKey mod key ch+  Tb.Event Tb.EventResize _ _ _ w h _ _ ->+    EventResize w h+  Tb.Event Tb.EventMouse _ key _ _ _ x y ->+    EventMouse (parseMouse key) x y++-- | Parse a key 'Event'.+parseEventKey :: Tb.Mod -> Tb.Key -> Char -> Event+parseEventKey mod key ch =+  EventKey key' alt+ where+  key' :: Key+  key' =+    case ch of+      '\0' -> parseKey key+      _ -> KeyChar ch++  alt :: Bool+  alt =+    case mod of+      Tb.ModAlt -> True+      _ -> False++-- | Parse a 'Key' from a 'Tb.Key'.+parseKey :: Tb.Key -> Key+parseKey = \case+  Tb.KeyArrowDown -> KeyArrowDown+  Tb.KeyArrowLeft -> KeyArrowLeft+  Tb.KeyArrowRight -> KeyArrowRight+  Tb.KeyArrowUp -> KeyArrowUp+  Tb.KeyBackspace -> KeyBackspace+  Tb.KeyBackspace2 -> KeyBackspace2+  Tb.KeyCtrl2 -> KeyCtrl2+  Tb.KeyCtrl3 -> KeyCtrl3+  Tb.KeyCtrl4 -> KeyCtrl4+  Tb.KeyCtrl5 -> KeyCtrl5+  Tb.KeyCtrl6 -> KeyCtrl6+  Tb.KeyCtrl7 -> KeyCtrl7+  Tb.KeyCtrl8 -> KeyCtrl8+  Tb.KeyCtrlA -> KeyCtrlA+  Tb.KeyCtrlB -> KeyCtrlB+  Tb.KeyCtrlBackslash -> KeyCtrlBackslash+  Tb.KeyCtrlC -> KeyCtrlC+  Tb.KeyCtrlD -> KeyCtrlD+  Tb.KeyCtrlE -> KeyCtrlE+  Tb.KeyCtrlF -> KeyCtrlF+  Tb.KeyCtrlG -> KeyCtrlG+  Tb.KeyCtrlH -> KeyCtrlH+  Tb.KeyCtrlI -> KeyCtrlI+  Tb.KeyCtrlJ -> KeyCtrlJ+  Tb.KeyCtrlK -> KeyCtrlK+  Tb.KeyCtrlL -> KeyCtrlL+  Tb.KeyCtrlLsqBracket -> KeyCtrlLsqBracket+  Tb.KeyCtrlM -> KeyCtrlM+  Tb.KeyCtrlN -> KeyCtrlN+  Tb.KeyCtrlO -> KeyCtrlO+  Tb.KeyCtrlP -> KeyCtrlP+  Tb.KeyCtrlQ -> KeyCtrlQ+  Tb.KeyCtrlR -> KeyCtrlR+  Tb.KeyCtrlRsqBracket -> KeyCtrlRsqBracket+  Tb.KeyCtrlS -> KeyCtrlS+  Tb.KeyCtrlSlash -> KeyCtrlSlash+  Tb.KeyCtrlT -> KeyCtrlT+  Tb.KeyCtrlTilde -> KeyCtrlTilde+  Tb.KeyCtrlU -> KeyCtrlU+  Tb.KeyCtrlUnderscore -> KeyCtrlUnderscore+  Tb.KeyCtrlV -> KeyCtrlV+  Tb.KeyCtrlW -> KeyCtrlW+  Tb.KeyCtrlX -> KeyCtrlX+  Tb.KeyCtrlY -> KeyCtrlY+  Tb.KeyCtrlZ -> KeyCtrlZ+  Tb.KeyDelete -> KeyDelete+  Tb.KeyEnd -> KeyEnd+  Tb.KeyEnter -> KeyEnter+  Tb.KeyEsc -> KeyEsc+  Tb.KeyF1 -> KeyF1+  Tb.KeyF10 -> KeyF10+  Tb.KeyF11 -> KeyF11+  Tb.KeyF12 -> KeyF12+  Tb.KeyF2 -> KeyF2+  Tb.KeyF3 -> KeyF3+  Tb.KeyF4 -> KeyF4+  Tb.KeyF5 -> KeyF5+  Tb.KeyF6 -> KeyF6+  Tb.KeyF7 -> KeyF7+  Tb.KeyF8 -> KeyF8+  Tb.KeyF9 -> KeyF9+  Tb.KeyHome -> KeyHome+  Tb.KeyInsert -> KeyInsert+  Tb.KeyPageDn -> KeyPageDn+  Tb.KeyPageUp -> KeyPageUp+  Tb.KeySpace -> KeySpace+  Tb.KeyTab -> KeyTab+  key -> error ("parseKey: " ++ show key)++-- | Parse a 'Mouse' from a 'Tb.Key'.+parseMouse :: Tb.Key -> Mouse+parseMouse = \case+  Tb.KeyMouseLeft -> MouseLeft+  Tb.KeyMouseMiddle -> MouseMiddle+  Tb.KeyMouseRelease -> MouseRelease+  Tb.KeyMouseRight -> MouseRight+  Tb.KeyMouseWheelDown -> MouseWheelDown+  Tb.KeyMouseWheelUp -> MouseWheelUp+  key -> error ("parseMouse: " ++ show key)++--------------------------------------------------------------------------------+-- Attributes+--------------------------------------------------------------------------------++-- | A cell attribute, which includes its color, and whether or not it is+-- bold, underlined, and/or reversed.+--+-- A cell can only have one color, but may be (for example) bold /and/+-- underlined. The 'Monoid' instance combines 'Attr's this way, with a left+-- bias. That is,+--+-- @+-- red <> bold <> black <> underline = red <> bold <> underline+-- @+--+-- /Warning/: the 'Num' instance is /very partial/! It only includes an+-- implementation of 'fromInteger', for numeric literals.+data Attr+  = Attr !Word16 {- color -} !Word16 {- attr -}+  deriving (Eq)++instance Monoid Attr where+  mempty :: Attr+  mempty =+    Attr Tb._DEFAULT 0++  mappend :: Attr -> Attr -> Attr+  mappend =+    (<>)++-- | Only 'fromInteger' is defined.+instance Num Attr where+  fromInteger :: Integer -> Attr+  fromInteger n+    | n >= 0 && n < 256 =+        Attr (fromIntegral n) 0+    | otherwise =+        error ("Attr.fromInteger: " ++ show n ++ " out of range [0..255]")++  (+) = error ("Attr.(+): not defined")+  (*) = error ("Attr.(*): not defined")+  (-) = error ("Attr.(-): not defined")+  abs = error ("Attr.abs: not defined")+  signum = error ("Attr.signum: not defined")++-- | Left-biased color; attributes are merged.+instance Semigroup Attr where+  (<>) :: Attr -> Attr -> Attr+  Attr  0 ax <> Attr cy ay = Attr cy (ax .|. ay)+  Attr cx ax <> Attr  0 ay = Attr cx (ax .|. ay)+  Attr cx ax <> Attr  _ ay = Attr cx (ax .|. ay)++wordToAttr :: Word16 -> Attr+wordToAttr w =+  Attr (w .&. 0x00FF) (w .&. 0xFF00)++attrToWord :: Attr -> Word16+attrToWord (Attr x y) =+  x .|. y++-- | @black = 1@.+black :: Attr+black =+  Attr Tb._BLACK 0++-- | @red = 2@.+red :: Attr+red =+  Attr Tb._RED 0++-- | @green = 3@.+green :: Attr+green =+  Attr Tb._GREEN 0++-- | @yellow = 4@.+yellow :: Attr+yellow =+  Attr Tb._YELLOW 0++-- | @blue = 5@.+blue :: Attr+blue =+  Attr Tb._BLUE 0++-- | @magenta = 6@.+magenta :: Attr+magenta =+  Attr Tb._MAGENTA 0++-- | @cyan = 7@.+cyan :: Attr+cyan =+  Attr Tb._CYAN 0++-- | @white = 8@.+white :: Attr+white =+  Attr Tb._WHITE 0++-- | Bold modifier attribute.+bold :: Attr+bold =+  Attr Tb._DEFAULT Tb._BOLD++-- | Underline modifier attribute.+underline :: Attr+underline =+  Attr Tb._DEFAULT Tb._UNDERLINE++-- | Reverse modifier attribute.+reverse :: Attr+reverse =+  Attr Tb._DEFAULT Tb._REVERSE++--------------------------------------------------------------------------------+-- Foreign imports+--------------------------------------------------------------------------------++foreign import ccall safe "termbox.h tb_cell_buffer"+  tb_cell_buffer :: IO (Ptr Cell)
+ src/Termbox/Internal.chs view
@@ -0,0 +1,312 @@+-- | Lowest-level termbox bindings. No creativity here, just a 1:1 mapping.+-- Some of the enums are hand-written (source copied from generated module) so+-- I can insert haddocks.++{-# language InstanceSigs #-}++module Termbox.Internal where++#include <termbox.h>++import Data.Char (ord)+import Data.Word+import Foreign+import Foreign.C+import Prelude hiding (mod)++--------------------------------------------------------------------------------+-- Constants+--------------------------------------------------------------------------------++_INPUT_CURRENT, _INPUT_ESC, _INPUT_ALT, _INPUT_MOUSE :: Int+_HIDE_CURSOR :: Int+_BOLD, _UNDERLINE, _REVERSE :: Word16+_DEFAULT, _BLACK, _RED, _GREEN, _YELLOW, _BLUE, _MAGENTA, _CYAN, _WHITE :: Word16++_INPUT_CURRENT = {# const TB_INPUT_CURRENT #}+_INPUT_ESC     = {# const TB_INPUT_ESC     #}+_INPUT_ALT     = {# const TB_INPUT_ALT     #}+_INPUT_MOUSE   = {# const TB_INPUT_MOUSE   #}++_HIDE_CURSOR   = {# const TB_HIDE_CURSOR #}++_BOLD          = {# const TB_BOLD #}+_UNDERLINE     = {# const TB_UNDERLINE #}+_REVERSE       = {# const TB_REVERSE #}++_DEFAULT       = {# const TB_DEFAULT #}+_BLACK         = {# const TB_BLACK #}+_RED           = {# const TB_RED #}+_GREEN         = {# const TB_GREEN #}+_YELLOW        = {# const TB_YELLOW #}+_BLUE          = {# const TB_BLUE #}+_MAGENTA       = {# const TB_MAGENTA #}+_CYAN          = {# const TB_CYAN #}+_WHITE         = {# const TB_WHITE #}++--------------------------------------------------------------------------------+-- Enums+--------------------------------------------------------------------------------++{#+  enum define EventType+    { TB_EVENT_KEY as EventKey+    , TB_EVENT_RESIZE as EventResize+    , TB_EVENT_MOUSE as EventMouse+    }+#}++{#+  enum define InitResult+    { 0 as InitOk+    , TB_EUNSUPPORTED_TERMINAL as UnsupportedTerminal+    , TB_EFAILED_TO_OPEN_TTY as FailedToOpenTTY+    , TB_EPIPE_TRAP_ERROR as PipeTrapError+    }+#}++{#+  enum define Key+    { TB_KEY_F1 as KeyF1+    , TB_KEY_F2 as KeyF2+    , TB_KEY_F3 as KeyF3+    , TB_KEY_F4 as KeyF4+    , TB_KEY_F5 as KeyF5+    , TB_KEY_F6 as KeyF6+    , TB_KEY_F7 as KeyF7+    , TB_KEY_F8 as KeyF8+    , TB_KEY_F9 as KeyF9+    , TB_KEY_F10 as KeyF10+    , TB_KEY_F11 as KeyF11+    , TB_KEY_F12 as KeyF12+    , TB_KEY_INSERT as KeyInsert+    , TB_KEY_DELETE as KeyDelete+    , TB_KEY_HOME as KeyHome+    , TB_KEY_END as KeyEnd+    , TB_KEY_PGUP as KeyPageUp+    , TB_KEY_PGDN as KeyPageDn+    , TB_KEY_ARROW_UP as KeyArrowUp+    , TB_KEY_ARROW_DOWN as KeyArrowDown+    , TB_KEY_ARROW_LEFT as KeyArrowLeft+    , TB_KEY_ARROW_RIGHT as KeyArrowRight+    , TB_KEY_MOUSE_LEFT as KeyMouseLeft+    , TB_KEY_MOUSE_RIGHT as KeyMouseRight+    , TB_KEY_MOUSE_MIDDLE as KeyMouseMiddle+    , TB_KEY_MOUSE_RELEASE as KeyMouseRelease+    , TB_KEY_MOUSE_WHEEL_UP as KeyMouseWheelUp+    , TB_KEY_MOUSE_WHEEL_DOWN as KeyMouseWheelDown+    , TB_KEY_CTRL_TILDE as KeyCtrlTilde+    , TB_KEY_CTRL_2 as KeyCtrl2+    , TB_KEY_CTRL_A as KeyCtrlA+    , TB_KEY_CTRL_B as KeyCtrlB+    , TB_KEY_CTRL_C as KeyCtrlC+    , TB_KEY_CTRL_D as KeyCtrlD+    , TB_KEY_CTRL_E as KeyCtrlE+    , TB_KEY_CTRL_F as KeyCtrlF+    , TB_KEY_CTRL_G as KeyCtrlG+    , TB_KEY_BACKSPACE as KeyBackspace+    , TB_KEY_CTRL_H as KeyCtrlH+    , TB_KEY_TAB as KeyTab+    , TB_KEY_CTRL_I as KeyCtrlI+    , TB_KEY_CTRL_J as KeyCtrlJ+    , TB_KEY_CTRL_K as KeyCtrlK+    , TB_KEY_CTRL_L as KeyCtrlL+    , TB_KEY_ENTER as KeyEnter+    , TB_KEY_CTRL_M as KeyCtrlM+    , TB_KEY_CTRL_N as KeyCtrlN+    , TB_KEY_CTRL_O as KeyCtrlO+    , TB_KEY_CTRL_P as KeyCtrlP+    , TB_KEY_CTRL_Q as KeyCtrlQ+    , TB_KEY_CTRL_R as KeyCtrlR+    , TB_KEY_CTRL_S as KeyCtrlS+    , TB_KEY_CTRL_T as KeyCtrlT+    , TB_KEY_CTRL_U as KeyCtrlU+    , TB_KEY_CTRL_V as KeyCtrlV+    , TB_KEY_CTRL_W as KeyCtrlW+    , TB_KEY_CTRL_X as KeyCtrlX+    , TB_KEY_CTRL_Y as KeyCtrlY+    , TB_KEY_CTRL_Z as KeyCtrlZ+    , TB_KEY_ESC as KeyEsc+    , TB_KEY_CTRL_LSQ_BRACKET as KeyCtrlLsqBracket+    , TB_KEY_CTRL_3 as KeyCtrl3+    , TB_KEY_CTRL_4 as KeyCtrl4+    , TB_KEY_CTRL_BACKSLASH as KeyCtrlBackslash+    , TB_KEY_CTRL_5 as KeyCtrl5+    , TB_KEY_CTRL_RSQ_BRACKET as KeyCtrlRsqBracket+    , TB_KEY_CTRL_6 as KeyCtrl6+    , TB_KEY_CTRL_7 as KeyCtrl7+    , TB_KEY_CTRL_SLASH as KeyCtrlSlash+    , TB_KEY_CTRL_UNDERSCORE as KeyCtrlUnderscore+    , TB_KEY_SPACE as KeySpace+    , TB_KEY_BACKSPACE2 as KeyBackspace2+    , TB_KEY_CTRL_8 as KeyCtrl8+    } deriving (Show)+#}++{#+  enum define Mod+    { 0 as ModNone+    , TB_MOD_ALT as ModAlt+    }+#}++{#+  enum define OutputMode+    { TB_OUTPUT_CURRENT as OutputModeCurrent+    , TB_OUTPUT_NORMAL as OutputModeNormal+    , TB_OUTPUT_256 as OutputMode256+    , TB_OUTPUT_216 as OutputMode216+    , TB_OUTPUT_GRAYSCALE as OutputModeGrayscale+    }++#}++--------------------------------------------------------------------------------+-- Types+--------------------------------------------------------------------------------++sizeofCell :: Int+sizeofCell =+  {# sizeof tb_cell #}++alignofCell :: Int+alignofCell =+  {# alignof tb_cell #}++getCellCh :: Ptr a -> IO Char+getCellCh =+  fmap (toEnum . fromIntegral) . {# get tb_cell->ch #}++getCellFg :: Ptr a -> IO Word16+getCellFg =+  fmap fromIntegral . {# get tb_cell->fg #}++getCellBg :: Ptr a -> IO Word16+getCellBg =+  fmap fromIntegral . {# get tb_cell->bg #}++setCellCh :: Ptr a -> Char -> IO ()+setCellCh p =+  {# set tb_cell.ch #} p . fromIntegral . fromEnum++setCellFg :: Ptr a -> Word16 -> IO ()+setCellFg p =+  {# set tb_cell.fg #} p . fromIntegral++setCellBg :: Ptr a -> Word16 -> IO ()+setCellBg p =+  {# set tb_cell.bg #} p . fromIntegral++data Event+  = Event !EventType Mod Key Char Int Int Int Int++instance Storable Event where+  sizeOf :: Event -> Int+  sizeOf _ =+    {# sizeof tb_event #}++  alignment :: Event -> Int+  alignment _ =+    {# alignof tb_event #}++  peek :: Ptr Event -> IO Event+  peek p =+    Event+      <$> ((toEnum . fromIntegral) <$> {# get tb_event->type #} p)+      <*> ((toEnum . fromIntegral) <$> {# get tb_event->mod #} p)+      <*> ((toEnum . fromIntegral) <$> {# get tb_event->key #} p)+      <*> ((toEnum . fromIntegral) <$> {# get tb_event->ch #} p)+      <*> (fromIntegral <$> {# get tb_event->w #} p)+      <*> (fromIntegral <$> {# get tb_event->h #} p)+      <*> (fromIntegral <$> {# get tb_event->x #} p)+      <*> (fromIntegral <$> {# get tb_event->y #} p)++  poke :: Ptr Event -> Event -> IO ()+  poke p (Event typ mod key ch w h x y) = do+    {# set tb_event.type #} p (fromIntegral (fromEnum typ))+    {# set tb_event.mod #} p (fromIntegral (fromEnum mod))+    {# set tb_event.key #} p (fromIntegral (fromEnum key))+    {# set tb_event.ch #} p (fromIntegral (fromEnum ch))+    {# set tb_event.w #} p (fromIntegral w)+    {# set tb_event.h #} p (fromIntegral h)+    {# set tb_event.x #} p (fromIntegral x)+    {# set tb_event.y #} p (fromIntegral y)++{# pointer *tb_event as EventPtr -> Event #}++--------------------------------------------------------------------------------+-- Functions+--------------------------------------------------------------------------------++{#+  fun tb_change_cell as changeCell+    { `Int', `Int', charToUInt `Char', `Word16', `Word16' } -> `()'+#}++{#+  fun tb_clear as clear+    { } -> `()'+#}++{#+  fun tb_height as height+    { } -> `Int'+#}++{#+  fun tb_init as init+    { } -> `InitResult'+#}++{#+  fun tb_peek_event as peekEvent+    { `EventPtr', `Int' } -> `Int'+#}++{#+  fun tb_poll_event as pollEvent+    { `EventPtr' } -> `Int'+#}++{#+  fun tb_present as present+    { } -> `()'+#}++{#+  fun tb_select_input_mode as selectInputMode+    { `Int' } -> `Int'+#}++{#+  fun tb_select_output_mode as selectOutputMode+    { `OutputMode' } -> `OutputMode'+#}++{#+  fun tb_set_clear_attributes as setClearAttributes+    { `Word16', `Word16' } -> `()'+#}++{#+  fun tb_set_cursor as setCursor+    { `Int', `Int' } -> `()'+#}++{#+  fun tb_shutdown as shutdown+    { } -> `()'+#}++{#+  fun tb_width as width+    { } -> `Int'+#}++--------------------------------------------------------------------------------+-- Misc.+--------------------------------------------------------------------------------++charToUInt :: Char -> CUInt+charToUInt =+  fromIntegral . ord
+ termbox.cabal view
@@ -0,0 +1,79 @@+cabal-version: 2.0++name: termbox+version: 0.1.0+category: User Interfaces+description:+  This package provides a thin wrapper around @termbox v1.1.2@, a simple C+  library for writing text-based user interfaces: <https://github.com/nsf/termbox>+  .+  The full source of @termbox v1.1.2@ (1000 lines of C) is bundled; you do+  not need to install any system packages to use this library.+synopsis: termbox bindings+author: Mitchell Rosen+maintainer: Mitchell Rosen <mitchellwrosen@gmail.com>+homepage: https://github.com/mitchellwrosen/termbox+bug-reports: https://github.com/mitchellwrosen/termbox/issues+copyright: (c) 2018, Mitchell Rosen+license: BSD3+license-file: LICENSE+build-type: Simple+tested-with: GHC == 8.0.2, GHC == 8.2.2, GHC == 8.4.3++extra-source-files:+  .travis.yml+  cbits/LICENSE+  CHANGELOG.md+  include/termbox.h++library+  build-depends:+      array+    , base ^>= 4.9 || ^>= 4.10 || ^>= 4.11+  build-tool-depends:+    c2hs:c2hs+  c-sources:+    cbits/termbox.c+    cbits/utf8.c+  default-language:+    Haskell2010+  exposed-modules:+    Termbox+  ghc-options:+    -fprint-expanded-synonyms+    -fprint-explicit-foralls+    -fprint-explicit-kinds+    -fprint-unicode-syntax+    -Wall+    -Wcompat+    -Widentities+    -Wincomplete-record-updates+    -Wincomplete-uni-patterns+    -Wnoncanonical-monad-instances+    -Wnoncanonical-monadfail-instances+    -Wredundant-constraints+  if impl(ghc >= 8.4)+    ghc-options:+      -Wmissing-export-lists+      -Wpartial-fields+  hs-source-dirs:+    src+  include-dirs:+    include+  other-modules:+    Termbox.Internal++executable termbox-example-colors+  build-depends:+      base+    , termbox+  default-language:+    Haskell2010+  ghc-options:+    -rtsopts+    -threaded+    -Wall+  hs-source-dirs:+    examples+  main-is:+    Colors.hs