i

Owner: IIIlllIIIllI URL: git@github.com:nyangkosense/i.git

0.1

Commit 5d5665fbe140ee75ecdb5ff57b605cf77ff3416f by SeMi <sebastian.michalk@protonmail.com> on 2025-06-15 11:14:56 +0200
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..0f7e9b7
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT/X Consortium License
+
+© 2025 Sebastian sm@secpaste.dev
+
+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/Makefile b/Makefile
new file mode 100644
index 0000000..292e031
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,51 @@
+# i - terminal system information utility
+# See LICENSE file for copyright and license details.
+
+include config.mk
+
+SRC = main.c termbox.c
+OBJ = ${SRC:.c=.o}
+
+all: options i
+
+options:
+	@echo i build options:
+	@echo "CFLAGS   = ${CFLAGS}"
+	@echo "LDFLAGS  = ${LDFLAGS}"
+	@echo "CC       = ${CC}"
+
+.c.o:
+	${CC} -c ${CFLAGS} $<
+
+${OBJ}: config.h config.mk
+
+config.h:
+	cp config.def.h $@
+
+i: ${OBJ}
+	${CC} -o $@ ${OBJ} ${LDFLAGS}
+
+clean:
+	rm -f i ${OBJ} config.h i-${VERSION}.tar.gz *.o
+
+dist: clean
+	mkdir -p i-${VERSION}
+	cp -R LICENSE Makefile README config.mk config.def.h \
+		${SRC} i-${VERSION}
+	tar -cf i-${VERSION}.tar i-${VERSION}
+	gzip i-${VERSION}.tar
+	rm -rf i-${VERSION}
+
+install: all
+	mkdir -p ${DESTDIR}${PREFIX}/bin
+	cp -f i ${DESTDIR}${PREFIX}/bin
+	chmod 755 ${DESTDIR}${PREFIX}/bin/i
+	mkdir -p ${DESTDIR}${MANPREFIX}/man1
+	sed "s/VERSION/${VERSION}/g" < i.1 > ${DESTDIR}${MANPREFIX}/man1/i.1
+	chmod 644 ${DESTDIR}${MANPREFIX}/man1/i.1
+
+uninstall:
+	rm -f ${DESTDIR}${PREFIX}/bin/i \
+		${DESTDIR}${MANPREFIX}/man1/i.1
+
+.PHONY: all options clean dist install uninstall
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..132314b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,54 @@
+# i
+
+A minimalist system information utility with a hex dump aesthetic.
+
+![sysinfo demo](demo.gif)
+
+## Description
+
+"i" is a lightweight terminal-based system information tool that displays
+system stats in an immersive hex editor style interface. It shows real-time
+information about your system including time, uptime, memory usage, CPU usage,
+network status, battery status, and VPN connectivity.
+
+## Features
+
+* Real-time system monitoring
+* Hex dump background (cause why not) 
+* Battery status detection
+* Network interface monitoring
+* VPN status detection
+* Power controls (reboot/shutdown)
+* Adaptive terminal width layout
+* Small, hackable Codebase
+
+## Requirements
+
+* C99 compiler
+* termbox2 library
+* POSIX-compliant system
+
+## Installation
+
+Edit config.mk to match your local setup (i is installed into
+the /usr/local namespace by default).
+
+Afterwards enter the following command to build and install sysinfo
+(if necessary as root):
+
+    make clean install
+
+## Configuration
+
+The configuration of i is done by creating a custom config.h
+and (re)compiling the source code.
+
+## Controls
+
+* `q` or `ESC` - Quit
+* `r` - Reboot system  
+* `s` - Shutdown system
+
+## Credits
+
+Uses termbox2 for terminal UI rendering.
diff --git a/config.def.h b/config.def.h
new file mode 100644
index 0000000..d8544cc
--- /dev/null
+++ b/config.def.h
@@ -0,0 +1,268 @@
+/* See LICENSE file for copyright and license details. */
+
+/* refresh interval in seconds */
+static const int refresh_interval = 1;
+
+/* hex background refresh interval in seconds (can be fractional) */
+static const double hex_refresh_interval = 2;
+
+/* appearance */
+static const struct {
+	uint16_t fg;
+	uint16_t bg;
+} colors[] = {
+	/* element           foreground            background */
+	[0] = { TB_YELLOW,    TB_DEFAULT }, /* time */
+	[1] = { TB_GREEN,     TB_DEFAULT }, /* uptime */
+	[2] = { TB_BLUE,      TB_DEFAULT }, /* memory */
+	[3] = { TB_RED,       TB_DEFAULT }, /* cpu */
+	[4] = { TB_CYAN,      TB_DEFAULT }, /* network */
+	[5] = { TB_MAGENTA,   TB_DEFAULT }, /* battery */
+	[6] = { TB_WHITE,     TB_DEFAULT }, /* vpn */
+};
+
+/* battery path - adjust for your system */
+static const char *battery_path = "/sys/class/power_supply/BAT0";
+
+/* commands for power management (requires appropriate sudo permissions) */
+static const char *reboot_cmd = "sudo reboot";
+static const char *shutdown_cmd = "sudo shutdown -h now";
+
+/* ASCII art definitions for different operating systems */
+static const char *ascii_alpine[] = {
+	"   /\\ /\\",
+	"  /./ \\  \\",
+	" /./   \\  \\",
+	"/./    \\  \\",
+	"//      \\  \\",
+	"         \\",
+	NULL
+};
+
+static const char *ascii_android[] = {
+	"  ;,           ,;",
+	"   ';,.-----.,;'",
+	"  ,'           ',",
+	" /    O     O    \\",
+	"|                 |",
+	"'-----------------'",
+	NULL
+};
+
+static const char *ascii_arch[] = {
+	"       /\\",
+	"      /  \\",
+	"     /\\   \\",
+	"    /      \\",
+	"   /   ,,   \\",
+	"  /   |  |  -\\",
+	" /_-''    ''-_\\",
+	NULL
+};
+
+static const char *ascii_arco[] = {
+	"      /\\",
+	"     /  \\",
+	"    / /\\ \\",
+	"   / /  \\ \\",
+	"  / /    \\ \\",
+	" / / _____\\ \\",
+	"/_/  `----.\\_\\",
+	NULL
+};
+
+static const char *ascii_artix[] = {
+	"      /\\",
+	"     /  \\",
+	"    /`'.,\\",
+	"   /     ',",
+	"  /      ,`\\",
+	" /   ,.'`.  \\",
+	"/.,`'     `'.\\",
+	NULL
+};
+
+static const char *ascii_centos[] = {
+	" ____^____",
+	" |\\  |  /|",
+	" | \\ | / |",
+	"<---- ---->",
+	" | / | \\ |",
+	" |/__| __\\|",
+	"     v",
+	NULL
+};
+
+static const char *ascii_debian[] = {
+	"  _____",
+	" /  __ \\",
+	"|  /    |",
+	"|  \\___-",
+	"-_",
+	"  --_",
+	NULL
+};
+
+static const char *ascii_endeavour[] = {
+	"      /\\",
+	"    //  \\\\",
+	"   //    \\ \\",
+	" / //     _) )",
+	"/_/___-- __-",
+	" /____--",
+	NULL
+};
+
+static const char *ascii_fedora[] = {
+	"        ,'''''.    ",
+	"       |   ,.  |   ",
+	"       |  |  '_'   ",
+	"  ,....|  |..      ",
+	".'  ,_;|   ..'     ",
+	"|  |   |  |        ",
+	"|  ',_,'  |        ",
+	" '.     ,'         ",
+	"   '''''           ",
+	NULL
+};
+
+static const char *ascii_freebsd[] = {
+	"/\\,-'''''-,/\\",
+	"\\_)       (_/",
+	"|           |",
+	"|           |",
+	" ;         ;",
+	"  '-_____-'",
+	NULL
+};
+
+static const char *ascii_gentoo[] = {
+	" _-----_",
+	"(       \\",
+	"\\    0   \\",
+	" \\        )",
+	" /      _/",
+	"(     _-",
+	"\\____-",
+	NULL
+};
+
+static const char *ascii_linux[] = {
+	"    ___",
+	"   (.. |",
+	"   (<> |",
+	"  / __  \\",
+	" ( /  \\ /|",
+	"_/\\ __)/_)",
+	"\\/----\\/",
+	NULL
+};
+
+static const char *ascii_linux_mint[] = {
+	" ___________",
+	"|_          \\",
+	"  | | _____ |",
+	"  | | | | | |",
+	"  | | | | | |",
+	"  | \\__ ___/ |",
+	"  \\_________/",
+	NULL
+};
+
+static const char *ascii_macos[] = {
+	"       .:'",
+	"    _ :'_",
+	" .'`_`-'_`'.",
+	":________.-'",
+	":_______:",
+	" :_______`-;",
+	"  `._.-._.'",
+	NULL
+};
+
+static const char *ascii_manjaro[] = {
+	"||||||||| ||||",
+	"||||||||| ||||",
+	"||||      ||||",
+	"|||| |||| ||||",
+	"|||| |||| ||||",
+	"|||| |||| ||||",
+	"|||| |||| ||||",
+	NULL
+};
+
+static const char *ascii_nixos[] = {
+	"  \\\\  \\\\ //",
+	" ==\\\\__\\\\/ //",
+	"   //   \\\\//",
+	"==//     //==",
+	" //\\\\___//",
+	"// /\\\\  \\\\==",
+	"  // \\\\  \\\\",
+	NULL
+};
+
+static const char *ascii_opensuse[] = {
+	"  _______",
+	"__|   __ \\",
+	"     / .\\ \\",
+	"     \\__/ |",
+	"   _______|",
+	"   \\_______",
+	"__________/",
+	NULL
+};
+
+static const char *ascii_pop_os[] = {
+	"______",
+	"\\   _ \\        __",
+	" \\ \\ \\ \\      / /",
+	"  \\ \\_\\ \\    / /",
+	"   \\  ___\\  /_/",
+	"    \\ \\    _",
+	"   __\\_\\__(_)_",
+	"  (___________)",
+	NULL
+};
+
+static const char *ascii_slackware[] = {
+	"   ________",
+	"  /  ______|",
+	"  | |______",
+	"  \\______  \\",
+	"   ______| |",
+	"| |________/",
+	"|____________",
+	NULL
+};
+
+static const char *ascii_solus[] = {
+	"     /|",
+	"    / |\\",
+	"   /  | \\ _",
+	"  /___|__\\_\\",
+	" \\         /",
+	"  `-------´",
+	NULL
+};
+
+static const char *ascii_ubuntu[] = {
+	"         _",
+	"     ---(_)",
+	" _/  ---  \\",
+	"(_) |   |",
+	"  \\  --- _/",
+	"     ---(_)",
+	NULL
+};
+
+static const char *ascii_void[] = {
+	"    _______",
+	" _ \\______ -",
+	"| \\  ___  \\ |",
+	"| | /   \\ | |",
+	"| | \\___/ | |",
+	"| \\______ \\_|",
+	" -_______\\",
+	NULL
+};
diff --git a/config.mk b/config.mk
new file mode 100644
index 0000000..8deda06
--- /dev/null
+++ b/config.mk
@@ -0,0 +1,20 @@
+# i version
+VERSION = 0.1
+
+# Customize below to fit your system
+
+# paths
+PREFIX = /usr/local
+MANPREFIX = ${PREFIX}/share/man
+
+# includes and libs
+INCS = -I. -I/usr/include -I/usr/local/include
+LIBS = -L/usr/lib -L/usr/local/lib
+
+# flags
+CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_POSIX_C_SOURCE=200809L -DVERSION=\"${VERSION}\"
+CFLAGS   = -std=c99 -pedantic -Wall -Wextra -Os ${INCS} ${CPPFLAGS}
+LDFLAGS  = ${LIBS}
+
+# compiler and linker
+CC = cc
\ No newline at end of file
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..ae44f10
--- /dev/null
+++ b/main.c
@@ -0,0 +1,859 @@
+/* See LICENSE file for copyright and license details. */
+/* i - terminal system information utility */
+
+#define _POSIX_C_SOURCE 200809L
+
+#include <arpa/inet.h>
+#include <ifaddrs.h>
+#include <locale.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/statvfs.h>
+#include <sys/sysinfo.h>
+#include <sys/time.h>
+#include <time.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <sys/utsname.h>
+
+#include "termbox2.h"
+
+#include "config.h"
+
+#define MAXSTRLEN 256
+
+typedef struct {
+	char timestr[MAXSTRLEN];
+	char uptimestr[MAXSTRLEN];
+	char memorystr[MAXSTRLEN];
+	char cpustr[MAXSTRLEN];
+	char networkstr[MAXSTRLEN];
+	char batterystr[MAXSTRLEN];
+	char vpnstr[MAXSTRLEN];
+	char systemstr[MAXSTRLEN];
+	char ipstr[MAXSTRLEN];
+	char gatewaystr[MAXSTRLEN];
+	char dnsstr[MAXSTRLEN];
+} SysInfo;
+
+static void usage(void);
+static void die(const char *msg);
+static void printcenteredin(const char *str, int x, int y, int width, uint16_t fg, uint16_t bg);
+static void printat(const char *str, int x, int y, uint16_t fg, uint16_t bg);
+static void getcurrenttime(char *buffer);
+static void getuptime(char *buffer);
+static void getmemoryinfo(char *buffer);
+static void getcpuusage(char *buffer);
+static void getnetworkstatus(char *buffer);
+static void getbatterystatus(char *buffer);
+static void getvpnstatus(char *buffer);
+static void getipaddress(char *buffer);
+static void getgateway(char *buffer);
+static void getdns(char *buffer);
+static void collectsysteminfo(SysInfo *info);
+static void displayinfo(const SysInfo *info);
+static void drawbox(int x, int y, int width, int height, const char *title, uint16_t fg, uint16_t bg);
+static void drawseparator(int x, int y, int width, uint16_t fg, uint16_t bg);
+static void drawhexbanner(int x, int y, int width, uint16_t fg, uint16_t bg);
+static void drawhexbackground(int width, int height);
+static void detectsystem(char *buffer);
+static const char **getasciiart(const char *system);
+static void drawasciiart(const char **art, int x, int y, int width, int height, uint16_t fg, uint16_t bg);
+
+static char *argv0;
+
+static void
+usage(void)
+{
+	fprintf(stderr, "usage: %s\n", argv0);
+	exit(1);
+}
+
+static void
+die(const char *msg)
+{
+	fprintf(stderr, "%s: %s\n", argv0, msg);
+	exit(1);
+}
+
+
+static void
+printcenteredin(const char *str, int x, int y, int width, uint16_t fg, uint16_t bg)
+{
+	int len, center_x, i;
+
+	len = strlen(str);
+	center_x = x + (width - len) / 2;
+
+	for (i = 0; str[i]; i++)
+		tb_set_cell(center_x + i, y, str[i], fg, bg);
+}
+
+static void
+printat(const char *str, int x, int y, uint16_t fg, uint16_t bg)
+{
+	int i;
+
+	for (i = 0; str[i]; i++)
+		tb_set_cell(x + i, y, str[i], fg, bg);
+}
+
+
+static void
+getcurrenttime(char *buffer)
+{
+	time_t rawtime;
+	struct tm *timeinfo;
+
+	time(&rawtime);
+	timeinfo = localtime(&rawtime);
+	strftime(buffer, MAXSTRLEN, "%Y-%m-%d %H:%M:%S", timeinfo);
+}
+
+static void
+getuptime(char *buffer)
+{
+	struct sysinfo info;
+	int days, hours, minutes;
+
+	if (sysinfo(&info) == 0) {
+		days = info.uptime / 86400;
+		hours = (info.uptime % 86400) / 3600;
+		minutes = (info.uptime % 3600) / 60;
+		snprintf(buffer, MAXSTRLEN, "%dd %dh %dm", days, hours, minutes);
+	} else {
+		strcpy(buffer, "Unknown");
+	}
+}
+
+static void
+getmemoryinfo(char *buffer)
+{
+	FILE *fp;
+	unsigned long memtotal, memfree, memavailable, buffers, cached;
+	unsigned long totalmb, availablemb, usedmb;
+	int usagepercent;
+
+	memtotal = memfree = memavailable = buffers = cached = 0;
+
+	fp = fopen("/proc/meminfo", "r");
+	if (!fp) {
+		strcpy(buffer, "Unknown");
+		return;
+	}
+
+	char line[256];
+	while (fgets(line, sizeof(line), fp)) {
+		if (sscanf(line, "MemTotal: %lu kB", &memtotal) == 1) continue;
+		if (sscanf(line, "MemFree: %lu kB", &memfree) == 1) continue;
+		if (sscanf(line, "MemAvailable: %lu kB", &memavailable) == 1) continue;
+		if (sscanf(line, "Buffers: %lu kB", &buffers) == 1) continue;
+		if (sscanf(line, "Cached: %lu kB", &cached) == 1) continue;
+	}
+	fclose(fp);
+
+	if (memtotal > 0) {
+		totalmb = memtotal / 1024;
+		if (memavailable > 0) {
+			availablemb = memavailable / 1024;
+			usedmb = totalmb - availablemb;
+		} else {
+			availablemb = (memfree + buffers + cached) / 1024;
+			usedmb = totalmb - availablemb;
+		}
+		usagepercent = (usedmb * 100) / totalmb;
+
+		snprintf(buffer, MAXSTRLEN, "%lu MB / %lu MB (%d%%)",
+		         usedmb, totalmb, usagepercent);
+	} else {
+		strcpy(buffer, "Unknown");
+	}
+}
+
+static void
+getcpuusage(char *buffer)
+{
+	static long previdle = 0, prevtotal = 0;
+	FILE *fp;
+	long user, nice, system, idle, iowait, irq, softirq, steal;
+	long total, diffidle, difftotal;
+	int usage;
+
+	fp = fopen("/proc/stat", "r");
+	if (!fp) {
+		strcpy(buffer, "Unknown");
+		return;
+	}
+
+	fscanf(fp, "cpu %ld %ld %ld %ld %ld %ld %ld %ld",
+	       &user, &nice, &system, &idle, &iowait, &irq, &softirq, &steal);
+	fclose(fp);
+
+	total = user + nice + system + idle + iowait + irq + softirq + steal;
+	diffidle = idle - previdle;
+	difftotal = total - prevtotal;
+
+	if (difftotal > 0) {
+		usage = 100 - (diffidle * 100 / difftotal);
+		snprintf(buffer, MAXSTRLEN, "%d%%", usage);
+	} else {
+		strcpy(buffer, "0%");
+	}
+
+	previdle = idle;
+	prevtotal = total;
+}
+
+static void
+getnetworkstatus(char *buffer)
+{
+	struct ifaddrs *ifaddr, *ifa;
+	int connectedinterfaces;
+	char interfacelist[MAXSTRLEN];
+
+	connectedinterfaces = 0;
+	interfacelist[0] = '\0';
+
+	if (getifaddrs(&ifaddr) == -1) {
+		strcpy(buffer, "Unknown");
+		return;
+	}
+
+	for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
+		if (ifa->ifa_addr == NULL)
+			continue;
+
+		if (ifa->ifa_addr->sa_family == AF_INET &&
+		    strcmp(ifa->ifa_name, "lo") != 0) {
+			if (connectedinterfaces > 0)
+				strcat(interfacelist, ", ");
+			strcat(interfacelist, ifa->ifa_name);
+			connectedinterfaces++;
+		}
+	}
+
+	freeifaddrs(ifaddr);
+
+	if (connectedinterfaces > 0)
+		snprintf(buffer, MAXSTRLEN, "Connected: %s", interfacelist);
+	else
+		strcpy(buffer, "No network connection");
+}
+
+static void
+getbatterystatus(char *buffer)
+{
+	FILE *fp, *statusfp;
+	int capacity;
+	char status[32];
+
+	char path[MAXSTRLEN];
+	
+	snprintf(path, MAXSTRLEN, "%s/capacity", battery_path);
+	fp = fopen(path, "r");
+	if (fp) {
+		fscanf(fp, "%d", &capacity);
+		fclose(fp);
+
+		strcpy(status, "Unknown");
+		snprintf(path, MAXSTRLEN, "%s/status", battery_path);
+		statusfp = fopen(path, "r");
+		if (statusfp) {
+			fscanf(statusfp, "%s", status);
+			fclose(statusfp);
+		}
+
+		snprintf(buffer, MAXSTRLEN, "%d%% (%s)", capacity, status);
+	} else {
+		strcpy(buffer, "No battery detected");
+	}
+}
+
+static void
+getvpnstatus(char *buffer)
+{
+	FILE *fp;
+	char line[256];
+
+	fp = popen("ip route show | grep -E '(tun|tap|vpn)' | head -1", "r");
+	if (fp) {
+		if (fgets(line, sizeof(line), fp))
+			strcpy(buffer, "VPN: Active");
+		else
+			strcpy(buffer, "VPN: Inactive");
+		pclose(fp);
+	} else {
+		strcpy(buffer, "VPN: Unknown");
+	}
+}
+
+static void
+getipaddress(char *buffer)
+{
+	FILE *fp;
+	char line[256];
+
+	fp = popen("ip route get 8.8.8.8 2>/dev/null | awk '/src/ {print $7}' | head -1", "r");
+	if (fp) {
+		if (fgets(line, sizeof(line), fp)) {
+			line[strcspn(line, "\n")] = 0;
+			snprintf(buffer, MAXSTRLEN, "IP: %s", line);
+		} else {
+			strcpy(buffer, "IP: Unknown");
+		}
+		pclose(fp);
+	} else {
+		strcpy(buffer, "IP: Unknown");
+	}
+}
+
+static void
+getgateway(char *buffer)
+{
+	FILE *fp;
+	char line[256];
+
+	fp = popen("ip route | awk '/default/ {print $3}' | head -1", "r");
+	if (fp) {
+		if (fgets(line, sizeof(line), fp)) {
+			line[strcspn(line, "\n")] = 0;
+			snprintf(buffer, MAXSTRLEN, "Gateway: %s", line);
+		} else {
+			strcpy(buffer, "Gateway: Unknown");
+		}
+		pclose(fp);
+	} else {
+		strcpy(buffer, "Gateway: Unknown");
+	}
+}
+
+static void
+getdns(char *buffer)
+{
+	FILE *fp;
+	char line[256];
+
+	fp = fopen("/etc/resolv.conf", "r");
+	if (fp) {
+		while (fgets(line, sizeof(line), fp)) {
+			if (strncmp(line, "nameserver", 10) == 0) {
+				char *dns = line + 10;
+				while (*dns == ' ' || *dns == '\t') dns++;
+				dns[strcspn(dns, "\n")] = 0;
+				snprintf(buffer, MAXSTRLEN, "DNS: %s", dns);
+				fclose(fp);
+				return;
+			}
+		}
+		fclose(fp);
+	}
+	strcpy(buffer, "DNS: Unknown");
+}
+
+static void
+collectsysteminfo(SysInfo *info)
+{
+	getcurrenttime(info->timestr);
+	getuptime(info->uptimestr);
+	getmemoryinfo(info->memorystr);
+	getcpuusage(info->cpustr);
+	getnetworkstatus(info->networkstr);
+	getbatterystatus(info->batterystr);
+	getvpnstatus(info->vpnstr);
+	detectsystem(info->systemstr);
+	getipaddress(info->ipstr);
+	getgateway(info->gatewaystr);
+	getdns(info->dnsstr);
+}
+
+
+static void
+drawseparator(int x, int y, int width, uint16_t fg, uint16_t bg)
+{
+	int i;
+
+	tb_set_cell(x, y, 0x251C, fg, bg);
+	for (i = 1; i < width - 1; i++)
+		tb_set_cell(x + i, y, 0x2500, fg, bg);
+	tb_set_cell(x + width - 1, y, 0x2524, fg, bg);
+}
+
+
+static void
+drawhexbanner(int x, int y, int width, uint16_t fg, uint16_t bg)
+{
+	char hexline[512];
+	char hexdata[128];
+	char asciidata[64];
+	int bytes_per_line, max_bytes;
+	int i;
+	
+	/* Calculate how many bytes we can fit per line based on terminal width */
+	max_bytes = (width - 15) / 4;
+	if (max_bytes > 32) max_bytes = 32;
+	if (max_bytes < 8) max_bytes = 8;
+	bytes_per_line = (max_bytes / 8) * 8; /* Round to multiple of 8 */
+	
+	char banner_msg[] = "i v0.1";
+	int banner_len = strlen(banner_msg);
+	
+	/* Generate hex data for the banner line */
+	for (i = 0; i < bytes_per_line; i++) {
+		unsigned char byte;
+		if (i < banner_len) {
+			byte = banner_msg[i];
+		} else if (i == banner_len) {
+			byte = 0x00; /* Null terminator */
+		} else {
+			byte = 0x20; /* Space padding */
+		}
+		
+		sprintf(hexdata + (i * 3), "%02x ", byte);
+		asciidata[i] = (byte >= 32 && byte <= 126) ? byte : '.';
+	}
+	hexdata[bytes_per_line * 3 - 1] = '\0'; /* Remove last space */
+	asciidata[bytes_per_line] = '\0';
+	
+	int mid = (bytes_per_line / 2) * 3;
+	memmove(hexdata + mid + 1, hexdata + mid, strlen(hexdata + mid) + 1);
+	hexdata[mid] = ' ';
+	
+	snprintf(hexline, sizeof(hexline), "00000000  %s |%s|", hexdata, asciidata);
+	
+	int len = strlen(hexline);
+	while (len < width - 1) {
+		hexline[len++] = ' ';
+	}
+	hexline[width - 1] = '\0';
+	
+	printat(hexline, x, y, fg, bg);
+}
+
+static void
+drawhexbackground(int width, int height)
+{
+	char hexline[512];
+	char hexdata[128];
+	char asciidata[64];
+	int addr, i, j, bytes_per_line, max_bytes;
+	unsigned char byte;
+	
+	struct timeval tv;
+	gettimeofday(&tv, NULL);
+	unsigned int seed = (unsigned int)(tv.tv_sec * 1000000 + tv.tv_usec) & 0xFFFFFF;
+	
+	/* Calculate how many bytes we can fit per line based on terminal width */
+	/* Format: "00000000  xx xx xx xx  |ascii| " = ~10 + 3*bytes + 3 + bytes */
+	max_bytes = (width - 15) / 4;
+	if (max_bytes > 32) max_bytes = 32;
+	if (max_bytes < 8) max_bytes = 8;
+	bytes_per_line = (max_bytes / 8) * 8; /* Round to multiple of 8 */
+	
+	for (i = 0; i < height; i++) {
+		addr = i * bytes_per_line;
+		
+		for (j = 0; j < bytes_per_line; j++) {
+			seed = (seed * 1103515245 + 12345) & 0x7FFFFFFF;
+			byte = (seed >> 16) & 0xFF;
+			
+			if (j % 4 == 0) byte &= 0xF0; /* Some aligned data */
+			if (j == bytes_per_line/2) byte = 0x00; /* Null bytes */
+			
+			sprintf(hexdata + (j * 3), "%02x ", byte);
+			asciidata[j] = (byte >= 32 && byte <= 126) ? byte : '.';
+		}
+		hexdata[bytes_per_line * 3 - 1] = '\0'; /* Remove last space */
+		asciidata[bytes_per_line] = '\0';
+		
+		int mid = (bytes_per_line / 2) * 3;
+		memmove(hexdata + mid + 1, hexdata + mid, strlen(hexdata + mid) + 1);
+		hexdata[mid] = ' ';
+		
+		snprintf(hexline, sizeof(hexline), "%08x  %s |%s|", 
+		         addr, hexdata, asciidata);
+		
+		int len = strlen(hexline);
+		while (len < width - 1) {
+			hexline[len++] = ' ';
+		}
+		hexline[width - 1] = '\0';
+		
+		printat(hexline, 0, i, TB_BLACK | TB_BRIGHT, TB_DEFAULT);
+	}
+}
+
+static void
+detectsystem(char *buffer)
+{
+	FILE *fp;
+	char line[256];
+
+	strcpy(buffer, "linux");
+
+	fp = fopen("/etc/os-release", "r");
+	if (!fp) {
+		fp = fopen("/usr/lib/os-release", "r");
+		if (!fp)
+			return;
+	}
+
+	while (fgets(line, sizeof(line), fp)) {
+		if (strncmp(line, "ID=", 3) == 0) {
+			char *id;
+			id = line + 3;
+			
+			if (*id == '"') {
+				id++;
+				id[strcspn(id, "\"")] = 0;
+			} else {
+				id[strcspn(id, "\n")] = 0;
+			}
+			
+			strncpy(buffer, id, MAXSTRLEN - 1);
+			buffer[MAXSTRLEN - 1] = '\0';
+			break;
+		}
+	}
+	fclose(fp);
+}
+
+static const char **
+getasciiart(const char *system)
+{
+	if (!system)
+		return ascii_linux;
+
+	if (strcmp(system, "alpine") == 0)
+		return ascii_alpine;
+	if (strcmp(system, "android") == 0)
+		return ascii_android;
+	if (strcmp(system, "arch") == 0)
+		return ascii_arch;
+	if (strcmp(system, "arco") == 0)
+		return ascii_arco;
+	if (strcmp(system, "artix") == 0)
+		return ascii_artix;
+	if (strcmp(system, "centos") == 0)
+		return ascii_centos;
+	if (strcmp(system, "debian") == 0)
+		return ascii_debian;
+	if (strcmp(system, "endeavouros") == 0)
+		return ascii_endeavour;
+	if (strcmp(system, "fedora") == 0)
+		return ascii_fedora;
+	if (strcmp(system, "freebsd") == 0)
+		return ascii_freebsd;
+	if (strcmp(system, "gentoo") == 0)
+		return ascii_gentoo;
+	if (strcmp(system, "linuxmint") == 0)
+		return ascii_linux_mint;
+	if (strstr(system, "mint") != NULL)
+		return ascii_linux_mint;
+	if (strcmp(system, "darwin") == 0)
+		return ascii_macos;
+	if (strcmp(system, "manjaro") == 0)
+		return ascii_manjaro;
+	if (strcmp(system, "nixos") == 0)
+		return ascii_nixos;
+	if (strcmp(system, "opensuse") == 0)
+		return ascii_opensuse;
+	if (strcmp(system, "pop") == 0)
+		return ascii_pop_os;
+	if (strstr(system, "pop") != NULL)
+		return ascii_pop_os;
+	if (strcmp(system, "slackware") == 0)
+		return ascii_slackware;
+	if (strcmp(system, "solus") == 0)
+		return ascii_solus;
+	if (strcmp(system, "ubuntu") == 0)
+		return ascii_ubuntu;
+	if (strcmp(system, "void") == 0)
+		return ascii_void;
+
+	return ascii_linux;
+}
+
+static void
+drawasciiart(const char **art, int x, int y, int width, int height, uint16_t fg, uint16_t bg)
+{
+	int i, j, artlines, start_y, start_x;
+
+	if (!art)
+		return;
+
+	for (artlines = 0; art[artlines] != NULL; artlines++)
+		;
+
+	start_y = y + 1 + (height - 2 - artlines) / 2;
+	if (start_y < y + 1)
+		start_y = y + 1;
+
+	for (i = 0; i < artlines && start_y + i < y + height - 1; i++) {
+		start_x = x + 1;
+		
+		for (j = 0; art[i][j] && start_x + j < x + width - 1; j++) {
+			tb_set_cell(start_x + j, start_y + i, art[i][j], fg, TB_BLACK);
+		}
+	}
+}
+
+static void
+drawbox(int x, int y, int width, int height, const char *title, uint16_t fg, uint16_t bg)
+{
+	int i, j;
+	
+	(void)bg; /* Intentionally unused - we always use TB_BLACK for background */
+
+	/* Fill the box with solid background to cover hex dump */
+	for (i = 0; i < height; i++) {
+		for (j = 0; j < width; j++) {
+			tb_set_cell(x + j, y + i, ' ', fg, TB_BLACK);
+		}
+	}
+
+	/* Draw borders */
+	tb_set_cell(x, y, 0x250C, fg, TB_BLACK);
+	tb_set_cell(x + width - 1, y, 0x2510, fg, TB_BLACK);
+	tb_set_cell(x, y + height - 1, 0x2514, fg, TB_BLACK);
+	tb_set_cell(x + width - 1, y + height - 1, 0x2518, fg, TB_BLACK);
+
+	for (i = 1; i < width - 1; i++) {
+		tb_set_cell(x + i, y, 0x2500, fg, TB_BLACK);
+		tb_set_cell(x + i, y + height - 1, 0x2500, fg, TB_BLACK);
+	}
+
+	for (i = 1; i < height - 1; i++) {
+		tb_set_cell(x, y + i, 0x2502, fg, TB_BLACK);
+		tb_set_cell(x + width - 1, y + i, 0x2502, fg, TB_BLACK);
+	}
+
+	if (title && strlen(title) > 0) {
+		int title_x = x + (width - strlen(title) - 2) / 2;
+		tb_set_cell(title_x, y, 0x251C, fg, TB_BLACK);
+		printat(title, title_x + 1, y, fg | TB_BOLD, TB_BLACK);
+		tb_set_cell(title_x + strlen(title) + 1, y, 0x2524, fg, TB_BLACK);
+	}
+}
+
+static void
+displayinfo(const SysInfo *info)
+{
+	struct passwd *pw;
+	struct utsname buf;
+	char displayline[MAXSTRLEN];
+	char temp[64];
+	int width, height, memperc, cpuperc, battperc;
+	int hex_width, max_bytes, bytes_per_line;
+	int ascii_box_width, system_box_width, system_box_x;
+	uint16_t memcolor, cpucolor, battcolor;
+	FILE *hostname_fp;
+	char hostname[64];
+	const char **art;
+	uid_t uid;
+
+	width = tb_width();
+	height = tb_height();
+
+	max_bytes = (width - 15) / 4;
+	if (max_bytes > 32) max_bytes = 32;
+	if (max_bytes < 8) max_bytes = 8;
+	bytes_per_line = (max_bytes / 8) * 8; /* Round to multiple of 8 */
+	hex_width = 10 + (bytes_per_line * 3) + 1 + bytes_per_line + 1; /* addr + hex + space + ascii + | */
+
+	ascii_box_width = (hex_width - 8) / 6;
+	if (ascii_box_width < 20) ascii_box_width = 20;
+	system_box_width = hex_width - ascii_box_width - 6;
+	system_box_x = 2 + ascii_box_width + 2;
+
+	tb_clear();
+	
+	drawhexbackground(width, height);
+	
+	drawhexbanner(0, 1, width, TB_GREEN | TB_BOLD, TB_BLACK);
+
+	/* Draw ASCII art box */
+	art = getasciiart(info->systemstr);
+	drawbox(2, 6, ascii_box_width, 12, " OS ", TB_CYAN, TB_BLACK);
+	drawasciiart(art, 2, 6, ascii_box_width, 12, TB_CYAN | TB_BOLD, TB_BLACK);
+	
+	uid = getuid();
+	pw = getpwuid(uid);
+	
+	/* Get system information */
+	if (uname(&buf) != 0) {
+		strcpy(buf.sysname, "Unknown");
+		strcpy(buf.release, "Unknown");
+		strcpy(buf.machine, "Unknown");
+	}
+
+	strcpy(hostname, "Unknown");
+	hostname_fp = fopen("/etc/hostname", "r");
+	if (hostname_fp) {
+		if (fgets(hostname, sizeof(hostname), hostname_fp))
+			hostname[strcspn(hostname, "\n")] = 0;
+		fclose(hostname_fp);
+	}
+
+	drawbox(system_box_x, 6, system_box_width, 8, " SYSTEM ", TB_GREEN, TB_BLACK);
+	snprintf(displayline, MAXSTRLEN, "%s", info->timestr);
+	printcenteredin(displayline, system_box_x, 8, system_box_width, TB_YELLOW, TB_BLACK);
+	snprintf(displayline, MAXSTRLEN, "%s", info->uptimestr);
+	printcenteredin(displayline, system_box_x, 9, system_box_width, TB_GREEN, TB_BLACK);
+	
+	drawseparator(system_box_x + 2, 10, system_box_width - 4, TB_GREEN, TB_BLACK);
+	
+	snprintf(displayline, MAXSTRLEN, "Host: %s@%s", pw ? pw->pw_name : "Unknown", hostname);
+	printcenteredin(displayline, system_box_x, 11, system_box_width, TB_CYAN, TB_BLACK);
+
+	snprintf(displayline, MAXSTRLEN, "System: %s %s %s", buf.sysname, buf.release, buf.machine);
+	printcenteredin(displayline, system_box_x, 12, system_box_width, TB_CYAN, TB_BLACK);
+
+	drawbox(2, 19, (hex_width - 6) / 2, 9, " RESOURCES ", TB_YELLOW, TB_BLACK);
+	
+	if (sscanf(info->memorystr, "%*d MB / %*d MB (%d%%)", &memperc) != 1)
+		memperc = 0;
+	memcolor = memperc > 80 ? TB_RED : memperc > 60 ? TB_YELLOW : TB_GREEN;
+	
+	printcenteredin("Memory:", 2, 21, (hex_width - 6) / 2, TB_WHITE | TB_BOLD, TB_BLACK);
+	snprintf(temp, sizeof(temp), "%d%%", memperc);
+	printcenteredin(temp, 2, 22, (hex_width - 6) / 2, memcolor, TB_BLACK);
+	printcenteredin(info->memorystr, 2, 23, (hex_width - 6) / 2, TB_BLUE, TB_BLACK);
+
+	if (sscanf(info->cpustr, "%d%%", &cpuperc) != 1)
+		cpuperc = 0;
+	cpucolor = cpuperc > 80 ? TB_RED : cpuperc > 60 ? TB_YELLOW : TB_GREEN;
+	
+	printcenteredin("CPU:", 2, 25, (hex_width - 6) / 2, TB_WHITE | TB_BOLD, TB_BLACK);
+	snprintf(temp, sizeof(temp), "%d%%", cpuperc);
+	printcenteredin(temp, 2, 26, (hex_width - 6) / 2, cpucolor, TB_BLACK);
+
+	drawbox(2 + (hex_width - 6) / 2 + 2, 19, (hex_width - 6) / 2, 15, " CONNECTIVITY ", TB_BLUE, TB_BLACK);
+	
+	printcenteredin("Network:", 2 + (hex_width - 6) / 2 + 2, 21, (hex_width - 6) / 2, TB_WHITE | TB_BOLD, TB_BLACK);
+	if (strstr(info->networkstr, "Connected") != NULL) {
+		printcenteredin(info->networkstr, 2 + (hex_width - 6) / 2 + 2, 22, (hex_width - 6) / 2, TB_GREEN, TB_BLACK);
+	} else {
+		printcenteredin(info->networkstr, 2 + (hex_width - 6) / 2 + 2, 22, (hex_width - 6) / 2, TB_RED, TB_BLACK);
+	}
+	
+	printcenteredin(info->ipstr, 2 + (hex_width - 6) / 2 + 2, 24, (hex_width - 6) / 2, TB_CYAN, TB_BLACK);
+	printcenteredin(info->gatewaystr, 2 + (hex_width - 6) / 2 + 2, 25, (hex_width - 6) / 2, TB_CYAN, TB_BLACK);
+	printcenteredin(info->dnsstr, 2 + (hex_width - 6) / 2 + 2, 26, (hex_width - 6) / 2, TB_CYAN, TB_BLACK);
+	
+	printcenteredin("Security:", 2 + (hex_width - 6) / 2 + 2, 28, (hex_width - 6) / 2, TB_WHITE | TB_BOLD, TB_BLACK);
+	if (strstr(info->vpnstr, "Active") != NULL) {
+		printcenteredin(info->vpnstr, 2 + (hex_width - 6) / 2 + 2, 29, (hex_width - 6) / 2, TB_GREEN, TB_BLACK);
+	} else {
+		printcenteredin(info->vpnstr, 2 + (hex_width - 6) / 2 + 2, 29, (hex_width - 6) / 2, TB_WHITE, TB_BLACK);
+	}
+	
+	drawbox(2, 35, hex_width - 4, 6, " POWER ", TB_MAGENTA, TB_BLACK);
+	
+	if (strstr(info->batterystr, "No battery") == NULL) {
+		if (sscanf(info->batterystr, "%d%%", &battperc) != 1)
+			battperc = 0;
+		battcolor = battperc < 20 ? TB_RED : battperc < 50 ? TB_YELLOW : TB_GREEN;
+		
+		printcenteredin("Battery:", 2, 37, hex_width - 4, TB_WHITE | TB_BOLD, TB_BLACK);
+		snprintf(temp, sizeof(temp), "%d%%", battperc);
+		printcenteredin(temp, 2, 38, hex_width - 4, battcolor, TB_BLACK);
+		printcenteredin(info->batterystr, 2, 39, hex_width - 4, TB_MAGENTA, TB_BLACK);
+	} else {
+		printcenteredin("AC Power Only", 2, 37, hex_width - 4, TB_MAGENTA, TB_BLACK);
+		printcenteredin("No battery detected", 2, 38, hex_width - 4, TB_CYAN, TB_BLACK);
+	}
+
+	/* Create a footer box */
+	drawbox(2, height - 4, hex_width - 4, 3, "", TB_WHITE, TB_BLACK);
+	snprintf(displayline, MAXSTRLEN, "'q' Quit  *  'r' Reboot  *  's' Shutdown  *  Refreshes every %ds", refresh_interval);
+	printcenteredin(displayline, 2, height - 2, hex_width - 4,
+	              TB_WHITE | TB_BOLD, TB_BLACK);
+
+	tb_present();
+}
+
+int
+main(int argc, char *argv[])
+{
+	SysInfo info;
+	struct tb_event ev;
+	struct timeval lastupdate, lasthexupdate, currenttime;
+	int ret;
+
+	argv0 = argv[0];
+
+	if (argc != 1)
+		usage();
+
+	setlocale(LC_ALL, "");
+
+	ret = tb_init();
+	if (ret)
+		die("tb_init() failed");
+
+	tb_set_input_mode(TB_INPUT_ESC);
+
+	gettimeofday(&lastupdate, NULL);
+	gettimeofday(&lasthexupdate, NULL);
+
+	for (;;) {
+		gettimeofday(&currenttime, NULL);
+
+		double elapsed_update = (currenttime.tv_sec - lastupdate.tv_sec) + 
+		                       (currenttime.tv_usec - lastupdate.tv_usec) / 1000000.0;
+		double elapsed_hex = (currenttime.tv_sec - lasthexupdate.tv_sec) + 
+		                    (currenttime.tv_usec - lasthexupdate.tv_usec) / 1000000.0;
+
+		if (elapsed_update >= refresh_interval) {
+			collectsysteminfo(&info);
+			displayinfo(&info);
+			lastupdate = currenttime;
+			lasthexupdate = currenttime;
+		} else if (elapsed_hex >= hex_refresh_interval) {
+			displayinfo(&info);
+			lasthexupdate = currenttime;
+		}
+
+		ret = tb_peek_event(&ev, 100);
+
+		if (ret == TB_OK) {
+			if (ev.type == TB_EVENT_KEY) {
+				if (ev.ch == 'q' || ev.key == TB_KEY_ESC) {
+					break;
+				} else if (ev.ch == 'r') {
+					tb_shutdown();
+					printf("Rebooting system...\n");
+					fflush(stdout);
+					int ret_code = system(reboot_cmd);
+					if (ret_code != 0) {
+						printf("Reboot command failed with exit code: %d\n", ret_code);
+						printf("Command was: %s\n", reboot_cmd);
+						exit(1);
+					}
+					return 0;
+				} else if (ev.ch == 's') {
+					tb_shutdown();
+					printf("Shutting down system...\n");
+					fflush(stdout);
+					int ret_code = system(shutdown_cmd);
+					if (ret_code != 0) {
+						printf("Shutdown command failed with exit code: %d\n", ret_code);
+						printf("Command was: %s\n", shutdown_cmd);
+						exit(1);
+					}
+					return 0;
+				} else {
+					/* Debug: log unhandled keys */
+					// Uncomment for debugging: printf("Unhandled key: ch=%c (%d), key=%d\n", ev.ch, ev.ch, ev.key);
+				}
+			} else if (ev.type == TB_EVENT_RESIZE) {
+				displayinfo(&info);
+			}
+		}
+	}
+
+	tb_shutdown();
+	return 0;
+}
diff --git a/termbox.c b/termbox.c
new file mode 100644
index 0000000..5fd70b5
--- /dev/null
+++ b/termbox.c
@@ -0,0 +1,3 @@
+/* termbox2 implementation */
+#define TB_IMPL
+#include "termbox2.h"
diff --git a/termbox2.h b/termbox2.h
new file mode 100644
index 0000000..070fe98
--- /dev/null
+++ b/termbox2.h
@@ -0,0 +1,4297 @@
+/*
+MIT License
+
+Copyright (c) 2010-2020 nsf <no.smile.face@gmail.com>
+              2015-2025 Adam Saponara <as@php.net>
+
+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.
+*/
+
+#ifndef TERMBOX_H_INCL
+#define TERMBOX_H_INCL
+
+#ifndef _XOPEN_SOURCE
+#define _XOPEN_SOURCE
+#endif
+
+#ifndef _DEFAULT_SOURCE
+#define _DEFAULT_SOURCE
+#endif
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/select.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <termios.h>
+#include <unistd.h>
+#include <wchar.h>
+#include <wctype.h>
+
+#ifdef PATH_MAX
+#define TB_PATH_MAX PATH_MAX
+#else
+#define TB_PATH_MAX 4096
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// __ffi_start
+
+#define TB_VERSION_STR "2.6.0-dev"
+
+/* The following compile-time options are supported:
+ *
+ *     TB_OPT_ATTR_W: Integer width of `fg` and `bg` attributes. Valid values
+ *                    (assuming system support) are 16, 32, and 64. (See
+ *                    `uintattr_t`). 32 or 64 enables output mode
+ *                    `TB_OUTPUT_TRUECOLOR`. 64 enables additional style
+ *                    attributes. (See `tb_set_output_mode`.) Larger values
+ *                    consume more memory in exchange for more features.
+ *                    Defaults to 16.
+ *
+ *        TB_OPT_EGC: If set, enable extended grapheme cluster support
+ *                    (`tb_extend_cell`, `tb_set_cell_ex`). Consumes more
+ *                    memory. Defaults off.
+ *
+ * TB_OPT_PRINTF_BUF: Write buffer size for printf operations. Represents the
+ *                    largest string that can be sent in one call to
+ *                    `tb_print*` and `tb_send*` functions. Defaults to 4096.
+ *
+ *   TB_OPT_READ_BUF: Read buffer size for tty reads. Defaults to 64.
+ *
+ * TB_OPT_LIBC_WCHAR: If set, use libc's `wcwidth(3)`, `iswprint(3)`, etc
+ *                    instead of the built-in Unicode-aware versions. Note,
+ *                    libc's are locale-dependent and the caller must
+ *                    `setlocale(3)` `LC_CTYPE` to UTF-8. Defaults to built-in.
+ *
+ *  TB_OPT_TRUECOLOR: Deprecated. Sets TB_OPT_ATTR_W to 32 if not already set.
+ */
+
+#if defined(TB_LIB_OPTS) || 0 // __tb_lib_opts
+/* Ensure consistent compile-time options when using as a shared library */
+#undef TB_OPT_ATTR_W
+#undef TB_OPT_EGC
+#undef TB_OPT_PRINTF_BUF
+#undef TB_OPT_READ_BUF
+#undef TB_OPT_LIBC_WCHAR
+#define TB_OPT_ATTR_W 64
+#define TB_OPT_EGC
+#endif
+
+/* Ensure sane `TB_OPT_ATTR_W` (16, 32, or 64) */
+#if defined TB_OPT_ATTR_W && TB_OPT_ATTR_W == 16
+#elif defined TB_OPT_ATTR_W && TB_OPT_ATTR_W == 32
+#elif defined TB_OPT_ATTR_W && TB_OPT_ATTR_W == 64
+#else
+#undef TB_OPT_ATTR_W
+#if defined TB_OPT_TRUECOLOR // Deprecated. Back-compat for old flag.
+#define TB_OPT_ATTR_W 32
+#else
+#define TB_OPT_ATTR_W 16
+#endif
+#endif
+
+/* ASCII key constants (`tb_event.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'
+
+#define tb_key_i(i)             0xffff - (i)
+/* Terminal-dependent key constants (`tb_event.key`) and terminfo caps */
+/* BEGIN codegen h */
+/* Produced by ./codegen.sh on Tue, 03 Sep 2024 04:17:47 +0000 */
+#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_BACK_TAB         (0xffff - 22)
+#define TB_KEY_MOUSE_LEFT       (0xffff - 23)
+#define TB_KEY_MOUSE_RIGHT      (0xffff - 24)
+#define TB_KEY_MOUSE_MIDDLE     (0xffff - 25)
+#define TB_KEY_MOUSE_RELEASE    (0xffff - 26)
+#define TB_KEY_MOUSE_WHEEL_UP   (0xffff - 27)
+#define TB_KEY_MOUSE_WHEEL_DOWN (0xffff - 28)
+
+#define TB_CAP_F1               0
+#define TB_CAP_F2               1
+#define TB_CAP_F3               2
+#define TB_CAP_F4               3
+#define TB_CAP_F5               4
+#define TB_CAP_F6               5
+#define TB_CAP_F7               6
+#define TB_CAP_F8               7
+#define TB_CAP_F9               8
+#define TB_CAP_F10              9
+#define TB_CAP_F11              10
+#define TB_CAP_F12              11
+#define TB_CAP_INSERT           12
+#define TB_CAP_DELETE           13
+#define TB_CAP_HOME             14
+#define TB_CAP_END              15
+#define TB_CAP_PGUP             16
+#define TB_CAP_PGDN             17
+#define TB_CAP_ARROW_UP         18
+#define TB_CAP_ARROW_DOWN       19
+#define TB_CAP_ARROW_LEFT       20
+#define TB_CAP_ARROW_RIGHT      21
+#define TB_CAP_BACK_TAB         22
+#define TB_CAP__COUNT_KEYS      23
+#define TB_CAP_ENTER_CA         23
+#define TB_CAP_EXIT_CA          24
+#define TB_CAP_SHOW_CURSOR      25
+#define TB_CAP_HIDE_CURSOR      26
+#define TB_CAP_CLEAR_SCREEN     27
+#define TB_CAP_SGR0             28
+#define TB_CAP_UNDERLINE        29
+#define TB_CAP_BOLD             30
+#define TB_CAP_BLINK            31
+#define TB_CAP_ITALIC           32
+#define TB_CAP_REVERSE          33
+#define TB_CAP_ENTER_KEYPAD     34
+#define TB_CAP_EXIT_KEYPAD      35
+#define TB_CAP_DIM              36
+#define TB_CAP_INVISIBLE        37
+#define TB_CAP__COUNT           38
+/* END codegen h */
+
+/* Some hard-coded caps */
+#define TB_HARDCAP_ENTER_MOUSE  "\x1b[?1000h\x1b[?1002h\x1b[?1015h\x1b[?1006h"
+#define TB_HARDCAP_EXIT_MOUSE   "\x1b[?1006l\x1b[?1015l\x1b[?1002l\x1b[?1000l"
+#define TB_HARDCAP_STRIKEOUT    "\x1b[9m"
+#define TB_HARDCAP_UNDERLINE_2  "\x1b[21m"
+#define TB_HARDCAP_OVERLINE     "\x1b[53m"
+
+/* Colors (numeric) and attributes (bitwise) (`tb_cell.fg`, `tb_cell.bg`) */
+#define TB_DEFAULT              0x0000
+#define TB_BLACK                0x0001
+#define TB_RED                  0x0002
+#define TB_GREEN                0x0003
+#define TB_YELLOW               0x0004
+#define TB_BLUE                 0x0005
+#define TB_MAGENTA              0x0006
+#define TB_CYAN                 0x0007
+#define TB_WHITE                0x0008
+
+#if TB_OPT_ATTR_W == 16
+#define TB_BOLD      0x0100
+#define TB_UNDERLINE 0x0200
+#define TB_REVERSE   0x0400
+#define TB_ITALIC    0x0800
+#define TB_BLINK     0x1000
+#define TB_HI_BLACK  0x2000
+#define TB_BRIGHT    0x4000
+#define TB_DIM       0x8000
+#define TB_256_BLACK TB_HI_BLACK // `TB_256_BLACK` is deprecated
+#else
+// `TB_OPT_ATTR_W` is 32 or 64
+#define TB_BOLD                0x01000000
+#define TB_UNDERLINE           0x02000000
+#define TB_REVERSE             0x04000000
+#define TB_ITALIC              0x08000000
+#define TB_BLINK               0x10000000
+#define TB_HI_BLACK            0x20000000
+#define TB_BRIGHT              0x40000000
+#define TB_DIM                 0x80000000
+#define TB_TRUECOLOR_BOLD      TB_BOLD // `TB_TRUECOLOR_*` is deprecated
+#define TB_TRUECOLOR_UNDERLINE TB_UNDERLINE
+#define TB_TRUECOLOR_REVERSE   TB_REVERSE
+#define TB_TRUECOLOR_ITALIC    TB_ITALIC
+#define TB_TRUECOLOR_BLINK     TB_BLINK
+#define TB_TRUECOLOR_BLACK     TB_HI_BLACK
+#endif
+
+#if TB_OPT_ATTR_W == 64
+#define TB_STRIKEOUT   0x0000000100000000
+#define TB_UNDERLINE_2 0x0000000200000000
+#define TB_OVERLINE    0x0000000400000000
+#define TB_INVISIBLE   0x0000000800000000
+#endif
+
+/* Event types (`tb_event.type`) */
+#define TB_EVENT_KEY        1
+#define TB_EVENT_RESIZE     2
+#define TB_EVENT_MOUSE      3
+
+/* Key modifiers (bitwise) (`tb_event.mod`) */
+#define TB_MOD_ALT          1
+#define TB_MOD_CTRL         2
+#define TB_MOD_SHIFT        4
+#define TB_MOD_MOTION       8
+
+/* Input modes (bitwise) (`tb_set_input_mode`) */
+#define TB_INPUT_CURRENT    0
+#define TB_INPUT_ESC        1
+#define TB_INPUT_ALT        2
+#define TB_INPUT_MOUSE      4
+
+/* Output modes (`tb_set_output_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
+#if TB_OPT_ATTR_W >= 32
+#define TB_OUTPUT_TRUECOLOR 5
+#endif
+
+/* Common function return values unless otherwise noted.
+ *
+ * Library behavior is undefined after receiving `TB_ERR_MEM`. Callers may
+ * attempt reinitializing by freeing memory, invoking `tb_shutdown`, then
+ * `tb_init`.
+ */
+#define TB_OK                   0
+#define TB_ERR                  -1
+#define TB_ERR_NEED_MORE        -2
+#define TB_ERR_INIT_ALREADY     -3
+#define TB_ERR_INIT_OPEN        -4
+#define TB_ERR_MEM              -5
+#define TB_ERR_NO_EVENT         -6
+#define TB_ERR_NO_TERM          -7
+#define TB_ERR_NOT_INIT         -8
+#define TB_ERR_OUT_OF_BOUNDS    -9
+#define TB_ERR_READ             -10
+#define TB_ERR_RESIZE_IOCTL     -11
+#define TB_ERR_RESIZE_PIPE      -12
+#define TB_ERR_RESIZE_SIGACTION -13
+#define TB_ERR_POLL             -14
+#define TB_ERR_TCGETATTR        -15
+#define TB_ERR_TCSETATTR        -16
+#define TB_ERR_UNSUPPORTED_TERM -17
+#define TB_ERR_RESIZE_WRITE     -18
+#define TB_ERR_RESIZE_POLL      -19
+#define TB_ERR_RESIZE_READ      -20
+#define TB_ERR_RESIZE_SSCANF    -21
+#define TB_ERR_CAP_COLLISION    -22
+
+#define TB_ERR_SELECT           TB_ERR_POLL
+#define TB_ERR_RESIZE_SELECT    TB_ERR_RESIZE_POLL
+
+/* Deprecated. Function types to be used with `tb_set_func`. */
+#define TB_FUNC_EXTRACT_PRE     0
+#define TB_FUNC_EXTRACT_POST    1
+
+/* Define this to set the size of the buffer used in `tb_printf`
+ * and `tb_sendf`
+ */
+#ifndef TB_OPT_PRINTF_BUF
+#define TB_OPT_PRINTF_BUF 4096
+#endif
+
+/* Define this to set the size of the read buffer used when reading
+ * from the tty
+ */
+#ifndef TB_OPT_READ_BUF
+#define TB_OPT_READ_BUF 64
+#endif
+
+/* Define this for limited back compat with termbox v1 */
+#ifdef TB_OPT_V1_COMPAT
+#define tb_change_cell          tb_set_cell
+#define tb_put_cell(x, y, c)    tb_set_cell((x), (y), (c)->ch, (c)->fg, (c)->bg)
+#define tb_set_clear_attributes tb_set_clear_attrs
+#define tb_select_input_mode    tb_set_input_mode
+#define tb_select_output_mode   tb_set_output_mode
+#endif
+
+/* Define these to swap in a different allocator */
+#ifndef tb_malloc
+#define tb_malloc  malloc
+#define tb_realloc realloc
+#define tb_free    free
+#endif
+
+#if TB_OPT_ATTR_W == 64
+typedef uint64_t uintattr_t;
+#elif TB_OPT_ATTR_W == 32
+typedef uint32_t uintattr_t;
+#else // 16
+typedef uint16_t uintattr_t;
+#endif
+
+/* A cell in a 2d grid representing the terminal screen.
+ *
+ * The terminal screen is represented as 2d array of cells. The structure is
+ * optimized for dealing with single-width (`wcwidth==1`) Unicode codepoints,
+ * however some support for grapheme clusters (e.g., combining diacritical
+ * marks) and wide codepoints (e.g., Hiragana) is provided through `ech`,
+ * `nech`, and `cech` via `tb_set_cell_ex`. `ech` is only valid when `nech>0`,
+ * otherwise `ch` is used.
+ *
+ * For non-single-width codepoints, given `N=wcwidth(ch)/wcswidth(ech)`:
+ *
+ * when `N==0`: termbox forces a single-width cell. Callers should avoid this
+ *              if aiming to render text accurately. Callers may use
+ *              `tb_set_cell_ex` or `tb_print*` to render `N==0` combining
+ *              characters.
+ *
+ *  when `N>1`: termbox zeroes out the following `N-1` cells and skips sending
+ *              them to the tty. So, e.g., if the caller sets `x=0,y=0` to an
+ *              `N==2` codepoint, the caller's next set should be at `x=2,y=0`.
+ *              Anything set at `x=1,y=0` will be ignored. If there are not
+ *              enough columns remaining on the line to render `N` width, spaces
+ *              are sent instead.
+ *
+ * See `tb_present` for implementation.
+ */
+struct tb_cell {
+    uint32_t ch;   // a Unicode codepoint
+    uintattr_t fg; // bitwise foreground attributes
+    uintattr_t bg; // bitwise background attributes
+#ifdef TB_OPT_EGC
+    uint32_t *ech; // a grapheme cluster of Unicode codepoints, 0-terminated
+    size_t nech;   // num elements in ech, 0 means use ch instead of ech
+    size_t cech;   // num elements allocated for ech
+#endif
+};
+
+/* An incoming event from the tty.
+ *
+ * Given the event type, the following fields are relevant:
+ *
+ *    when `TB_EVENT_KEY`: `key` xor `ch` (one will be zero) and `mod`. Note
+ *                         there is overlap between `TB_MOD_CTRL` and
+ *                         `TB_KEY_CTRL_*`. `TB_MOD_CTRL` and `TB_MOD_SHIFT` are
+ *                         only set as modifiers to `TB_KEY_ARROW_*`.
+ *
+ * when `TB_EVENT_RESIZE`: `w` and `h`
+ *
+ *  when `TB_EVENT_MOUSE`: `key` (`TB_KEY_MOUSE_*`), `x`, and `y`
+ */
+struct tb_event {
+    uint8_t type; // one of `TB_EVENT_*` constants
+    uint8_t mod;  // bitwise `TB_MOD_*` constants
+    uint16_t key; // one of `TB_KEY_*` constants
+    uint32_t ch;  // a Unicode codepoint
+    int32_t w;    // resize width
+    int32_t h;    // resize height
+    int32_t x;    // mouse x
+    int32_t y;    // mouse y
+};
+
+/* Initialize the termbox library. This function should be called before any
+ * other functions. `tb_init` is equivalent to `tb_init_file("/dev/tty")`. After
+ * successful initialization, the library must be finalized using `tb_shutdown`.
+ */
+int tb_init(void);
+int tb_init_file(const char *path);
+int tb_init_fd(int ttyfd);
+int tb_init_rwfd(int rfd, int wfd);
+int tb_shutdown(void);
+
+/* Return the size of the internal back buffer (which is the same as terminal's
+ * window size in rows and columns). The internal buffer can be resized after
+ * `tb_clear` or `tb_present` calls. Both dimensions have an unspecified
+ * negative value when called before `tb_init` or after `tb_shutdown`.
+ */
+int tb_width(void);
+int tb_height(void);
+
+/* Clear the internal back buffer using `TB_DEFAULT` or the attributes set by
+ * `tb_set_clear_attrs`.
+ */
+int tb_clear(void);
+int tb_set_clear_attrs(uintattr_t fg, uintattr_t bg);
+
+/* Synchronize the internal back buffer with the terminal by writing to tty. */
+int tb_present(void);
+
+/* Clear the internal front buffer effectively forcing a complete re-render of
+ * the back buffer to the tty. It is not necessary to call this under normal
+ * circumstances.
+ */
+int tb_invalidate(void);
+
+/* Set the position of the cursor. Upper-left cell is (0, 0). */
+int tb_set_cursor(int cx, int cy);
+int tb_hide_cursor(void);
+
+/* Set cell contents in the internal back buffer at the specified position.
+ *
+ * Use `tb_set_cell_ex` for rendering grapheme clusters (e.g., combining
+ * diacritical marks).
+ *
+ * Calling `tb_set_cell(x, y, ch, fg, bg)` is equivalent to
+ * `tb_set_cell_ex(x, y, &ch, 1, fg, bg)`.
+ *
+ * `tb_extend_cell` is a shortcut for appending 1 codepoint to `tb_cell.ech`.
+ *
+ * Non-printable (`iswprint(3)`) codepoints are replaced with `U+FFFD` at render
+ * time.
+ */
+int tb_set_cell(int x, int y, uint32_t ch, uintattr_t fg, uintattr_t bg);
+int tb_set_cell_ex(int x, int y, uint32_t *ch, size_t nch, uintattr_t fg,
+    uintattr_t bg);
+int tb_extend_cell(int x, int y, uint32_t ch);
+
+/* Set the input mode. Termbox has two input modes:
+ *
+ * 1. `TB_INPUT_ESC`
+ *    When escape (`\x1b`) is in the buffer and there's no match for an escape
+ *    sequence, a key event for `TB_KEY_ESC` is returned.
+ *
+ * 2. `TB_INPUT_ALT`
+ *    When escape (`\x1b`) is in the buffer and there's no match for an escape
+ *    sequence, the next keyboard event is returned with a `TB_MOD_ALT`
+ *    modifier.
+ *
+ * You can also apply `TB_INPUT_MOUSE` via bitwise OR operation to either of the
+ * modes (e.g., `TB_INPUT_ESC | TB_INPUT_MOUSE`) to receive `TB_EVENT_MOUSE`
+ * events. If none of the main two modes were set, but the mouse mode was,
+ * `TB_INPUT_ESC` is used. If for some reason you've decided to use
+ * `TB_INPUT_ESC | TB_INPUT_ALT`, it will behave as if only `TB_INPUT_ESC` was
+ * selected.
+ *
+ * If mode is `TB_INPUT_CURRENT`, return the current input mode.
+ *
+ * The default input mode is `TB_INPUT_ESC`.
+ */
+int tb_set_input_mode(int mode);
+
+/* Set the output mode. Termbox has multiple output modes:
+ *
+ * 1. `TB_OUTPUT_NORMAL`     => [0..8]
+ *
+ *    This mode provides 8 different colors:
+ *      `TB_BLACK`, `TB_RED`, `TB_GREEN`, `TB_YELLOW`,
+ *      `TB_BLUE`, `TB_MAGENTA`, `TB_CYAN`, `TB_WHITE`
+ *
+ *    Plus `TB_DEFAULT` which skips sending a color code (i.e., uses the
+ *    terminal's default color).
+ *
+ *    Colors (including `TB_DEFAULT`) may be bitwise OR'd with attributes:
+ *      `TB_BOLD`, `TB_UNDERLINE`, `TB_REVERSE`, `TB_ITALIC`, `TB_BLINK`,
+ *      `TB_BRIGHT`, `TB_DIM`
+ *
+ *    The following style attributes are also available if compiled with
+ *    `TB_OPT_ATTR_W` set to 64:
+ *      `TB_STRIKEOUT`, `TB_UNDERLINE_2`, `TB_OVERLINE`, `TB_INVISIBLE`
+ *
+ *    As in all modes, the value 0 is interpreted as `TB_DEFAULT` for
+ *    convenience.
+ *
+ *    Some notes: `TB_REVERSE` and `TB_BRIGHT` can be applied as either `fg` or
+ *    `bg` attributes for the same effect. The rest of the attributes apply to
+ *    `fg` only and are ignored as `bg` attributes.
+ *
+ *    Example usage: `tb_set_cell(x, y, '@', TB_BLACK | TB_BOLD, TB_RED)`
+ *
+ * 2. `TB_OUTPUT_256`        => [0..255] + `TB_HI_BLACK`
+ *
+ *    In this mode you get 256 distinct colors (plus default):
+ *                0x00   (1): `TB_DEFAULT`
+ *       `TB_HI_BLACK`   (1): `TB_BLACK` in `TB_OUTPUT_NORMAL`
+ *          0x01..0x07   (7): the next 7 colors as in `TB_OUTPUT_NORMAL`
+ *          0x08..0x0f   (8): bright versions of the above
+ *          0x10..0xe7 (216): 216 different colors
+ *          0xe8..0xff  (24): 24 different shades of gray
+ *
+ *    All `TB_*` style attributes except `TB_BRIGHT` may be bitwise OR'd as in
+ *    `TB_OUTPUT_NORMAL`.
+ *
+ *    Note `TB_HI_BLACK` must be used for black, as 0x00 represents default.
+ *
+ * 3. `TB_OUTPUT_216`        => [0..216]
+ *
+ *    This mode supports the 216-color range of `TB_OUTPUT_256` only, but you
+ *    don't need to provide an offset:
+ *                0x00   (1): `TB_DEFAULT`
+ *          0x01..0xd8 (216): 216 different colors
+ *
+ * 4. `TB_OUTPUT_GRAYSCALE`  => [0..24]
+ *
+ *    This mode supports the 24-color range of `TB_OUTPUT_256` only, but you
+ *    don't need to provide an offset:
+ *                0x00   (1): `TB_DEFAULT`
+ *          0x01..0x18  (24): 24 different shades of gray
+ *
+ * 5. `TB_OUTPUT_TRUECOLOR`  => [0x000000..0xffffff] + `TB_HI_BLACK`
+ *
+ *    This mode provides 24-bit color on supported terminals. The format is
+ *    0xRRGGBB.
+ *
+ *    All `TB_*` style attributes except `TB_BRIGHT` may be bitwise OR'd as in
+ *    `TB_OUTPUT_NORMAL`.
+ *
+ *    Note `TB_HI_BLACK` must be used for black, as 0x000000 represents default.
+ *
+ * To use the terminal default color (i.e., to not send an escape code), pass
+ * `TB_DEFAULT`. For convenience, the value 0 is interpreted as `TB_DEFAULT` in
+ * all modes.
+ *
+ * Note, cell attributes persist after switching output modes. Any translation
+ * between, for example, `TB_OUTPUT_NORMAL`'s `TB_RED` and
+ * `TB_OUTPUT_TRUECOLOR`'s 0xff0000 must be performed by the caller. Also note
+ * that cells previously rendered in one mode may persist unchanged until the
+ * front buffer is cleared (such as after a resize event) at which point it will
+ * be re-interpreted and flushed according to the current mode. Callers may
+ * invoke `tb_invalidate` if it is desirable to immediately re-interpret and
+ * flush the entire screen according to the current mode.
+ *
+ * Note, not all terminals support all output modes, especially beyond
+ * `TB_OUTPUT_NORMAL`. There is also no very reliable way to determine color
+ * support dynamically. If portability is desired, callers are recommended to
+ * use `TB_OUTPUT_NORMAL` or make output mode end-user configurable. The same
+ * advice applies to style attributes.
+ *
+ * If mode is `TB_OUTPUT_CURRENT`, return the current output mode.
+ *
+ * The default output mode is `TB_OUTPUT_NORMAL`.
+ */
+int tb_set_output_mode(int mode);
+
+/* Wait for an event up to `timeout_ms` milliseconds and populate `event` with
+ * it. If no event is available within the timeout period, `TB_ERR_NO_EVENT`
+ * is returned. On a resize event, the underlying `select(2)` call may be
+ * interrupted, yielding a return code of `TB_ERR_POLL`. In this case, you may
+ * check `errno` via `tb_last_errno`. If it's `EINTR`, you may elect to ignore
+ * that and call `tb_peek_event` again.
+ */
+int tb_peek_event(struct tb_event *event, int timeout_ms);
+
+/* Same as `tb_peek_event` except no timeout. */
+int tb_poll_event(struct tb_event *event);
+
+/* Internal termbox fds that can be used with `poll(2)`, `select(2)`, etc.
+ * externally. Callers must invoke `tb_poll_event` or `tb_peek_event` if
+ * fds become readable.
+ */
+int tb_get_fds(int *ttyfd, int *resizefd);
+
+/* Print and printf functions. Specify param `out_w` to determine width of
+ * printed string. Strings are interpreted as UTF-8.
+ *
+ * Non-printable characters (`iswprint(3)`) and truncated UTF-8 byte sequences
+ * are replaced with U+FFFD.
+ *
+ * Newlines (`\n`) are supported with the caveat that `out_w` will return the
+ * width of the string as if it were on a single line.
+ *
+ * If the starting coordinate is out of bounds, `TB_ERR_OUT_OF_BOUNDS` is
+ * returned. If the starting coordinate is in bounds, but goes out of bounds,
+ * then the out-of-bounds portions of the string are ignored.
+ *
+ * For finer control, use `tb_set_cell`.
+ */
+int tb_print(int x, int y, uintattr_t fg, uintattr_t bg, const char *str);
+int tb_printf(int x, int y, uintattr_t fg, uintattr_t bg, const char *fmt, ...);
+int tb_print_ex(int x, int y, uintattr_t fg, uintattr_t bg, size_t *out_w,
+    const char *str);
+int tb_printf_ex(int x, int y, uintattr_t fg, uintattr_t bg, size_t *out_w,
+    const char *fmt, ...);
+
+/* Send raw bytes to terminal. */
+int tb_send(const char *buf, size_t nbuf);
+int tb_sendf(const char *fmt, ...);
+
+/* Deprecated. Set custom callbacks. `fn_type` is one of `TB_FUNC_*` constants,
+ * `fn` is a compatible function pointer, or NULL to clear.
+ *
+ * `TB_FUNC_EXTRACT_PRE`:
+ *   If specified, invoke this function BEFORE termbox tries to extract any
+ *   escape sequences from the input buffer.
+ *
+ * `TB_FUNC_EXTRACT_POST`:
+ *   If specified, invoke this function AFTER termbox tries (and fails) to
+ *   extract any escape sequences from the input buffer.
+ */
+int tb_set_func(int fn_type, int (*fn)(struct tb_event *, size_t *));
+
+/* Return byte length of codepoint given first byte of UTF-8 sequence (1-6). */
+int tb_utf8_char_length(char c);
+
+/* Convert UTF-8 null-terminated byte sequence to UTF-32 codepoint.
+ *
+ * If `c` is an empty C string, return 0. `out` is left unchanged.
+ *
+ * If a null byte is encountered in the middle of the codepoint, return a
+ * negative number indicating how many bytes were processed. `out` is left
+ * unchanged.
+ *
+ * Otherwise, return byte length of codepoint (1-6).
+ */
+int tb_utf8_char_to_unicode(uint32_t *out, const char *c);
+
+/* Convert UTF-32 codepoint to UTF-8 null-terminated byte sequence.
+ *
+ * `out` must be char[7] or greater. Return byte length of codepoint (1-6).
+ */
+int tb_utf8_unicode_to_char(char *out, uint32_t c);
+
+/* Library utility functions */
+int tb_last_errno(void);
+const char *tb_strerror(int err);
+struct tb_cell *tb_cell_buffer(void); // Deprecated
+int tb_has_truecolor(void);
+int tb_has_egc(void);
+int tb_attr_width(void);
+const char *tb_version(void);
+int tb_iswprint(uint32_t ch);
+int tb_wcwidth(uint32_t ch);
+
+/* Deprecation notice!
+ *
+ * The following will be removed in version 3.x (ABI version 3):
+ *
+ *   TB_256_BLACK           (use TB_HI_BLACK)
+ *   TB_OPT_TRUECOLOR       (use TB_OPT_ATTR_W)
+ *   TB_TRUECOLOR_BOLD      (use TB_BOLD)
+ *   TB_TRUECOLOR_UNDERLINE (use TB_UNDERLINE)
+ *   TB_TRUECOLOR_REVERSE   (use TB_REVERSE)
+ *   TB_TRUECOLOR_ITALIC    (use TB_ITALIC)
+ *   TB_TRUECOLOR_BLINK     (use TB_BLINK)
+ *   TB_TRUECOLOR_BLACK     (use TB_HI_BLACK)
+ *   tb_cell_buffer
+ *   tb_set_func
+ *   TB_FUNC_EXTRACT_PRE
+ *   TB_FUNC_EXTRACT_POST
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // TERMBOX_H_INCL
+
+#ifdef TB_IMPL
+
+#define if_err_return(rv, expr)                                                \
+    if (((rv) = (expr)) != TB_OK) return (rv)
+#define if_err_break(rv, expr)                                                 \
+    if (((rv) = (expr)) != TB_OK) break
+#define if_ok_return(rv, expr)                                                 \
+    if (((rv) = (expr)) == TB_OK) return (rv)
+#define if_ok_or_need_more_return(rv, expr)                                    \
+    if (((rv) = (expr)) == TB_OK || (rv) == TB_ERR_NEED_MORE) return (rv)
+
+#define send_literal(rv, a)                                                    \
+    if_err_return((rv), bytebuf_nputs(&global.out, (a), sizeof(a) - 1))
+
+#define send_num(rv, nbuf, n)                                                  \
+    if_err_return((rv),                                                        \
+        bytebuf_nputs(&global.out, (nbuf), convert_num((n), (nbuf))))
+
+#define snprintf_or_return(rv, str, sz, fmt, ...)                              \
+    do {                                                                       \
+        (rv) = snprintf((str), (sz), (fmt), __VA_ARGS__);                      \
+        if ((rv) < 0 || (rv) >= (int)(sz)) return TB_ERR;                      \
+    } while (0)
+
+#define if_not_init_return()                                                   \
+    if (!global.initialized) return TB_ERR_NOT_INIT
+
+struct bytebuf_t {
+    char *buf;
+    size_t len;
+    size_t cap;
+};
+
+struct cellbuf_t {
+    int width;
+    int height;
+    struct tb_cell *cells;
+};
+
+struct cap_trie_t {
+    char c;
+    struct cap_trie_t *children;
+    size_t nchildren;
+    int is_leaf;
+    uint16_t key;
+    uint8_t mod;
+};
+
+struct tb_global_t {
+    int ttyfd;
+    int rfd;
+    int wfd;
+    int ttyfd_open;
+    int resize_pipefd[2];
+    int width;
+    int height;
+    int cursor_x;
+    int cursor_y;
+    int last_x;
+    int last_y;
+    uintattr_t fg;
+    uintattr_t bg;
+    uintattr_t last_fg;
+    uintattr_t last_bg;
+    int input_mode;
+    int output_mode;
+    char *terminfo;
+    size_t nterminfo;
+    const char *caps[TB_CAP__COUNT];
+    struct cap_trie_t cap_trie;
+    struct bytebuf_t in;
+    struct bytebuf_t out;
+    struct cellbuf_t back;
+    struct cellbuf_t front;
+    struct termios orig_tios;
+    int has_orig_tios;
+    int last_errno;
+    int initialized;
+    int (*fn_extract_esc_pre)(struct tb_event *, size_t *);
+    int (*fn_extract_esc_post)(struct tb_event *, size_t *);
+    char errbuf[1024];
+};
+
+static struct tb_global_t global = {0};
+
+/* BEGIN codegen c */
+/* Produced by ./codegen.sh on Tue, 03 Sep 2024 04:17:48 +0000 */
+
+static const int16_t terminfo_cap_indexes[] = {
+    66,  // kf1 (TB_CAP_F1)
+    68,  // kf2 (TB_CAP_F2)
+    69,  // kf3 (TB_CAP_F3)
+    70,  // kf4 (TB_CAP_F4)
+    71,  // kf5 (TB_CAP_F5)
+    72,  // kf6 (TB_CAP_F6)
+    73,  // kf7 (TB_CAP_F7)
+    74,  // kf8 (TB_CAP_F8)
+    75,  // kf9 (TB_CAP_F9)
+    67,  // kf10 (TB_CAP_F10)
+    216, // kf11 (TB_CAP_F11)
+    217, // kf12 (TB_CAP_F12)
+    77,  // kich1 (TB_CAP_INSERT)
+    59,  // kdch1 (TB_CAP_DELETE)
+    76,  // khome (TB_CAP_HOME)
+    164, // kend (TB_CAP_END)
+    82,  // kpp (TB_CAP_PGUP)
+    81,  // knp (TB_CAP_PGDN)
+    87,  // kcuu1 (TB_CAP_ARROW_UP)
+    61,  // kcud1 (TB_CAP_ARROW_DOWN)
+    79,  // kcub1 (TB_CAP_ARROW_LEFT)
+    83,  // kcuf1 (TB_CAP_ARROW_RIGHT)
+    148, // kcbt (TB_CAP_BACK_TAB)
+    28,  // smcup (TB_CAP_ENTER_CA)
+    40,  // rmcup (TB_CAP_EXIT_CA)
+    16,  // cnorm (TB_CAP_SHOW_CURSOR)
+    13,  // civis (TB_CAP_HIDE_CURSOR)
+    5,   // clear (TB_CAP_CLEAR_SCREEN)
+    39,  // sgr0 (TB_CAP_SGR0)
+    36,  // smul (TB_CAP_UNDERLINE)
+    27,  // bold (TB_CAP_BOLD)
+    26,  // blink (TB_CAP_BLINK)
+    311, // sitm (TB_CAP_ITALIC)
+    34,  // rev (TB_CAP_REVERSE)
+    89,  // smkx (TB_CAP_ENTER_KEYPAD)
+    88,  // rmkx (TB_CAP_EXIT_KEYPAD)
+    30,  // dim (TB_CAP_DIM)
+    32,  // invis (TB_CAP_INVISIBLE)
+};
+
+// xterm
+static const char *xterm_caps[] = {
+    "\033OP",                  // kf1 (TB_CAP_F1)
+    "\033OQ",                  // kf2 (TB_CAP_F2)
+    "\033OR",                  // kf3 (TB_CAP_F3)
+    "\033OS",                  // kf4 (TB_CAP_F4)
+    "\033[15~",                // kf5 (TB_CAP_F5)
+    "\033[17~",                // kf6 (TB_CAP_F6)
+    "\033[18~",                // kf7 (TB_CAP_F7)
+    "\033[19~",                // kf8 (TB_CAP_F8)
+    "\033[20~",                // kf9 (TB_CAP_F9)
+    "\033[21~",                // kf10 (TB_CAP_F10)
+    "\033[23~",                // kf11 (TB_CAP_F11)
+    "\033[24~",                // kf12 (TB_CAP_F12)
+    "\033[2~",                 // kich1 (TB_CAP_INSERT)
+    "\033[3~",                 // kdch1 (TB_CAP_DELETE)
+    "\033OH",                  // khome (TB_CAP_HOME)
+    "\033OF",                  // kend (TB_CAP_END)
+    "\033[5~",                 // kpp (TB_CAP_PGUP)
+    "\033[6~",                 // knp (TB_CAP_PGDN)
+    "\033OA",                  // kcuu1 (TB_CAP_ARROW_UP)
+    "\033OB",                  // kcud1 (TB_CAP_ARROW_DOWN)
+    "\033OD",                  // kcub1 (TB_CAP_ARROW_LEFT)
+    "\033OC",                  // kcuf1 (TB_CAP_ARROW_RIGHT)
+    "\033[Z",                  // kcbt (TB_CAP_BACK_TAB)
+    "\033[?1049h\033[22;0;0t", // smcup (TB_CAP_ENTER_CA)
+    "\033[?1049l\033[23;0;0t", // rmcup (TB_CAP_EXIT_CA)
+    "\033[?12l\033[?25h",      // cnorm (TB_CAP_SHOW_CURSOR)
+    "\033[?25l",               // civis (TB_CAP_HIDE_CURSOR)
+    "\033[H\033[2J",           // clear (TB_CAP_CLEAR_SCREEN)
+    "\033(B\033[m",            // sgr0 (TB_CAP_SGR0)
+    "\033[4m",                 // smul (TB_CAP_UNDERLINE)
+    "\033[1m",                 // bold (TB_CAP_BOLD)
+    "\033[5m",                 // blink (TB_CAP_BLINK)
+    "\033[3m",                 // sitm (TB_CAP_ITALIC)
+    "\033[7m",                 // rev (TB_CAP_REVERSE)
+    "\033[?1h\033=",           // smkx (TB_CAP_ENTER_KEYPAD)
+    "\033[?1l\033>",           // rmkx (TB_CAP_EXIT_KEYPAD)
+    "\033[2m",                 // dim (TB_CAP_DIM)
+    "\033[8m",                 // invis (TB_CAP_INVISIBLE)
+};
+
+// linux
+static const char *linux_caps[] = {
+    "\033[[A",           // kf1 (TB_CAP_F1)
+    "\033[[B",           // kf2 (TB_CAP_F2)
+    "\033[[C",           // kf3 (TB_CAP_F3)
+    "\033[[D",           // kf4 (TB_CAP_F4)
+    "\033[[E",           // kf5 (TB_CAP_F5)
+    "\033[17~",          // kf6 (TB_CAP_F6)
+    "\033[18~",          // kf7 (TB_CAP_F7)
+    "\033[19~",          // kf8 (TB_CAP_F8)
+    "\033[20~",          // kf9 (TB_CAP_F9)
+    "\033[21~",          // kf10 (TB_CAP_F10)
+    "\033[23~",          // kf11 (TB_CAP_F11)
+    "\033[24~",          // kf12 (TB_CAP_F12)
+    "\033[2~",           // kich1 (TB_CAP_INSERT)
+    "\033[3~",           // kdch1 (TB_CAP_DELETE)
+    "\033[1~",           // khome (TB_CAP_HOME)
+    "\033[4~",           // kend (TB_CAP_END)
+    "\033[5~",           // kpp (TB_CAP_PGUP)
+    "\033[6~",           // knp (TB_CAP_PGDN)
+    "\033[A",            // kcuu1 (TB_CAP_ARROW_UP)
+    "\033[B",            // kcud1 (TB_CAP_ARROW_DOWN)
+    "\033[D",            // kcub1 (TB_CAP_ARROW_LEFT)
+    "\033[C",            // kcuf1 (TB_CAP_ARROW_RIGHT)
+    "\033\011",          // kcbt (TB_CAP_BACK_TAB)
+    "",                  // smcup (TB_CAP_ENTER_CA)
+    "",                  // rmcup (TB_CAP_EXIT_CA)
+    "\033[?25h\033[?0c", // cnorm (TB_CAP_SHOW_CURSOR)
+    "\033[?25l\033[?1c", // civis (TB_CAP_HIDE_CURSOR)
+    "\033[H\033[J",      // clear (TB_CAP_CLEAR_SCREEN)
+    "\033[m\017",        // sgr0 (TB_CAP_SGR0)
+    "\033[4m",           // smul (TB_CAP_UNDERLINE)
+    "\033[1m",           // bold (TB_CAP_BOLD)
+    "\033[5m",           // blink (TB_CAP_BLINK)
+    "",                  // sitm (TB_CAP_ITALIC)
+    "\033[7m",           // rev (TB_CAP_REVERSE)
+    "",                  // smkx (TB_CAP_ENTER_KEYPAD)
+    "",                  // rmkx (TB_CAP_EXIT_KEYPAD)
+    "\033[2m",           // dim (TB_CAP_DIM)
+    "",                  // invis (TB_CAP_INVISIBLE)
+};
+
+// screen
+static const char *screen_caps[] = {
+    "\033OP",            // kf1 (TB_CAP_F1)
+    "\033OQ",            // kf2 (TB_CAP_F2)
+    "\033OR",            // kf3 (TB_CAP_F3)
+    "\033OS",            // kf4 (TB_CAP_F4)
+    "\033[15~",          // kf5 (TB_CAP_F5)
+    "\033[17~",          // kf6 (TB_CAP_F6)
+    "\033[18~",          // kf7 (TB_CAP_F7)
+    "\033[19~",          // kf8 (TB_CAP_F8)
+    "\033[20~",          // kf9 (TB_CAP_F9)
+    "\033[21~",          // kf10 (TB_CAP_F10)
+    "\033[23~",          // kf11 (TB_CAP_F11)
+    "\033[24~",          // kf12 (TB_CAP_F12)
+    "\033[2~",           // kich1 (TB_CAP_INSERT)
+    "\033[3~",           // kdch1 (TB_CAP_DELETE)
+    "\033[1~",           // khome (TB_CAP_HOME)
+    "\033[4~",           // kend (TB_CAP_END)
+    "\033[5~",           // kpp (TB_CAP_PGUP)
+    "\033[6~",           // knp (TB_CAP_PGDN)
+    "\033OA",            // kcuu1 (TB_CAP_ARROW_UP)
+    "\033OB",            // kcud1 (TB_CAP_ARROW_DOWN)
+    "\033OD",            // kcub1 (TB_CAP_ARROW_LEFT)
+    "\033OC",            // kcuf1 (TB_CAP_ARROW_RIGHT)
+    "\033[Z",            // kcbt (TB_CAP_BACK_TAB)
+    "\033[?1049h",       // smcup (TB_CAP_ENTER_CA)
+    "\033[?1049l",       // rmcup (TB_CAP_EXIT_CA)
+    "\033[34h\033[?25h", // cnorm (TB_CAP_SHOW_CURSOR)
+    "\033[?25l",         // civis (TB_CAP_HIDE_CURSOR)
+    "\033[H\033[J",      // clear (TB_CAP_CLEAR_SCREEN)
+    "\033[m\017",        // sgr0 (TB_CAP_SGR0)
+    "\033[4m",           // smul (TB_CAP_UNDERLINE)
+    "\033[1m",           // bold (TB_CAP_BOLD)
+    "\033[5m",           // blink (TB_CAP_BLINK)
+    "",                  // sitm (TB_CAP_ITALIC)
+    "\033[7m",           // rev (TB_CAP_REVERSE)
+    "\033[?1h\033=",     // smkx (TB_CAP_ENTER_KEYPAD)
+    "\033[?1l\033>",     // rmkx (TB_CAP_EXIT_KEYPAD)
+    "\033[2m",           // dim (TB_CAP_DIM)
+    "",                  // invis (TB_CAP_INVISIBLE)
+};
+
+// rxvt-256color
+static const char *rxvt_256color_caps[] = {
+    "\033[11~",              // kf1 (TB_CAP_F1)
+    "\033[12~",              // kf2 (TB_CAP_F2)
+    "\033[13~",              // kf3 (TB_CAP_F3)
+    "\033[14~",              // kf4 (TB_CAP_F4)
+    "\033[15~",              // kf5 (TB_CAP_F5)
+    "\033[17~",              // kf6 (TB_CAP_F6)
+    "\033[18~",              // kf7 (TB_CAP_F7)
+    "\033[19~",              // kf8 (TB_CAP_F8)
+    "\033[20~",              // kf9 (TB_CAP_F9)
+    "\033[21~",              // kf10 (TB_CAP_F10)
+    "\033[23~",              // kf11 (TB_CAP_F11)
+    "\033[24~",              // kf12 (TB_CAP_F12)
+    "\033[2~",               // kich1 (TB_CAP_INSERT)
+    "\033[3~",               // kdch1 (TB_CAP_DELETE)
+    "\033[7~",               // khome (TB_CAP_HOME)
+    "\033[8~",               // kend (TB_CAP_END)
+    "\033[5~",               // kpp (TB_CAP_PGUP)
+    "\033[6~",               // knp (TB_CAP_PGDN)
+    "\033[A",                // kcuu1 (TB_CAP_ARROW_UP)
+    "\033[B",                // kcud1 (TB_CAP_ARROW_DOWN)
+    "\033[D",                // kcub1 (TB_CAP_ARROW_LEFT)
+    "\033[C",                // kcuf1 (TB_CAP_ARROW_RIGHT)
+    "\033[Z",                // kcbt (TB_CAP_BACK_TAB)
+    "\0337\033[?47h",        // smcup (TB_CAP_ENTER_CA)
+    "\033[2J\033[?47l\0338", // rmcup (TB_CAP_EXIT_CA)
+    "\033[?25h",             // cnorm (TB_CAP_SHOW_CURSOR)
+    "\033[?25l",             // civis (TB_CAP_HIDE_CURSOR)
+    "\033[H\033[2J",         // clear (TB_CAP_CLEAR_SCREEN)
+    "\033[m\017",            // sgr0 (TB_CAP_SGR0)
+    "\033[4m",               // smul (TB_CAP_UNDERLINE)
+    "\033[1m",               // bold (TB_CAP_BOLD)
+    "\033[5m",               // blink (TB_CAP_BLINK)
+    "",                      // sitm (TB_CAP_ITALIC)
+    "\033[7m",               // rev (TB_CAP_REVERSE)
+    "\033=",                 // smkx (TB_CAP_ENTER_KEYPAD)
+    "\033>",                 // rmkx (TB_CAP_EXIT_KEYPAD)
+    "",                      // dim (TB_CAP_DIM)
+    "",                      // invis (TB_CAP_INVISIBLE)
+};
+
+// rxvt-unicode
+static const char *rxvt_unicode_caps[] = {
+    "\033[11~",           // kf1 (TB_CAP_F1)
+    "\033[12~",           // kf2 (TB_CAP_F2)
+    "\033[13~",           // kf3 (TB_CAP_F3)
+    "\033[14~",           // kf4 (TB_CAP_F4)
+    "\033[15~",           // kf5 (TB_CAP_F5)
+    "\033[17~",           // kf6 (TB_CAP_F6)
+    "\033[18~",           // kf7 (TB_CAP_F7)
+    "\033[19~",           // kf8 (TB_CAP_F8)
+    "\033[20~",           // kf9 (TB_CAP_F9)
+    "\033[21~",           // kf10 (TB_CAP_F10)
+    "\033[23~",           // kf11 (TB_CAP_F11)
+    "\033[24~",           // kf12 (TB_CAP_F12)
+    "\033[2~",            // kich1 (TB_CAP_INSERT)
+    "\033[3~",            // kdch1 (TB_CAP_DELETE)
+    "\033[7~",            // khome (TB_CAP_HOME)
+    "\033[8~",            // kend (TB_CAP_END)
+    "\033[5~",            // kpp (TB_CAP_PGUP)
+    "\033[6~",            // knp (TB_CAP_PGDN)
+    "\033[A",             // kcuu1 (TB_CAP_ARROW_UP)
+    "\033[B",             // kcud1 (TB_CAP_ARROW_DOWN)
+    "\033[D",             // kcub1 (TB_CAP_ARROW_LEFT)
+    "\033[C",             // kcuf1 (TB_CAP_ARROW_RIGHT)
+    "\033[Z",             // kcbt (TB_CAP_BACK_TAB)
+    "\033[?1049h",        // smcup (TB_CAP_ENTER_CA)
+    "\033[r\033[?1049l",  // rmcup (TB_CAP_EXIT_CA)
+    "\033[?12l\033[?25h", // cnorm (TB_CAP_SHOW_CURSOR)
+    "\033[?25l",          // civis (TB_CAP_HIDE_CURSOR)
+    "\033[H\033[2J",      // clear (TB_CAP_CLEAR_SCREEN)
+    "\033[m\033(B",       // sgr0 (TB_CAP_SGR0)
+    "\033[4m",            // smul (TB_CAP_UNDERLINE)
+    "\033[1m",            // bold (TB_CAP_BOLD)
+    "\033[5m",            // blink (TB_CAP_BLINK)
+    "\033[3m",            // sitm (TB_CAP_ITALIC)
+    "\033[7m",            // rev (TB_CAP_REVERSE)
+    "\033=",              // smkx (TB_CAP_ENTER_KEYPAD)
+    "\033>",              // rmkx (TB_CAP_EXIT_KEYPAD)
+    "",                   // dim (TB_CAP_DIM)
+    "",                   // invis (TB_CAP_INVISIBLE)
+};
+
+// Eterm
+static const char *eterm_caps[] = {
+    "\033[11~",              // kf1 (TB_CAP_F1)
+    "\033[12~",              // kf2 (TB_CAP_F2)
+    "\033[13~",              // kf3 (TB_CAP_F3)
+    "\033[14~",              // kf4 (TB_CAP_F4)
+    "\033[15~",              // kf5 (TB_CAP_F5)
+    "\033[17~",              // kf6 (TB_CAP_F6)
+    "\033[18~",              // kf7 (TB_CAP_F7)
+    "\033[19~",              // kf8 (TB_CAP_F8)
+    "\033[20~",              // kf9 (TB_CAP_F9)
+    "\033[21~",              // kf10 (TB_CAP_F10)
+    "\033[23~",              // kf11 (TB_CAP_F11)
+    "\033[24~",              // kf12 (TB_CAP_F12)
+    "\033[2~",               // kich1 (TB_CAP_INSERT)
+    "\033[3~",               // kdch1 (TB_CAP_DELETE)
+    "\033[7~",               // khome (TB_CAP_HOME)
+    "\033[8~",               // kend (TB_CAP_END)
+    "\033[5~",               // kpp (TB_CAP_PGUP)
+    "\033[6~",               // knp (TB_CAP_PGDN)
+    "\033[A",                // kcuu1 (TB_CAP_ARROW_UP)
+    "\033[B",                // kcud1 (TB_CAP_ARROW_DOWN)
+    "\033[D",                // kcub1 (TB_CAP_ARROW_LEFT)
+    "\033[C",                // kcuf1 (TB_CAP_ARROW_RIGHT)
+    "",                      // kcbt (TB_CAP_BACK_TAB)
+    "\0337\033[?47h",        // smcup (TB_CAP_ENTER_CA)
+    "\033[2J\033[?47l\0338", // rmcup (TB_CAP_EXIT_CA)
+    "\033[?25h",             // cnorm (TB_CAP_SHOW_CURSOR)
+    "\033[?25l",             // civis (TB_CAP_HIDE_CURSOR)
+    "\033[H\033[2J",         // clear (TB_CAP_CLEAR_SCREEN)
+    "\033[m\017",            // sgr0 (TB_CAP_SGR0)
+    "\033[4m",               // smul (TB_CAP_UNDERLINE)
+    "\033[1m",               // bold (TB_CAP_BOLD)
+    "\033[5m",               // blink (TB_CAP_BLINK)
+    "",                      // sitm (TB_CAP_ITALIC)
+    "\033[7m",               // rev (TB_CAP_REVERSE)
+    "",                      // smkx (TB_CAP_ENTER_KEYPAD)
+    "",                      // rmkx (TB_CAP_EXIT_KEYPAD)
+    "",                      // dim (TB_CAP_DIM)
+    "",                      // invis (TB_CAP_INVISIBLE)
+};
+
+static struct {
+    const char *name;
+    const char **caps;
+    const char *alias;
+} builtin_terms[] = {
+    {"xterm",         xterm_caps,         ""    },
+    {"linux",         linux_caps,         ""    },
+    {"screen",        screen_caps,        "tmux"},
+    {"rxvt-256color", rxvt_256color_caps, ""    },
+    {"rxvt-unicode",  rxvt_unicode_caps,  "rxvt"},
+    {"Eterm",         eterm_caps,         ""    },
+    {NULL,            NULL,               NULL  },
+};
+
+/* END codegen c */
+
+static struct {
+    const char *cap;
+    const uint16_t key;
+    const uint8_t mod;
+} builtin_mod_caps[] = {
+    // xterm arrows
+    {"\x1b[1;2A",    TB_KEY_ARROW_UP,    TB_MOD_SHIFT                           },
+    {"\x1b[1;3A",    TB_KEY_ARROW_UP,    TB_MOD_ALT                             },
+    {"\x1b[1;4A",    TB_KEY_ARROW_UP,    TB_MOD_ALT | TB_MOD_SHIFT              },
+    {"\x1b[1;5A",    TB_KEY_ARROW_UP,    TB_MOD_CTRL                            },
+    {"\x1b[1;6A",    TB_KEY_ARROW_UP,    TB_MOD_CTRL | TB_MOD_SHIFT             },
+    {"\x1b[1;7A",    TB_KEY_ARROW_UP,    TB_MOD_CTRL | TB_MOD_ALT               },
+    {"\x1b[1;8A",    TB_KEY_ARROW_UP,    TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
+
+    {"\x1b[1;2B",    TB_KEY_ARROW_DOWN,  TB_MOD_SHIFT                           },
+    {"\x1b[1;3B",    TB_KEY_ARROW_DOWN,  TB_MOD_ALT                             },
+    {"\x1b[1;4B",    TB_KEY_ARROW_DOWN,  TB_MOD_ALT | TB_MOD_SHIFT              },
+    {"\x1b[1;5B",    TB_KEY_ARROW_DOWN,  TB_MOD_CTRL                            },
+    {"\x1b[1;6B",    TB_KEY_ARROW_DOWN,  TB_MOD_CTRL | TB_MOD_SHIFT             },
+    {"\x1b[1;7B",    TB_KEY_ARROW_DOWN,  TB_MOD_CTRL | TB_MOD_ALT               },
+    {"\x1b[1;8B",    TB_KEY_ARROW_DOWN,  TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
+
+    {"\x1b[1;2C",    TB_KEY_ARROW_RIGHT, TB_MOD_SHIFT                           },
+    {"\x1b[1;3C",    TB_KEY_ARROW_RIGHT, TB_MOD_ALT                             },
+    {"\x1b[1;4C",    TB_KEY_ARROW_RIGHT, TB_MOD_ALT | TB_MOD_SHIFT              },
+    {"\x1b[1;5C",    TB_KEY_ARROW_RIGHT, TB_MOD_CTRL                            },
+    {"\x1b[1;6C",    TB_KEY_ARROW_RIGHT, TB_MOD_CTRL | TB_MOD_SHIFT             },
+    {"\x1b[1;7C",    TB_KEY_ARROW_RIGHT, TB_MOD_CTRL | TB_MOD_ALT               },
+    {"\x1b[1;8C",    TB_KEY_ARROW_RIGHT, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
+
+    {"\x1b[1;2D",    TB_KEY_ARROW_LEFT,  TB_MOD_SHIFT                           },
+    {"\x1b[1;3D",    TB_KEY_ARROW_LEFT,  TB_MOD_ALT                             },
+    {"\x1b[1;4D",    TB_KEY_ARROW_LEFT,  TB_MOD_ALT | TB_MOD_SHIFT              },
+    {"\x1b[1;5D",    TB_KEY_ARROW_LEFT,  TB_MOD_CTRL                            },
+    {"\x1b[1;6D",    TB_KEY_ARROW_LEFT,  TB_MOD_CTRL | TB_MOD_SHIFT             },
+    {"\x1b[1;7D",    TB_KEY_ARROW_LEFT,  TB_MOD_CTRL | TB_MOD_ALT               },
+    {"\x1b[1;8D",    TB_KEY_ARROW_LEFT,  TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
+
+    // xterm keys
+    {"\x1b[1;2H",    TB_KEY_HOME,        TB_MOD_SHIFT                           },
+    {"\x1b[1;3H",    TB_KEY_HOME,        TB_MOD_ALT                             },
+    {"\x1b[1;4H",    TB_KEY_HOME,        TB_MOD_ALT | TB_MOD_SHIFT              },
+    {"\x1b[1;5H",    TB_KEY_HOME,        TB_MOD_CTRL                            },
+    {"\x1b[1;6H",    TB_KEY_HOME,        TB_MOD_CTRL | TB_MOD_SHIFT             },
+    {"\x1b[1;7H",    TB_KEY_HOME,        TB_MOD_CTRL | TB_MOD_ALT               },
+    {"\x1b[1;8H",    TB_KEY_HOME,        TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
+
+    {"\x1b[1;2F",    TB_KEY_END,         TB_MOD_SHIFT                           },
+    {"\x1b[1;3F",    TB_KEY_END,         TB_MOD_ALT                             },
+    {"\x1b[1;4F",    TB_KEY_END,         TB_MOD_ALT | TB_MOD_SHIFT              },
+    {"\x1b[1;5F",    TB_KEY_END,         TB_MOD_CTRL                            },
+    {"\x1b[1;6F",    TB_KEY_END,         TB_MOD_CTRL | TB_MOD_SHIFT             },
+    {"\x1b[1;7F",    TB_KEY_END,         TB_MOD_CTRL | TB_MOD_ALT               },
+    {"\x1b[1;8F",    TB_KEY_END,         TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
+
+    {"\x1b[2;2~",    TB_KEY_INSERT,      TB_MOD_SHIFT                           },
+    {"\x1b[2;3~",    TB_KEY_INSERT,      TB_MOD_ALT                             },
+    {"\x1b[2;4~",    TB_KEY_INSERT,      TB_MOD_ALT | TB_MOD_SHIFT              },
+    {"\x1b[2;5~",    TB_KEY_INSERT,      TB_MOD_CTRL                            },
+    {"\x1b[2;6~",    TB_KEY_INSERT,      TB_MOD_CTRL | TB_MOD_SHIFT             },
+    {"\x1b[2;7~",    TB_KEY_INSERT,      TB_MOD_CTRL | TB_MOD_ALT               },
+    {"\x1b[2;8~",    TB_KEY_INSERT,      TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
+
+    {"\x1b[3;2~",    TB_KEY_DELETE,      TB_MOD_SHIFT                           },
+    {"\x1b[3;3~",    TB_KEY_DELETE,      TB_MOD_ALT                             },
+    {"\x1b[3;4~",    TB_KEY_DELETE,      TB_MOD_ALT | TB_MOD_SHIFT              },
+    {"\x1b[3;5~",    TB_KEY_DELETE,      TB_MOD_CTRL                            },
+    {"\x1b[3;6~",    TB_KEY_DELETE,      TB_MOD_CTRL | TB_MOD_SHIFT             },
+    {"\x1b[3;7~",    TB_KEY_DELETE,      TB_MOD_CTRL | TB_MOD_ALT               },
+    {"\x1b[3;8~",    TB_KEY_DELETE,      TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
+
+    {"\x1b[5;2~",    TB_KEY_PGUP,        TB_MOD_SHIFT                           },
+    {"\x1b[5;3~",    TB_KEY_PGUP,        TB_MOD_ALT                             },
+    {"\x1b[5;4~",    TB_KEY_PGUP,        TB_MOD_ALT | TB_MOD_SHIFT              },
+    {"\x1b[5;5~",    TB_KEY_PGUP,        TB_MOD_CTRL                            },
+    {"\x1b[5;6~",    TB_KEY_PGUP,        TB_MOD_CTRL | TB_MOD_SHIFT             },
+    {"\x1b[5;7~",    TB_KEY_PGUP,        TB_MOD_CTRL | TB_MOD_ALT               },
+    {"\x1b[5;8~",    TB_KEY_PGUP,        TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
+
+    {"\x1b[6;2~",    TB_KEY_PGDN,        TB_MOD_SHIFT                           },
+    {"\x1b[6;3~",    TB_KEY_PGDN,        TB_MOD_ALT                             },
+    {"\x1b[6;4~",    TB_KEY_PGDN,        TB_MOD_ALT | TB_MOD_SHIFT              },
+    {"\x1b[6;5~",    TB_KEY_PGDN,        TB_MOD_CTRL                            },
+    {"\x1b[6;6~",    TB_KEY_PGDN,        TB_MOD_CTRL | TB_MOD_SHIFT             },
+    {"\x1b[6;7~",    TB_KEY_PGDN,        TB_MOD_CTRL | TB_MOD_ALT               },
+    {"\x1b[6;8~",    TB_KEY_PGDN,        TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
+
+    {"\x1b[1;2P",    TB_KEY_F1,          TB_MOD_SHIFT                           },
+    {"\x1b[1;3P",    TB_KEY_F1,          TB_MOD_ALT                             },
+    {"\x1b[1;4P",    TB_KEY_F1,          TB_MOD_ALT | TB_MOD_SHIFT              },
+    {"\x1b[1;5P",    TB_KEY_F1,          TB_MOD_CTRL                            },
+    {"\x1b[1;6P",    TB_KEY_F1,          TB_MOD_CTRL | TB_MOD_SHIFT             },
+    {"\x1b[1;7P",    TB_KEY_F1,          TB_MOD_CTRL | TB_MOD_ALT               },
+    {"\x1b[1;8P",    TB_KEY_F1,          TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
+
+    {"\x1b[1;2Q",    TB_KEY_F2,          TB_MOD_SHIFT                           },
+    {"\x1b[1;3Q",    TB_KEY_F2,          TB_MOD_ALT                             },
+    {"\x1b[1;4Q",    TB_KEY_F2,          TB_MOD_ALT | TB_MOD_SHIFT              },
+    {"\x1b[1;5Q",    TB_KEY_F2,          TB_MOD_CTRL                            },
+    {"\x1b[1;6Q",    TB_KEY_F2,          TB_MOD_CTRL | TB_MOD_SHIFT             },
+    {"\x1b[1;7Q",    TB_KEY_F2,          TB_MOD_CTRL | TB_MOD_ALT               },
+    {"\x1b[1;8Q",    TB_KEY_F2,          TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
+
+    {"\x1b[1;2R",    TB_KEY_F3,          TB_MOD_SHIFT                           },
+    {"\x1b[1;3R",    TB_KEY_F3,          TB_MOD_ALT                             },
+    {"\x1b[1;4R",    TB_KEY_F3,          TB_MOD_ALT | TB_MOD_SHIFT              },
+    {"\x1b[1;5R",    TB_KEY_F3,          TB_MOD_CTRL                            },
+    {"\x1b[1;6R",    TB_KEY_F3,          TB_MOD_CTRL | TB_MOD_SHIFT             },
+    {"\x1b[1;7R",    TB_KEY_F3,          TB_MOD_CTRL | TB_MOD_ALT               },
+    {"\x1b[1;8R",    TB_KEY_F3,          TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
+
+    {"\x1b[1;2S",    TB_KEY_F4,          TB_MOD_SHIFT                           },
+    {"\x1b[1;3S",    TB_KEY_F4,          TB_MOD_ALT                             },
+    {"\x1b[1;4S",    TB_KEY_F4,          TB_MOD_ALT | TB_MOD_SHIFT              },
+    {"\x1b[1;5S",    TB_KEY_F4,          TB_MOD_CTRL                            },
+    {"\x1b[1;6S",    TB_KEY_F4,          TB_MOD_CTRL | TB_MOD_SHIFT             },
+    {"\x1b[1;7S",    TB_KEY_F4,          TB_MOD_CTRL | TB_MOD_ALT               },
+    {"\x1b[1;8S",    TB_KEY_F4,          TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
+
+    {"\x1b[15;2~",   TB_KEY_F5,          TB_MOD_SHIFT                           },
+    {"\x1b[15;3~",   TB_KEY_F5,          TB_MOD_ALT                             },
+    {"\x1b[15;4~",   TB_KEY_F5,          TB_MOD_ALT | TB_MOD_SHIFT              },
+    {"\x1b[15;5~",   TB_KEY_F5,          TB_MOD_CTRL                            },
+    {"\x1b[15;6~",   TB_KEY_F5,          TB_MOD_CTRL | TB_MOD_SHIFT             },
+    {"\x1b[15;7~",   TB_KEY_F5,          TB_MOD_CTRL | TB_MOD_ALT               },
+    {"\x1b[15;8~",   TB_KEY_F5,          TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
+
+    {"\x1b[17;2~",   TB_KEY_F6,          TB_MOD_SHIFT                           },
+    {"\x1b[17;3~",   TB_KEY_F6,          TB_MOD_ALT                             },
+    {"\x1b[17;4~",   TB_KEY_F6,          TB_MOD_ALT | TB_MOD_SHIFT              },
+    {"\x1b[17;5~",   TB_KEY_F6,          TB_MOD_CTRL                            },
+    {"\x1b[17;6~",   TB_KEY_F6,          TB_MOD_CTRL | TB_MOD_SHIFT             },
+    {"\x1b[17;7~",   TB_KEY_F6,          TB_MOD_CTRL | TB_MOD_ALT               },
+    {"\x1b[17;8~",   TB_KEY_F6,          TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
+
+    {"\x1b[18;2~",   TB_KEY_F7,          TB_MOD_SHIFT                           },
+    {"\x1b[18;3~",   TB_KEY_F7,          TB_MOD_ALT                             },
+    {"\x1b[18;4~",   TB_KEY_F7,          TB_MOD_ALT | TB_MOD_SHIFT              },
+    {"\x1b[18;5~",   TB_KEY_F7,          TB_MOD_CTRL                            },
+    {"\x1b[18;6~",   TB_KEY_F7,          TB_MOD_CTRL | TB_MOD_SHIFT             },
+    {"\x1b[18;7~",   TB_KEY_F7,          TB_MOD_CTRL | TB_MOD_ALT               },
+    {"\x1b[18;8~",   TB_KEY_F7,          TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
+
+    {"\x1b[19;2~",   TB_KEY_F8,          TB_MOD_SHIFT                           },
+    {"\x1b[19;3~",   TB_KEY_F8,          TB_MOD_ALT                             },
+    {"\x1b[19;4~",   TB_KEY_F8,          TB_MOD_ALT | TB_MOD_SHIFT              },
+    {"\x1b[19;5~",   TB_KEY_F8,          TB_MOD_CTRL                            },
+    {"\x1b[19;6~",   TB_KEY_F8,          TB_MOD_CTRL | TB_MOD_SHIFT             },
+    {"\x1b[19;7~",   TB_KEY_F8,          TB_MOD_CTRL | TB_MOD_ALT               },
+    {"\x1b[19;8~",   TB_KEY_F8,          TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
+
+    {"\x1b[20;2~",   TB_KEY_F9,          TB_MOD_SHIFT                           },
+    {"\x1b[20;3~",   TB_KEY_F9,          TB_MOD_ALT                             },
+    {"\x1b[20;4~",   TB_KEY_F9,          TB_MOD_ALT | TB_MOD_SHIFT              },
+    {"\x1b[20;5~",   TB_KEY_F9,          TB_MOD_CTRL                            },
+    {"\x1b[20;6~",   TB_KEY_F9,          TB_MOD_CTRL | TB_MOD_SHIFT             },
+    {"\x1b[20;7~",   TB_KEY_F9,          TB_MOD_CTRL | TB_MOD_ALT               },
+    {"\x1b[20;8~",   TB_KEY_F9,          TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
+
+    {"\x1b[21;2~",   TB_KEY_F10,         TB_MOD_SHIFT                           },
+    {"\x1b[21;3~",   TB_KEY_F10,         TB_MOD_ALT                             },
+    {"\x1b[21;4~",   TB_KEY_F10,         TB_MOD_ALT | TB_MOD_SHIFT              },
+    {"\x1b[21;5~",   TB_KEY_F10,         TB_MOD_CTRL                            },
+    {"\x1b[21;6~",   TB_KEY_F10,         TB_MOD_CTRL | TB_MOD_SHIFT             },
+    {"\x1b[21;7~",   TB_KEY_F10,         TB_MOD_CTRL | TB_MOD_ALT               },
+    {"\x1b[21;8~",   TB_KEY_F10,         TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
+
+    {"\x1b[23;2~",   TB_KEY_F11,         TB_MOD_SHIFT                           },
+    {"\x1b[23;3~",   TB_KEY_F11,         TB_MOD_ALT                             },
+    {"\x1b[23;4~",   TB_KEY_F11,         TB_MOD_ALT | TB_MOD_SHIFT              },
+    {"\x1b[23;5~",   TB_KEY_F11,         TB_MOD_CTRL                            },
+    {"\x1b[23;6~",   TB_KEY_F11,         TB_MOD_CTRL | TB_MOD_SHIFT             },
+    {"\x1b[23;7~",   TB_KEY_F11,         TB_MOD_CTRL | TB_MOD_ALT               },
+    {"\x1b[23;8~",   TB_KEY_F11,         TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
+
+    {"\x1b[24;2~",   TB_KEY_F12,         TB_MOD_SHIFT                           },
+    {"\x1b[24;3~",   TB_KEY_F12,         TB_MOD_ALT                             },
+    {"\x1b[24;4~",   TB_KEY_F12,         TB_MOD_ALT | TB_MOD_SHIFT              },
+    {"\x1b[24;5~",   TB_KEY_F12,         TB_MOD_CTRL                            },
+    {"\x1b[24;6~",   TB_KEY_F12,         TB_MOD_CTRL | TB_MOD_SHIFT             },
+    {"\x1b[24;7~",   TB_KEY_F12,         TB_MOD_CTRL | TB_MOD_ALT               },
+    {"\x1b[24;8~",   TB_KEY_F12,         TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
+
+    // rxvt arrows
+    {"\x1b[a",       TB_KEY_ARROW_UP,    TB_MOD_SHIFT                           },
+    {"\x1b\x1b[A",   TB_KEY_ARROW_UP,    TB_MOD_ALT                             },
+    {"\x1b\x1b[a",   TB_KEY_ARROW_UP,    TB_MOD_ALT | TB_MOD_SHIFT              },
+    {"\x1bOa",       TB_KEY_ARROW_UP,    TB_MOD_CTRL                            },
+    {"\x1b\x1bOa",   TB_KEY_ARROW_UP,    TB_MOD_CTRL | TB_MOD_ALT               },
+
+    {"\x1b[b",       TB_KEY_ARROW_DOWN,  TB_MOD_SHIFT                           },
+    {"\x1b\x1b[B",   TB_KEY_ARROW_DOWN,  TB_MOD_ALT                             },
+    {"\x1b\x1b[b",   TB_KEY_ARROW_DOWN,  TB_MOD_ALT | TB_MOD_SHIFT              },
+    {"\x1bOb",       TB_KEY_ARROW_DOWN,  TB_MOD_CTRL                            },
+    {"\x1b\x1bOb",   TB_KEY_ARROW_DOWN,  TB_MOD_CTRL | TB_MOD_ALT               },
+
+    {"\x1b[c",       TB_KEY_ARROW_RIGHT, TB_MOD_SHIFT                           },
+    {"\x1b\x1b[C",   TB_KEY_ARROW_RIGHT, TB_MOD_ALT                             },
+    {"\x1b\x1b[c",   TB_KEY_ARROW_RIGHT, TB_MOD_ALT | TB_MOD_SHIFT              },
+    {"\x1bOc",       TB_KEY_ARROW_RIGHT, TB_MOD_CTRL                            },
+    {"\x1b\x1bOc",   TB_KEY_ARROW_RIGHT, TB_MOD_CTRL | TB_MOD_ALT               },
+
+    {"\x1b[d",       TB_KEY_ARROW_LEFT,  TB_MOD_SHIFT                           },
+    {"\x1b\x1b[D",   TB_KEY_ARROW_LEFT,  TB_MOD_ALT                             },
+    {"\x1b\x1b[d",   TB_KEY_ARROW_LEFT,  TB_MOD_ALT | TB_MOD_SHIFT              },
+    {"\x1bOd",       TB_KEY_ARROW_LEFT,  TB_MOD_CTRL                            },
+    {"\x1b\x1bOd",   TB_KEY_ARROW_LEFT,  TB_MOD_CTRL | TB_MOD_ALT               },
+
+    // rxvt keys
+    {"\x1b[7$",      TB_KEY_HOME,        TB_MOD_SHIFT                           },
+    {"\x1b\x1b[7~",  TB_KEY_HOME,        TB_MOD_ALT                             },
+    {"\x1b\x1b[7$",  TB_KEY_HOME,        TB_MOD_ALT | TB_MOD_SHIFT              },
+    {"\x1b[7^",      TB_KEY_HOME,        TB_MOD_CTRL                            },
+    {"\x1b[7@",      TB_KEY_HOME,        TB_MOD_CTRL | TB_MOD_SHIFT             },
+    {"\x1b\x1b[7^",  TB_KEY_HOME,        TB_MOD_CTRL | TB_MOD_ALT               },
+    {"\x1b\x1b[7@",  TB_KEY_HOME,        TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
+
+    {"\x1b\x1b[8~",  TB_KEY_END,         TB_MOD_ALT                             },
+    {"\x1b\x1b[8$",  TB_KEY_END,         TB_MOD_ALT | TB_MOD_SHIFT              },
+    {"\x1b[8^",      TB_KEY_END,         TB_MOD_CTRL                            },
+    {"\x1b\x1b[8^",  TB_KEY_END,         TB_MOD_CTRL | TB_MOD_ALT               },
+    {"\x1b\x1b[8@",  TB_KEY_END,         TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
+    {"\x1b[8@",      TB_KEY_END,         TB_MOD_CTRL | TB_MOD_SHIFT             },
+    {"\x1b[8$",      TB_KEY_END,         TB_MOD_SHIFT                           },
+
+    {"\x1b\x1b[2~",  TB_KEY_INSERT,      TB_MOD_ALT                             },
+    {"\x1b\x1b[2$",  TB_KEY_INSERT,      TB_MOD_ALT | TB_MOD_SHIFT              },
+    {"\x1b[2^",      TB_KEY_INSERT,      TB_MOD_CTRL                            },
+    {"\x1b\x1b[2^",  TB_KEY_INSERT,      TB_MOD_CTRL | TB_MOD_ALT               },
+    {"\x1b\x1b[2@",  TB_KEY_INSERT,      TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
+    {"\x1b[2@",      TB_KEY_INSERT,      TB_MOD_CTRL | TB_MOD_SHIFT             },
+    {"\x1b[2$",      TB_KEY_INSERT,      TB_MOD_SHIFT                           },
+
+    {"\x1b\x1b[3~",  TB_KEY_DELETE,      TB_MOD_ALT                             },
+    {"\x1b\x1b[3$",  TB_KEY_DELETE,      TB_MOD_ALT | TB_MOD_SHIFT              },
+    {"\x1b[3^",      TB_KEY_DELETE,      TB_MOD_CTRL                            },
+    {"\x1b\x1b[3^",  TB_KEY_DELETE,      TB_MOD_CTRL | TB_MOD_ALT               },
+    {"\x1b\x1b[3@",  TB_KEY_DELETE,      TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
+    {"\x1b[3@",      TB_KEY_DELETE,      TB_MOD_CTRL | TB_MOD_SHIFT             },
+    {"\x1b[3$",      TB_KEY_DELETE,      TB_MOD_SHIFT                           },
+
+    {"\x1b\x1b[5~",  TB_KEY_PGUP,        TB_MOD_ALT                             },
+    {"\x1b\x1b[5$",  TB_KEY_PGUP,        TB_MOD_ALT | TB_MOD_SHIFT              },
+    {"\x1b[5^",      TB_KEY_PGUP,        TB_MOD_CTRL                            },
+    {"\x1b\x1b[5^",  TB_KEY_PGUP,        TB_MOD_CTRL | TB_MOD_ALT               },
+    {"\x1b\x1b[5@",  TB_KEY_PGUP,        TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
+    {"\x1b[5@",      TB_KEY_PGUP,        TB_MOD_CTRL | TB_MOD_SHIFT             },
+    {"\x1b[5$",      TB_KEY_PGUP,        TB_MOD_SHIFT                           },
+
+    {"\x1b\x1b[6~",  TB_KEY_PGDN,        TB_MOD_ALT                             },
+    {"\x1b\x1b[6$",  TB_KEY_PGDN,        TB_MOD_ALT | TB_MOD_SHIFT              },
+    {"\x1b[6^",      TB_KEY_PGDN,        TB_MOD_CTRL                            },
+    {"\x1b\x1b[6^",  TB_KEY_PGDN,        TB_MOD_CTRL | TB_MOD_ALT               },
+    {"\x1b\x1b[6@",  TB_KEY_PGDN,        TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
+    {"\x1b[6@",      TB_KEY_PGDN,        TB_MOD_CTRL | TB_MOD_SHIFT             },
+    {"\x1b[6$",      TB_KEY_PGDN,        TB_MOD_SHIFT                           },
+
+    {"\x1b\x1b[11~", TB_KEY_F1,          TB_MOD_ALT                             },
+    {"\x1b\x1b[23~", TB_KEY_F1,          TB_MOD_ALT | TB_MOD_SHIFT              },
+    {"\x1b[11^",     TB_KEY_F1,          TB_MOD_CTRL                            },
+    {"\x1b\x1b[11^", TB_KEY_F1,          TB_MOD_CTRL | TB_MOD_ALT               },
+    {"\x1b\x1b[23^", TB_KEY_F1,          TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
+    {"\x1b[23^",     TB_KEY_F1,          TB_MOD_CTRL | TB_MOD_SHIFT             },
+    {"\x1b[23~",     TB_KEY_F1,          TB_MOD_SHIFT                           },
+
+    {"\x1b\x1b[12~", TB_KEY_F2,          TB_MOD_ALT                             },
+    {"\x1b\x1b[24~", TB_KEY_F2,          TB_MOD_ALT | TB_MOD_SHIFT              },
+    {"\x1b[12^",     TB_KEY_F2,          TB_MOD_CTRL                            },
+    {"\x1b\x1b[12^", TB_KEY_F2,          TB_MOD_CTRL | TB_MOD_ALT               },
+    {"\x1b\x1b[24^", TB_KEY_F2,          TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
+    {"\x1b[24^",     TB_KEY_F2,          TB_MOD_CTRL | TB_MOD_SHIFT             },
+    {"\x1b[24~",     TB_KEY_F2,          TB_MOD_SHIFT                           },
+
+    {"\x1b\x1b[13~", TB_KEY_F3,          TB_MOD_ALT                             },
+    {"\x1b\x1b[25~", TB_KEY_F3,          TB_MOD_ALT | TB_MOD_SHIFT              },
+    {"\x1b[13^",     TB_KEY_F3,          TB_MOD_CTRL                            },
+    {"\x1b\x1b[13^", TB_KEY_F3,          TB_MOD_CTRL | TB_MOD_ALT               },
+    {"\x1b\x1b[25^", TB_KEY_F3,          TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
+    {"\x1b[25^",     TB_KEY_F3,          TB_MOD_CTRL | TB_MOD_SHIFT             },
+    {"\x1b[25~",     TB_KEY_F3,          TB_MOD_SHIFT                           },
+
+    {"\x1b\x1b[14~", TB_KEY_F4,          TB_MOD_ALT                             },
+    {"\x1b\x1b[26~", TB_KEY_F4,          TB_MOD_ALT | TB_MOD_SHIFT              },
+    {"\x1b[14^",     TB_KEY_F4,          TB_MOD_CTRL                            },
+    {"\x1b\x1b[14^", TB_KEY_F4,          TB_MOD_CTRL | TB_MOD_ALT               },
+    {"\x1b\x1b[26^", TB_KEY_F4,          TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
+    {"\x1b[26^",     TB_KEY_F4,          TB_MOD_CTRL | TB_MOD_SHIFT             },
+    {"\x1b[26~",     TB_KEY_F4,          TB_MOD_SHIFT                           },
+
+    {"\x1b\x1b[15~", TB_KEY_F5,          TB_MOD_ALT                             },
+    {"\x1b\x1b[28~", TB_KEY_F5,          TB_MOD_ALT | TB_MOD_SHIFT              },
+    {"\x1b[15^",     TB_KEY_F5,          TB_MOD_CTRL                            },
+    {"\x1b\x1b[15^", TB_KEY_F5,          TB_MOD_CTRL | TB_MOD_ALT               },
+    {"\x1b\x1b[28^", TB_KEY_F5,          TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
+    {"\x1b[28^",     TB_KEY_F5,          TB_MOD_CTRL | TB_MOD_SHIFT             },
+    {"\x1b[28~",     TB_KEY_F5,          TB_MOD_SHIFT                           },
+
+    {"\x1b\x1b[17~", TB_KEY_F6,          TB_MOD_ALT                             },
+    {"\x1b\x1b[29~", TB_KEY_F6,          TB_MOD_ALT | TB_MOD_SHIFT              },
+    {"\x1b[17^",     TB_KEY_F6,          TB_MOD_CTRL                            },
+    {"\x1b\x1b[17^", TB_KEY_F6,          TB_MOD_CTRL | TB_MOD_ALT               },
+    {"\x1b\x1b[29^", TB_KEY_F6,          TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
+    {"\x1b[29^",     TB_KEY_F6,          TB_MOD_CTRL | TB_MOD_SHIFT             },
+    {"\x1b[29~",     TB_KEY_F6,          TB_MOD_SHIFT                           },
+
+    {"\x1b\x1b[18~", TB_KEY_F7,          TB_MOD_ALT                             },
+    {"\x1b\x1b[31~", TB_KEY_F7,          TB_MOD_ALT | TB_MOD_SHIFT              },
+    {"\x1b[18^",     TB_KEY_F7,          TB_MOD_CTRL                            },
+    {"\x1b\x1b[18^", TB_KEY_F7,          TB_MOD_CTRL | TB_MOD_ALT               },
+    {"\x1b\x1b[31^", TB_KEY_F7,          TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
+    {"\x1b[31^",     TB_KEY_F7,          TB_MOD_CTRL | TB_MOD_SHIFT             },
+    {"\x1b[31~",     TB_KEY_F7,          TB_MOD_SHIFT                           },
+
+    {"\x1b\x1b[19~", TB_KEY_F8,          TB_MOD_ALT                             },
+    {"\x1b\x1b[32~", TB_KEY_F8,          TB_MOD_ALT | TB_MOD_SHIFT              },
+    {"\x1b[19^",     TB_KEY_F8,          TB_MOD_CTRL                            },
+    {"\x1b\x1b[19^", TB_KEY_F8,          TB_MOD_CTRL | TB_MOD_ALT               },
+    {"\x1b\x1b[32^", TB_KEY_F8,          TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
+    {"\x1b[32^",     TB_KEY_F8,          TB_MOD_CTRL | TB_MOD_SHIFT             },
+    {"\x1b[32~",     TB_KEY_F8,          TB_MOD_SHIFT                           },
+
+    {"\x1b\x1b[20~", TB_KEY_F9,          TB_MOD_ALT                             },
+    {"\x1b\x1b[33~", TB_KEY_F9,          TB_MOD_ALT | TB_MOD_SHIFT              },
+    {"\x1b[20^",     TB_KEY_F9,          TB_MOD_CTRL                            },
+    {"\x1b\x1b[20^", TB_KEY_F9,          TB_MOD_CTRL | TB_MOD_ALT               },
+    {"\x1b\x1b[33^", TB_KEY_F9,          TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
+    {"\x1b[33^",     TB_KEY_F9,          TB_MOD_CTRL | TB_MOD_SHIFT             },
+    {"\x1b[33~",     TB_KEY_F9,          TB_MOD_SHIFT                           },
+
+    {"\x1b\x1b[21~", TB_KEY_F10,         TB_MOD_ALT                             },
+    {"\x1b\x1b[34~", TB_KEY_F10,         TB_MOD_ALT | TB_MOD_SHIFT              },
+    {"\x1b[21^",     TB_KEY_F10,         TB_MOD_CTRL                            },
+    {"\x1b\x1b[21^", TB_KEY_F10,         TB_MOD_CTRL | TB_MOD_ALT               },
+    {"\x1b\x1b[34^", TB_KEY_F10,         TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
+    {"\x1b[34^",     TB_KEY_F10,         TB_MOD_CTRL | TB_MOD_SHIFT             },
+    {"\x1b[34~",     TB_KEY_F10,         TB_MOD_SHIFT                           },
+
+    {"\x1b\x1b[23~", TB_KEY_F11,         TB_MOD_ALT                             },
+    {"\x1b\x1b[23$", TB_KEY_F11,         TB_MOD_ALT | TB_MOD_SHIFT              },
+    {"\x1b[23^",     TB_KEY_F11,         TB_MOD_CTRL                            },
+    {"\x1b\x1b[23^", TB_KEY_F11,         TB_MOD_CTRL | TB_MOD_ALT               },
+    {"\x1b\x1b[23@", TB_KEY_F11,         TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
+    {"\x1b[23@",     TB_KEY_F11,         TB_MOD_CTRL | TB_MOD_SHIFT             },
+    {"\x1b[23$",     TB_KEY_F11,         TB_MOD_SHIFT                           },
+
+    {"\x1b\x1b[24~", TB_KEY_F12,         TB_MOD_ALT                             },
+    {"\x1b\x1b[24$", TB_KEY_F12,         TB_MOD_ALT | TB_MOD_SHIFT              },
+    {"\x1b[24^",     TB_KEY_F12,         TB_MOD_CTRL                            },
+    {"\x1b\x1b[24^", TB_KEY_F12,         TB_MOD_CTRL | TB_MOD_ALT               },
+    {"\x1b\x1b[24@", TB_KEY_F12,         TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT},
+    {"\x1b[24@",     TB_KEY_F12,         TB_MOD_CTRL | TB_MOD_SHIFT             },
+    {"\x1b[24$",     TB_KEY_F12,         TB_MOD_SHIFT                           },
+
+    // linux console/putty arrows
+    {"\x1b[A",       TB_KEY_ARROW_UP,    TB_MOD_SHIFT                           },
+    {"\x1b[B",       TB_KEY_ARROW_DOWN,  TB_MOD_SHIFT                           },
+    {"\x1b[C",       TB_KEY_ARROW_RIGHT, TB_MOD_SHIFT                           },
+    {"\x1b[D",       TB_KEY_ARROW_LEFT,  TB_MOD_SHIFT                           },
+
+    // more putty arrows
+    {"\x1bOA",       TB_KEY_ARROW_UP,    TB_MOD_CTRL                            },
+    {"\x1b\x1bOA",   TB_KEY_ARROW_UP,    TB_MOD_CTRL | TB_MOD_ALT               },
+    {"\x1bOB",       TB_KEY_ARROW_DOWN,  TB_MOD_CTRL                            },
+    {"\x1b\x1bOB",   TB_KEY_ARROW_DOWN,  TB_MOD_CTRL | TB_MOD_ALT               },
+    {"\x1bOC",       TB_KEY_ARROW_RIGHT, TB_MOD_CTRL                            },
+    {"\x1b\x1bOC",   TB_KEY_ARROW_RIGHT, TB_MOD_CTRL | TB_MOD_ALT               },
+    {"\x1bOD",       TB_KEY_ARROW_LEFT,  TB_MOD_CTRL                            },
+    {"\x1b\x1bOD",   TB_KEY_ARROW_LEFT,  TB_MOD_CTRL | TB_MOD_ALT               },
+
+    {NULL,           0,                  0                                      },
+};
+
+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};
+
+#ifndef TB_OPT_LIBC_WCHAR
+static struct {
+    uint32_t range_start;
+    uint32_t range_end;
+    int width; // -1 means iswprint==0, otherwise wcwidth value (0, 1, or 2)
+} wcwidth_table[] = {
+    // clang-format off
+    {0x000001, 0x00001f, -1}, {0x000020, 0x00007e,  1}, {0x00007f, 0x00009f, -1},
+    {0x0000a0, 0x0002ff,  1}, {0x000300, 0x00036f,  0}, {0x000370, 0x000377,  1},
+    {0x000378, 0x000379, -1}, {0x00037a, 0x00037f,  1}, {0x000380, 0x000383, -1},
+    {0x000384, 0x00038a,  1}, {0x00038b, 0x00038b, -1}, {0x00038c, 0x00038c,  1},
+    {0x00038d, 0x00038d, -1}, {0x00038e, 0x0003a1,  1}, {0x0003a2, 0x0003a2, -1},
+    {0x0003a3, 0x000482,  1}, {0x000483, 0x000489,  0}, {0x00048a, 0x00052f,  1},
+    {0x000530, 0x000530, -1}, {0x000531, 0x000556,  1}, {0x000557, 0x000558, -1},
+    {0x000559, 0x00058a,  1}, {0x00058b, 0x00058c, -1}, {0x00058d, 0x00058f,  1},
+    {0x000590, 0x000590, -1}, {0x000591, 0x0005bd,  0}, {0x0005be, 0x0005be,  1},
+    {0x0005bf, 0x0005bf,  0}, {0x0005c0, 0x0005c0,  1}, {0x0005c1, 0x0005c2,  0},
+    {0x0005c3, 0x0005c3,  1}, {0x0005c4, 0x0005c5,  0}, {0x0005c6, 0x0005c6,  1},
+    {0x0005c7, 0x0005c7,  0}, {0x0005c8, 0x0005cf, -1}, {0x0005d0, 0x0005ea,  1},
+    {0x0005eb, 0x0005ee, -1}, {0x0005ef, 0x0005f4,  1}, {0x0005f5, 0x0005ff, -1},
+    {0x000600, 0x00060f,  1}, {0x000610, 0x00061a,  0}, {0x00061b, 0x00061b,  1},
+    {0x00061c, 0x00061c,  0}, {0x00061d, 0x00064a,  1}, {0x00064b, 0x00065f,  0},
+    {0x000660, 0x00066f,  1}, {0x000670, 0x000670,  0}, {0x000671, 0x0006d5,  1},
+    {0x0006d6, 0x0006dc,  0}, {0x0006dd, 0x0006de,  1}, {0x0006df, 0x0006e4,  0},
+    {0x0006e5, 0x0006e6,  1}, {0x0006e7, 0x0006e8,  0}, {0x0006e9, 0x0006e9,  1},
+    {0x0006ea, 0x0006ed,  0}, {0x0006ee, 0x00070d,  1}, {0x00070e, 0x00070e, -1},
+    {0x00070f, 0x000710,  1}, {0x000711, 0x000711,  0}, {0x000712, 0x00072f,  1},
+    {0x000730, 0x00074a,  0}, {0x00074b, 0x00074c, -1}, {0x00074d, 0x0007a5,  1},
+    {0x0007a6, 0x0007b0,  0}, {0x0007b1, 0x0007b1,  1}, {0x0007b2, 0x0007bf, -1},
+    {0x0007c0, 0x0007ea,  1}, {0x0007eb, 0x0007f3,  0}, {0x0007f4, 0x0007fa,  1},
+    {0x0007fb, 0x0007fc, -1}, {0x0007fd, 0x0007fd,  0}, {0x0007fe, 0x000815,  1},
+    {0x000816, 0x000819,  0}, {0x00081a, 0x00081a,  1}, {0x00081b, 0x000823,  0},
+    {0x000824, 0x000824,  1}, {0x000825, 0x000827,  0}, {0x000828, 0x000828,  1},
+    {0x000829, 0x00082d,  0}, {0x00082e, 0x00082f, -1}, {0x000830, 0x00083e,  1},
+    {0x00083f, 0x00083f, -1}, {0x000840, 0x000858,  1}, {0x000859, 0x00085b,  0},
+    {0x00085c, 0x00085d, -1}, {0x00085e, 0x00085e,  1}, {0x00085f, 0x00085f, -1},
+    {0x000860, 0x00086a,  1}, {0x00086b, 0x00086f, -1}, {0x000870, 0x00088e,  1},
+    {0x00088f, 0x00088f, -1}, {0x000890, 0x000891,  1}, {0x000892, 0x000896, -1},
+    {0x000897, 0x00089f,  0}, {0x0008a0, 0x0008c9,  1}, {0x0008ca, 0x0008e1,  0},
+    {0x0008e2, 0x0008e2,  1}, {0x0008e3, 0x000902,  0}, {0x000903, 0x000939,  1},
+    {0x00093a, 0x00093a,  0}, {0x00093b, 0x00093b,  1}, {0x00093c, 0x00093c,  0},
+    {0x00093d, 0x000940,  1}, {0x000941, 0x000948,  0}, {0x000949, 0x00094c,  1},
+    {0x00094d, 0x00094d,  0}, {0x00094e, 0x000950,  1}, {0x000951, 0x000957,  0},
+    {0x000958, 0x000961,  1}, {0x000962, 0x000963,  0}, {0x000964, 0x000980,  1},
+    {0x000981, 0x000981,  0}, {0x000982, 0x000983,  1}, {0x000984, 0x000984, -1},
+    {0x000985, 0x00098c,  1}, {0x00098d, 0x00098e, -1}, {0x00098f, 0x000990,  1},
+    {0x000991, 0x000992, -1}, {0x000993, 0x0009a8,  1}, {0x0009a9, 0x0009a9, -1},
+    {0x0009aa, 0x0009b0,  1}, {0x0009b1, 0x0009b1, -1}, {0x0009b2, 0x0009b2,  1},
+    {0x0009b3, 0x0009b5, -1}, {0x0009b6, 0x0009b9,  1}, {0x0009ba, 0x0009bb, -1},
+    {0x0009bc, 0x0009bc,  0}, {0x0009bd, 0x0009c0,  1}, {0x0009c1, 0x0009c4,  0},
+    {0x0009c5, 0x0009c6, -1}, {0x0009c7, 0x0009c8,  1}, {0x0009c9, 0x0009ca, -1},
+    {0x0009cb, 0x0009cc,  1}, {0x0009cd, 0x0009cd,  0}, {0x0009ce, 0x0009ce,  1},
+    {0x0009cf, 0x0009d6, -1}, {0x0009d7, 0x0009d7,  1}, {0x0009d8, 0x0009db, -1},
+    {0x0009dc, 0x0009dd,  1}, {0x0009de, 0x0009de, -1}, {0x0009df, 0x0009e1,  1},
+    {0x0009e2, 0x0009e3,  0}, {0x0009e4, 0x0009e5, -1}, {0x0009e6, 0x0009fd,  1},
+    {0x0009fe, 0x0009fe,  0}, {0x0009ff, 0x000a00, -1}, {0x000a01, 0x000a02,  0},
+    {0x000a03, 0x000a03,  1}, {0x000a04, 0x000a04, -1}, {0x000a05, 0x000a0a,  1},
+    {0x000a0b, 0x000a0e, -1}, {0x000a0f, 0x000a10,  1}, {0x000a11, 0x000a12, -1},
+    {0x000a13, 0x000a28,  1}, {0x000a29, 0x000a29, -1}, {0x000a2a, 0x000a30,  1},
+    {0x000a31, 0x000a31, -1}, {0x000a32, 0x000a33,  1}, {0x000a34, 0x000a34, -1},
+    {0x000a35, 0x000a36,  1}, {0x000a37, 0x000a37, -1}, {0x000a38, 0x000a39,  1},
+    {0x000a3a, 0x000a3b, -1}, {0x000a3c, 0x000a3c,  0}, {0x000a3d, 0x000a3d, -1},
+    {0x000a3e, 0x000a40,  1}, {0x000a41, 0x000a42,  0}, {0x000a43, 0x000a46, -1},
+    {0x000a47, 0x000a48,  0}, {0x000a49, 0x000a4a, -1}, {0x000a4b, 0x000a4d,  0},
+    {0x000a4e, 0x000a50, -1}, {0x000a51, 0x000a51,  0}, {0x000a52, 0x000a58, -1},
+    {0x000a59, 0x000a5c,  1}, {0x000a5d, 0x000a5d, -1}, {0x000a5e, 0x000a5e,  1},
+    {0x000a5f, 0x000a65, -1}, {0x000a66, 0x000a6f,  1}, {0x000a70, 0x000a71,  0},
+    {0x000a72, 0x000a74,  1}, {0x000a75, 0x000a75,  0}, {0x000a76, 0x000a76,  1},
+    {0x000a77, 0x000a80, -1}, {0x000a81, 0x000a82,  0}, {0x000a83, 0x000a83,  1},
+    {0x000a84, 0x000a84, -1}, {0x000a85, 0x000a8d,  1}, {0x000a8e, 0x000a8e, -1},
+    {0x000a8f, 0x000a91,  1}, {0x000a92, 0x000a92, -1}, {0x000a93, 0x000aa8,  1},
+    {0x000aa9, 0x000aa9, -1}, {0x000aaa, 0x000ab0,  1}, {0x000ab1, 0x000ab1, -1},
+    {0x000ab2, 0x000ab3,  1}, {0x000ab4, 0x000ab4, -1}, {0x000ab5, 0x000ab9,  1},
+    {0x000aba, 0x000abb, -1}, {0x000abc, 0x000abc,  0}, {0x000abd, 0x000ac0,  1},
+    {0x000ac1, 0x000ac5,  0}, {0x000ac6, 0x000ac6, -1}, {0x000ac7, 0x000ac8,  0},
+    {0x000ac9, 0x000ac9,  1}, {0x000aca, 0x000aca, -1}, {0x000acb, 0x000acc,  1},
+    {0x000acd, 0x000acd,  0}, {0x000ace, 0x000acf, -1}, {0x000ad0, 0x000ad0,  1},
+    {0x000ad1, 0x000adf, -1}, {0x000ae0, 0x000ae1,  1}, {0x000ae2, 0x000ae3,  0},
+    {0x000ae4, 0x000ae5, -1}, {0x000ae6, 0x000af1,  1}, {0x000af2, 0x000af8, -1},
+    {0x000af9, 0x000af9,  1}, {0x000afa, 0x000aff,  0}, {0x000b00, 0x000b00, -1},
+    {0x000b01, 0x000b01,  0}, {0x000b02, 0x000b03,  1}, {0x000b04, 0x000b04, -1},
+    {0x000b05, 0x000b0c,  1}, {0x000b0d, 0x000b0e, -1}, {0x000b0f, 0x000b10,  1},
+    {0x000b11, 0x000b12, -1}, {0x000b13, 0x000b28,  1}, {0x000b29, 0x000b29, -1},
+    {0x000b2a, 0x000b30,  1}, {0x000b31, 0x000b31, -1}, {0x000b32, 0x000b33,  1},
+    {0x000b34, 0x000b34, -1}, {0x000b35, 0x000b39,  1}, {0x000b3a, 0x000b3b, -1},
+    {0x000b3c, 0x000b3c,  0}, {0x000b3d, 0x000b3e,  1}, {0x000b3f, 0x000b3f,  0},
+    {0x000b40, 0x000b40,  1}, {0x000b41, 0x000b44,  0}, {0x000b45, 0x000b46, -1},
+    {0x000b47, 0x000b48,  1}, {0x000b49, 0x000b4a, -1}, {0x000b4b, 0x000b4c,  1},
+    {0x000b4d, 0x000b4d,  0}, {0x000b4e, 0x000b54, -1}, {0x000b55, 0x000b56,  0},
+    {0x000b57, 0x000b57,  1}, {0x000b58, 0x000b5b, -1}, {0x000b5c, 0x000b5d,  1},
+    {0x000b5e, 0x000b5e, -1}, {0x000b5f, 0x000b61,  1}, {0x000b62, 0x000b63,  0},
+    {0x000b64, 0x000b65, -1}, {0x000b66, 0x000b77,  1}, {0x000b78, 0x000b81, -1},
+    {0x000b82, 0x000b82,  0}, {0x000b83, 0x000b83,  1}, {0x000b84, 0x000b84, -1},
+    {0x000b85, 0x000b8a,  1}, {0x000b8b, 0x000b8d, -1}, {0x000b8e, 0x000b90,  1},
+    {0x000b91, 0x000b91, -1}, {0x000b92, 0x000b95,  1}, {0x000b96, 0x000b98, -1},
+    {0x000b99, 0x000b9a,  1}, {0x000b9b, 0x000b9b, -1}, {0x000b9c, 0x000b9c,  1},
+    {0x000b9d, 0x000b9d, -1}, {0x000b9e, 0x000b9f,  1}, {0x000ba0, 0x000ba2, -1},
+    {0x000ba3, 0x000ba4,  1}, {0x000ba5, 0x000ba7, -1}, {0x000ba8, 0x000baa,  1},
+    {0x000bab, 0x000bad, -1}, {0x000bae, 0x000bb9,  1}, {0x000bba, 0x000bbd, -1},
+    {0x000bbe, 0x000bbf,  1}, {0x000bc0, 0x000bc0,  0}, {0x000bc1, 0x000bc2,  1},
+    {0x000bc3, 0x000bc5, -1}, {0x000bc6, 0x000bc8,  1}, {0x000bc9, 0x000bc9, -1},
+    {0x000bca, 0x000bcc,  1}, {0x000bcd, 0x000bcd,  0}, {0x000bce, 0x000bcf, -1},
+    {0x000bd0, 0x000bd0,  1}, {0x000bd1, 0x000bd6, -1}, {0x000bd7, 0x000bd7,  1},
+    {0x000bd8, 0x000be5, -1}, {0x000be6, 0x000bfa,  1}, {0x000bfb, 0x000bff, -1},
+    {0x000c00, 0x000c00,  0}, {0x000c01, 0x000c03,  1}, {0x000c04, 0x000c04,  0},
+    {0x000c05, 0x000c0c,  1}, {0x000c0d, 0x000c0d, -1}, {0x000c0e, 0x000c10,  1},
+    {0x000c11, 0x000c11, -1}, {0x000c12, 0x000c28,  1}, {0x000c29, 0x000c29, -1},
+    {0x000c2a, 0x000c39,  1}, {0x000c3a, 0x000c3b, -1}, {0x000c3c, 0x000c3c,  0},
+    {0x000c3d, 0x000c3d,  1}, {0x000c3e, 0x000c40,  0}, {0x000c41, 0x000c44,  1},
+    {0x000c45, 0x000c45, -1}, {0x000c46, 0x000c48,  0}, {0x000c49, 0x000c49, -1},
+    {0x000c4a, 0x000c4d,  0}, {0x000c4e, 0x000c54, -1}, {0x000c55, 0x000c56,  0},
+    {0x000c57, 0x000c57, -1}, {0x000c58, 0x000c5a,  1}, {0x000c5b, 0x000c5c, -1},
+    {0x000c5d, 0x000c5d,  1}, {0x000c5e, 0x000c5f, -1}, {0x000c60, 0x000c61,  1},
+    {0x000c62, 0x000c63,  0}, {0x000c64, 0x000c65, -1}, {0x000c66, 0x000c6f,  1},
+    {0x000c70, 0x000c76, -1}, {0x000c77, 0x000c80,  1}, {0x000c81, 0x000c81,  0},
+    {0x000c82, 0x000c8c,  1}, {0x000c8d, 0x000c8d, -1}, {0x000c8e, 0x000c90,  1},
+    {0x000c91, 0x000c91, -1}, {0x000c92, 0x000ca8,  1}, {0x000ca9, 0x000ca9, -1},
+    {0x000caa, 0x000cb3,  1}, {0x000cb4, 0x000cb4, -1}, {0x000cb5, 0x000cb9,  1},
+    {0x000cba, 0x000cbb, -1}, {0x000cbc, 0x000cbc,  0}, {0x000cbd, 0x000cbe,  1},
+    {0x000cbf, 0x000cbf,  0}, {0x000cc0, 0x000cc4,  1}, {0x000cc5, 0x000cc5, -1},
+    {0x000cc6, 0x000cc6,  0}, {0x000cc7, 0x000cc8,  1}, {0x000cc9, 0x000cc9, -1},
+    {0x000cca, 0x000ccb,  1}, {0x000ccc, 0x000ccd,  0}, {0x000cce, 0x000cd4, -1},
+    {0x000cd5, 0x000cd6,  1}, {0x000cd7, 0x000cdc, -1}, {0x000cdd, 0x000cde,  1},
+    {0x000cdf, 0x000cdf, -1}, {0x000ce0, 0x000ce1,  1}, {0x000ce2, 0x000ce3,  0},
+    {0x000ce4, 0x000ce5, -1}, {0x000ce6, 0x000cef,  1}, {0x000cf0, 0x000cf0, -1},
+    {0x000cf1, 0x000cf3,  1}, {0x000cf4, 0x000cff, -1}, {0x000d00, 0x000d01,  0},
+    {0x000d02, 0x000d0c,  1}, {0x000d0d, 0x000d0d, -1}, {0x000d0e, 0x000d10,  1},
+    {0x000d11, 0x000d11, -1}, {0x000d12, 0x000d3a,  1}, {0x000d3b, 0x000d3c,  0},
+    {0x000d3d, 0x000d40,  1}, {0x000d41, 0x000d44,  0}, {0x000d45, 0x000d45, -1},
+    {0x000d46, 0x000d48,  1}, {0x000d49, 0x000d49, -1}, {0x000d4a, 0x000d4c,  1},
+    {0x000d4d, 0x000d4d,  0}, {0x000d4e, 0x000d4f,  1}, {0x000d50, 0x000d53, -1},
+    {0x000d54, 0x000d61,  1}, {0x000d62, 0x000d63,  0}, {0x000d64, 0x000d65, -1},
+    {0x000d66, 0x000d7f,  1}, {0x000d80, 0x000d80, -1}, {0x000d81, 0x000d81,  0},
+    {0x000d82, 0x000d83,  1}, {0x000d84, 0x000d84, -1}, {0x000d85, 0x000d96,  1},
+    {0x000d97, 0x000d99, -1}, {0x000d9a, 0x000db1,  1}, {0x000db2, 0x000db2, -1},
+    {0x000db3, 0x000dbb,  1}, {0x000dbc, 0x000dbc, -1}, {0x000dbd, 0x000dbd,  1},
+    {0x000dbe, 0x000dbf, -1}, {0x000dc0, 0x000dc6,  1}, {0x000dc7, 0x000dc9, -1},
+    {0x000dca, 0x000dca,  0}, {0x000dcb, 0x000dce, -1}, {0x000dcf, 0x000dd1,  1},
+    {0x000dd2, 0x000dd4,  0}, {0x000dd5, 0x000dd5, -1}, {0x000dd6, 0x000dd6,  0},
+    {0x000dd7, 0x000dd7, -1}, {0x000dd8, 0x000ddf,  1}, {0x000de0, 0x000de5, -1},
+    {0x000de6, 0x000def,  1}, {0x000df0, 0x000df1, -1}, {0x000df2, 0x000df4,  1},
+    {0x000df5, 0x000e00, -1}, {0x000e01, 0x000e30,  1}, {0x000e31, 0x000e31,  0},
+    {0x000e32, 0x000e33,  1}, {0x000e34, 0x000e3a,  0}, {0x000e3b, 0x000e3e, -1},
+    {0x000e3f, 0x000e46,  1}, {0x000e47, 0x000e4e,  0}, {0x000e4f, 0x000e5b,  1},
+    {0x000e5c, 0x000e80, -1}, {0x000e81, 0x000e82,  1}, {0x000e83, 0x000e83, -1},
+    {0x000e84, 0x000e84,  1}, {0x000e85, 0x000e85, -1}, {0x000e86, 0x000e8a,  1},
+    {0x000e8b, 0x000e8b, -1}, {0x000e8c, 0x000ea3,  1}, {0x000ea4, 0x000ea4, -1},
+    {0x000ea5, 0x000ea5,  1}, {0x000ea6, 0x000ea6, -1}, {0x000ea7, 0x000eb0,  1},
+    {0x000eb1, 0x000eb1,  0}, {0x000eb2, 0x000eb3,  1}, {0x000eb4, 0x000ebc,  0},
+    {0x000ebd, 0x000ebd,  1}, {0x000ebe, 0x000ebf, -1}, {0x000ec0, 0x000ec4,  1},
+    {0x000ec5, 0x000ec5, -1}, {0x000ec6, 0x000ec6,  1}, {0x000ec7, 0x000ec7, -1},
+    {0x000ec8, 0x000ece,  0}, {0x000ecf, 0x000ecf, -1}, {0x000ed0, 0x000ed9,  1},
+    {0x000eda, 0x000edb, -1}, {0x000edc, 0x000edf,  1}, {0x000ee0, 0x000eff, -1},
+    {0x000f00, 0x000f17,  1}, {0x000f18, 0x000f19,  0}, {0x000f1a, 0x000f34,  1},
+    {0x000f35, 0x000f35,  0}, {0x000f36, 0x000f36,  1}, {0x000f37, 0x000f37,  0},
+    {0x000f38, 0x000f38,  1}, {0x000f39, 0x000f39,  0}, {0x000f3a, 0x000f47,  1},
+    {0x000f48, 0x000f48, -1}, {0x000f49, 0x000f6c,  1}, {0x000f6d, 0x000f70, -1},
+    {0x000f71, 0x000f7e,  0}, {0x000f7f, 0x000f7f,  1}, {0x000f80, 0x000f84,  0},
+    {0x000f85, 0x000f85,  1}, {0x000f86, 0x000f87,  0}, {0x000f88, 0x000f8c,  1},
+    {0x000f8d, 0x000f97,  0}, {0x000f98, 0x000f98, -1}, {0x000f99, 0x000fbc,  0},
+    {0x000fbd, 0x000fbd, -1}, {0x000fbe, 0x000fc5,  1}, {0x000fc6, 0x000fc6,  0},
+    {0x000fc7, 0x000fcc,  1}, {0x000fcd, 0x000fcd, -1}, {0x000fce, 0x000fda,  1},
+    {0x000fdb, 0x000fff, -1}, {0x001000, 0x00102c,  1}, {0x00102d, 0x001030,  0},
+    {0x001031, 0x001031,  1}, {0x001032, 0x001037,  0}, {0x001038, 0x001038,  1},
+    {0x001039, 0x00103a,  0}, {0x00103b, 0x00103c,  1}, {0x00103d, 0x00103e,  0},
+    {0x00103f, 0x001057,  1}, {0x001058, 0x001059,  0}, {0x00105a, 0x00105d,  1},
+    {0x00105e, 0x001060,  0}, {0x001061, 0x001070,  1}, {0x001071, 0x001074,  0},
+    {0x001075, 0x001081,  1}, {0x001082, 0x001082,  0}, {0x001083, 0x001084,  1},
+    {0x001085, 0x001086,  0}, {0x001087, 0x00108c,  1}, {0x00108d, 0x00108d,  0},
+    {0x00108e, 0x00109c,  1}, {0x00109d, 0x00109d,  0}, {0x00109e, 0x0010c5,  1},
+    {0x0010c6, 0x0010c6, -1}, {0x0010c7, 0x0010c7,  1}, {0x0010c8, 0x0010cc, -1},
+    {0x0010cd, 0x0010cd,  1}, {0x0010ce, 0x0010cf, -1}, {0x0010d0, 0x0010ff,  1},
+    {0x001100, 0x00115f,  2}, {0x001160, 0x0011ff,  0}, {0x001200, 0x001248,  1},
+    {0x001249, 0x001249, -1}, {0x00124a, 0x00124d,  1}, {0x00124e, 0x00124f, -1},
+    {0x001250, 0x001256,  1}, {0x001257, 0x001257, -1}, {0x001258, 0x001258,  1},
+    {0x001259, 0x001259, -1}, {0x00125a, 0x00125d,  1}, {0x00125e, 0x00125f, -1},
+    {0x001260, 0x001288,  1}, {0x001289, 0x001289, -1}, {0x00128a, 0x00128d,  1},
+    {0x00128e, 0x00128f, -1}, {0x001290, 0x0012b0,  1}, {0x0012b1, 0x0012b1, -1},
+    {0x0012b2, 0x0012b5,  1}, {0x0012b6, 0x0012b7, -1}, {0x0012b8, 0x0012be,  1},
+    {0x0012bf, 0x0012bf, -1}, {0x0012c0, 0x0012c0,  1}, {0x0012c1, 0x0012c1, -1},
+    {0x0012c2, 0x0012c5,  1}, {0x0012c6, 0x0012c7, -1}, {0x0012c8, 0x0012d6,  1},
+    {0x0012d7, 0x0012d7, -1}, {0x0012d8, 0x001310,  1}, {0x001311, 0x001311, -1},
+    {0x001312, 0x001315,  1}, {0x001316, 0x001317, -1}, {0x001318, 0x00135a,  1},
+    {0x00135b, 0x00135c, -1}, {0x00135d, 0x00135f,  0}, {0x001360, 0x00137c,  1},
+    {0x00137d, 0x00137f, -1}, {0x001380, 0x001399,  1}, {0x00139a, 0x00139f, -1},
+    {0x0013a0, 0x0013f5,  1}, {0x0013f6, 0x0013f7, -1}, {0x0013f8, 0x0013fd,  1},
+    {0x0013fe, 0x0013ff, -1}, {0x001400, 0x00169c,  1}, {0x00169d, 0x00169f, -1},
+    {0x0016a0, 0x0016f8,  1}, {0x0016f9, 0x0016ff, -1}, {0x001700, 0x001711,  1},
+    {0x001712, 0x001714,  0}, {0x001715, 0x001715,  1}, {0x001716, 0x00171e, -1},
+    {0x00171f, 0x001731,  1}, {0x001732, 0x001733,  0}, {0x001734, 0x001736,  1},
+    {0x001737, 0x00173f, -1}, {0x001740, 0x001751,  1}, {0x001752, 0x001753,  0},
+    {0x001754, 0x00175f, -1}, {0x001760, 0x00176c,  1}, {0x00176d, 0x00176d, -1},
+    {0x00176e, 0x001770,  1}, {0x001771, 0x001771, -1}, {0x001772, 0x001773,  0},
+    {0x001774, 0x00177f, -1}, {0x001780, 0x0017b3,  1}, {0x0017b4, 0x0017b5,  0},
+    {0x0017b6, 0x0017b6,  1}, {0x0017b7, 0x0017bd,  0}, {0x0017be, 0x0017c5,  1},
+    {0x0017c6, 0x0017c6,  0}, {0x0017c7, 0x0017c8,  1}, {0x0017c9, 0x0017d3,  0},
+    {0x0017d4, 0x0017dc,  1}, {0x0017dd, 0x0017dd,  0}, {0x0017de, 0x0017df, -1},
+    {0x0017e0, 0x0017e9,  1}, {0x0017ea, 0x0017ef, -1}, {0x0017f0, 0x0017f9,  1},
+    {0x0017fa, 0x0017ff, -1}, {0x001800, 0x00180a,  1}, {0x00180b, 0x00180f,  0},
+    {0x001810, 0x001819,  1}, {0x00181a, 0x00181f, -1}, {0x001820, 0x001878,  1},
+    {0x001879, 0x00187f, -1}, {0x001880, 0x001884,  1}, {0x001885, 0x001886,  0},
+    {0x001887, 0x0018a8,  1}, {0x0018a9, 0x0018a9,  0}, {0x0018aa, 0x0018aa,  1},
+    {0x0018ab, 0x0018af, -1}, {0x0018b0, 0x0018f5,  1}, {0x0018f6, 0x0018ff, -1},
+    {0x001900, 0x00191e,  1}, {0x00191f, 0x00191f, -1}, {0x001920, 0x001922,  0},
+    {0x001923, 0x001926,  1}, {0x001927, 0x001928,  0}, {0x001929, 0x00192b,  1},
+    {0x00192c, 0x00192f, -1}, {0x001930, 0x001931,  1}, {0x001932, 0x001932,  0},
+    {0x001933, 0x001938,  1}, {0x001939, 0x00193b,  0}, {0x00193c, 0x00193f, -1},
+    {0x001940, 0x001940,  1}, {0x001941, 0x001943, -1}, {0x001944, 0x00196d,  1},
+    {0x00196e, 0x00196f, -1}, {0x001970, 0x001974,  1}, {0x001975, 0x00197f, -1},
+    {0x001980, 0x0019ab,  1}, {0x0019ac, 0x0019af, -1}, {0x0019b0, 0x0019c9,  1},
+    {0x0019ca, 0x0019cf, -1}, {0x0019d0, 0x0019da,  1}, {0x0019db, 0x0019dd, -1},
+    {0x0019de, 0x001a16,  1}, {0x001a17, 0x001a18,  0}, {0x001a19, 0x001a1a,  1},
+    {0x001a1b, 0x001a1b,  0}, {0x001a1c, 0x001a1d, -1}, {0x001a1e, 0x001a55,  1},
+    {0x001a56, 0x001a56,  0}, {0x001a57, 0x001a57,  1}, {0x001a58, 0x001a5e,  0},
+    {0x001a5f, 0x001a5f, -1}, {0x001a60, 0x001a60,  0}, {0x001a61, 0x001a61,  1},
+    {0x001a62, 0x001a62,  0}, {0x001a63, 0x001a64,  1}, {0x001a65, 0x001a6c,  0},
+    {0x001a6d, 0x001a72,  1}, {0x001a73, 0x001a7c,  0}, {0x001a7d, 0x001a7e, -1},
+    {0x001a7f, 0x001a7f,  0}, {0x001a80, 0x001a89,  1}, {0x001a8a, 0x001a8f, -1},
+    {0x001a90, 0x001a99,  1}, {0x001a9a, 0x001a9f, -1}, {0x001aa0, 0x001aad,  1},
+    {0x001aae, 0x001aaf, -1}, {0x001ab0, 0x001ace,  0}, {0x001acf, 0x001aff, -1},
+    {0x001b00, 0x001b03,  0}, {0x001b04, 0x001b33,  1}, {0x001b34, 0x001b34,  0},
+    {0x001b35, 0x001b35,  1}, {0x001b36, 0x001b3a,  0}, {0x001b3b, 0x001b3b,  1},
+    {0x001b3c, 0x001b3c,  0}, {0x001b3d, 0x001b41,  1}, {0x001b42, 0x001b42,  0},
+    {0x001b43, 0x001b4c,  1}, {0x001b4d, 0x001b4d, -1}, {0x001b4e, 0x001b6a,  1},
+    {0x001b6b, 0x001b73,  0}, {0x001b74, 0x001b7f,  1}, {0x001b80, 0x001b81,  0},
+    {0x001b82, 0x001ba1,  1}, {0x001ba2, 0x001ba5,  0}, {0x001ba6, 0x001ba7,  1},
+    {0x001ba8, 0x001ba9,  0}, {0x001baa, 0x001baa,  1}, {0x001bab, 0x001bad,  0},
+    {0x001bae, 0x001be5,  1}, {0x001be6, 0x001be6,  0}, {0x001be7, 0x001be7,  1},
+    {0x001be8, 0x001be9,  0}, {0x001bea, 0x001bec,  1}, {0x001bed, 0x001bed,  0},
+    {0x001bee, 0x001bee,  1}, {0x001bef, 0x001bf1,  0}, {0x001bf2, 0x001bf3,  1},
+    {0x001bf4, 0x001bfb, -1}, {0x001bfc, 0x001c2b,  1}, {0x001c2c, 0x001c33,  0},
+    {0x001c34, 0x001c35,  1}, {0x001c36, 0x001c37,  0}, {0x001c38, 0x001c3a, -1},
+    {0x001c3b, 0x001c49,  1}, {0x001c4a, 0x001c4c, -1}, {0x001c4d, 0x001c8a,  1},
+    {0x001c8b, 0x001c8f, -1}, {0x001c90, 0x001cba,  1}, {0x001cbb, 0x001cbc, -1},
+    {0x001cbd, 0x001cc7,  1}, {0x001cc8, 0x001ccf, -1}, {0x001cd0, 0x001cd2,  0},
+    {0x001cd3, 0x001cd3,  1}, {0x001cd4, 0x001ce0,  0}, {0x001ce1, 0x001ce1,  1},
+    {0x001ce2, 0x001ce8,  0}, {0x001ce9, 0x001cec,  1}, {0x001ced, 0x001ced,  0},
+    {0x001cee, 0x001cf3,  1}, {0x001cf4, 0x001cf4,  0}, {0x001cf5, 0x001cf7,  1},
+    {0x001cf8, 0x001cf9,  0}, {0x001cfa, 0x001cfa,  1}, {0x001cfb, 0x001cff, -1},
+    {0x001d00, 0x001dbf,  1}, {0x001dc0, 0x001dff,  0}, {0x001e00, 0x001f15,  1},
+    {0x001f16, 0x001f17, -1}, {0x001f18, 0x001f1d,  1}, {0x001f1e, 0x001f1f, -1},
+    {0x001f20, 0x001f45,  1}, {0x001f46, 0x001f47, -1}, {0x001f48, 0x001f4d,  1},
+    {0x001f4e, 0x001f4f, -1}, {0x001f50, 0x001f57,  1}, {0x001f58, 0x001f58, -1},
+    {0x001f59, 0x001f59,  1}, {0x001f5a, 0x001f5a, -1}, {0x001f5b, 0x001f5b,  1},
+    {0x001f5c, 0x001f5c, -1}, {0x001f5d, 0x001f5d,  1}, {0x001f5e, 0x001f5e, -1},
+    {0x001f5f, 0x001f7d,  1}, {0x001f7e, 0x001f7f, -1}, {0x001f80, 0x001fb4,  1},
+    {0x001fb5, 0x001fb5, -1}, {0x001fb6, 0x001fc4,  1}, {0x001fc5, 0x001fc5, -1},
+    {0x001fc6, 0x001fd3,  1}, {0x001fd4, 0x001fd5, -1}, {0x001fd6, 0x001fdb,  1},
+    {0x001fdc, 0x001fdc, -1}, {0x001fdd, 0x001fef,  1}, {0x001ff0, 0x001ff1, -1},
+    {0x001ff2, 0x001ff4,  1}, {0x001ff5, 0x001ff5, -1}, {0x001ff6, 0x001ffe,  1},
+    {0x001fff, 0x001fff, -1}, {0x002000, 0x00200a,  1}, {0x00200b, 0x00200f,  0},
+    {0x002010, 0x002027,  1}, {0x002028, 0x002029, -1}, {0x00202a, 0x00202e,  0},
+    {0x00202f, 0x00205f,  1}, {0x002060, 0x002064,  0}, {0x002065, 0x002065, -1},
+    {0x002066, 0x00206f,  0}, {0x002070, 0x002071,  1}, {0x002072, 0x002073, -1},
+    {0x002074, 0x00208e,  1}, {0x00208f, 0x00208f, -1}, {0x002090, 0x00209c,  1},
+    {0x00209d, 0x00209f, -1}, {0x0020a0, 0x0020c0,  1}, {0x0020c1, 0x0020cf, -1},
+    {0x0020d0, 0x0020f0,  0}, {0x0020f1, 0x0020ff, -1}, {0x002100, 0x00218b,  1},
+    {0x00218c, 0x00218f, -1}, {0x002190, 0x002319,  1}, {0x00231a, 0x00231b,  2},
+    {0x00231c, 0x002328,  1}, {0x002329, 0x00232a,  2}, {0x00232b, 0x0023e8,  1},
+    {0x0023e9, 0x0023ec,  2}, {0x0023ed, 0x0023ef,  1}, {0x0023f0, 0x0023f0,  2},
+    {0x0023f1, 0x0023f2,  1}, {0x0023f3, 0x0023f3,  2}, {0x0023f4, 0x002429,  1},
+    {0x00242a, 0x00243f, -1}, {0x002440, 0x00244a,  1}, {0x00244b, 0x00245f, -1},
+    {0x002460, 0x0025fc,  1}, {0x0025fd, 0x0025fe,  2}, {0x0025ff, 0x002613,  1},
+    {0x002614, 0x002615,  2}, {0x002616, 0x00262f,  1}, {0x002630, 0x002637,  2},
+    {0x002638, 0x002647,  1}, {0x002648, 0x002653,  2}, {0x002654, 0x00267e,  1},
+    {0x00267f, 0x00267f,  2}, {0x002680, 0x002689,  1}, {0x00268a, 0x00268f,  2},
+    {0x002690, 0x002692,  1}, {0x002693, 0x002693,  2}, {0x002694, 0x0026a0,  1},
+    {0x0026a1, 0x0026a1,  2}, {0x0026a2, 0x0026a9,  1}, {0x0026aa, 0x0026ab,  2},
+    {0x0026ac, 0x0026bc,  1}, {0x0026bd, 0x0026be,  2}, {0x0026bf, 0x0026c3,  1},
+    {0x0026c4, 0x0026c5,  2}, {0x0026c6, 0x0026cd,  1}, {0x0026ce, 0x0026ce,  2},
+    {0x0026cf, 0x0026d3,  1}, {0x0026d4, 0x0026d4,  2}, {0x0026d5, 0x0026e9,  1},
+    {0x0026ea, 0x0026ea,  2}, {0x0026eb, 0x0026f1,  1}, {0x0026f2, 0x0026f3,  2},
+    {0x0026f4, 0x0026f4,  1}, {0x0026f5, 0x0026f5,  2}, {0x0026f6, 0x0026f9,  1},
+    {0x0026fa, 0x0026fa,  2}, {0x0026fb, 0x0026fc,  1}, {0x0026fd, 0x0026fd,  2},
+    {0x0026fe, 0x002704,  1}, {0x002705, 0x002705,  2}, {0x002706, 0x002709,  1},
+    {0x00270a, 0x00270b,  2}, {0x00270c, 0x002727,  1}, {0x002728, 0x002728,  2},
+    {0x002729, 0x00274b,  1}, {0x00274c, 0x00274c,  2}, {0x00274d, 0x00274d,  1},
+    {0x00274e, 0x00274e,  2}, {0x00274f, 0x002752,  1}, {0x002753, 0x002755,  2},
+    {0x002756, 0x002756,  1}, {0x002757, 0x002757,  2}, {0x002758, 0x002794,  1},
+    {0x002795, 0x002797,  2}, {0x002798, 0x0027af,  1}, {0x0027b0, 0x0027b0,  2},
+    {0x0027b1, 0x0027be,  1}, {0x0027bf, 0x0027bf,  2}, {0x0027c0, 0x002b1a,  1},
+    {0x002b1b, 0x002b1c,  2}, {0x002b1d, 0x002b4f,  1}, {0x002b50, 0x002b50,  2},
+    {0x002b51, 0x002b54,  1}, {0x002b55, 0x002b55,  2}, {0x002b56, 0x002b73,  1},
+    {0x002b74, 0x002b75, -1}, {0x002b76, 0x002b95,  1}, {0x002b96, 0x002b96, -1},
+    {0x002b97, 0x002cee,  1}, {0x002cef, 0x002cf1,  0}, {0x002cf2, 0x002cf3,  1},
+    {0x002cf4, 0x002cf8, -1}, {0x002cf9, 0x002d25,  1}, {0x002d26, 0x002d26, -1},
+    {0x002d27, 0x002d27,  1}, {0x002d28, 0x002d2c, -1}, {0x002d2d, 0x002d2d,  1},
+    {0x002d2e, 0x002d2f, -1}, {0x002d30, 0x002d67,  1}, {0x002d68, 0x002d6e, -1},
+    {0x002d6f, 0x002d70,  1}, {0x002d71, 0x002d7e, -1}, {0x002d7f, 0x002d7f,  0},
+    {0x002d80, 0x002d96,  1}, {0x002d97, 0x002d9f, -1}, {0x002da0, 0x002da6,  1},
+    {0x002da7, 0x002da7, -1}, {0x002da8, 0x002dae,  1}, {0x002daf, 0x002daf, -1},
+    {0x002db0, 0x002db6,  1}, {0x002db7, 0x002db7, -1}, {0x002db8, 0x002dbe,  1},
+    {0x002dbf, 0x002dbf, -1}, {0x002dc0, 0x002dc6,  1}, {0x002dc7, 0x002dc7, -1},
+    {0x002dc8, 0x002dce,  1}, {0x002dcf, 0x002dcf, -1}, {0x002dd0, 0x002dd6,  1},
+    {0x002dd7, 0x002dd7, -1}, {0x002dd8, 0x002dde,  1}, {0x002ddf, 0x002ddf, -1},
+    {0x002de0, 0x002dff,  0}, {0x002e00, 0x002e5d,  1}, {0x002e5e, 0x002e7f, -1},
+    {0x002e80, 0x002e99,  2}, {0x002e9a, 0x002e9a, -1}, {0x002e9b, 0x002ef3,  2},
+    {0x002ef4, 0x002eff, -1}, {0x002f00, 0x002fd5,  2}, {0x002fd6, 0x002fef, -1},
+    {0x002ff0, 0x003029,  2}, {0x00302a, 0x00302d,  0}, {0x00302e, 0x00303e,  2},
+    {0x00303f, 0x00303f,  1}, {0x003040, 0x003040, -1}, {0x003041, 0x003096,  2},
+    {0x003097, 0x003098, -1}, {0x003099, 0x00309a,  0}, {0x00309b, 0x0030ff,  2},
+    {0x003100, 0x003104, -1}, {0x003105, 0x00312f,  2}, {0x003130, 0x003130, -1},
+    {0x003131, 0x003163,  2}, {0x003164, 0x003164,  0}, {0x003165, 0x00318e,  2},
+    {0x00318f, 0x00318f, -1}, {0x003190, 0x0031e5,  2}, {0x0031e6, 0x0031ee, -1},
+    {0x0031ef, 0x00321e,  2}, {0x00321f, 0x00321f, -1}, {0x003220, 0x00a48c,  2},
+    {0x00a48d, 0x00a48f, -1}, {0x00a490, 0x00a4c6,  2}, {0x00a4c7, 0x00a4cf, -1},
+    {0x00a4d0, 0x00a62b,  1}, {0x00a62c, 0x00a63f, -1}, {0x00a640, 0x00a66e,  1},
+    {0x00a66f, 0x00a672,  0}, {0x00a673, 0x00a673,  1}, {0x00a674, 0x00a67d,  0},
+    {0x00a67e, 0x00a69d,  1}, {0x00a69e, 0x00a69f,  0}, {0x00a6a0, 0x00a6ef,  1},
+    {0x00a6f0, 0x00a6f1,  0}, {0x00a6f2, 0x00a6f7,  1}, {0x00a6f8, 0x00a6ff, -1},
+    {0x00a700, 0x00a7cd,  1}, {0x00a7ce, 0x00a7cf, -1}, {0x00a7d0, 0x00a7d1,  1},
+    {0x00a7d2, 0x00a7d2, -1}, {0x00a7d3, 0x00a7d3,  1}, {0x00a7d4, 0x00a7d4, -1},
+    {0x00a7d5, 0x00a7dc,  1}, {0x00a7dd, 0x00a7f1, -1}, {0x00a7f2, 0x00a801,  1},
+    {0x00a802, 0x00a802,  0}, {0x00a803, 0x00a805,  1}, {0x00a806, 0x00a806,  0},
+    {0x00a807, 0x00a80a,  1}, {0x00a80b, 0x00a80b,  0}, {0x00a80c, 0x00a824,  1},
+    {0x00a825, 0x00a826,  0}, {0x00a827, 0x00a82b,  1}, {0x00a82c, 0x00a82c,  0},
+    {0x00a82d, 0x00a82f, -1}, {0x00a830, 0x00a839,  1}, {0x00a83a, 0x00a83f, -1},
+    {0x00a840, 0x00a877,  1}, {0x00a878, 0x00a87f, -1}, {0x00a880, 0x00a8c3,  1},
+    {0x00a8c4, 0x00a8c5,  0}, {0x00a8c6, 0x00a8cd, -1}, {0x00a8ce, 0x00a8d9,  1},
+    {0x00a8da, 0x00a8df, -1}, {0x00a8e0, 0x00a8f1,  0}, {0x00a8f2, 0x00a8fe,  1},
+    {0x00a8ff, 0x00a8ff,  0}, {0x00a900, 0x00a925,  1}, {0x00a926, 0x00a92d,  0},
+    {0x00a92e, 0x00a946,  1}, {0x00a947, 0x00a951,  0}, {0x00a952, 0x00a953,  1},
+    {0x00a954, 0x00a95e, -1}, {0x00a95f, 0x00a95f,  1}, {0x00a960, 0x00a97c,  2},
+    {0x00a97d, 0x00a97f, -1}, {0x00a980, 0x00a982,  0}, {0x00a983, 0x00a9b2,  1},
+    {0x00a9b3, 0x00a9b3,  0}, {0x00a9b4, 0x00a9b5,  1}, {0x00a9b6, 0x00a9b9,  0},
+    {0x00a9ba, 0x00a9bb,  1}, {0x00a9bc, 0x00a9bd,  0}, {0x00a9be, 0x00a9cd,  1},
+    {0x00a9ce, 0x00a9ce, -1}, {0x00a9cf, 0x00a9d9,  1}, {0x00a9da, 0x00a9dd, -1},
+    {0x00a9de, 0x00a9e4,  1}, {0x00a9e5, 0x00a9e5,  0}, {0x00a9e6, 0x00a9fe,  1},
+    {0x00a9ff, 0x00a9ff, -1}, {0x00aa00, 0x00aa28,  1}, {0x00aa29, 0x00aa2e,  0},
+    {0x00aa2f, 0x00aa30,  1}, {0x00aa31, 0x00aa32,  0}, {0x00aa33, 0x00aa34,  1},
+    {0x00aa35, 0x00aa36,  0}, {0x00aa37, 0x00aa3f, -1}, {0x00aa40, 0x00aa42,  1},
+    {0x00aa43, 0x00aa43,  0}, {0x00aa44, 0x00aa4b,  1}, {0x00aa4c, 0x00aa4c,  0},
+    {0x00aa4d, 0x00aa4d,  1}, {0x00aa4e, 0x00aa4f, -1}, {0x00aa50, 0x00aa59,  1},
+    {0x00aa5a, 0x00aa5b, -1}, {0x00aa5c, 0x00aa7b,  1}, {0x00aa7c, 0x00aa7c,  0},
+    {0x00aa7d, 0x00aaaf,  1}, {0x00aab0, 0x00aab0,  0}, {0x00aab1, 0x00aab1,  1},
+    {0x00aab2, 0x00aab4,  0}, {0x00aab5, 0x00aab6,  1}, {0x00aab7, 0x00aab8,  0},
+    {0x00aab9, 0x00aabd,  1}, {0x00aabe, 0x00aabf,  0}, {0x00aac0, 0x00aac0,  1},
+    {0x00aac1, 0x00aac1,  0}, {0x00aac2, 0x00aac2,  1}, {0x00aac3, 0x00aada, -1},
+    {0x00aadb, 0x00aaeb,  1}, {0x00aaec, 0x00aaed,  0}, {0x00aaee, 0x00aaf5,  1},
+    {0x00aaf6, 0x00aaf6,  0}, {0x00aaf7, 0x00ab00, -1}, {0x00ab01, 0x00ab06,  1},
+    {0x00ab07, 0x00ab08, -1}, {0x00ab09, 0x00ab0e,  1}, {0x00ab0f, 0x00ab10, -1},
+    {0x00ab11, 0x00ab16,  1}, {0x00ab17, 0x00ab1f, -1}, {0x00ab20, 0x00ab26,  1},
+    {0x00ab27, 0x00ab27, -1}, {0x00ab28, 0x00ab2e,  1}, {0x00ab2f, 0x00ab2f, -1},
+    {0x00ab30, 0x00ab6b,  1}, {0x00ab6c, 0x00ab6f, -1}, {0x00ab70, 0x00abe4,  1},
+    {0x00abe5, 0x00abe5,  0}, {0x00abe6, 0x00abe7,  1}, {0x00abe8, 0x00abe8,  0},
+    {0x00abe9, 0x00abec,  1}, {0x00abed, 0x00abed,  0}, {0x00abee, 0x00abef, -1},
+    {0x00abf0, 0x00abf9,  1}, {0x00abfa, 0x00abff, -1}, {0x00ac00, 0x00d7a3,  2},
+    {0x00d7a4, 0x00d7af, -1}, {0x00d7b0, 0x00d7c6,  0}, {0x00d7c7, 0x00d7ca, -1},
+    {0x00d7cb, 0x00d7fb,  0}, {0x00d7fc, 0x00dfff, -1}, {0x00e000, 0x00f8ff,  1},
+    {0x00f900, 0x00fa6d,  2}, {0x00fa6e, 0x00fa6f, -1}, {0x00fa70, 0x00fad9,  2},
+    {0x00fada, 0x00faff, -1}, {0x00fb00, 0x00fb06,  1}, {0x00fb07, 0x00fb12, -1},
+    {0x00fb13, 0x00fb17,  1}, {0x00fb18, 0x00fb1c, -1}, {0x00fb1d, 0x00fb1d,  1},
+    {0x00fb1e, 0x00fb1e,  0}, {0x00fb1f, 0x00fb36,  1}, {0x00fb37, 0x00fb37, -1},
+    {0x00fb38, 0x00fb3c,  1}, {0x00fb3d, 0x00fb3d, -1}, {0x00fb3e, 0x00fb3e,  1},
+    {0x00fb3f, 0x00fb3f, -1}, {0x00fb40, 0x00fb41,  1}, {0x00fb42, 0x00fb42, -1},
+    {0x00fb43, 0x00fb44,  1}, {0x00fb45, 0x00fb45, -1}, {0x00fb46, 0x00fbc2,  1},
+    {0x00fbc3, 0x00fbd2, -1}, {0x00fbd3, 0x00fd8f,  1}, {0x00fd90, 0x00fd91, -1},
+    {0x00fd92, 0x00fdc7,  1}, {0x00fdc8, 0x00fdce, -1}, {0x00fdcf, 0x00fdcf,  1},
+    {0x00fdd0, 0x00fdef, -1}, {0x00fdf0, 0x00fdff,  1}, {0x00fe00, 0x00fe0f,  0},
+    {0x00fe10, 0x00fe19,  2}, {0x00fe1a, 0x00fe1f, -1}, {0x00fe20, 0x00fe2f,  0},
+    {0x00fe30, 0x00fe52,  2}, {0x00fe53, 0x00fe53, -1}, {0x00fe54, 0x00fe66,  2},
+    {0x00fe67, 0x00fe67, -1}, {0x00fe68, 0x00fe6b,  2}, {0x00fe6c, 0x00fe6f, -1},
+    {0x00fe70, 0x00fe74,  1}, {0x00fe75, 0x00fe75, -1}, {0x00fe76, 0x00fefc,  1},
+    {0x00fefd, 0x00fefe, -1}, {0x00feff, 0x00feff,  0}, {0x00ff00, 0x00ff00, -1},
+    {0x00ff01, 0x00ff60,  2}, {0x00ff61, 0x00ff9f,  1}, {0x00ffa0, 0x00ffa0,  0},
+    {0x00ffa1, 0x00ffbe,  1}, {0x00ffbf, 0x00ffc1, -1}, {0x00ffc2, 0x00ffc7,  1},
+    {0x00ffc8, 0x00ffc9, -1}, {0x00ffca, 0x00ffcf,  1}, {0x00ffd0, 0x00ffd1, -1},
+    {0x00ffd2, 0x00ffd7,  1}, {0x00ffd8, 0x00ffd9, -1}, {0x00ffda, 0x00ffdc,  1},
+    {0x00ffdd, 0x00ffdf, -1}, {0x00ffe0, 0x00ffe6,  2}, {0x00ffe7, 0x00ffe7, -1},
+    {0x00ffe8, 0x00ffee,  1}, {0x00ffef, 0x00fff8, -1}, {0x00fff9, 0x00fffd,  1},
+    {0x00fffe, 0x00ffff, -1}, {0x010000, 0x01000b,  1}, {0x01000c, 0x01000c, -1},
+    {0x01000d, 0x010026,  1}, {0x010027, 0x010027, -1}, {0x010028, 0x01003a,  1},
+    {0x01003b, 0x01003b, -1}, {0x01003c, 0x01003d,  1}, {0x01003e, 0x01003e, -1},
+    {0x01003f, 0x01004d,  1}, {0x01004e, 0x01004f, -1}, {0x010050, 0x01005d,  1},
+    {0x01005e, 0x01007f, -1}, {0x010080, 0x0100fa,  1}, {0x0100fb, 0x0100ff, -1},
+    {0x010100, 0x010102,  1}, {0x010103, 0x010106, -1}, {0x010107, 0x010133,  1},
+    {0x010134, 0x010136, -1}, {0x010137, 0x01018e,  1}, {0x01018f, 0x01018f, -1},
+    {0x010190, 0x01019c,  1}, {0x01019d, 0x01019f, -1}, {0x0101a0, 0x0101a0,  1},
+    {0x0101a1, 0x0101cf, -1}, {0x0101d0, 0x0101fc,  1}, {0x0101fd, 0x0101fd,  0},
+    {0x0101fe, 0x01027f, -1}, {0x010280, 0x01029c,  1}, {0x01029d, 0x01029f, -1},
+    {0x0102a0, 0x0102d0,  1}, {0x0102d1, 0x0102df, -1}, {0x0102e0, 0x0102e0,  0},
+    {0x0102e1, 0x0102fb,  1}, {0x0102fc, 0x0102ff, -1}, {0x010300, 0x010323,  1},
+    {0x010324, 0x01032c, -1}, {0x01032d, 0x01034a,  1}, {0x01034b, 0x01034f, -1},
+    {0x010350, 0x010375,  1}, {0x010376, 0x01037a,  0}, {0x01037b, 0x01037f, -1},
+    {0x010380, 0x01039d,  1}, {0x01039e, 0x01039e, -1}, {0x01039f, 0x0103c3,  1},
+    {0x0103c4, 0x0103c7, -1}, {0x0103c8, 0x0103d5,  1}, {0x0103d6, 0x0103ff, -1},
+    {0x010400, 0x01049d,  1}, {0x01049e, 0x01049f, -1}, {0x0104a0, 0x0104a9,  1},
+    {0x0104aa, 0x0104af, -1}, {0x0104b0, 0x0104d3,  1}, {0x0104d4, 0x0104d7, -1},
+    {0x0104d8, 0x0104fb,  1}, {0x0104fc, 0x0104ff, -1}, {0x010500, 0x010527,  1},
+    {0x010528, 0x01052f, -1}, {0x010530, 0x010563,  1}, {0x010564, 0x01056e, -1},
+    {0x01056f, 0x01057a,  1}, {0x01057b, 0x01057b, -1}, {0x01057c, 0x01058a,  1},
+    {0x01058b, 0x01058b, -1}, {0x01058c, 0x010592,  1}, {0x010593, 0x010593, -1},
+    {0x010594, 0x010595,  1}, {0x010596, 0x010596, -1}, {0x010597, 0x0105a1,  1},
+    {0x0105a2, 0x0105a2, -1}, {0x0105a3, 0x0105b1,  1}, {0x0105b2, 0x0105b2, -1},
+    {0x0105b3, 0x0105b9,  1}, {0x0105ba, 0x0105ba, -1}, {0x0105bb, 0x0105bc,  1},
+    {0x0105bd, 0x0105bf, -1}, {0x0105c0, 0x0105f3,  1}, {0x0105f4, 0x0105ff, -1},
+    {0x010600, 0x010736,  1}, {0x010737, 0x01073f, -1}, {0x010740, 0x010755,  1},
+    {0x010756, 0x01075f, -1}, {0x010760, 0x010767,  1}, {0x010768, 0x01077f, -1},
+    {0x010780, 0x010785,  1}, {0x010786, 0x010786, -1}, {0x010787, 0x0107b0,  1},
+    {0x0107b1, 0x0107b1, -1}, {0x0107b2, 0x0107ba,  1}, {0x0107bb, 0x0107ff, -1},
+    {0x010800, 0x010805,  1}, {0x010806, 0x010807, -1}, {0x010808, 0x010808,  1},
+    {0x010809, 0x010809, -1}, {0x01080a, 0x010835,  1}, {0x010836, 0x010836, -1},
+    {0x010837, 0x010838,  1}, {0x010839, 0x01083b, -1}, {0x01083c, 0x01083c,  1},
+    {0x01083d, 0x01083e, -1}, {0x01083f, 0x010855,  1}, {0x010856, 0x010856, -1},
+    {0x010857, 0x01089e,  1}, {0x01089f, 0x0108a6, -1}, {0x0108a7, 0x0108af,  1},
+    {0x0108b0, 0x0108df, -1}, {0x0108e0, 0x0108f2,  1}, {0x0108f3, 0x0108f3, -1},
+    {0x0108f4, 0x0108f5,  1}, {0x0108f6, 0x0108fa, -1}, {0x0108fb, 0x01091b,  1},
+    {0x01091c, 0x01091e, -1}, {0x01091f, 0x010939,  1}, {0x01093a, 0x01093e, -1},
+    {0x01093f, 0x01093f,  1}, {0x010940, 0x01097f, -1}, {0x010980, 0x0109b7,  1},
+    {0x0109b8, 0x0109bb, -1}, {0x0109bc, 0x0109cf,  1}, {0x0109d0, 0x0109d1, -1},
+    {0x0109d2, 0x010a00,  1}, {0x010a01, 0x010a03,  0}, {0x010a04, 0x010a04, -1},
+    {0x010a05, 0x010a06,  0}, {0x010a07, 0x010a0b, -1}, {0x010a0c, 0x010a0f,  0},
+    {0x010a10, 0x010a13,  1}, {0x010a14, 0x010a14, -1}, {0x010a15, 0x010a17,  1},
+    {0x010a18, 0x010a18, -1}, {0x010a19, 0x010a35,  1}, {0x010a36, 0x010a37, -1},
+    {0x010a38, 0x010a3a,  0}, {0x010a3b, 0x010a3e, -1}, {0x010a3f, 0x010a3f,  0},
+    {0x010a40, 0x010a48,  1}, {0x010a49, 0x010a4f, -1}, {0x010a50, 0x010a58,  1},
+    {0x010a59, 0x010a5f, -1}, {0x010a60, 0x010a9f,  1}, {0x010aa0, 0x010abf, -1},
+    {0x010ac0, 0x010ae4,  1}, {0x010ae5, 0x010ae6,  0}, {0x010ae7, 0x010aea, -1},
+    {0x010aeb, 0x010af6,  1}, {0x010af7, 0x010aff, -1}, {0x010b00, 0x010b35,  1},
+    {0x010b36, 0x010b38, -1}, {0x010b39, 0x010b55,  1}, {0x010b56, 0x010b57, -1},
+    {0x010b58, 0x010b72,  1}, {0x010b73, 0x010b77, -1}, {0x010b78, 0x010b91,  1},
+    {0x010b92, 0x010b98, -1}, {0x010b99, 0x010b9c,  1}, {0x010b9d, 0x010ba8, -1},
+    {0x010ba9, 0x010baf,  1}, {0x010bb0, 0x010bff, -1}, {0x010c00, 0x010c48,  1},
+    {0x010c49, 0x010c7f, -1}, {0x010c80, 0x010cb2,  1}, {0x010cb3, 0x010cbf, -1},
+    {0x010cc0, 0x010cf2,  1}, {0x010cf3, 0x010cf9, -1}, {0x010cfa, 0x010d23,  1},
+    {0x010d24, 0x010d27,  0}, {0x010d28, 0x010d2f, -1}, {0x010d30, 0x010d39,  1},
+    {0x010d3a, 0x010d3f, -1}, {0x010d40, 0x010d65,  1}, {0x010d66, 0x010d68, -1},
+    {0x010d69, 0x010d6d,  0}, {0x010d6e, 0x010d85,  1}, {0x010d86, 0x010d8d, -1},
+    {0x010d8e, 0x010d8f,  1}, {0x010d90, 0x010e5f, -1}, {0x010e60, 0x010e7e,  1},
+    {0x010e7f, 0x010e7f, -1}, {0x010e80, 0x010ea9,  1}, {0x010eaa, 0x010eaa, -1},
+    {0x010eab, 0x010eac,  0}, {0x010ead, 0x010ead,  1}, {0x010eae, 0x010eaf, -1},
+    {0x010eb0, 0x010eb1,  1}, {0x010eb2, 0x010ec1, -1}, {0x010ec2, 0x010ec4,  1},
+    {0x010ec5, 0x010efb, -1}, {0x010efc, 0x010eff,  0}, {0x010f00, 0x010f27,  1},
+    {0x010f28, 0x010f2f, -1}, {0x010f30, 0x010f45,  1}, {0x010f46, 0x010f50,  0},
+    {0x010f51, 0x010f59,  1}, {0x010f5a, 0x010f6f, -1}, {0x010f70, 0x010f81,  1},
+    {0x010f82, 0x010f85,  0}, {0x010f86, 0x010f89,  1}, {0x010f8a, 0x010faf, -1},
+    {0x010fb0, 0x010fcb,  1}, {0x010fcc, 0x010fdf, -1}, {0x010fe0, 0x010ff6,  1},
+    {0x010ff7, 0x010fff, -1}, {0x011000, 0x011000,  1}, {0x011001, 0x011001,  0},
+    {0x011002, 0x011037,  1}, {0x011038, 0x011046,  0}, {0x011047, 0x01104d,  1},
+    {0x01104e, 0x011051, -1}, {0x011052, 0x01106f,  1}, {0x011070, 0x011070,  0},
+    {0x011071, 0x011072,  1}, {0x011073, 0x011074,  0}, {0x011075, 0x011075,  1},
+    {0x011076, 0x01107e, -1}, {0x01107f, 0x011081,  0}, {0x011082, 0x0110b2,  1},
+    {0x0110b3, 0x0110b6,  0}, {0x0110b7, 0x0110b8,  1}, {0x0110b9, 0x0110ba,  0},
+    {0x0110bb, 0x0110c1,  1}, {0x0110c2, 0x0110c2,  0}, {0x0110c3, 0x0110cc, -1},
+    {0x0110cd, 0x0110cd,  1}, {0x0110ce, 0x0110cf, -1}, {0x0110d0, 0x0110e8,  1},
+    {0x0110e9, 0x0110ef, -1}, {0x0110f0, 0x0110f9,  1}, {0x0110fa, 0x0110ff, -1},
+    {0x011100, 0x011102,  0}, {0x011103, 0x011126,  1}, {0x011127, 0x01112b,  0},
+    {0x01112c, 0x01112c,  1}, {0x01112d, 0x011134,  0}, {0x011135, 0x011135, -1},
+    {0x011136, 0x011147,  1}, {0x011148, 0x01114f, -1}, {0x011150, 0x011172,  1},
+    {0x011173, 0x011173,  0}, {0x011174, 0x011176,  1}, {0x011177, 0x01117f, -1},
+    {0x011180, 0x011181,  0}, {0x011182, 0x0111b5,  1}, {0x0111b6, 0x0111be,  0},
+    {0x0111bf, 0x0111c8,  1}, {0x0111c9, 0x0111cc,  0}, {0x0111cd, 0x0111ce,  1},
+    {0x0111cf, 0x0111cf,  0}, {0x0111d0, 0x0111df,  1}, {0x0111e0, 0x0111e0, -1},
+    {0x0111e1, 0x0111f4,  1}, {0x0111f5, 0x0111ff, -1}, {0x011200, 0x011211,  1},
+    {0x011212, 0x011212, -1}, {0x011213, 0x01122e,  1}, {0x01122f, 0x011231,  0},
+    {0x011232, 0x011233,  1}, {0x011234, 0x011234,  0}, {0x011235, 0x011235,  1},
+    {0x011236, 0x011237,  0}, {0x011238, 0x01123d,  1}, {0x01123e, 0x01123e,  0},
+    {0x01123f, 0x011240,  1}, {0x011241, 0x011241,  0}, {0x011242, 0x01127f, -1},
+    {0x011280, 0x011286,  1}, {0x011287, 0x011287, -1}, {0x011288, 0x011288,  1},
+    {0x011289, 0x011289, -1}, {0x01128a, 0x01128d,  1}, {0x01128e, 0x01128e, -1},
+    {0x01128f, 0x01129d,  1}, {0x01129e, 0x01129e, -1}, {0x01129f, 0x0112a9,  1},
+    {0x0112aa, 0x0112af, -1}, {0x0112b0, 0x0112de,  1}, {0x0112df, 0x0112df,  0},
+    {0x0112e0, 0x0112e2,  1}, {0x0112e3, 0x0112ea,  0}, {0x0112eb, 0x0112ef, -1},
+    {0x0112f0, 0x0112f9,  1}, {0x0112fa, 0x0112ff, -1}, {0x011300, 0x011301,  0},
+    {0x011302, 0x011303,  1}, {0x011304, 0x011304, -1}, {0x011305, 0x01130c,  1},
+    {0x01130d, 0x01130e, -1}, {0x01130f, 0x011310,  1}, {0x011311, 0x011312, -1},
+    {0x011313, 0x011328,  1}, {0x011329, 0x011329, -1}, {0x01132a, 0x011330,  1},
+    {0x011331, 0x011331, -1}, {0x011332, 0x011333,  1}, {0x011334, 0x011334, -1},
+    {0x011335, 0x011339,  1}, {0x01133a, 0x01133a, -1}, {0x01133b, 0x01133c,  0},
+    {0x01133d, 0x01133f,  1}, {0x011340, 0x011340,  0}, {0x011341, 0x011344,  1},
+    {0x011345, 0x011346, -1}, {0x011347, 0x011348,  1}, {0x011349, 0x01134a, -1},
+    {0x01134b, 0x01134d,  1}, {0x01134e, 0x01134f, -1}, {0x011350, 0x011350,  1},
+    {0x011351, 0x011356, -1}, {0x011357, 0x011357,  1}, {0x011358, 0x01135c, -1},
+    {0x01135d, 0x011363,  1}, {0x011364, 0x011365, -1}, {0x011366, 0x01136c,  0},
+    {0x01136d, 0x01136f, -1}, {0x011370, 0x011374,  0}, {0x011375, 0x01137f, -1},
+    {0x011380, 0x011389,  1}, {0x01138a, 0x01138a, -1}, {0x01138b, 0x01138b,  1},
+    {0x01138c, 0x01138d, -1}, {0x01138e, 0x01138e,  1}, {0x01138f, 0x01138f, -1},
+    {0x011390, 0x0113b5,  1}, {0x0113b6, 0x0113b6, -1}, {0x0113b7, 0x0113ba,  1},
+    {0x0113bb, 0x0113c0,  0}, {0x0113c1, 0x0113c1, -1}, {0x0113c2, 0x0113c2,  1},
+    {0x0113c3, 0x0113c4, -1}, {0x0113c5, 0x0113c5,  1}, {0x0113c6, 0x0113c6, -1},
+    {0x0113c7, 0x0113ca,  1}, {0x0113cb, 0x0113cb, -1}, {0x0113cc, 0x0113cd,  1},
+    {0x0113ce, 0x0113ce,  0}, {0x0113cf, 0x0113cf,  1}, {0x0113d0, 0x0113d0,  0},
+    {0x0113d1, 0x0113d1,  1}, {0x0113d2, 0x0113d2,  0}, {0x0113d3, 0x0113d5,  1},
+    {0x0113d6, 0x0113d6, -1}, {0x0113d7, 0x0113d8,  1}, {0x0113d9, 0x0113e0, -1},
+    {0x0113e1, 0x0113e2,  0}, {0x0113e3, 0x0113ff, -1}, {0x011400, 0x011437,  1},
+    {0x011438, 0x01143f,  0}, {0x011440, 0x011441,  1}, {0x011442, 0x011444,  0},
+    {0x011445, 0x011445,  1}, {0x011446, 0x011446,  0}, {0x011447, 0x01145b,  1},
+    {0x01145c, 0x01145c, -1}, {0x01145d, 0x01145d,  1}, {0x01145e, 0x01145e,  0},
+    {0x01145f, 0x011461,  1}, {0x011462, 0x01147f, -1}, {0x011480, 0x0114b2,  1},
+    {0x0114b3, 0x0114b8,  0}, {0x0114b9, 0x0114b9,  1}, {0x0114ba, 0x0114ba,  0},
+    {0x0114bb, 0x0114be,  1}, {0x0114bf, 0x0114c0,  0}, {0x0114c1, 0x0114c1,  1},
+    {0x0114c2, 0x0114c3,  0}, {0x0114c4, 0x0114c7,  1}, {0x0114c8, 0x0114cf, -1},
+    {0x0114d0, 0x0114d9,  1}, {0x0114da, 0x01157f, -1}, {0x011580, 0x0115b1,  1},
+    {0x0115b2, 0x0115b5,  0}, {0x0115b6, 0x0115b7, -1}, {0x0115b8, 0x0115bb,  1},
+    {0x0115bc, 0x0115bd,  0}, {0x0115be, 0x0115be,  1}, {0x0115bf, 0x0115c0,  0},
+    {0x0115c1, 0x0115db,  1}, {0x0115dc, 0x0115dd,  0}, {0x0115de, 0x0115ff, -1},
+    {0x011600, 0x011632,  1}, {0x011633, 0x01163a,  0}, {0x01163b, 0x01163c,  1},
+    {0x01163d, 0x01163d,  0}, {0x01163e, 0x01163e,  1}, {0x01163f, 0x011640,  0},
+    {0x011641, 0x011644,  1}, {0x011645, 0x01164f, -1}, {0x011650, 0x011659,  1},
+    {0x01165a, 0x01165f, -1}, {0x011660, 0x01166c,  1}, {0x01166d, 0x01167f, -1},
+    {0x011680, 0x0116aa,  1}, {0x0116ab, 0x0116ab,  0}, {0x0116ac, 0x0116ac,  1},
+    {0x0116ad, 0x0116ad,  0}, {0x0116ae, 0x0116af,  1}, {0x0116b0, 0x0116b5,  0},
+    {0x0116b6, 0x0116b6,  1}, {0x0116b7, 0x0116b7,  0}, {0x0116b8, 0x0116b9,  1},
+    {0x0116ba, 0x0116bf, -1}, {0x0116c0, 0x0116c9,  1}, {0x0116ca, 0x0116cf, -1},
+    {0x0116d0, 0x0116e3,  1}, {0x0116e4, 0x0116ff, -1}, {0x011700, 0x01171a,  1},
+    {0x01171b, 0x01171c, -1}, {0x01171d, 0x01171d,  0}, {0x01171e, 0x01171e,  1},
+    {0x01171f, 0x01171f,  0}, {0x011720, 0x011721,  1}, {0x011722, 0x011725,  0},
+    {0x011726, 0x011726,  1}, {0x011727, 0x01172b,  0}, {0x01172c, 0x01172f, -1},
+    {0x011730, 0x011746,  1}, {0x011747, 0x0117ff, -1}, {0x011800, 0x01182e,  1},
+    {0x01182f, 0x011837,  0}, {0x011838, 0x011838,  1}, {0x011839, 0x01183a,  0},
+    {0x01183b, 0x01183b,  1}, {0x01183c, 0x01189f, -1}, {0x0118a0, 0x0118f2,  1},
+    {0x0118f3, 0x0118fe, -1}, {0x0118ff, 0x011906,  1}, {0x011907, 0x011908, -1},
+    {0x011909, 0x011909,  1}, {0x01190a, 0x01190b, -1}, {0x01190c, 0x011913,  1},
+    {0x011914, 0x011914, -1}, {0x011915, 0x011916,  1}, {0x011917, 0x011917, -1},
+    {0x011918, 0x011935,  1}, {0x011936, 0x011936, -1}, {0x011937, 0x011938,  1},
+    {0x011939, 0x01193a, -1}, {0x01193b, 0x01193c,  0}, {0x01193d, 0x01193d,  1},
+    {0x01193e, 0x01193e,  0}, {0x01193f, 0x011942,  1}, {0x011943, 0x011943,  0},
+    {0x011944, 0x011946,  1}, {0x011947, 0x01194f, -1}, {0x011950, 0x011959,  1},
+    {0x01195a, 0x01199f, -1}, {0x0119a0, 0x0119a7,  1}, {0x0119a8, 0x0119a9, -1},
+    {0x0119aa, 0x0119d3,  1}, {0x0119d4, 0x0119d7,  0}, {0x0119d8, 0x0119d9, -1},
+    {0x0119da, 0x0119db,  0}, {0x0119dc, 0x0119df,  1}, {0x0119e0, 0x0119e0,  0},
+    {0x0119e1, 0x0119e4,  1}, {0x0119e5, 0x0119ff, -1}, {0x011a00, 0x011a00,  1},
+    {0x011a01, 0x011a0a,  0}, {0x011a0b, 0x011a32,  1}, {0x011a33, 0x011a38,  0},
+    {0x011a39, 0x011a3a,  1}, {0x011a3b, 0x011a3e,  0}, {0x011a3f, 0x011a46,  1},
+    {0x011a47, 0x011a47,  0}, {0x011a48, 0x011a4f, -1}, {0x011a50, 0x011a50,  1},
+    {0x011a51, 0x011a56,  0}, {0x011a57, 0x011a58,  1}, {0x011a59, 0x011a5b,  0},
+    {0x011a5c, 0x011a89,  1}, {0x011a8a, 0x011a96,  0}, {0x011a97, 0x011a97,  1},
+    {0x011a98, 0x011a99,  0}, {0x011a9a, 0x011aa2,  1}, {0x011aa3, 0x011aaf, -1},
+    {0x011ab0, 0x011af8,  1}, {0x011af9, 0x011aff, -1}, {0x011b00, 0x011b09,  1},
+    {0x011b0a, 0x011bbf, -1}, {0x011bc0, 0x011be1,  1}, {0x011be2, 0x011bef, -1},
+    {0x011bf0, 0x011bf9,  1}, {0x011bfa, 0x011bff, -1}, {0x011c00, 0x011c08,  1},
+    {0x011c09, 0x011c09, -1}, {0x011c0a, 0x011c2f,  1}, {0x011c30, 0x011c36,  0},
+    {0x011c37, 0x011c37, -1}, {0x011c38, 0x011c3d,  0}, {0x011c3e, 0x011c3e,  1},
+    {0x011c3f, 0x011c3f,  0}, {0x011c40, 0x011c45,  1}, {0x011c46, 0x011c4f, -1},
+    {0x011c50, 0x011c6c,  1}, {0x011c6d, 0x011c6f, -1}, {0x011c70, 0x011c8f,  1},
+    {0x011c90, 0x011c91, -1}, {0x011c92, 0x011ca7,  0}, {0x011ca8, 0x011ca8, -1},
+    {0x011ca9, 0x011ca9,  1}, {0x011caa, 0x011cb0,  0}, {0x011cb1, 0x011cb1,  1},
+    {0x011cb2, 0x011cb3,  0}, {0x011cb4, 0x011cb4,  1}, {0x011cb5, 0x011cb6,  0},
+    {0x011cb7, 0x011cff, -1}, {0x011d00, 0x011d06,  1}, {0x011d07, 0x011d07, -1},
+    {0x011d08, 0x011d09,  1}, {0x011d0a, 0x011d0a, -1}, {0x011d0b, 0x011d30,  1},
+    {0x011d31, 0x011d36,  0}, {0x011d37, 0x011d39, -1}, {0x011d3a, 0x011d3a,  0},
+    {0x011d3b, 0x011d3b, -1}, {0x011d3c, 0x011d3d,  0}, {0x011d3e, 0x011d3e, -1},
+    {0x011d3f, 0x011d45,  0}, {0x011d46, 0x011d46,  1}, {0x011d47, 0x011d47,  0},
+    {0x011d48, 0x011d4f, -1}, {0x011d50, 0x011d59,  1}, {0x011d5a, 0x011d5f, -1},
+    {0x011d60, 0x011d65,  1}, {0x011d66, 0x011d66, -1}, {0x011d67, 0x011d68,  1},
+    {0x011d69, 0x011d69, -1}, {0x011d6a, 0x011d8e,  1}, {0x011d8f, 0x011d8f, -1},
+    {0x011d90, 0x011d91,  0}, {0x011d92, 0x011d92, -1}, {0x011d93, 0x011d94,  1},
+    {0x011d95, 0x011d95,  0}, {0x011d96, 0x011d96,  1}, {0x011d97, 0x011d97,  0},
+    {0x011d98, 0x011d98,  1}, {0x011d99, 0x011d9f, -1}, {0x011da0, 0x011da9,  1},
+    {0x011daa, 0x011edf, -1}, {0x011ee0, 0x011ef2,  1}, {0x011ef3, 0x011ef4,  0},
+    {0x011ef5, 0x011ef8,  1}, {0x011ef9, 0x011eff, -1}, {0x011f00, 0x011f01,  0},
+    {0x011f02, 0x011f10,  1}, {0x011f11, 0x011f11, -1}, {0x011f12, 0x011f35,  1},
+    {0x011f36, 0x011f3a,  0}, {0x011f3b, 0x011f3d, -1}, {0x011f3e, 0x011f3f,  1},
+    {0x011f40, 0x011f40,  0}, {0x011f41, 0x011f41,  1}, {0x011f42, 0x011f42,  0},
+    {0x011f43, 0x011f59,  1}, {0x011f5a, 0x011f5a,  0}, {0x011f5b, 0x011faf, -1},
+    {0x011fb0, 0x011fb0,  1}, {0x011fb1, 0x011fbf, -1}, {0x011fc0, 0x011ff1,  1},
+    {0x011ff2, 0x011ffe, -1}, {0x011fff, 0x012399,  1}, {0x01239a, 0x0123ff, -1},
+    {0x012400, 0x01246e,  1}, {0x01246f, 0x01246f, -1}, {0x012470, 0x012474,  1},
+    {0x012475, 0x01247f, -1}, {0x012480, 0x012543,  1}, {0x012544, 0x012f8f, -1},
+    {0x012f90, 0x012ff2,  1}, {0x012ff3, 0x012fff, -1}, {0x013000, 0x01343f,  1},
+    {0x013440, 0x013440,  0}, {0x013441, 0x013446,  1}, {0x013447, 0x013455,  0},
+    {0x013456, 0x01345f, -1}, {0x013460, 0x0143fa,  1}, {0x0143fb, 0x0143ff, -1},
+    {0x014400, 0x014646,  1}, {0x014647, 0x0160ff, -1}, {0x016100, 0x01611d,  1},
+    {0x01611e, 0x016129,  0}, {0x01612a, 0x01612c,  1}, {0x01612d, 0x01612f,  0},
+    {0x016130, 0x016139,  1}, {0x01613a, 0x0167ff, -1}, {0x016800, 0x016a38,  1},
+    {0x016a39, 0x016a3f, -1}, {0x016a40, 0x016a5e,  1}, {0x016a5f, 0x016a5f, -1},
+    {0x016a60, 0x016a69,  1}, {0x016a6a, 0x016a6d, -1}, {0x016a6e, 0x016abe,  1},
+    {0x016abf, 0x016abf, -1}, {0x016ac0, 0x016ac9,  1}, {0x016aca, 0x016acf, -1},
+    {0x016ad0, 0x016aed,  1}, {0x016aee, 0x016aef, -1}, {0x016af0, 0x016af4,  0},
+    {0x016af5, 0x016af5,  1}, {0x016af6, 0x016aff, -1}, {0x016b00, 0x016b2f,  1},
+    {0x016b30, 0x016b36,  0}, {0x016b37, 0x016b45,  1}, {0x016b46, 0x016b4f, -1},
+    {0x016b50, 0x016b59,  1}, {0x016b5a, 0x016b5a, -1}, {0x016b5b, 0x016b61,  1},
+    {0x016b62, 0x016b62, -1}, {0x016b63, 0x016b77,  1}, {0x016b78, 0x016b7c, -1},
+    {0x016b7d, 0x016b8f,  1}, {0x016b90, 0x016d3f, -1}, {0x016d40, 0x016d79,  1},
+    {0x016d7a, 0x016e3f, -1}, {0x016e40, 0x016e9a,  1}, {0x016e9b, 0x016eff, -1},
+    {0x016f00, 0x016f4a,  1}, {0x016f4b, 0x016f4e, -1}, {0x016f4f, 0x016f4f,  0},
+    {0x016f50, 0x016f87,  1}, {0x016f88, 0x016f8e, -1}, {0x016f8f, 0x016f92,  0},
+    {0x016f93, 0x016f9f,  1}, {0x016fa0, 0x016fdf, -1}, {0x016fe0, 0x016fe3,  2},
+    {0x016fe4, 0x016fe4,  0}, {0x016fe5, 0x016fef, -1}, {0x016ff0, 0x016ff1,  2},
+    {0x016ff2, 0x016fff, -1}, {0x017000, 0x0187f7,  2}, {0x0187f8, 0x0187ff, -1},
+    {0x018800, 0x018cd5,  2}, {0x018cd6, 0x018cfe, -1}, {0x018cff, 0x018d08,  2},
+    {0x018d09, 0x01afef, -1}, {0x01aff0, 0x01aff3,  2}, {0x01aff4, 0x01aff4, -1},
+    {0x01aff5, 0x01affb,  2}, {0x01affc, 0x01affc, -1}, {0x01affd, 0x01affe,  2},
+    {0x01afff, 0x01afff, -1}, {0x01b000, 0x01b122,  2}, {0x01b123, 0x01b131, -1},
+    {0x01b132, 0x01b132,  2}, {0x01b133, 0x01b14f, -1}, {0x01b150, 0x01b152,  2},
+    {0x01b153, 0x01b154, -1}, {0x01b155, 0x01b155,  2}, {0x01b156, 0x01b163, -1},
+    {0x01b164, 0x01b167,  2}, {0x01b168, 0x01b16f, -1}, {0x01b170, 0x01b2fb,  2},
+    {0x01b2fc, 0x01bbff, -1}, {0x01bc00, 0x01bc6a,  1}, {0x01bc6b, 0x01bc6f, -1},
+    {0x01bc70, 0x01bc7c,  1}, {0x01bc7d, 0x01bc7f, -1}, {0x01bc80, 0x01bc88,  1},
+    {0x01bc89, 0x01bc8f, -1}, {0x01bc90, 0x01bc99,  1}, {0x01bc9a, 0x01bc9b, -1},
+    {0x01bc9c, 0x01bc9c,  1}, {0x01bc9d, 0x01bc9e,  0}, {0x01bc9f, 0x01bc9f,  1},
+    {0x01bca0, 0x01bca3,  0}, {0x01bca4, 0x01cbff, -1}, {0x01cc00, 0x01ccf9,  1},
+    {0x01ccfa, 0x01ccff, -1}, {0x01cd00, 0x01ceb3,  1}, {0x01ceb4, 0x01ceff, -1},
+    {0x01cf00, 0x01cf2d,  0}, {0x01cf2e, 0x01cf2f, -1}, {0x01cf30, 0x01cf46,  0},
+    {0x01cf47, 0x01cf4f, -1}, {0x01cf50, 0x01cfc3,  1}, {0x01cfc4, 0x01cfff, -1},
+    {0x01d000, 0x01d0f5,  1}, {0x01d0f6, 0x01d0ff, -1}, {0x01d100, 0x01d126,  1},
+    {0x01d127, 0x01d128, -1}, {0x01d129, 0x01d166,  1}, {0x01d167, 0x01d169,  0},
+    {0x01d16a, 0x01d172,  1}, {0x01d173, 0x01d182,  0}, {0x01d183, 0x01d184,  1},
+    {0x01d185, 0x01d18b,  0}, {0x01d18c, 0x01d1a9,  1}, {0x01d1aa, 0x01d1ad,  0},
+    {0x01d1ae, 0x01d1ea,  1}, {0x01d1eb, 0x01d1ff, -1}, {0x01d200, 0x01d241,  1},
+    {0x01d242, 0x01d244,  0}, {0x01d245, 0x01d245,  1}, {0x01d246, 0x01d2bf, -1},
+    {0x01d2c0, 0x01d2d3,  1}, {0x01d2d4, 0x01d2df, -1}, {0x01d2e0, 0x01d2f3,  1},
+    {0x01d2f4, 0x01d2ff, -1}, {0x01d300, 0x01d356,  2}, {0x01d357, 0x01d35f, -1},
+    {0x01d360, 0x01d376,  2}, {0x01d377, 0x01d378,  1}, {0x01d379, 0x01d3ff, -1},
+    {0x01d400, 0x01d454,  1}, {0x01d455, 0x01d455, -1}, {0x01d456, 0x01d49c,  1},
+    {0x01d49d, 0x01d49d, -1}, {0x01d49e, 0x01d49f,  1}, {0x01d4a0, 0x01d4a1, -1},
+    {0x01d4a2, 0x01d4a2,  1}, {0x01d4a3, 0x01d4a4, -1}, {0x01d4a5, 0x01d4a6,  1},
+    {0x01d4a7, 0x01d4a8, -1}, {0x01d4a9, 0x01d4ac,  1}, {0x01d4ad, 0x01d4ad, -1},
+    {0x01d4ae, 0x01d4b9,  1}, {0x01d4ba, 0x01d4ba, -1}, {0x01d4bb, 0x01d4bb,  1},
+    {0x01d4bc, 0x01d4bc, -1}, {0x01d4bd, 0x01d4c3,  1}, {0x01d4c4, 0x01d4c4, -1},
+    {0x01d4c5, 0x01d505,  1}, {0x01d506, 0x01d506, -1}, {0x01d507, 0x01d50a,  1},
+    {0x01d50b, 0x01d50c, -1}, {0x01d50d, 0x01d514,  1}, {0x01d515, 0x01d515, -1},
+    {0x01d516, 0x01d51c,  1}, {0x01d51d, 0x01d51d, -1}, {0x01d51e, 0x01d539,  1},
+    {0x01d53a, 0x01d53a, -1}, {0x01d53b, 0x01d53e,  1}, {0x01d53f, 0x01d53f, -1},
+    {0x01d540, 0x01d544,  1}, {0x01d545, 0x01d545, -1}, {0x01d546, 0x01d546,  1},
+    {0x01d547, 0x01d549, -1}, {0x01d54a, 0x01d550,  1}, {0x01d551, 0x01d551, -1},
+    {0x01d552, 0x01d6a5,  1}, {0x01d6a6, 0x01d6a7, -1}, {0x01d6a8, 0x01d7cb,  1},
+    {0x01d7cc, 0x01d7cd, -1}, {0x01d7ce, 0x01d9ff,  1}, {0x01da00, 0x01da36,  0},
+    {0x01da37, 0x01da3a,  1}, {0x01da3b, 0x01da6c,  0}, {0x01da6d, 0x01da74,  1},
+    {0x01da75, 0x01da75,  0}, {0x01da76, 0x01da83,  1}, {0x01da84, 0x01da84,  0},
+    {0x01da85, 0x01da8b,  1}, {0x01da8c, 0x01da9a, -1}, {0x01da9b, 0x01da9f,  0},
+    {0x01daa0, 0x01daa0, -1}, {0x01daa1, 0x01daaf,  0}, {0x01dab0, 0x01deff, -1},
+    {0x01df00, 0x01df1e,  1}, {0x01df1f, 0x01df24, -1}, {0x01df25, 0x01df2a,  1},
+    {0x01df2b, 0x01dfff, -1}, {0x01e000, 0x01e006,  0}, {0x01e007, 0x01e007, -1},
+    {0x01e008, 0x01e018,  0}, {0x01e019, 0x01e01a, -1}, {0x01e01b, 0x01e021,  0},
+    {0x01e022, 0x01e022, -1}, {0x01e023, 0x01e024,  0}, {0x01e025, 0x01e025, -1},
+    {0x01e026, 0x01e02a,  0}, {0x01e02b, 0x01e02f, -1}, {0x01e030, 0x01e06d,  1},
+    {0x01e06e, 0x01e08e, -1}, {0x01e08f, 0x01e08f,  0}, {0x01e090, 0x01e0ff, -1},
+    {0x01e100, 0x01e12c,  1}, {0x01e12d, 0x01e12f, -1}, {0x01e130, 0x01e136,  0},
+    {0x01e137, 0x01e13d,  1}, {0x01e13e, 0x01e13f, -1}, {0x01e140, 0x01e149,  1},
+    {0x01e14a, 0x01e14d, -1}, {0x01e14e, 0x01e14f,  1}, {0x01e150, 0x01e28f, -1},
+    {0x01e290, 0x01e2ad,  1}, {0x01e2ae, 0x01e2ae,  0}, {0x01e2af, 0x01e2bf, -1},
+    {0x01e2c0, 0x01e2eb,  1}, {0x01e2ec, 0x01e2ef,  0}, {0x01e2f0, 0x01e2f9,  1},
+    {0x01e2fa, 0x01e2fe, -1}, {0x01e2ff, 0x01e2ff,  1}, {0x01e300, 0x01e4cf, -1},
+    {0x01e4d0, 0x01e4eb,  1}, {0x01e4ec, 0x01e4ef,  0}, {0x01e4f0, 0x01e4f9,  1},
+    {0x01e4fa, 0x01e5cf, -1}, {0x01e5d0, 0x01e5ed,  1}, {0x01e5ee, 0x01e5ef,  0},
+    {0x01e5f0, 0x01e5fa,  1}, {0x01e5fb, 0x01e5fe, -1}, {0x01e5ff, 0x01e5ff,  1},
+    {0x01e600, 0x01e7df, -1}, {0x01e7e0, 0x01e7e6,  1}, {0x01e7e7, 0x01e7e7, -1},
+    {0x01e7e8, 0x01e7eb,  1}, {0x01e7ec, 0x01e7ec, -1}, {0x01e7ed, 0x01e7ee,  1},
+    {0x01e7ef, 0x01e7ef, -1}, {0x01e7f0, 0x01e7fe,  1}, {0x01e7ff, 0x01e7ff, -1},
+    {0x01e800, 0x01e8c4,  1}, {0x01e8c5, 0x01e8c6, -1}, {0x01e8c7, 0x01e8cf,  1},
+    {0x01e8d0, 0x01e8d6,  0}, {0x01e8d7, 0x01e8ff, -1}, {0x01e900, 0x01e943,  1},
+    {0x01e944, 0x01e94a,  0}, {0x01e94b, 0x01e94b,  1}, {0x01e94c, 0x01e94f, -1},
+    {0x01e950, 0x01e959,  1}, {0x01e95a, 0x01e95d, -1}, {0x01e95e, 0x01e95f,  1},
+    {0x01e960, 0x01ec70, -1}, {0x01ec71, 0x01ecb4,  1}, {0x01ecb5, 0x01ed00, -1},
+    {0x01ed01, 0x01ed3d,  1}, {0x01ed3e, 0x01edff, -1}, {0x01ee00, 0x01ee03,  1},
+    {0x01ee04, 0x01ee04, -1}, {0x01ee05, 0x01ee1f,  1}, {0x01ee20, 0x01ee20, -1},
+    {0x01ee21, 0x01ee22,  1}, {0x01ee23, 0x01ee23, -1}, {0x01ee24, 0x01ee24,  1},
+    {0x01ee25, 0x01ee26, -1}, {0x01ee27, 0x01ee27,  1}, {0x01ee28, 0x01ee28, -1},
+    {0x01ee29, 0x01ee32,  1}, {0x01ee33, 0x01ee33, -1}, {0x01ee34, 0x01ee37,  1},
+    {0x01ee38, 0x01ee38, -1}, {0x01ee39, 0x01ee39,  1}, {0x01ee3a, 0x01ee3a, -1},
+    {0x01ee3b, 0x01ee3b,  1}, {0x01ee3c, 0x01ee41, -1}, {0x01ee42, 0x01ee42,  1},
+    {0x01ee43, 0x01ee46, -1}, {0x01ee47, 0x01ee47,  1}, {0x01ee48, 0x01ee48, -1},
+    {0x01ee49, 0x01ee49,  1}, {0x01ee4a, 0x01ee4a, -1}, {0x01ee4b, 0x01ee4b,  1},
+    {0x01ee4c, 0x01ee4c, -1}, {0x01ee4d, 0x01ee4f,  1}, {0x01ee50, 0x01ee50, -1},
+    {0x01ee51, 0x01ee52,  1}, {0x01ee53, 0x01ee53, -1}, {0x01ee54, 0x01ee54,  1},
+    {0x01ee55, 0x01ee56, -1}, {0x01ee57, 0x01ee57,  1}, {0x01ee58, 0x01ee58, -1},
+    {0x01ee59, 0x01ee59,  1}, {0x01ee5a, 0x01ee5a, -1}, {0x01ee5b, 0x01ee5b,  1},
+    {0x01ee5c, 0x01ee5c, -1}, {0x01ee5d, 0x01ee5d,  1}, {0x01ee5e, 0x01ee5e, -1},
+    {0x01ee5f, 0x01ee5f,  1}, {0x01ee60, 0x01ee60, -1}, {0x01ee61, 0x01ee62,  1},
+    {0x01ee63, 0x01ee63, -1}, {0x01ee64, 0x01ee64,  1}, {0x01ee65, 0x01ee66, -1},
+    {0x01ee67, 0x01ee6a,  1}, {0x01ee6b, 0x01ee6b, -1}, {0x01ee6c, 0x01ee72,  1},
+    {0x01ee73, 0x01ee73, -1}, {0x01ee74, 0x01ee77,  1}, {0x01ee78, 0x01ee78, -1},
+    {0x01ee79, 0x01ee7c,  1}, {0x01ee7d, 0x01ee7d, -1}, {0x01ee7e, 0x01ee7e,  1},
+    {0x01ee7f, 0x01ee7f, -1}, {0x01ee80, 0x01ee89,  1}, {0x01ee8a, 0x01ee8a, -1},
+    {0x01ee8b, 0x01ee9b,  1}, {0x01ee9c, 0x01eea0, -1}, {0x01eea1, 0x01eea3,  1},
+    {0x01eea4, 0x01eea4, -1}, {0x01eea5, 0x01eea9,  1}, {0x01eeaa, 0x01eeaa, -1},
+    {0x01eeab, 0x01eebb,  1}, {0x01eebc, 0x01eeef, -1}, {0x01eef0, 0x01eef1,  1},
+    {0x01eef2, 0x01efff, -1}, {0x01f000, 0x01f003,  1}, {0x01f004, 0x01f004,  2},
+    {0x01f005, 0x01f02b,  1}, {0x01f02c, 0x01f02f, -1}, {0x01f030, 0x01f093,  1},
+    {0x01f094, 0x01f09f, -1}, {0x01f0a0, 0x01f0ae,  1}, {0x01f0af, 0x01f0b0, -1},
+    {0x01f0b1, 0x01f0bf,  1}, {0x01f0c0, 0x01f0c0, -1}, {0x01f0c1, 0x01f0ce,  1},
+    {0x01f0cf, 0x01f0cf,  2}, {0x01f0d0, 0x01f0d0, -1}, {0x01f0d1, 0x01f0f5,  1},
+    {0x01f0f6, 0x01f0ff, -1}, {0x01f100, 0x01f18d,  1}, {0x01f18e, 0x01f18e,  2},
+    {0x01f18f, 0x01f190,  1}, {0x01f191, 0x01f19a,  2}, {0x01f19b, 0x01f1ad,  1},
+    {0x01f1ae, 0x01f1e5, -1}, {0x01f1e6, 0x01f1ff,  1}, {0x01f200, 0x01f202,  2},
+    {0x01f203, 0x01f20f, -1}, {0x01f210, 0x01f23b,  2}, {0x01f23c, 0x01f23f, -1},
+    {0x01f240, 0x01f248,  2}, {0x01f249, 0x01f24f, -1}, {0x01f250, 0x01f251,  2},
+    {0x01f252, 0x01f25f, -1}, {0x01f260, 0x01f265,  2}, {0x01f266, 0x01f2ff, -1},
+    {0x01f300, 0x01f320,  2}, {0x01f321, 0x01f32c,  1}, {0x01f32d, 0x01f335,  2},
+    {0x01f336, 0x01f336,  1}, {0x01f337, 0x01f37c,  2}, {0x01f37d, 0x01f37d,  1},
+    {0x01f37e, 0x01f393,  2}, {0x01f394, 0x01f39f,  1}, {0x01f3a0, 0x01f3ca,  2},
+    {0x01f3cb, 0x01f3ce,  1}, {0x01f3cf, 0x01f3d3,  2}, {0x01f3d4, 0x01f3df,  1},
+    {0x01f3e0, 0x01f3f0,  2}, {0x01f3f1, 0x01f3f3,  1}, {0x01f3f4, 0x01f3f4,  2},
+    {0x01f3f5, 0x01f3f7,  1}, {0x01f3f8, 0x01f43e,  2}, {0x01f43f, 0x01f43f,  1},
+    {0x01f440, 0x01f440,  2}, {0x01f441, 0x01f441,  1}, {0x01f442, 0x01f4fc,  2},
+    {0x01f4fd, 0x01f4fe,  1}, {0x01f4ff, 0x01f53d,  2}, {0x01f53e, 0x01f54a,  1},
+    {0x01f54b, 0x01f54e,  2}, {0x01f54f, 0x01f54f,  1}, {0x01f550, 0x01f567,  2},
+    {0x01f568, 0x01f579,  1}, {0x01f57a, 0x01f57a,  2}, {0x01f57b, 0x01f594,  1},
+    {0x01f595, 0x01f596,  2}, {0x01f597, 0x01f5a3,  1}, {0x01f5a4, 0x01f5a4,  2},
+    {0x01f5a5, 0x01f5fa,  1}, {0x01f5fb, 0x01f64f,  2}, {0x01f650, 0x01f67f,  1},
+    {0x01f680, 0x01f6c5,  2}, {0x01f6c6, 0x01f6cb,  1}, {0x01f6cc, 0x01f6cc,  2},
+    {0x01f6cd, 0x01f6cf,  1}, {0x01f6d0, 0x01f6d2,  2}, {0x01f6d3, 0x01f6d4,  1},
+    {0x01f6d5, 0x01f6d7,  2}, {0x01f6d8, 0x01f6db, -1}, {0x01f6dc, 0x01f6df,  2},
+    {0x01f6e0, 0x01f6ea,  1}, {0x01f6eb, 0x01f6ec,  2}, {0x01f6ed, 0x01f6ef, -1},
+    {0x01f6f0, 0x01f6f3,  1}, {0x01f6f4, 0x01f6fc,  2}, {0x01f6fd, 0x01f6ff, -1},
+    {0x01f700, 0x01f776,  1}, {0x01f777, 0x01f77a, -1}, {0x01f77b, 0x01f7d9,  1},
+    {0x01f7da, 0x01f7df, -1}, {0x01f7e0, 0x01f7eb,  2}, {0x01f7ec, 0x01f7ef, -1},
+    {0x01f7f0, 0x01f7f0,  2}, {0x01f7f1, 0x01f7ff, -1}, {0x01f800, 0x01f80b,  1},
+    {0x01f80c, 0x01f80f, -1}, {0x01f810, 0x01f847,  1}, {0x01f848, 0x01f84f, -1},
+    {0x01f850, 0x01f859,  1}, {0x01f85a, 0x01f85f, -1}, {0x01f860, 0x01f887,  1},
+    {0x01f888, 0x01f88f, -1}, {0x01f890, 0x01f8ad,  1}, {0x01f8ae, 0x01f8af, -1},
+    {0x01f8b0, 0x01f8bb,  1}, {0x01f8bc, 0x01f8bf, -1}, {0x01f8c0, 0x01f8c1,  1},
+    {0x01f8c2, 0x01f8ff, -1}, {0x01f900, 0x01f90b,  1}, {0x01f90c, 0x01f93a,  2},
+    {0x01f93b, 0x01f93b,  1}, {0x01f93c, 0x01f945,  2}, {0x01f946, 0x01f946,  1},
+    {0x01f947, 0x01f9ff,  2}, {0x01fa00, 0x01fa53,  1}, {0x01fa54, 0x01fa5f, -1},
+    {0x01fa60, 0x01fa6d,  1}, {0x01fa6e, 0x01fa6f, -1}, {0x01fa70, 0x01fa7c,  2},
+    {0x01fa7d, 0x01fa7f, -1}, {0x01fa80, 0x01fa89,  2}, {0x01fa8a, 0x01fa8e, -1},
+    {0x01fa8f, 0x01fac6,  2}, {0x01fac7, 0x01facd, -1}, {0x01face, 0x01fadc,  2},
+    {0x01fadd, 0x01fade, -1}, {0x01fadf, 0x01fae9,  2}, {0x01faea, 0x01faef, -1},
+    {0x01faf0, 0x01faf8,  2}, {0x01faf9, 0x01faff, -1}, {0x01fb00, 0x01fb92,  1},
+    {0x01fb93, 0x01fb93, -1}, {0x01fb94, 0x01fbf9,  1}, {0x01fbfa, 0x01ffff, -1},
+    {0x020000, 0x02a6df,  2}, {0x02a6e0, 0x02a6ff, -1}, {0x02a700, 0x02b739,  2},
+    {0x02b73a, 0x02b73f, -1}, {0x02b740, 0x02b81d,  2}, {0x02b81e, 0x02b81f, -1},
+    {0x02b820, 0x02cea1,  2}, {0x02cea2, 0x02ceaf, -1}, {0x02ceb0, 0x02ebe0,  2},
+    {0x02ebe1, 0x02ebef, -1}, {0x02ebf0, 0x02ee5d,  2}, {0x02ee5e, 0x02f7ff, -1},
+    {0x02f800, 0x02fa1d,  2}, {0x02fa1e, 0x02ffff, -1}, {0x030000, 0x03134a,  2},
+    {0x03134b, 0x03134f, -1}, {0x031350, 0x0323af,  2}, {0x0323b0, 0x0e0000, -1},
+    {0x0e0001, 0x0e0001,  0}, {0x0e0002, 0x0e001f, -1}, {0x0e0020, 0x0e007f,  0},
+    {0x0e0080, 0x0e00ff, -1}, {0x0e0100, 0x0e01ef,  0}, {0x0e01f0, 0x0effff, -1},
+    {0x0f0000, 0x0ffffd,  1}, {0x0ffffe, 0x0fffff, -1}, {0x100000, 0x10fffd,  1},
+    {0x10fffe, 0x10ffff, -1},
+    // clang-format on
+};
+#define WCWIDTH_TABLE_LENGTH 2143
+#endif // ifndef TB_OPT_LIBC_WCHAR
+
+static int tb_reset(void);
+static int tb_printf_inner(int x, int y, uintattr_t fg, uintattr_t bg,
+    size_t *out_w, const char *fmt, va_list vl);
+static int init_term_attrs(void);
+static int init_term_caps(void);
+static int init_cap_trie(void);
+static int cap_trie_add(const char *cap, uint16_t key, uint8_t mod);
+static int cap_trie_find(const char *buf, size_t nbuf, struct cap_trie_t **last,
+    size_t *depth);
+static int cap_trie_deinit(struct cap_trie_t *node);
+static int init_resize_handler(void);
+static int send_init_escape_codes(void);
+static int send_clear(void);
+static int update_term_size(void);
+static int update_term_size_via_esc(void);
+static int init_cellbuf(void);
+static int tb_deinit(void);
+static int load_terminfo(void);
+static int load_terminfo_from_path(const char *path, const char *term);
+static int read_terminfo_path(const char *path);
+static int parse_terminfo_caps(void);
+static int load_builtin_caps(void);
+static const char *get_terminfo_string(int16_t offsets_pos, int16_t offsets_len,
+    int16_t table_pos, int16_t table_size, int16_t index);
+static int get_terminfo_int16(int offset, int16_t *val);
+static int wait_event(struct tb_event *event, int timeout);
+static int extract_event(struct tb_event *event);
+static int extract_esc(struct tb_event *event);
+static int extract_esc_user(struct tb_event *event, int is_post);
+static int extract_esc_cap(struct tb_event *event);
+static int extract_esc_mouse(struct tb_event *event);
+static int resize_cellbufs(void);
+static void handle_resize(int sig);
+static int send_attr(uintattr_t fg, uintattr_t bg);
+static int send_sgr(uint32_t fg, uint32_t bg, int fg_is_default,
+    int bg_is_default);
+static int send_cursor_if(int x, int y);
+static int send_char(int x, int y, uint32_t ch);
+static int send_cluster(int x, int y, uint32_t *ch, size_t nch);
+static int convert_num(uint32_t num, char *buf);
+static int cell_cmp(struct tb_cell *a, struct tb_cell *b);
+static int cell_copy(struct tb_cell *dst, struct tb_cell *src);
+static int cell_set(struct tb_cell *cell, uint32_t *ch, size_t nch,
+    uintattr_t fg, uintattr_t bg);
+static int cell_reserve_ech(struct tb_cell *cell, size_t n);
+static int cell_free(struct tb_cell *cell);
+static int cellbuf_init(struct cellbuf_t *c, int w, int h);
+static int cellbuf_free(struct cellbuf_t *c);
+static int cellbuf_clear(struct cellbuf_t *c);
+static int cellbuf_get(struct cellbuf_t *c, int x, int y, struct tb_cell **out);
+static int cellbuf_in_bounds(struct cellbuf_t *c, int x, int y);
+static int cellbuf_resize(struct cellbuf_t *c, int w, int h);
+static int bytebuf_puts(struct bytebuf_t *b, const char *str);
+static int bytebuf_nputs(struct bytebuf_t *b, const char *str, size_t nstr);
+static int bytebuf_shift(struct bytebuf_t *b, size_t n);
+static int bytebuf_flush(struct bytebuf_t *b, int fd);
+static int bytebuf_reserve(struct bytebuf_t *b, size_t sz);
+static int bytebuf_free(struct bytebuf_t *b);
+static int tb_iswprint_ex(uint32_t ch, int *width);
+static int tb_wcswidth(uint32_t *ch, size_t nch);
+
+int tb_init(void) {
+    return tb_init_file("/dev/tty");
+}
+
+int tb_init_file(const char *path) {
+    if (global.initialized) return TB_ERR_INIT_ALREADY;
+    int ttyfd = open(path, O_RDWR);
+    if (ttyfd < 0) {
+        global.last_errno = errno;
+        return TB_ERR_INIT_OPEN;
+    }
+    global.ttyfd_open = 1;
+    return tb_init_fd(ttyfd);
+}
+
+int tb_init_fd(int ttyfd) {
+    return tb_init_rwfd(ttyfd, ttyfd);
+}
+
+int tb_init_rwfd(int rfd, int wfd) {
+    int rv;
+
+    tb_reset();
+    global.ttyfd = rfd == wfd && isatty(rfd) ? rfd : -1;
+    global.rfd = rfd;
+    global.wfd = wfd;
+
+    do {
+        if_err_break(rv, init_term_attrs());
+        if_err_break(rv, init_term_caps());
+        if_err_break(rv, init_cap_trie());
+        if_err_break(rv, init_resize_handler());
+        if_err_break(rv, send_init_escape_codes());
+        if_err_break(rv, send_clear());
+        if_err_break(rv, update_term_size());
+        if_err_break(rv, init_cellbuf());
+        global.initialized = 1;
+    } while (0);
+
+    if (rv != TB_OK) tb_deinit();
+
+    return rv;
+}
+
+int tb_shutdown(void) {
+    if_not_init_return();
+    tb_deinit();
+    return TB_OK;
+}
+
+int tb_width(void) {
+    if_not_init_return();
+    return global.width;
+}
+
+int tb_height(void) {
+    if_not_init_return();
+    return global.height;
+}
+
+int tb_clear(void) {
+    if_not_init_return();
+    return cellbuf_clear(&global.back);
+}
+
+int tb_set_clear_attrs(uintattr_t fg, uintattr_t bg) {
+    if_not_init_return();
+    global.fg = fg;
+    global.bg = bg;
+    return TB_OK;
+}
+
+int tb_present(void) {
+    if_not_init_return();
+
+    int rv;
+
+    // TODO: Assert global.back.(width,height) == global.front.(width,height)
+
+    global.last_x = -1;
+    global.last_y = -1;
+
+    int x, y, i;
+    for (y = 0; y < global.front.height; y++) {
+        for (x = 0; x < global.front.width;) {
+            struct tb_cell *back, *front;
+            if_err_return(rv, cellbuf_get(&global.back, x, y, &back));
+            if_err_return(rv, cellbuf_get(&global.front, x, y, &front));
+
+            int w;
+            {
+#ifdef TB_OPT_EGC
+                if (back->nech > 0)
+                    w = tb_wcswidth(back->ech, back->nech);
+                else
+#endif
+                    w = tb_wcwidth((wchar_t)back->ch);
+            }
+            if (w < 1) w = 1; // wcwidth qreturns -1 for invalid codepoints
+
+            if (cell_cmp(back, front) != 0) {
+                cell_copy(front, back);
+
+                send_attr(back->fg, back->bg);
+                if (w > 1 && x >= global.front.width - (w - 1)) {
+                    // Not enough room for wide char, send spaces
+                    for (i = x; i < global.front.width; i++) {
+                        send_char(i, y, ' ');
+                    }
+                } else {
+                    {
+#ifdef TB_OPT_EGC
+                        if (back->nech > 0)
+                            send_cluster(x, y, back->ech, back->nech);
+                        else
+#endif
+                            send_char(x, y, back->ch);
+                    }
+
+                    // When wcwidth>1, we need to advance the cursor by more
+                    // than 1, thereby skipping some cells. Set these skipped
+                    // cells to an invalid codepoint in the front buffer, so
+                    // that if this cell is later replaced by a wcwidth==1 char,
+                    // we'll get a cell_cmp diff for the skipped cells and
+                    // properly re-render.
+                    for (i = 1; i < w; i++) {
+                        struct tb_cell *front_wide;
+                        uint32_t invalid = -1;
+                        if_err_return(rv,
+                            cellbuf_get(&global.front, x + i, y, &front_wide));
+                        if_err_return(rv,
+                            cell_set(front_wide, &invalid, 1, -1, -1));
+                    }
+                }
+            }
+            x += w;
+        }
+    }
+
+    if_err_return(rv, send_cursor_if(global.cursor_x, global.cursor_y));
+    if_err_return(rv, bytebuf_flush(&global.out, global.wfd));
+
+    return TB_OK;
+}
+
+int tb_invalidate(void) {
+    int rv;
+    if_not_init_return();
+    if_err_return(rv, resize_cellbufs());
+    return TB_OK;
+}
+
+int tb_set_cursor(int cx, int cy) {
+    if_not_init_return();
+    int rv;
+    if (cx < 0) cx = 0;
+    if (cy < 0) cy = 0;
+    if (global.cursor_x == -1) {
+        if_err_return(rv,
+            bytebuf_puts(&global.out, global.caps[TB_CAP_SHOW_CURSOR]));
+    }
+    if_err_return(rv, send_cursor_if(cx, cy));
+    global.cursor_x = cx;
+    global.cursor_y = cy;
+    return TB_OK;
+}
+
+int tb_hide_cursor(void) {
+    if_not_init_return();
+    int rv;
+    if (global.cursor_x >= 0) {
+        if_err_return(rv,
+            bytebuf_puts(&global.out, global.caps[TB_CAP_HIDE_CURSOR]));
+    }
+    global.cursor_x = -1;
+    global.cursor_y = -1;
+    return TB_OK;
+}
+
+int tb_set_cell(int x, int y, uint32_t ch, uintattr_t fg, uintattr_t bg) {
+    return tb_set_cell_ex(x, y, &ch, 1, fg, bg);
+}
+
+int tb_set_cell_ex(int x, int y, uint32_t *ch, size_t nch, uintattr_t fg,
+    uintattr_t bg) {
+    if_not_init_return();
+    int rv;
+    struct tb_cell *cell;
+    if_err_return(rv, cellbuf_get(&global.back, x, y, &cell));
+    if_err_return(rv, cell_set(cell, ch, nch, fg, bg));
+    return TB_OK;
+}
+
+int tb_extend_cell(int x, int y, uint32_t ch) {
+    if_not_init_return();
+#ifdef TB_OPT_EGC
+    // TODO: iswprint ch?
+    int rv;
+    struct tb_cell *cell;
+    size_t nech;
+    if_err_return(rv, cellbuf_get(&global.back, x, y, &cell));
+    if (cell->nech > 0) { // append to ech
+        nech = cell->nech + 1;
+        if_err_return(rv, cell_reserve_ech(cell, nech + 1));
+        cell->ech[nech - 1] = ch;
+    } else { // make new ech
+        nech = 2;
+        if_err_return(rv, cell_reserve_ech(cell, nech + 1));
+        cell->ech[0] = cell->ch;
+        cell->ech[1] = ch;
+    }
+    cell->ech[nech] = '\0';
+    cell->nech = nech;
+    return TB_OK;
+#else
+    (void)x;
+    (void)y;
+    (void)ch;
+    return TB_ERR;
+#endif
+}
+
+int tb_set_input_mode(int mode) {
+    if_not_init_return();
+
+    if (mode == TB_INPUT_CURRENT) return global.input_mode;
+
+    int esc_or_alt = TB_INPUT_ESC | TB_INPUT_ALT;
+    if ((mode & esc_or_alt) == 0) {
+        // neither specified; flip on ESC
+        mode |= TB_INPUT_ESC;
+    } else if ((mode & esc_or_alt) == esc_or_alt) {
+        // both specified; flip off ALT
+        mode &= ~TB_INPUT_ALT;
+    }
+
+    if (mode & TB_INPUT_MOUSE) {
+        bytebuf_puts(&global.out, TB_HARDCAP_ENTER_MOUSE);
+        bytebuf_flush(&global.out, global.wfd);
+    } else {
+        bytebuf_puts(&global.out, TB_HARDCAP_EXIT_MOUSE);
+        bytebuf_flush(&global.out, global.wfd);
+    }
+
+    global.input_mode = mode;
+    return TB_OK;
+}
+
+int tb_set_output_mode(int mode) {
+    if_not_init_return();
+    switch (mode) {
+        case TB_OUTPUT_CURRENT:
+            return global.output_mode;
+        case TB_OUTPUT_NORMAL:
+        case TB_OUTPUT_256:
+        case TB_OUTPUT_216:
+        case TB_OUTPUT_GRAYSCALE:
+#if TB_OPT_ATTR_W >= 32
+        case TB_OUTPUT_TRUECOLOR:
+#endif
+            global.last_fg = ~global.fg;
+            global.last_bg = ~global.bg;
+            global.output_mode = mode;
+            return TB_OK;
+    }
+    return TB_ERR;
+}
+
+int tb_peek_event(struct tb_event *event, int timeout_ms) {
+    if_not_init_return();
+    return wait_event(event, timeout_ms);
+}
+
+int tb_poll_event(struct tb_event *event) {
+    if_not_init_return();
+    return wait_event(event, -1);
+}
+
+int tb_get_fds(int *ttyfd, int *resizefd) {
+    if_not_init_return();
+
+    *ttyfd = global.rfd;
+    *resizefd = global.resize_pipefd[0];
+
+    return TB_OK;
+}
+
+int tb_print(int x, int y, uintattr_t fg, uintattr_t bg, const char *str) {
+    return tb_print_ex(x, y, fg, bg, NULL, str);
+}
+
+int tb_print_ex(int x, int y, uintattr_t fg, uintattr_t bg, size_t *out_w,
+    const char *str) {
+    int rv, w, ix, x_prev;
+    uint32_t uni;
+
+    if_not_init_return();
+
+    if (!cellbuf_in_bounds(&global.back, x, y)) {
+        return TB_ERR_OUT_OF_BOUNDS;
+    }
+
+    ix = x;
+    x_prev = x;
+    if (out_w) *out_w = 0;
+
+    while (*str) {
+        rv = tb_utf8_char_to_unicode(&uni, str);
+
+        if (rv < 0) {
+            uni = 0xfffd; // replace invalid UTF-8 char with U+FFFD
+            str += rv * -1;
+        } else if (rv > 0) {
+            str += rv;
+        } else {
+            break; // shouldn't get here
+        }
+
+        if (uni == '\n') { // TODO: \r, \t, \v, \f, etc?
+            x = ix;
+            x_prev = x;
+            y += 1;
+            continue;
+        } else if (!tb_iswprint_ex(uni, &w)) {
+            uni = 0xfffd; // replace non-printable with U+FFFD
+            w = 1;
+        }
+
+        if (w < 0) {
+            return TB_ERR;   // shouldn't happen if iswprint
+        } else if (w == 0) { // combining character
+            if (cellbuf_in_bounds(&global.back, x_prev, y)) {
+                if_err_return(rv, tb_extend_cell(x_prev, y, uni));
+            }
+        } else {
+            if (cellbuf_in_bounds(&global.back, x, y)) {
+                if_err_return(rv, tb_set_cell(x, y, uni, fg, bg));
+            }
+            x_prev = x;
+            x += w;
+            if (out_w) *out_w += w;
+        }
+    }
+
+    return TB_OK;
+}
+
+int tb_printf(int x, int y, uintattr_t fg, uintattr_t bg, const char *fmt,
+    ...) {
+    int rv;
+    va_list vl;
+    va_start(vl, fmt);
+    rv = tb_printf_inner(x, y, fg, bg, NULL, fmt, vl);
+    va_end(vl);
+    return rv;
+}
+
+int tb_printf_ex(int x, int y, uintattr_t fg, uintattr_t bg, size_t *out_w,
+    const char *fmt, ...) {
+    int rv;
+    va_list vl;
+    va_start(vl, fmt);
+    rv = tb_printf_inner(x, y, fg, bg, out_w, fmt, vl);
+    va_end(vl);
+    return rv;
+}
+
+int tb_send(const char *buf, size_t nbuf) {
+    return bytebuf_nputs(&global.out, buf, nbuf);
+}
+
+int tb_sendf(const char *fmt, ...) {
+    int rv;
+    char buf[TB_OPT_PRINTF_BUF];
+    va_list vl;
+    va_start(vl, fmt);
+    rv = vsnprintf(buf, sizeof(buf), fmt, vl);
+    va_end(vl);
+    if (rv < 0 || rv >= (int)sizeof(buf)) {
+        return TB_ERR;
+    }
+    return tb_send(buf, (size_t)rv);
+}
+
+int tb_set_func(int fn_type, int (*fn)(struct tb_event *, size_t *)) {
+    switch (fn_type) {
+        case TB_FUNC_EXTRACT_PRE:
+            global.fn_extract_esc_pre = fn;
+            return TB_OK;
+        case TB_FUNC_EXTRACT_POST:
+            global.fn_extract_esc_post = fn;
+            return TB_OK;
+    }
+    return TB_ERR;
+}
+
+struct tb_cell *tb_cell_buffer(void) {
+    if (!global.initialized) return NULL;
+    return global.back.cells;
+}
+
+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 0;
+
+    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 && c[i] != '\0'; ++i) {
+        result <<= 6;
+        result |= c[i] & 0x3f;
+    }
+
+    if (i != len) return i * -1;
+
+    *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;
+    out[len] = '\0';
+
+    return len;
+}
+
+int tb_last_errno(void) {
+    return global.last_errno;
+}
+
+const char *tb_strerror(int err) {
+    switch (err) {
+        case TB_OK:
+            return "Success";
+        case TB_ERR_NEED_MORE:
+            return "Not enough input";
+        case TB_ERR_INIT_ALREADY:
+            return "Termbox initialized already";
+        case TB_ERR_MEM:
+            return "Out of memory";
+        case TB_ERR_NO_EVENT:
+            return "No event";
+        case TB_ERR_NO_TERM:
+            return "No TERM in environment";
+        case TB_ERR_NOT_INIT:
+            return "Termbox not initialized";
+        case TB_ERR_OUT_OF_BOUNDS:
+            return "Out of bounds";
+        case TB_ERR_UNSUPPORTED_TERM:
+            return "Unsupported terminal";
+        case TB_ERR_CAP_COLLISION:
+            return "Termcaps collision";
+        case TB_ERR_RESIZE_SSCANF:
+            return "Terminal width/height not received by sscanf() after "
+                   "resize";
+        case TB_ERR:
+        case TB_ERR_INIT_OPEN:
+        case TB_ERR_READ:
+        case TB_ERR_RESIZE_IOCTL:
+        case TB_ERR_RESIZE_PIPE:
+        case TB_ERR_RESIZE_SIGACTION:
+        case TB_ERR_POLL:
+        case TB_ERR_TCGETATTR:
+        case TB_ERR_TCSETATTR:
+        case TB_ERR_RESIZE_WRITE:
+        case TB_ERR_RESIZE_POLL:
+        case TB_ERR_RESIZE_READ:
+        default:
+            strerror_r(global.last_errno, global.errbuf, sizeof(global.errbuf));
+            return (const char *)global.errbuf;
+    }
+}
+
+int tb_has_truecolor(void) {
+#if TB_OPT_ATTR_W >= 32
+    return 1;
+#else
+    return 0;
+#endif
+}
+
+int tb_has_egc(void) {
+#ifdef TB_OPT_EGC
+    return 1;
+#else
+    return 0;
+#endif
+}
+
+int tb_attr_width(void) {
+    return TB_OPT_ATTR_W;
+}
+
+const char *tb_version(void) {
+    return TB_VERSION_STR;
+}
+
+static int tb_reset(void) {
+    int ttyfd_open = global.ttyfd_open;
+    memset(&global, 0, sizeof(global));
+    global.ttyfd = -1;
+    global.rfd = -1;
+    global.wfd = -1;
+    global.ttyfd_open = ttyfd_open;
+    global.resize_pipefd[0] = -1;
+    global.resize_pipefd[1] = -1;
+    global.width = -1;
+    global.height = -1;
+    global.cursor_x = -1;
+    global.cursor_y = -1;
+    global.last_x = -1;
+    global.last_y = -1;
+    global.fg = TB_DEFAULT;
+    global.bg = TB_DEFAULT;
+    global.last_fg = ~global.fg;
+    global.last_bg = ~global.bg;
+    global.input_mode = TB_INPUT_ESC;
+    global.output_mode = TB_OUTPUT_NORMAL;
+    return TB_OK;
+}
+
+static int init_term_attrs(void) {
+    if (global.ttyfd < 0) return TB_OK;
+
+    if (tcgetattr(global.ttyfd, &global.orig_tios) != 0) {
+        global.last_errno = errno;
+        return TB_ERR_TCGETATTR;
+    }
+
+    struct termios tios;
+    memcpy(&tios, &global.orig_tios, sizeof(tios));
+    global.has_orig_tios = 1;
+
+    cfmakeraw(&tios);
+    tios.c_cc[VMIN] = 1;
+    tios.c_cc[VTIME] = 0;
+
+    if (tcsetattr(global.ttyfd, TCSAFLUSH, &tios) != 0) {
+        global.last_errno = errno;
+        return TB_ERR_TCSETATTR;
+    }
+
+    return TB_OK;
+}
+
+int tb_printf_inner(int x, int y, uintattr_t fg, uintattr_t bg, size_t *out_w,
+    const char *fmt, va_list vl) {
+    int rv;
+    char buf[TB_OPT_PRINTF_BUF];
+    rv = vsnprintf(buf, sizeof(buf), fmt, vl);
+    if (rv < 0 || rv >= (int)sizeof(buf)) {
+        return TB_ERR;
+    }
+    return tb_print_ex(x, y, fg, bg, out_w, buf);
+}
+
+static int init_term_caps(void) {
+    if (load_terminfo() == TB_OK) {
+        return parse_terminfo_caps();
+    }
+    return load_builtin_caps();
+}
+
+static int init_cap_trie(void) {
+    int rv, i;
+
+    // Add caps from terminfo or built-in
+    //
+    // Collisions are expected as some terminfo entries have dupes. (For
+    // example, att605-pc collides on TB_CAP_F4 and TB_CAP_DELETE.) First cap
+    // in TB_CAP_* index order will win.
+    //
+    // TODO: Reorder TB_CAP_* so more critical caps come first.
+    for (i = 0; i < TB_CAP__COUNT_KEYS; i++) {
+        rv = cap_trie_add(global.caps[i], tb_key_i(i), 0);
+        if (rv != TB_OK && rv != TB_ERR_CAP_COLLISION) return rv;
+    }
+
+    // Add built-in mod caps
+    //
+    // Collisions are OK here as well. This can happen if global.caps collides
+    // with builtin_mod_caps. It is desirable to give precedence to global.caps
+    // here.
+    for (i = 0; builtin_mod_caps[i].cap != NULL; i++) {
+        rv = cap_trie_add(builtin_mod_caps[i].cap, builtin_mod_caps[i].key,
+            builtin_mod_caps[i].mod);
+        if (rv != TB_OK && rv != TB_ERR_CAP_COLLISION) return rv;
+    }
+
+    return TB_OK;
+}
+
+static int cap_trie_add(const char *cap, uint16_t key, uint8_t mod) {
+    struct cap_trie_t *next, *node = &global.cap_trie;
+    size_t i, j;
+
+    if (!cap || strlen(cap) <= 0) return TB_OK; // Nothing to do for empty caps
+
+    for (i = 0; cap[i] != '\0'; i++) {
+        char c = cap[i];
+        next = NULL;
+
+        // Check if c is already a child of node
+        for (j = 0; j < node->nchildren; j++) {
+            if (node->children[j].c == c) {
+                next = &node->children[j];
+                break;
+            }
+        }
+        if (!next) {
+            // We need to add a new child to node
+            node->nchildren += 1;
+            node->children = (struct cap_trie_t *)tb_realloc(node->children,
+                sizeof(*node) * node->nchildren);
+            if (!node->children) {
+                return TB_ERR_MEM;
+            }
+            next = &node->children[node->nchildren - 1];
+            memset(next, 0, sizeof(*next));
+            next->c = c;
+        }
+
+        // Continue
+        node = next;
+    }
+
+    if (node->is_leaf) {
+        // Already a leaf here
+        return TB_ERR_CAP_COLLISION;
+    }
+
+    node->is_leaf = 1;
+    node->key = key;
+    node->mod = mod;
+    return TB_OK;
+}
+
+static int cap_trie_find(const char *buf, size_t nbuf, struct cap_trie_t **last,
+    size_t *depth) {
+    struct cap_trie_t *next, *node = &global.cap_trie;
+    size_t i, j;
+    *last = node;
+    *depth = 0;
+    for (i = 0; i < nbuf; i++) {
+        char c = buf[i];
+        next = NULL;
+
+        // Find c in node.children
+        for (j = 0; j < node->nchildren; j++) {
+            if (node->children[j].c == c) {
+                next = &node->children[j];
+                break;
+            }
+        }
+        if (!next) {
+            // Not found
+            return TB_OK;
+        }
+        node = next;
+        *last = node;
+        *depth += 1;
+        if (node->is_leaf && node->nchildren < 1) {
+            break;
+        }
+    }
+    return TB_OK;
+}
+
+static int cap_trie_deinit(struct cap_trie_t *node) {
+    size_t j;
+    for (j = 0; j < node->nchildren; j++) {
+        cap_trie_deinit(&node->children[j]);
+    }
+    if (node->children) tb_free(node->children);
+    memset(node, 0, sizeof(*node));
+    return TB_OK;
+}
+
+static int init_resize_handler(void) {
+    if (pipe(global.resize_pipefd) != 0) {
+        global.last_errno = errno;
+        return TB_ERR_RESIZE_PIPE;
+    }
+
+    struct sigaction sa;
+    memset(&sa, 0, sizeof(sa));
+    sa.sa_handler = handle_resize;
+    if (sigaction(SIGWINCH, &sa, NULL) != 0) {
+        global.last_errno = errno;
+        return TB_ERR_RESIZE_SIGACTION;
+    }
+
+    return TB_OK;
+}
+
+static int send_init_escape_codes(void) {
+    int rv;
+    if_err_return(rv, bytebuf_puts(&global.out, global.caps[TB_CAP_ENTER_CA]));
+    if_err_return(rv,
+        bytebuf_puts(&global.out, global.caps[TB_CAP_ENTER_KEYPAD]));
+    if_err_return(rv,
+        bytebuf_puts(&global.out, global.caps[TB_CAP_HIDE_CURSOR]));
+    return TB_OK;
+}
+
+static int send_clear(void) {
+    int rv;
+
+    if_err_return(rv, send_attr(global.fg, global.bg));
+    if_err_return(rv,
+        bytebuf_puts(&global.out, global.caps[TB_CAP_CLEAR_SCREEN]));
+
+    if_err_return(rv, send_cursor_if(global.cursor_x, global.cursor_y));
+    if_err_return(rv, bytebuf_flush(&global.out, global.wfd));
+
+    global.last_x = -1;
+    global.last_y = -1;
+
+    return TB_OK;
+}
+
+static int update_term_size(void) {
+    int rv, ioctl_errno;
+
+    if (global.ttyfd < 0) return TB_OK;
+
+    struct winsize sz;
+    memset(&sz, 0, sizeof(sz));
+
+    // Try ioctl TIOCGWINSZ
+    if (ioctl(global.ttyfd, TIOCGWINSZ, &sz) == 0) {
+        global.width = sz.ws_col;
+        global.height = sz.ws_row;
+        return TB_OK;
+    }
+    ioctl_errno = errno;
+
+    // Try >cursor(9999,9999), >u7, <u6
+    if_ok_return(rv, update_term_size_via_esc());
+
+    global.last_errno = ioctl_errno;
+    return TB_ERR_RESIZE_IOCTL;
+}
+
+static int update_term_size_via_esc(void) {
+#ifndef TB_RESIZE_FALLBACK_MS
+#define TB_RESIZE_FALLBACK_MS 1000
+#endif
+
+    char move_and_report[] = "\x1b[9999;9999H\x1b[6n";
+    ssize_t write_rv =
+        write(global.wfd, move_and_report, strlen(move_and_report));
+    if (write_rv != (ssize_t)strlen(move_and_report)) {
+        return TB_ERR_RESIZE_WRITE;
+    }
+
+    fd_set fds;
+    FD_ZERO(&fds);
+    FD_SET(global.rfd, &fds);
+
+    struct timeval timeout;
+    timeout.tv_sec = 0;
+    timeout.tv_usec = TB_RESIZE_FALLBACK_MS * 1000;
+
+    int select_rv = select(global.rfd + 1, &fds, NULL, NULL, &timeout);
+
+    if (select_rv != 1) {
+        global.last_errno = errno;
+        return TB_ERR_RESIZE_POLL;
+    }
+
+    char buf[TB_OPT_READ_BUF];
+    ssize_t read_rv = read(global.rfd, buf, sizeof(buf) - 1);
+    if (read_rv < 1) {
+        global.last_errno = errno;
+        return TB_ERR_RESIZE_READ;
+    }
+    buf[read_rv] = '\0';
+
+    int rw, rh;
+    if (sscanf(buf, "\x1b[%d;%dR", &rh, &rw) != 2) {
+        return TB_ERR_RESIZE_SSCANF;
+    }
+
+    global.width = rw;
+    global.height = rh;
+    return TB_OK;
+}
+
+static int init_cellbuf(void) {
+    int rv;
+    if_err_return(rv, cellbuf_init(&global.back, global.width, global.height));
+    if_err_return(rv, cellbuf_init(&global.front, global.width, global.height));
+    if_err_return(rv, cellbuf_clear(&global.back));
+    if_err_return(rv, cellbuf_clear(&global.front));
+    return TB_OK;
+}
+
+static int tb_deinit(void) {
+    if (global.caps[0] != NULL && global.wfd >= 0) {
+        bytebuf_puts(&global.out, global.caps[TB_CAP_SHOW_CURSOR]);
+        bytebuf_puts(&global.out, global.caps[TB_CAP_SGR0]);
+        bytebuf_puts(&global.out, global.caps[TB_CAP_CLEAR_SCREEN]);
+        bytebuf_puts(&global.out, global.caps[TB_CAP_EXIT_CA]);
+        bytebuf_puts(&global.out, global.caps[TB_CAP_EXIT_KEYPAD]);
+        bytebuf_puts(&global.out, TB_HARDCAP_EXIT_MOUSE);
+        bytebuf_flush(&global.out, global.wfd);
+    }
+    if (global.ttyfd >= 0) {
+        if (global.has_orig_tios) {
+            tcsetattr(global.ttyfd, TCSAFLUSH, &global.orig_tios);
+        }
+        if (global.ttyfd_open) {
+            close(global.ttyfd);
+            global.ttyfd_open = 0;
+        }
+    }
+
+    struct sigaction sa;
+    memset(&sa, 0, sizeof(sa));
+    sa.sa_handler = SIG_DFL;
+    sigaction(SIGWINCH, &sa, NULL);
+    if (global.resize_pipefd[0] >= 0) close(global.resize_pipefd[0]);
+    if (global.resize_pipefd[1] >= 0) close(global.resize_pipefd[1]);
+
+    cellbuf_free(&global.back);
+    cellbuf_free(&global.front);
+    bytebuf_free(&global.in);
+    bytebuf_free(&global.out);
+
+    if (global.terminfo) tb_free(global.terminfo);
+
+    cap_trie_deinit(&global.cap_trie);
+
+    tb_reset();
+    return TB_OK;
+}
+
+static int load_terminfo(void) {
+    int rv;
+    char tmp[TB_PATH_MAX];
+
+    // See terminfo(5) "Fetching Compiled Descriptions" for a description of
+    // this behavior. Some of these paths are compile-time ncurses options, so
+    // best guesses are used here.
+    const char *term = getenv("TERM");
+    if (!term) return TB_ERR;
+
+    // If TERMINFO is set, try that directory and stop
+    const char *terminfo = getenv("TERMINFO");
+    if (terminfo) return load_terminfo_from_path(terminfo, term);
+
+    // Next try ~/.terminfo
+    const char *home = getenv("HOME");
+    if (home) {
+        snprintf_or_return(rv, tmp, sizeof(tmp), "%s/.terminfo", home);
+        if_ok_return(rv, load_terminfo_from_path(tmp, term));
+    }
+
+    // Next try TERMINFO_DIRS
+    //
+    // Note, empty entries are supposed to be interpretted as the "compiled-in
+    // default", which is of course system-dependent. Previously /etc/terminfo
+    // was used here. Let's skip empty entries altogether rather than give
+    // precedence to a guess, and check common paths after this loop.
+    const char *dirs = getenv("TERMINFO_DIRS");
+    if (dirs) {
+        snprintf_or_return(rv, tmp, sizeof(tmp), "%s", dirs);
+        char *dir = strtok(tmp, ":");
+        while (dir) {
+            const char *cdir = dir;
+            if (*cdir != '\0') {
+                if_ok_return(rv, load_terminfo_from_path(cdir, term));
+            }
+            dir = strtok(NULL, ":");
+        }
+    }
+
+#ifdef TB_TERMINFO_DIR
+    if_ok_return(rv, load_terminfo_from_path(TB_TERMINFO_DIR, term));
+#endif
+    if_ok_return(rv, load_terminfo_from_path("/usr/local/etc/terminfo", term));
+    if_ok_return(rv,
+        load_terminfo_from_path("/usr/local/share/terminfo", term));
+    if_ok_return(rv, load_terminfo_from_path("/usr/local/lib/terminfo", term));
+    if_ok_return(rv, load_terminfo_from_path("/etc/terminfo", term));
+    if_ok_return(rv, load_terminfo_from_path("/usr/share/terminfo", term));
+    if_ok_return(rv, load_terminfo_from_path("/usr/lib/terminfo", term));
+    if_ok_return(rv, load_terminfo_from_path("/usr/share/lib/terminfo", term));
+    if_ok_return(rv, load_terminfo_from_path("/lib/terminfo", term));
+
+    return TB_ERR;
+}
+
+static int load_terminfo_from_path(const char *path, const char *term) {
+    int rv;
+    char tmp[TB_PATH_MAX];
+
+    // Look for term at this terminfo location, e.g., <terminfo>/x/xterm
+    snprintf_or_return(rv, tmp, sizeof(tmp), "%s/%c/%s", path, term[0], term);
+    if_ok_return(rv, read_terminfo_path(tmp));
+
+#ifdef __APPLE__
+    // Try the Darwin equivalent path, e.g., <terminfo>/78/xterm
+    snprintf_or_return(rv, tmp, sizeof(tmp), "%s/%x/%s", path, term[0], term);
+    return read_terminfo_path(tmp);
+#endif
+
+    return TB_ERR;
+}
+
+static int read_terminfo_path(const char *path) {
+    FILE *fp = fopen(path, "rb");
+    if (!fp) return TB_ERR;
+
+    struct stat st;
+    if (fstat(fileno(fp), &st) != 0) {
+        fclose(fp);
+        return TB_ERR;
+    }
+
+    size_t fsize = st.st_size;
+    char *data = (char *)tb_malloc(fsize);
+    if (!data) {
+        fclose(fp);
+        return TB_ERR;
+    }
+
+    if (fread(data, 1, fsize, fp) != fsize) {
+        fclose(fp);
+        tb_free(data);
+        return TB_ERR;
+    }
+
+    global.terminfo = data;
+    global.nterminfo = fsize;
+
+    fclose(fp);
+    return TB_OK;
+}
+
+static int parse_terminfo_caps(void) {
+    // See term(5) "LEGACY STORAGE FORMAT" and "EXTENDED STORAGE FORMAT" for a
+    // description of this behavior.
+
+    // Ensure there's at least a header's worth of data
+    if (global.nterminfo < 6 * (int)sizeof(int16_t)) return TB_ERR;
+
+    int16_t magic_number, nbytes_names, nbytes_bools, num_ints, num_offsets,
+        nbytes_strings;
+    size_t nbytes_header = 6 * sizeof(int16_t);
+    // header[0] the magic number (octal 0432 or 01036)
+    // header[1] the size, in bytes, of the names section
+    // header[2] the number of bytes in the boolean section
+    // header[3] the number of short integers in the numbers section
+    // header[4] the number of offsets (short integers) in the strings section
+    // header[5] the size, in bytes, of the string table
+    get_terminfo_int16(0 * sizeof(int16_t), &magic_number);
+    get_terminfo_int16(1 * sizeof(int16_t), &nbytes_names);
+    get_terminfo_int16(2 * sizeof(int16_t), &nbytes_bools);
+    get_terminfo_int16(3 * sizeof(int16_t), &num_ints);
+    get_terminfo_int16(4 * sizeof(int16_t), &num_offsets);
+    get_terminfo_int16(5 * sizeof(int16_t), &nbytes_strings);
+
+    // Legacy ints are 16-bit, extended ints are 32-bit
+    const int bytes_per_int = magic_number == 01036 ? 4  // 32-bit
+                                                    : 2; // 16-bit
+
+    // > Between the boolean section and the number section, a null byte will be
+    // > inserted, if necessary, to ensure that the number section begins on an
+    // > even byte
+    const int align_offset = (nbytes_names + nbytes_bools) % 2 != 0 ? 1 : 0;
+
+    const int pos_str_offsets =
+        nbytes_header  // header (12 bytes)
+        + nbytes_names // length of names section
+        + nbytes_bools // length of boolean section
+        + align_offset +
+        (num_ints * bytes_per_int); // length of numbers section
+
+    const int pos_str_table =
+        pos_str_offsets +
+        (num_offsets * sizeof(int16_t)); // length of string offsets table
+
+    // Load caps
+    int i;
+    for (i = 0; i < TB_CAP__COUNT; i++) {
+        const char *cap = get_terminfo_string(pos_str_offsets, num_offsets,
+            pos_str_table, nbytes_strings, terminfo_cap_indexes[i]);
+        if (!cap) {
+            // Something is not right
+            return TB_ERR;
+        }
+        global.caps[i] = cap;
+    }
+
+    return TB_OK;
+}
+
+static int load_builtin_caps(void) {
+    int i, j;
+    const char *term = getenv("TERM");
+
+    if (!term) return TB_ERR_NO_TERM;
+
+    // Check for exact TERM match
+    for (i = 0; builtin_terms[i].name != NULL; i++) {
+        if (strcmp(term, builtin_terms[i].name) == 0) {
+            for (j = 0; j < TB_CAP__COUNT; j++) {
+                global.caps[j] = builtin_terms[i].caps[j];
+            }
+            return TB_OK;
+        }
+    }
+
+    // Check for partial TERM or alias match
+    for (i = 0; builtin_terms[i].name != NULL; i++) {
+        if (strstr(term, builtin_terms[i].name) != NULL ||
+            (*(builtin_terms[i].alias) != '\0' &&
+                strstr(term, builtin_terms[i].alias) != NULL))
+        {
+            for (j = 0; j < TB_CAP__COUNT; j++) {
+                global.caps[j] = builtin_terms[i].caps[j];
+            }
+            return TB_OK;
+        }
+    }
+
+    return TB_ERR_UNSUPPORTED_TERM;
+}
+
+static const char *get_terminfo_string(int16_t offsets_pos, int16_t offsets_len,
+    int16_t table_pos, int16_t table_size, int16_t index) {
+    if (index >= offsets_len) {
+        // An index beyond the offset table indicates absent
+        // See `convert_strings` in tinfo `read_entry.c`
+        return "";
+    }
+
+    int16_t table_offset;
+    int table_offset_offset = (int)offsets_pos + (index * (int)sizeof(int16_t));
+    if (get_terminfo_int16(table_offset_offset, &table_offset) != TB_OK) {
+        // offset beyond end of terminfo entry
+        // Truncated/corrupt terminfo entry?
+        return NULL;
+    }
+
+    if (table_offset < 0 || table_offset >= table_size) {
+        // A negative offset indicates absent
+        // An offset beyond the string table indicates absent
+        // See `convert_strings` in tinfo `read_entry.c`
+        return "";
+    }
+
+    int str_offset = (int)table_pos + (int)table_offset;
+    if (str_offset >= (int)global.nterminfo) {
+        // string beyond end of terminfo entry
+        // Truncated/corrupt terminfo entry?
+        return NULL;
+    }
+
+    return (const char *)(global.terminfo + str_offset);
+}
+
+static int get_terminfo_int16(int offset, int16_t *val) {
+    if (offset < 0 || offset >= (int)global.nterminfo) {
+        *val = -1;
+        return TB_ERR;
+    }
+    memcpy(val, global.terminfo + offset, sizeof(int16_t));
+    return TB_OK;
+}
+
+static int wait_event(struct tb_event *event, int timeout) {
+    int rv;
+    char buf[TB_OPT_READ_BUF];
+
+    memset(event, 0, sizeof(*event));
+    if_ok_return(rv, extract_event(event));
+
+    fd_set fds;
+    struct timeval tv;
+    tv.tv_sec = timeout / 1000;
+    tv.tv_usec = (timeout - (tv.tv_sec * 1000)) * 1000;
+
+    do {
+        FD_ZERO(&fds);
+        FD_SET(global.rfd, &fds);
+        FD_SET(global.resize_pipefd[0], &fds);
+
+        int maxfd = global.resize_pipefd[0] > global.rfd
+                        ? global.resize_pipefd[0]
+                        : global.rfd;
+
+        int select_rv =
+            select(maxfd + 1, &fds, NULL, NULL, (timeout < 0) ? NULL : &tv);
+
+        if (select_rv < 0) {
+            // Let EINTR/EAGAIN bubble up
+            global.last_errno = errno;
+            return TB_ERR_POLL;
+        } else if (select_rv == 0) {
+            return TB_ERR_NO_EVENT;
+        }
+
+        int tty_has_events = (FD_ISSET(global.rfd, &fds));
+        int resize_has_events = (FD_ISSET(global.resize_pipefd[0], &fds));
+
+        if (tty_has_events) {
+            ssize_t read_rv = read(global.rfd, buf, sizeof(buf));
+            if (read_rv < 0) {
+                global.last_errno = errno;
+                return TB_ERR_READ;
+            } else if (read_rv > 0) {
+                bytebuf_nputs(&global.in, buf, read_rv);
+            }
+        }
+
+        if (resize_has_events) {
+            int ignore = 0;
+            read(global.resize_pipefd[0], &ignore, sizeof(ignore));
+            // TODO: Harden against errors encountered mid-resize
+            if_err_return(rv, update_term_size());
+            if_err_return(rv, resize_cellbufs());
+            event->type = TB_EVENT_RESIZE;
+            event->w = global.width;
+            event->h = global.height;
+            return TB_OK;
+        }
+
+        memset(event, 0, sizeof(*event));
+        if_ok_return(rv, extract_event(event));
+    } while (timeout == -1);
+
+    return rv;
+}
+
+static int extract_event(struct tb_event *event) {
+    int rv;
+    struct bytebuf_t *in = &global.in;
+
+    if (in->len == 0) return TB_ERR;
+
+    if (in->buf[0] == '\x1b') {
+        // Escape sequence?
+        // In TB_INPUT_ESC, skip if the buffer is a single escape char
+        if (!((global.input_mode & TB_INPUT_ESC) && in->len == 1)) {
+            if_ok_or_need_more_return(rv, extract_esc(event));
+        }
+
+        // Escape key?
+        if (global.input_mode & TB_INPUT_ESC) {
+            event->type = TB_EVENT_KEY;
+            event->ch = 0;
+            event->key = TB_KEY_ESC;
+            event->mod = 0;
+            bytebuf_shift(in, 1);
+            return TB_OK;
+        }
+
+        // Recurse for alt key
+        event->mod |= TB_MOD_ALT;
+        bytebuf_shift(in, 1);
+        return extract_event(event);
+    }
+
+    // ASCII control key?
+    int is_ctrl =
+        (uint16_t)in->buf[0] < TB_KEY_SPACE || in->buf[0] == TB_KEY_BACKSPACE2;
+    if (is_ctrl) {
+        event->type = TB_EVENT_KEY;
+        event->ch = 0;
+        event->key = (uint16_t)in->buf[0];
+        event->mod |= TB_MOD_CTRL;
+        bytebuf_shift(in, 1);
+        return TB_OK;
+    }
+
+    // UTF-8?
+    if (in->len >= (size_t)tb_utf8_char_length(in->buf[0])) {
+        event->type = TB_EVENT_KEY;
+        tb_utf8_char_to_unicode(&event->ch, in->buf);
+        event->key = 0;
+        bytebuf_shift(in, tb_utf8_char_length(in->buf[0]));
+        return TB_OK;
+    }
+
+    // Need more input
+    return TB_ERR;
+}
+
+static int extract_esc(struct tb_event *event) {
+    int rv;
+    if_ok_or_need_more_return(rv, extract_esc_user(event, 0));
+    if_ok_or_need_more_return(rv, extract_esc_cap(event));
+    if_ok_or_need_more_return(rv, extract_esc_mouse(event));
+    if_ok_or_need_more_return(rv, extract_esc_user(event, 1));
+    return TB_ERR;
+}
+
+static int extract_esc_user(struct tb_event *event, int is_post) {
+    int rv;
+    size_t consumed = 0;
+    struct bytebuf_t *in = &global.in;
+    int (*fn)(struct tb_event *, size_t *);
+
+    fn = is_post ? global.fn_extract_esc_post : global.fn_extract_esc_pre;
+
+    if (!fn) return TB_ERR;
+
+    rv = fn(event, &consumed);
+    if (rv == TB_OK) bytebuf_shift(in, consumed);
+
+    if_ok_or_need_more_return(rv, rv);
+    return TB_ERR;
+}
+
+static int extract_esc_cap(struct tb_event *event) {
+    int rv;
+    struct bytebuf_t *in = &global.in;
+    struct cap_trie_t *node;
+    size_t depth;
+
+    if_err_return(rv, cap_trie_find(in->buf, in->len, &node, &depth));
+    if (node->is_leaf) {
+        // Found a leaf node
+        event->type = TB_EVENT_KEY;
+        event->ch = 0;
+        event->key = node->key;
+        event->mod = node->mod;
+        bytebuf_shift(in, depth);
+        return TB_OK;
+    } else if (node->nchildren > 0 && in->len <= depth) {
+        // Found a branch node (not enough input)
+        return TB_ERR_NEED_MORE;
+    }
+
+    return TB_ERR;
+}
+
+static int extract_esc_mouse(struct tb_event *event) {
+    struct bytebuf_t *in = &global.in;
+
+    enum { TYPE_VT200 = 0, TYPE_1006, TYPE_1015, TYPE_MAX };
+
+    const char *cmp[TYPE_MAX] = {//
+        // X10 mouse encoding, the simplest one
+        // \x1b [ M Cb Cx Cy
+        [TYPE_VT200] = "\x1b[M",
+        // xterm 1006 extended mode or urxvt 1015 extended mode
+        // xterm: \x1b [ < Cb ; Cx ; Cy (M or m)
+        [TYPE_1006] = "\x1b[<",
+        // urxvt: \x1b [ Cb ; Cx ; Cy M
+        [TYPE_1015] = "\x1b["};
+
+    int type = 0;
+    int ret = TB_ERR;
+
+    // Unrolled at compile-time (probably)
+    for (; type < TYPE_MAX; type++) {
+        size_t size = strlen(cmp[type]);
+
+        if (in->len >= size && (strncmp(cmp[type], in->buf, size)) == 0) {
+            break;
+        }
+    }
+
+    if (type == TYPE_MAX) {
+        ret = TB_ERR; // No match
+        return ret;
+    }
+
+    size_t buf_shift = 0;
+
+    switch (type) {
+        case TYPE_VT200:
+            if (in->len >= 6) {
+                int b = in->buf[3] - 0x20;
+                int fail = 0;
+
+                switch (b & 3) {
+                    case 0:
+                        event->key = ((b & 64) != 0) ? TB_KEY_MOUSE_WHEEL_UP
+                                                     : TB_KEY_MOUSE_LEFT;
+                        break;
+                    case 1:
+                        event->key = ((b & 64) != 0) ? TB_KEY_MOUSE_WHEEL_DOWN
+                                                     : TB_KEY_MOUSE_MIDDLE;
+                        break;
+                    case 2:
+                        event->key = TB_KEY_MOUSE_RIGHT;
+                        break;
+                    case 3:
+                        event->key = TB_KEY_MOUSE_RELEASE;
+                        break;
+                    default:
+                        ret = TB_ERR;
+                        fail = 1;
+                        break;
+                }
+
+                if (!fail) {
+                    if ((b & 32) != 0) {
+                        event->mod |= TB_MOD_MOTION;
+                    }
+
+                    // the coord is 1,1 for upper left
+                    event->x = ((uint8_t)in->buf[4]) - 0x21;
+                    event->y = ((uint8_t)in->buf[5]) - 0x21;
+
+                    ret = TB_OK;
+                }
+
+                buf_shift = 6;
+            }
+            break;
+        case TYPE_1006:
+            // fallthrough
+        case TYPE_1015: {
+            size_t index_fail = (size_t)-1;
+
+            enum {
+                FIRST_M = 0,
+                FIRST_SEMICOLON,
+                LAST_SEMICOLON,
+                FIRST_LAST_MAX
+            };
+
+            size_t indices[FIRST_LAST_MAX] = {index_fail, index_fail,
+                index_fail};
+            int m_is_capital = 0;
+
+            for (size_t i = 0; i < in->len; i++) {
+                if (in->buf[i] == ';') {
+                    if (indices[FIRST_SEMICOLON] == index_fail) {
+                        indices[FIRST_SEMICOLON] = i;
+                    } else {
+                        indices[LAST_SEMICOLON] = i;
+                    }
+                } else if (indices[FIRST_M] == index_fail) {
+                    if (in->buf[i] == 'm' || in->buf[i] == 'M') {
+                        m_is_capital = (in->buf[i] == 'M');
+                        indices[FIRST_M] = i;
+                    }
+                }
+            }
+
+            if (indices[FIRST_M] == index_fail ||
+                indices[FIRST_SEMICOLON] == index_fail ||
+                indices[LAST_SEMICOLON] == index_fail)
+            {
+                ret = TB_ERR;
+            } else {
+                int start = (type == TYPE_1015 ? 2 : 3);
+
+                unsigned n1 = strtoul(&in->buf[start], NULL, 10);
+                unsigned n2 =
+                    strtoul(&in->buf[indices[FIRST_SEMICOLON] + 1], NULL, 10);
+                unsigned n3 =
+                    strtoul(&in->buf[indices[LAST_SEMICOLON] + 1], NULL, 10);
+
+                if (type == TYPE_1015) {
+                    n1 -= 0x20;
+                }
+
+                int fail = 0;
+
+                switch (n1 & 3) {
+                    case 0:
+                        event->key = ((n1 & 64) != 0) ? TB_KEY_MOUSE_WHEEL_UP
+                                                      : TB_KEY_MOUSE_LEFT;
+                        break;
+                    case 1:
+                        event->key = ((n1 & 64) != 0) ? TB_KEY_MOUSE_WHEEL_DOWN
+                                                      : TB_KEY_MOUSE_MIDDLE;
+                        break;
+                    case 2:
+                        event->key = TB_KEY_MOUSE_RIGHT;
+                        break;
+                    case 3:
+                        event->key = TB_KEY_MOUSE_RELEASE;
+                        break;
+                    default:
+                        ret = TB_ERR;
+                        fail = 1;
+                        break;
+                }
+
+                buf_shift = in->len;
+
+                if (!fail) {
+                    if (!m_is_capital) {
+                        // on xterm mouse release is signaled by lowercase m
+                        event->key = TB_KEY_MOUSE_RELEASE;
+                    }
+
+                    if ((n1 & 32) != 0) {
+                        event->mod |= TB_MOD_MOTION;
+                    }
+
+                    event->x = ((uint8_t)n2) - 1;
+                    event->y = ((uint8_t)n3) - 1;
+
+                    ret = TB_OK;
+                }
+            }
+        } break;
+        case TYPE_MAX:
+            ret = TB_ERR;
+    }
+
+    if (buf_shift > 0) bytebuf_shift(in, buf_shift);
+
+    if (ret == TB_OK) event->type = TB_EVENT_MOUSE;
+
+    return ret;
+}
+
+static int resize_cellbufs(void) {
+    int rv;
+    if_err_return(rv,
+        cellbuf_resize(&global.back, global.width, global.height));
+    if_err_return(rv,
+        cellbuf_resize(&global.front, global.width, global.height));
+    if_err_return(rv, cellbuf_clear(&global.front));
+    if_err_return(rv, send_clear());
+    return TB_OK;
+}
+
+static void handle_resize(int sig) {
+    int errno_copy = errno;
+    write(global.resize_pipefd[1], &sig, sizeof(sig));
+    errno = errno_copy;
+}
+
+static int send_attr(uintattr_t fg, uintattr_t bg) {
+    int rv;
+
+    if (fg == global.last_fg && bg == global.last_bg) {
+        return TB_OK;
+    }
+
+    if_err_return(rv, bytebuf_puts(&global.out, global.caps[TB_CAP_SGR0]));
+
+    uint32_t cfg, cbg;
+    switch (global.output_mode) {
+        default:
+        case TB_OUTPUT_NORMAL:
+            // The minus 1 below is because our colors are 1-indexed starting
+            // from black. Black is represented by a 30, 40, 90, or 100 for fg,
+            // bg, bright fg, or bright bg respectively. Red is 31, 41, 91,
+            // 101, etc.
+            cfg = (fg & TB_BRIGHT ? 90 : 30) + (fg & 0x0f) - 1;
+            cbg = (bg & TB_BRIGHT ? 100 : 40) + (bg & 0x0f) - 1;
+            break;
+
+        case TB_OUTPUT_256:
+            cfg = fg & 0xff;
+            cbg = bg & 0xff;
+            if (fg & TB_HI_BLACK) cfg = 0;
+            if (bg & TB_HI_BLACK) cbg = 0;
+            break;
+
+        case TB_OUTPUT_216:
+            cfg = fg & 0xff;
+            cbg = bg & 0xff;
+            if (cfg > 216) cfg = 216;
+            if (cbg > 216) cbg = 216;
+            cfg += 0x0f;
+            cbg += 0x0f;
+            break;
+
+        case TB_OUTPUT_GRAYSCALE:
+            cfg = fg & 0xff;
+            cbg = bg & 0xff;
+            if (cfg > 24) cfg = 24;
+            if (cbg > 24) cbg = 24;
+            cfg += 0xe7;
+            cbg += 0xe7;
+            break;
+
+#if TB_OPT_ATTR_W >= 32
+        case TB_OUTPUT_TRUECOLOR:
+            cfg = fg & 0xffffff;
+            cbg = bg & 0xffffff;
+            if (fg & TB_HI_BLACK) cfg = 0;
+            if (bg & TB_HI_BLACK) cbg = 0;
+            break;
+#endif
+    }
+
+    if (fg & TB_BOLD)
+        if_err_return(rv, bytebuf_puts(&global.out, global.caps[TB_CAP_BOLD]));
+
+    if (fg & TB_BLINK)
+        if_err_return(rv, bytebuf_puts(&global.out, global.caps[TB_CAP_BLINK]));
+
+    if (fg & TB_UNDERLINE)
+        if_err_return(rv,
+            bytebuf_puts(&global.out, global.caps[TB_CAP_UNDERLINE]));
+
+    if (fg & TB_ITALIC)
+        if_err_return(rv,
+            bytebuf_puts(&global.out, global.caps[TB_CAP_ITALIC]));
+
+    if (fg & TB_DIM)
+        if_err_return(rv, bytebuf_puts(&global.out, global.caps[TB_CAP_DIM]));
+
+#if TB_OPT_ATTR_W == 64
+    if (fg & TB_STRIKEOUT)
+        if_err_return(rv, bytebuf_puts(&global.out, TB_HARDCAP_STRIKEOUT));
+
+    if (fg & TB_UNDERLINE_2)
+        if_err_return(rv, bytebuf_puts(&global.out, TB_HARDCAP_UNDERLINE_2));
+
+    if (fg & TB_OVERLINE)
+        if_err_return(rv, bytebuf_puts(&global.out, TB_HARDCAP_OVERLINE));
+
+    if (fg & TB_INVISIBLE)
+        if_err_return(rv,
+            bytebuf_puts(&global.out, global.caps[TB_CAP_INVISIBLE]));
+#endif
+
+    if ((fg & TB_REVERSE) || (bg & TB_REVERSE))
+        if_err_return(rv,
+            bytebuf_puts(&global.out, global.caps[TB_CAP_REVERSE]));
+
+    int fg_is_default = (fg & 0xff) == 0;
+    int bg_is_default = (bg & 0xff) == 0;
+    if (global.output_mode == TB_OUTPUT_256) {
+        if (fg & TB_HI_BLACK) fg_is_default = 0;
+        if (bg & TB_HI_BLACK) bg_is_default = 0;
+    }
+#if TB_OPT_ATTR_W >= 32
+    if (global.output_mode == TB_OUTPUT_TRUECOLOR) {
+        fg_is_default = ((fg & 0xffffff) == 0) && ((fg & TB_HI_BLACK) == 0);
+        bg_is_default = ((bg & 0xffffff) == 0) && ((bg & TB_HI_BLACK) == 0);
+    }
+#endif
+
+    if_err_return(rv, send_sgr(cfg, cbg, fg_is_default, bg_is_default));
+
+    global.last_fg = fg;
+    global.last_bg = bg;
+
+    return TB_OK;
+}
+
+static int send_sgr(uint32_t cfg, uint32_t cbg, int fg_is_default,
+    int bg_is_default) {
+    int rv;
+    char nbuf[32];
+
+    if (fg_is_default && bg_is_default) {
+        return TB_OK;
+    }
+
+    switch (global.output_mode) {
+        default:
+        case TB_OUTPUT_NORMAL:
+            send_literal(rv, "\x1b[");
+            if (!fg_is_default) {
+                send_num(rv, nbuf, cfg);
+                if (!bg_is_default) {
+                    send_literal(rv, ";");
+                }
+            }
+            if (!bg_is_default) {
+                send_num(rv, nbuf, cbg);
+            }
+            send_literal(rv, "m");
+            break;
+
+        case TB_OUTPUT_256:
+        case TB_OUTPUT_216:
+        case TB_OUTPUT_GRAYSCALE:
+            send_literal(rv, "\x1b[");
+            if (!fg_is_default) {
+                send_literal(rv, "38;5;");
+                send_num(rv, nbuf, cfg);
+                if (!bg_is_default) {
+                    send_literal(rv, ";");
+                }
+            }
+            if (!bg_is_default) {
+                send_literal(rv, "48;5;");
+                send_num(rv, nbuf, cbg);
+            }
+            send_literal(rv, "m");
+            break;
+
+#if TB_OPT_ATTR_W >= 32
+        case TB_OUTPUT_TRUECOLOR:
+            send_literal(rv, "\x1b[");
+            if (!fg_is_default) {
+                send_literal(rv, "38;2;");
+                send_num(rv, nbuf, (cfg >> 16) & 0xff);
+                send_literal(rv, ";");
+                send_num(rv, nbuf, (cfg >> 8) & 0xff);
+                send_literal(rv, ";");
+                send_num(rv, nbuf, cfg & 0xff);
+                if (!bg_is_default) {
+                    send_literal(rv, ";");
+                }
+            }
+            if (!bg_is_default) {
+                send_literal(rv, "48;2;");
+                send_num(rv, nbuf, (cbg >> 16) & 0xff);
+                send_literal(rv, ";");
+                send_num(rv, nbuf, (cbg >> 8) & 0xff);
+                send_literal(rv, ";");
+                send_num(rv, nbuf, cbg & 0xff);
+            }
+            send_literal(rv, "m");
+            break;
+#endif
+    }
+    return TB_OK;
+}
+
+static int send_cursor_if(int x, int y) {
+    int rv;
+    char nbuf[32];
+    if (x < 0 || y < 0) {
+        return TB_OK;
+    }
+    send_literal(rv, "\x1b[");
+    send_num(rv, nbuf, y + 1);
+    send_literal(rv, ";");
+    send_num(rv, nbuf, x + 1);
+    send_literal(rv, "H");
+    return TB_OK;
+}
+
+static int send_char(int x, int y, uint32_t ch) {
+    return send_cluster(x, y, &ch, 1);
+}
+
+static int send_cluster(int x, int y, uint32_t *ch, size_t nch) {
+    int rv;
+    char chu8[8];
+
+    if (global.last_x != x - 1 || global.last_y != y) {
+        if_err_return(rv, send_cursor_if(x, y));
+    }
+    global.last_x = x;
+    global.last_y = y;
+
+    int i;
+    for (i = 0; i < (int)nch; i++) {
+        uint32_t ch32 = *(ch + i);
+        if (!tb_iswprint(ch32)) {
+            ch32 = 0xfffd; // replace non-printable codepoints with U+FFFD
+        }
+        int chu8_len = tb_utf8_unicode_to_char(chu8, ch32);
+        if_err_return(rv, bytebuf_nputs(&global.out, chu8, (size_t)chu8_len));
+    }
+
+    return TB_OK;
+}
+
+static int convert_num(uint32_t num, char *buf) {
+    int i, l = 0;
+    char ch;
+    do {
+        buf[l++] = (char)('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;
+}
+
+static int cell_cmp(struct tb_cell *a, struct tb_cell *b) {
+    if (a->ch != b->ch || a->fg != b->fg || a->bg != b->bg) {
+        return 1;
+    }
+#ifdef TB_OPT_EGC
+    if (a->nech != b->nech) {
+        return 1;
+    } else if (a->nech > 0) { // a->nech == b->nech
+        return memcmp(a->ech, b->ech, a->nech);
+    }
+#endif
+    return 0;
+}
+
+static int cell_copy(struct tb_cell *dst, struct tb_cell *src) {
+#ifdef TB_OPT_EGC
+    if (src->nech > 0) {
+        return cell_set(dst, src->ech, src->nech, src->fg, src->bg);
+    }
+#endif
+    return cell_set(dst, &src->ch, 1, src->fg, src->bg);
+}
+
+static int cell_set(struct tb_cell *cell, uint32_t *ch, size_t nch,
+    uintattr_t fg, uintattr_t bg) {
+    // TODO: iswprint ch?
+    cell->ch = ch ? *ch : 0;
+    cell->fg = fg;
+    cell->bg = bg;
+#ifdef TB_OPT_EGC
+    if (nch <= 1) {
+        cell->nech = 0;
+    } else {
+        int rv;
+        if_err_return(rv, cell_reserve_ech(cell, nch + 1));
+        memcpy(cell->ech, ch, sizeof(*ch) * nch);
+        cell->ech[nch] = '\0';
+        cell->nech = nch;
+    }
+#else
+    (void)nch;
+    (void)cell_reserve_ech;
+#endif
+    return TB_OK;
+}
+
+static int cell_reserve_ech(struct tb_cell *cell, size_t n) {
+#ifdef TB_OPT_EGC
+    if (cell->cech >= n) return TB_OK;
+    cell->ech = (uint32_t *)tb_realloc(cell->ech, n * sizeof(cell->ch));
+    if (!cell->ech) return TB_ERR_MEM;
+    cell->cech = n;
+    return TB_OK;
+#else
+    (void)cell;
+    (void)n;
+    return TB_ERR;
+#endif
+}
+
+static int cell_free(struct tb_cell *cell) {
+#ifdef TB_OPT_EGC
+    if (cell->ech) tb_free(cell->ech);
+#endif
+    memset(cell, 0, sizeof(*cell));
+    return TB_OK;
+}
+
+static int cellbuf_init(struct cellbuf_t *c, int w, int h) {
+    c->cells = (struct tb_cell *)tb_malloc(sizeof(struct tb_cell) * w * h);
+    if (!c->cells) return TB_ERR_MEM;
+    memset(c->cells, 0, sizeof(struct tb_cell) * w * h);
+    c->width = w;
+    c->height = h;
+    return TB_OK;
+}
+
+static int cellbuf_free(struct cellbuf_t *c) {
+    if (c->cells) {
+        int i;
+        for (i = 0; i < c->width * c->height; i++) {
+            cell_free(&c->cells[i]);
+        }
+        tb_free(c->cells);
+    }
+    memset(c, 0, sizeof(*c));
+    return TB_OK;
+}
+
+static int cellbuf_clear(struct cellbuf_t *c) {
+    int rv, i;
+    uint32_t space = (uint32_t)' ';
+    for (i = 0; i < c->width * c->height; i++) {
+        if_err_return(rv,
+            cell_set(&c->cells[i], &space, 1, global.fg, global.bg));
+    }
+    return TB_OK;
+}
+
+static int cellbuf_get(struct cellbuf_t *c, int x, int y,
+    struct tb_cell **out) {
+    if (!cellbuf_in_bounds(c, x, y)) {
+        *out = NULL;
+        return TB_ERR_OUT_OF_BOUNDS;
+    }
+    *out = &c->cells[(y * c->width) + x];
+    return TB_OK;
+}
+
+static int cellbuf_in_bounds(struct cellbuf_t *c, int x, int y) {
+    if (x < 0 || x >= c->width || y < 0 || y >= c->height) {
+        return 0;
+    }
+    return 1;
+}
+
+static int cellbuf_resize(struct cellbuf_t *c, int w, int h) {
+    int rv;
+
+    int ow = c->width;
+    int oh = c->height;
+
+    if (ow == w && oh == h) {
+        return TB_OK;
+    }
+
+    w = w < 1 ? 1 : w;
+    h = h < 1 ? 1 : h;
+
+    int minw = (w < ow) ? w : ow;
+    int minh = (h < oh) ? h : oh;
+
+    struct tb_cell *prev = c->cells;
+
+    if_err_return(rv, cellbuf_init(c, w, h));
+    if_err_return(rv, cellbuf_clear(c));
+
+    int x, y;
+    for (x = 0; x < minw; x++) {
+        for (y = 0; y < minh; y++) {
+            struct tb_cell *src, *dst;
+            src = &prev[(y * ow) + x];
+            if_err_return(rv, cellbuf_get(c, x, y, &dst));
+            if_err_return(rv, cell_copy(dst, src));
+        }
+    }
+
+    tb_free(prev);
+
+    return TB_OK;
+}
+
+static int bytebuf_puts(struct bytebuf_t *b, const char *str) {
+    if (!str || strlen(str) <= 0) return TB_OK; // Nothing to do for empty caps
+    return bytebuf_nputs(b, str, (size_t)strlen(str));
+}
+
+static int bytebuf_nputs(struct bytebuf_t *b, const char *str, size_t nstr) {
+    int rv;
+    if_err_return(rv, bytebuf_reserve(b, b->len + nstr + 1));
+    memcpy(b->buf + b->len, str, nstr);
+    b->len += nstr;
+    b->buf[b->len] = '\0';
+    return TB_OK;
+}
+
+static int bytebuf_shift(struct bytebuf_t *b, size_t n) {
+    if (n > b->len) n = b->len;
+    size_t nmove = b->len - n;
+    memmove(b->buf, b->buf + n, nmove);
+    b->len -= n;
+    return TB_OK;
+}
+
+static int bytebuf_flush(struct bytebuf_t *b, int fd) {
+    if (b->len <= 0) return TB_OK;
+    ssize_t write_rv = write(fd, b->buf, b->len);
+    if (write_rv < 0 || (size_t)write_rv != b->len) {
+        // Note, errno will be 0 on partial write
+        global.last_errno = errno;
+        return TB_ERR;
+    }
+    b->len = 0;
+    return TB_OK;
+}
+
+static int bytebuf_reserve(struct bytebuf_t *b, size_t sz) {
+    if (b->cap >= sz) return TB_OK;
+
+    size_t newcap = b->cap > 0 ? b->cap : 1;
+    while (newcap < sz) {
+        newcap *= 2;
+    }
+
+    char *newbuf;
+    if (b->buf) {
+        newbuf = (char *)tb_realloc(b->buf, newcap);
+    } else {
+        newbuf = (char *)tb_malloc(newcap);
+    }
+    if (!newbuf) return TB_ERR_MEM;
+
+    b->buf = newbuf;
+    b->cap = newcap;
+    return TB_OK;
+}
+
+static int bytebuf_free(struct bytebuf_t *b) {
+    if (b->buf) tb_free(b->buf);
+    memset(b, 0, sizeof(*b));
+    return TB_OK;
+}
+
+int tb_iswprint(uint32_t ch) {
+#ifdef TB_OPT_LIBC_WCHAR
+    return iswprint((wint_t)ch);
+#else
+    return tb_iswprint_ex(ch, NULL);
+#endif
+}
+
+int tb_wcwidth(uint32_t ch) {
+#ifdef TB_OPT_LIBC_WCHAR
+    return wcwidth((wchar_t)ch);
+#else
+    return tb_wcswidth(&ch, 1);
+#endif
+}
+
+static int tb_wcswidth(uint32_t *ch, size_t nch) {
+#ifdef TB_OPT_LIBC_WCHAR
+    return wcswidth((wchar_t *)ch, nch);
+#else
+    int sw = 0;
+    size_t i = 0;
+    for (i = 0; i < nch; i++) {
+        int w;
+        tb_iswprint_ex(ch[i], &w);
+        if (w < 0) return -1;
+        sw += w;
+    }
+    return sw;
+#endif
+}
+
+static int tb_iswprint_ex(uint32_t ch, int *w) {
+#ifdef TB_OPT_LIBC_WCHAR
+    if (w) *w = wcwidth((wint_t)ch);
+    return iswprint(ch);
+#else
+    int lo = 0, hi = WCWIDTH_TABLE_LENGTH - 1;
+    if (ch >= 0x20 && ch <= 0x7e) { // fast path for ASCII
+        if (w) *w = 1;
+        return 1;
+    } else if (ch == 0) { // Special case for null, which is not represented in
+        if (w) *w = 0;    // wcwidth_table since it's the only codepoint that is
+        return 0;         // iswprint==0 but not wcwidth==-1. (It's wcwidth==0.)
+    }
+    while (lo <= hi) {
+        int i = (lo + hi) / 2;
+        if (ch < wcwidth_table[i].range_start) {
+            hi = i - 1;
+        } else if (ch > wcwidth_table[i].range_end) {
+            lo = i + 1;
+        } else {
+            if (w) *w = wcwidth_table[i].width;
+            return wcwidth_table[i].width >= 0 ? 1 : 0;
+        }
+    }
+    if (w) *w = -1; // invalid codepoint
+    return 0;
+#endif
+}
+
+#endif // TB_IMPL