From 16e00dd5f26ce342e9562bec08f529d98c23c01c Mon Sep 17 00:00:00 2001
From: Mattias Andrée <m@maandree.se>
Date: Sun, 16 Feb 2025 14:23:16 +0100
Subject: Improve code organisation, documentation, and m code improvement
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Mattias Andrée <m@maandree.se>
---
 Makefile                              |  12 +-
 common.h                              |  68 +++
 libterminput.c                        | 922 ----------------------------------
 libterminput_check_utf8_char__.c      |  36 ++
 libterminput_clear_flags.c            |  11 +
 libterminput_encode_utf8__.c          |  44 ++
 libterminput_is_ready.c               |   5 +
 libterminput_read.c                   | 653 ++++++++++++++++++++++++
 libterminput_read_bracketed_paste__.c | 108 ++++
 libterminput_set_flags.c              |  10 +
 libterminput_utf8_decode__.c          |  79 +++
 11 files changed, 1024 insertions(+), 924 deletions(-)
 create mode 100644 common.h
 delete mode 100644 libterminput.c
 create mode 100644 libterminput_check_utf8_char__.c
 create mode 100644 libterminput_clear_flags.c
 create mode 100644 libterminput_encode_utf8__.c
 create mode 100644 libterminput_is_ready.c
 create mode 100644 libterminput_read.c
 create mode 100644 libterminput_read_bracketed_paste__.c
 create mode 100644 libterminput_set_flags.c
 create mode 100644 libterminput_utf8_decode__.c

diff --git a/Makefile b/Makefile
index 5295a38..e958335 100644
--- a/Makefile
+++ b/Makefile
@@ -16,10 +16,18 @@ LIB_VERSION = $(LIB_MAJOR).$(LIB_MINOR)
 
 
 OBJ =\
-	libterminput.o
+	libterminput_read.o\
+	libterminput_is_ready.o\
+	libterminput_set_flags.o\
+	libterminput_clear_flags.o\
+	libterminput_encode_utf8__.o\
+	libterminput_check_utf8_char__.o\
+	libterminput_utf8_decode__.o\
+	libterminput_read_bracketed_paste__.o\
 
 HDR =\
-	libterminput.h
+	libterminput.h\
+	common.h
 
 TESTS =\
 	interactive-test\
diff --git a/common.h b/common.h
new file mode 100644
index 0000000..05b5269
--- /dev/null
+++ b/common.h
@@ -0,0 +1,68 @@
+/* See LICENSE file for copyright and license details. */
+#include "libterminput.h"
+
+#include <alloca.h>
+#include <ctype.h>
+#include <limits.h>
+#include <string.h>
+#include <unistd.h>
+
+
+#if defined(__GNUC__)
+# define HIDDEN __attribute__((__visibility__("hidden")))
+#else
+# define HIDDEN
+#endif
+
+
+struct input {
+	enum libterminput_mod mods;
+	char symbol[7];
+};
+
+
+/**
+ * Encode a Unicode codepoint in UTF-8
+ * 
+ * @param  codepoint  The codepoint to encode
+ * @param  buffer     Output buffer for the NUL-byte terminated UTF-8 encoding of `codepoint`
+ */
+HIDDEN void libterminput_encode_utf8__(unsigned long long int codepoint, char buffer[7]);
+
+/**
+ * Validate an UTF-8 byte sequence, up to one codepoint encoding
+ *
+ * @param   s        The buffer to read from
+ * @param   size     The number of bytes available in `s`
+ * @param   len_out  Output parameter for the encoding length of the
+ *                   codepoint encoded at the beginning of `s`
+ * @return           1 if `s` begins with a valid codepoint,
+ *                   0 if `size` is too small to determine the validity,
+ *                   -1 if the byte sequence is illegal
+ */
+HIDDEN int libterminput_check_utf8_char__(const char *s, size_t size, size_t *len_out);
+
+/**
+ * Decode a Unicode codepoint encoded in UTF-8
+ * 
+ * @param   s   The buffer to read from
+ * @param   ip  Pointer to the current position in `s`, will be updated
+ * @return      The first encode codepoint, 0 if invalid (or if 0)
+ */
+HIDDEN unsigned long long int libterminput_utf8_decode__(const char *s, size_t *ip);
+
+/**
+ * Get input, from the terminal that, that appear after
+ * the start marker for a bracketed paste
+ * 
+ * @param   fd     The file descriptor to the terminal
+ * @param   input  Output parameter for input
+ * @param   ctx    State for the terminal, parts of the state may be stored in `input`
+ * @return         1 normally, 0 on end of input, -1 on error
+ *
+ * @throws  Any reason specified for read(3)
+ */
+HIDDEN int libterminput_read_bracketed_paste__(int fd, union libterminput_input *input, struct libterminput_state *ctx);
+
+
+#undef HIDDEN
diff --git a/libterminput.c b/libterminput.c
deleted file mode 100644
index 86666c5..0000000
--- a/libterminput.c
+++ /dev/null
@@ -1,922 +0,0 @@
-/* See LICENSE file for copyright and license details. */
-#include "libterminput.h"
-
-#include <alloca.h>
-#include <ctype.h>
-#include <limits.h>
-#include <string.h>
-#include <unistd.h>
-
-
-struct input {
-	enum libterminput_mod mods;
-	char symbol[7];
-};
-
-
-static int
-read_input(int fd, struct input *input, struct libterminput_state *ctx)
-{
-	unsigned char c, tc;
-	ssize_t r;
-
-	/* Get next byte from input */
-	if (ctx->stored_head != ctx->stored_tail) {
-		c = ((unsigned char *)ctx->stored)[ctx->stored_tail++];
-		if (ctx->stored_tail == ctx->stored_head)
-			ctx->stored_tail = ctx->stored_head = 0;
-	} else {
-		r = read(fd, ctx->stored, sizeof(ctx->stored));
-		if (r <= 0)
-			return (int)r;
-		c = (unsigned char)ctx->stored[0];
-		if (r > 1) {
-			ctx->stored_tail = 1;
-			ctx->stored_head = (size_t)r;
-		}
-	}
-
-again:
-	if (ctx->n) {
-		/* Continuation of multibyte-character */
-		if ((c & 0xC0) != 0x80) {
-			/* Short multibyte-character: return short and store read byte from next input */
-			input->mods = ctx->mods;
-			ctx->partial[(unsigned char)ctx->npartial] = '\0';
-			ctx->n = 0;
-			ctx->npartial = 0;
-			ctx->mods = 0;
-			ctx->stored[ctx->stored_head++] = (char)c;
-			strcpy(input->symbol, ctx->partial);
-			return 1;
-		} else {
-			/* Store byte, and if done, return */
-			ctx->partial[(unsigned char)ctx->npartial++] = (char)c;
-			if (ctx->npartial == ctx->n) {
-				ctx->partial[(unsigned char)ctx->npartial] = '\0';
-				input->mods = ctx->mods;
-				ctx->npartial = 0;
-				ctx->mods = 0;
-				ctx->n = 0;
-				strcpy(input->symbol, ctx->partial);
-				return 1;
-			}
-		}
-	} else if (c == 033 && !*ctx->key) {
-		/* ESC at the beginning, save as a Meta/ESC (for default behaviour) */
-		if ((ctx->flags & LIBTERMINPUT_ESC_ON_BLOCK) && ctx->stored_tail == ctx->stored_head) {
-			input->symbol[0] = (char)c;
-			input->symbol[1] = '\0';
-			input->mods = ctx->mods;
-			ctx->mods = 0;
-			return 1;
-		}
-		ctx->meta += 1;
-	} else if (c == 0) {
-		/* CTRL on Space */
-		input->symbol[0] = ' ';
-		input->symbol[1] = '\0';
-		input->mods = ctx->mods | LIBTERMINPUT_CTRL;
-		ctx->mods = 0;
-		return 1;
-	} else if (c < (unsigned char)' ' && (char)c != '\t' && (char)c != '\b' && (char)c != '\n') {
-		/* CTRL on some some character key */
-		input->symbol[0] = (char)c + '@';
-		input->symbol[1] = '\0';
-		input->mods = ctx->mods | LIBTERMINPUT_CTRL;
-		ctx->mods = 0;
-		return 1;
-	} else if ((c & 0xC0) == 0xC0 && c != 0xFF) {
-		/* Beginning of multibyte-character */
-		ctx->n = 0;
-		for (tc = c; tc & 0x80; tc <<= 1)
-			ctx->n++;
-		if (ctx->n > 6) {
-			/* If overlong, return first byte a single-byte-character */
-			input->symbol[0] = (char)c;
-			input->symbol[1] = '\0';
-			input->mods = ctx->mods;
-			ctx->mods = 0;
-			return 1;
-		}
-		ctx->partial[0] = (char)c;
-		ctx->npartial = 1;
-	} else if (c & 0x80) {
-		/* 8th bit set to signify META */
-		c ^= 0x80;
-		ctx->mods |= LIBTERMINPUT_META;
-		goto again;
-	} else {
-		/* Single-byte-character */
-		input->symbol[0] = (char)c;
-		input->symbol[1] = '\0';
-		input->mods = ctx->mods;
-		ctx->mods = 0;
-		return 1;
-	}
-
-	input->symbol[0] = '\0';
-	input->mods = -1;
-	return 1;
-}
-
-
-static void
-encode_utf8(unsigned long long int codepoint, char buffer[7])
-{
-	static const char masks[6] = {(char)0x00, (char)0xC0, (char)0xE0, (char)0xF0, (char)0xF8, (char)0xFC};
-	static const unsigned long long int limits[6] = {
-		1ULL << (7 + 0 * 6),
-		1ULL << (5 + 1 * 6),
-		1ULL << (4 + 2 * 6),
-		1ULL << (3 + 3 * 6),
-		1ULL << (2 + 4 * 6),
-		1ULL << (1 + 5 * 6)
-	};
-	size_t len;
-	for (len = 0; codepoint >= limits[len]; len++);
-	buffer[0] = masks[len];
-	len += 1;
-	buffer[len] = '\0';
-	for (; --len; codepoint >>= 6)
-		buffer[len] = (char)((codepoint & 0x3FULL) | 0x80ULL);
-	buffer[0] |= (char)codepoint;
-}
-
-
-static int
-check_utf8_char(const char *s, size_t *lenp, size_t size)
-{
-	size_t i;
-	*lenp = 0;
-	if (!size) {
-		return 0;
-	} else if ((*s & 0x80) == 0) {
-		*lenp = 1;
-		return 1;
-	} else if ((*s & 0xE0) == 0xC0) {
-		*lenp = 2;
-	} else if ((*s & 0xF0) == 0xE0) {
-		*lenp = 3;
-	} else if ((*s & 0xF8) == 0xF0) {
-		*lenp = 4;
-	} else if ((*s & 0xFC) == 0xF8) {
-		*lenp = 5;
-	} else if ((*s & 0xFE) == 0xFC) {
-		*lenp = 6;
-	} else {
-		*lenp = 0;
-		return -1;
-	}
-	for (i = 1; i < *lenp; i++) {
-		if (i == size)
-			return 0;
-		if ((s[i] & 0xC0) != 0x80)
-			return -1;
-	}
-	return 1;
-}
-
-
-static unsigned long long int
-utf8_decode(const char *s, size_t *ip)
-{
-	unsigned long long int cp = 0;
-	size_t len;
-
-	if ((s[*ip] & 0x80) == 0) {
-		return (unsigned long long int)s[(*ip)++];
-	} else if ((s[*ip] & 0xE0) == 0xC0) {
-		cp = (unsigned long long int)((unsigned char)s[(*ip)++] ^ 0xC0U);
-		len = 2;
-		goto need_1;
-	} else if ((s[*ip] & 0xF0) == 0xE0) {
-		cp = (unsigned long long int)((unsigned char)s[(*ip)++] ^ 0xE0U);
-		len = 3;
-		goto need_2;
-	} else if ((s[*ip] & 0xF8) == 0xF0) {
-		cp = (unsigned long long int)((unsigned char)s[(*ip)++] ^ 0xF0U);
-		len = 4;
-		goto need_3;
-	} else if ((s[*ip] & 0xFC) == 0xF8) {
-		cp = (unsigned long long int)((unsigned char)s[(*ip)++] ^ 0xF8U);
-		len = 5;
-		goto need_4;
-	} else if ((s[*ip] & 0xFE) == 0xFC) {
-		cp = (unsigned long long int)((unsigned char)s[(*ip)++] ^ 0xFCU);
-		len = 6;
-		goto need_5;
-	}
-
-need_5:
-	if ((s[*ip] & 0xC0) != 0x80) return 0;
-	cp <<= 6;
-	cp |= (unsigned long long int)((unsigned char)s[(*ip)++] ^ 0x80U);
-
-need_4:
-	if ((s[*ip] & 0xC0) != 0x80) return 0;
-	cp <<= 6;
-	cp |= (unsigned long long int)((unsigned char)s[(*ip)++] ^ 0x80U);
-
-need_3:
-	if ((s[*ip] & 0xC0) != 0x80) return 0;
-	cp <<= 6;
-	cp |= (unsigned long long int)((unsigned char)s[(*ip)++] ^ 0x80U);
-
-need_2:
-	if ((s[*ip] & 0xC0) != 0x80) return 0;
-	cp <<= 6;
-	cp |= (unsigned long long int)((unsigned char)s[(*ip)++] ^ 0x80U);
-
-need_1:
-	if ((s[*ip] & 0xC0) != 0x80) return 0;
-	cp <<= 6;
-	cp |= (unsigned long long int)((unsigned char)s[(*ip)++] ^ 0x80U);
-
-	/* Let's ignore the 0x10FFFF upper bound. */
-
-	if (cp < 1ULL << (7 + 0 * 6))
-		return 0;
-	if (cp < 1ULL << (5 + 1 * 6))
-		return len > 2 ? 0ULL : cp;
-	if (cp < 1ULL << (4 + 2 * 6))
-		return len > 3 ? 0ULL : cp;
-	if (cp < 1ULL << (3 + 3 * 6))
-		return len > 4 ? 0ULL : cp;
-	if (cp < 1ULL << (2 + 4 * 6))
-		return len > 5 ? 0ULL : cp;
-	if (cp < 1ULL << (1 + 5 * 6))
-		return len > 6 ? 0ULL : cp;
-
-	return 0;
-}
-
-
-static void
-parse_sequence(union libterminput_input *input, struct libterminput_state *ctx)
-{
-	unsigned long long int *nums, numsbuf[6];
-	size_t keylen, n, nnums = 0, pos;
-	char *p;
-
-	/* Get number of numbers in the sequence, and allocate an array of at least 2 */
-	if (ctx->key[0] == '[' && (ctx->key[1] == '<' ? isdigit(ctx->key[2]) : isdigit(ctx->key[1])))
-		nnums += 1;
-	for (n = 2, p = ctx->key; *p; p++) {
-		if (*p == ';') {
-			n += 1;
-			nnums += 1;
-		}
-	}
-	nums = alloca(n * sizeof(*nums));
-	nums[0] = nums[1] = 0;
-
-	/* Read numbers and remove numbers and delimiters */
-	for (keylen = 0, n = 0, p = ctx->key; *p; p++) {
-		if (*p == ';') {
-			nums[++n] = 0; /* We made sure above to allocate one extra */
-		} else if (!isdigit(*p)) {
-			ctx->key[keylen++] = *p;
-		} else if (n < 3) {
-			if (nums[n] < (ULLONG_MAX - (*p & 15)) / 10)
-				nums[n] = nums[n] * 10 + (*p & 15);
-			else
-				nums[n] = ULLONG_MAX;
-		}
-	}
-	ctx->key[keylen] = '\0';
-
-	/* Get times and mods, and reset symbol, and more as keypress */
-	input->type = LIBTERMINPUT_KEYPRESS;
-	input->keypress.symbol[0] = '\0';
-	input->keypress.times = nums[0] + !nums[0];
-	input->keypress.mods = nums[1] > 1 ? nums[1] - 1 : 0;
-	input->keypress.mods |= ctx->meta > 1 ? LIBTERMINPUT_META : 0;
-
-	switch (ctx->key[0]) {
-	case '[':
-		switch (keylen) {
-		case 2:
-			switch (ctx->key[1]) {
-			case 'A': input->keypress.key = LIBTERMINPUT_UP;    break;
-			case 'B': input->keypress.key = LIBTERMINPUT_DOWN;  break;
-			case 'C': input->keypress.key = LIBTERMINPUT_RIGHT; break;
-			case 'D': input->keypress.key = LIBTERMINPUT_LEFT;  break;
-			case 'E': input->keypress.key = LIBTERMINPUT_BEGIN; break;
-			case 'F': input->keypress.key = LIBTERMINPUT_END;   break;
-			case 'G': input->keypress.key = LIBTERMINPUT_BEGIN; break;
-			case 'H': input->keypress.key = LIBTERMINPUT_HOME;  break;
-			case 'M':
-				if (ctx->flags & LIBTERMINPUT_MACRO_ON_CSI_M) {
-					input->keypress.key = LIBTERMINPUT_MACRO;
-				} else if (nnums >= 3) {
-					/* Parsing for \e[?1000;1015h output. */
-					nums[0] -= 32ULL;
-				decimal_mouse_tracking_set_press:
-					input->mouseevent.event = LIBTERMINPUT_PRESS;
-				decimal_mouse_tracking:
-					input->mouseevent.type = LIBTERMINPUT_MOUSEEVENT;
-					input->mouseevent.x = (size_t)nums[1] + (size_t)!nums[1];
-					input->mouseevent.y = (size_t)nums[2] + (size_t)!nums[2];
-					input->mouseevent.mods = (enum libterminput_mod)((nums[0] >> 2) & 7ULL);
-					if (nums[0] & 32)
-						input->mouseevent.event = LIBTERMINPUT_MOTION;
-					nums[0] = (nums[0] & 3ULL) | ((nums[0] >> 4) & ~3ULL);
-					if (nums[0] < 4) {
-						nums[0] = (nums[0] + 1) & 3;
-						if (!nums[0] && input->mouseevent.event == LIBTERMINPUT_PRESS) {
-							input->mouseevent.event = LIBTERMINPUT_RELEASE;
-							nums[0] = 1;
-						}
-					}
-					input->mouseevent.button = (enum libterminput_button)nums[0];
-				} else if (!nnums & !(ctx->flags & LIBTERMINPUT_DECSET_1005)) {
-					/* Parsing output for legacy mouse tracking output. */
-					ctx->mouse_tracking = 0;
-					nums = numsbuf;
-					nums[0] = (unsigned long long int)(unsigned char)ctx->stored[ctx->stored_tail++];
-					nums[1] = (unsigned long long int)(unsigned char)ctx->stored[ctx->stored_tail++];
-					nums[2] = (unsigned long long int)(unsigned char)ctx->stored[ctx->stored_tail++];
-					nums[0] = (nums[0] - 32ULL) & 255ULL;
-					nums[1] = (nums[1] - 32ULL) & 255ULL;
-					nums[2] = (nums[2] - 32ULL) & 255ULL;
-					if (ctx->stored_head == ctx->stored_tail)
-						ctx->stored_head = ctx->stored_tail = 0;
-					goto decimal_mouse_tracking_set_press;
-				} else if (!nnums) {
-					/* Parsing for semi-legacy \e[?1000;1005h output. */
-					ctx->mouse_tracking = 0;
-					nums = numsbuf;
-					pos = ctx->stored_tail;
-					if ((nums[0] = utf8_decode(ctx->stored, &ctx->stored_tail)) < 32 ||
-					    (nums[1] = utf8_decode(ctx->stored, &ctx->stored_tail)) < 32 ||
-					    (nums[2] = utf8_decode(ctx->stored, &ctx->stored_tail)) < 32) {
-						ctx->stored_tail = pos;
-						input->keypress.key = LIBTERMINPUT_MACRO;
-						return;
-					}
-					nums[0] = nums[0] - 32ULL;
-					nums[1] = nums[1] - 32ULL;
-					nums[2] = nums[2] - 32ULL;
-					if (ctx->stored_head == ctx->stored_tail)
-						ctx->stored_head = ctx->stored_tail = 0;
-					goto decimal_mouse_tracking_set_press;
-				} else {
-					goto suppress;
-				}
-				break;
-			case 'P':
-				input->keypress.key = LIBTERMINPUT_F1;
-				if (ctx->flags & LIBTERMINPUT_PAUSE_ON_CSI_P)
-					input->keypress.key = LIBTERMINPUT_PAUSE;
-				break;
-			case 'Q':
-				input->keypress.key = LIBTERMINPUT_F2;
-				break;
-			case 'R':
-				if ((ctx->flags & LIBTERMINPUT_AWAITING_CURSOR_POSITION) && nnums >= 2) {
-					input->position.type = LIBTERMINPUT_CURSOR_POSITION;
-					input->position.y = (size_t)nums[0] + (size_t)!nums[0];
-					input->position.x = (size_t)nums[1] + (size_t)!nums[1];
-				} else {
-					input->keypress.key = LIBTERMINPUT_F3;
-				}
-				break;
-			case 'S':
-				input->keypress.key = LIBTERMINPUT_F4;
-				break;
-			case 'T':
-				/* Parsing output for legacy mouse highlight tracking output. (\e[?1001h) */
-				ctx->mouse_tracking = 0;
-				nums = numsbuf;
-				nums[0] = (unsigned long long int)(unsigned char)ctx->stored[ctx->stored_tail++];
-				nums[1] = (unsigned long long int)(unsigned char)ctx->stored[ctx->stored_tail++];
-				nums[2] = (unsigned long long int)(unsigned char)ctx->stored[ctx->stored_tail++];
-				nums[3] = (unsigned long long int)(unsigned char)ctx->stored[ctx->stored_tail++];
-				nums[4] = (unsigned long long int)(unsigned char)ctx->stored[ctx->stored_tail++];
-				nums[5] = (unsigned long long int)(unsigned char)ctx->stored[ctx->stored_tail++];
-				nums[0] = (nums[0] - 32ULL) & 255ULL;
-				nums[1] = (nums[1] - 32ULL) & 255ULL;
-				nums[2] = (nums[2] - 32ULL) & 255ULL;
-				nums[3] = (nums[3] - 32ULL) & 255ULL;
-				nums[4] = (nums[4] - 32ULL) & 255ULL;
-				nums[5] = (nums[5] - 32ULL) & 255ULL;
-				if (ctx->stored_head == ctx->stored_tail)
-					ctx->stored_head = ctx->stored_tail = 0;
-				input->mouseevent.type = LIBTERMINPUT_MOUSEEVENT;
-				input->mouseevent.event = LIBTERMINPUT_HIGHLIGHT_OUTSIDE;
-				input->mouseevent.mods = 0;
-				input->mouseevent.button = LIBTERMINPUT_BUTTON1;
-				input->mouseevent.start_x = (size_t)nums[0] + (size_t)!nums[0];
-				input->mouseevent.start_y = (size_t)nums[1] + (size_t)!nums[1];
-				input->mouseevent.end_x = (size_t)nums[2] + (size_t)!nums[2];
-				input->mouseevent.end_y = (size_t)nums[3] + (size_t)!nums[3];
-				input->mouseevent.x = (size_t)nums[4] + (size_t)!nums[4];
-				input->mouseevent.y = (size_t)nums[5] + (size_t)!nums[5];
-				break;
-			case 'U': input->keypress.key = LIBTERMINPUT_NEXT;  break;
-			case 'V': input->keypress.key = LIBTERMINPUT_PRIOR; break;
-			case 'Z':
-				if (!(ctx->flags & LIBTERMINPUT_SEPARATE_BACKTAB)) {
-					input->keypress.key = LIBTERMINPUT_TAB;
-					input->keypress.mods |= LIBTERMINPUT_SHIFT;
-				} else {
-					input->keypress.key = LIBTERMINPUT_BACKTAB;
-				}
-				break;
-			case 'a':
-				input->keypress.key = LIBTERMINPUT_UP;
-				input->keypress.mods |= LIBTERMINPUT_SHIFT;
-				break;
-			case 'b':
-				input->keypress.key = LIBTERMINPUT_DOWN;
-				input->keypress.mods |= LIBTERMINPUT_SHIFT;
-				break;
-			case 'c':
-				input->keypress.key = LIBTERMINPUT_RIGHT;
-				input->keypress.mods |= LIBTERMINPUT_SHIFT;
-				break;
-			case 'd':
-				input->keypress.key = LIBTERMINPUT_LEFT;
-				input->keypress.mods |= LIBTERMINPUT_SHIFT;
-				break;
-			case 'n':
-				if (nnums == 1 && nums[0] == 0) {
-					input->type = LIBTERMINPUT_TERMINAL_IS_OK;
-				} else if (nnums == 1 && nums[0] == 3) {
-					input->type = LIBTERMINPUT_TERMINAL_IS_NOT_OK;
-				} else {
-					goto suppress;
-				}
-				break;
-			case 't':
-				/* Parsing output for legacy mouse highlight tracking output (\e[?1001h). */
-				ctx->mouse_tracking = 0;
-				nums = numsbuf;
-				nums[0] = (unsigned long long int)(unsigned char)ctx->stored[ctx->stored_tail++];
-				nums[1] = (unsigned long long int)(unsigned char)ctx->stored[ctx->stored_tail++];
-				nums[0] = (nums[0] - 32ULL) & 255ULL;
-				nums[1] = (nums[1] - 32ULL) & 255ULL;
-				if (ctx->stored_head == ctx->stored_tail)
-					ctx->stored_head = ctx->stored_tail = 0;
-				input->mouseevent.type = LIBTERMINPUT_MOUSEEVENT;
-				input->mouseevent.event = LIBTERMINPUT_HIGHLIGHT_INSIDE;
-				input->mouseevent.mods = 0;
-				input->mouseevent.button = LIBTERMINPUT_BUTTON1;
-				input->mouseevent.x = (size_t)nums[0] + (size_t)!nums[0];
-				input->mouseevent.y = (size_t)nums[1] + (size_t)!nums[1];
-				break;
-			case 'u':
-				if (nums[0] > 0x10FFFFULL || (nums[0] & 0xFFF800ULL) == 0xD800ULL) {
-					input->type = LIBTERMINPUT_NONE;
-					break;
-				}
-				encode_utf8(nums[0], input->keypress.symbol);
-				input->keypress.times = 1;
-				break;
-			case '$':
-				input->keypress.mods |= LIBTERMINPUT_SHIFT;
-				if (nums[0] >= 200)
-					goto suppress;
-				goto tilde_case;
-			case '@':
-				if (ctx->flags & LIBTERMINPUT_INS_ON_CSI_AT) {
-					input->keypress.key = LIBTERMINPUT_INS;
-					break;
-				}
-				input->keypress.mods |= LIBTERMINPUT_SHIFT;
-				/* fall through */
-			case '^':
-				input->keypress.mods |= LIBTERMINPUT_CTRL;
-				if (nums[0] >= 200)
-					goto suppress;
-				/* fall through */
-			case '~':
-			tilde_case:
-				input->keypress.times = 1;
-				switch (nums[0]) {
-				case  1: input->keypress.key = LIBTERMINPUT_HOME;  break;
-				case  2: input->keypress.key = LIBTERMINPUT_INS;   break;
-				case  3: input->keypress.key = LIBTERMINPUT_DEL;   break;
-				case  4: input->keypress.key = LIBTERMINPUT_END;   break;
-				case  5: input->keypress.key = LIBTERMINPUT_PRIOR; break;
-				case  6: input->keypress.key = LIBTERMINPUT_NEXT;  break;
-				case  7: input->keypress.key = LIBTERMINPUT_HOME;  break;
-				case  8: input->keypress.key = LIBTERMINPUT_END;   break;
-				case  9: input->keypress.key = LIBTERMINPUT_ESC;   break; /* just made this one up */
-				case 11: input->keypress.key = LIBTERMINPUT_F1;    break;
-				case 12: input->keypress.key = LIBTERMINPUT_F2;    break;
-				case 13: input->keypress.key = LIBTERMINPUT_F3;    break;
-				case 14: input->keypress.key = LIBTERMINPUT_F4;    break;
-				case 15: input->keypress.key = LIBTERMINPUT_F5;    break;
-				case 17: input->keypress.key = LIBTERMINPUT_F6;    break;
-				case 18: input->keypress.key = LIBTERMINPUT_F7;    break;
-				case 19: input->keypress.key = LIBTERMINPUT_F8;    break;
-				case 20: input->keypress.key = LIBTERMINPUT_F9;    break;
-				case 21: input->keypress.key = LIBTERMINPUT_F10;   break;
-				case 23: input->keypress.key = LIBTERMINPUT_F11;   break;
-				case 24: input->keypress.key = LIBTERMINPUT_F12;   break;
-				case 25: input->keypress.key = LIBTERMINPUT_F1;    break;
-				case 26: input->keypress.key = LIBTERMINPUT_F2;    break;
-				case 28: input->keypress.key = LIBTERMINPUT_F3;    break;
-				case 29: input->keypress.key = LIBTERMINPUT_F4;    break;
-				case 31: input->keypress.key = LIBTERMINPUT_F5;    break;
-				case 32: input->keypress.key = LIBTERMINPUT_F6;    break;
-				case 33: input->keypress.key = LIBTERMINPUT_F7;    break;
-				case 34: input->keypress.key = LIBTERMINPUT_F8;    break;
-				case 200:
-					ctx->bracketed_paste = 1;
-					input->type = LIBTERMINPUT_BRACKETED_PASTE_START;
-					return;
-				case 201:
-					ctx->bracketed_paste = 0;
-					input->type = LIBTERMINPUT_BRACKETED_PASTE_END;
-					return;
-				default:
-					goto suppress;
-				}
-				if (25 <= nums[0] && nums[0] <= 34)
-					input->keypress.mods |= LIBTERMINPUT_SHIFT;
-				break;
-			default:
-				goto suppress;
-			}
-			break;
-		case 3:
-			switch (ctx->key[1] == '[' ? ctx->key[2] : 0) {
-			case 'A': input->keypress.key = LIBTERMINPUT_F1; break;
-			case 'B': input->keypress.key = LIBTERMINPUT_F2; break;
-			case 'C': input->keypress.key = LIBTERMINPUT_F3; break;
-			case 'D': input->keypress.key = LIBTERMINPUT_F4; break;
-			case 'E': input->keypress.key = LIBTERMINPUT_F5; break;
-			default:
-				if (ctx->key[1] == '<' && (ctx->key[2] == 'M' || ctx->key[2] == 'm') && nnums >= 3) {
-					/* Parsing for \e[?1003;1006h output. */
-					input->mouseevent.event = LIBTERMINPUT_PRESS;
-					if (ctx->key[2] == 'm')
-						input->mouseevent.event = LIBTERMINPUT_RELEASE;
-					goto decimal_mouse_tracking;
-				} else {
-					goto suppress;
-				}
-			}
-			break;
-		default:
-			goto suppress;
-		}
-		break;
-
-	case 'O':
-		switch (!ctx->key[2] ? ctx->key[1] : 0) {
-		case 'A': input->keypress.key = LIBTERMINPUT_UP;              break;
-		case 'B': input->keypress.key = LIBTERMINPUT_DOWN;            break;
-		case 'C': input->keypress.key = LIBTERMINPUT_RIGHT;           break;
-		case 'D': input->keypress.key = LIBTERMINPUT_LEFT;            break;
-		case 'E': input->keypress.key = LIBTERMINPUT_BEGIN;           break; /* not attested */
-		case 'F': input->keypress.key = LIBTERMINPUT_END;             break;
-		case 'G': input->keypress.key = LIBTERMINPUT_BEGIN;           break; /* not attested */
-		case 'H': input->keypress.key = LIBTERMINPUT_HOME;            break;
-		case 'P': input->keypress.key = LIBTERMINPUT_F1;              break;
-		case 'Q': input->keypress.key = LIBTERMINPUT_F2;              break;
-		case 'R': input->keypress.key = LIBTERMINPUT_F3;              break;
-		case 'S': input->keypress.key = LIBTERMINPUT_F4;              break;
-		case 'p': input->keypress.key = LIBTERMINPUT_KEYPAD_0;        break;
-		case 'q': input->keypress.key = LIBTERMINPUT_KEYPAD_1;        break;
-		case 'r': input->keypress.key = LIBTERMINPUT_KEYPAD_2;        break;
-		case 's': input->keypress.key = LIBTERMINPUT_KEYPAD_3;        break;
-		case 't': input->keypress.key = LIBTERMINPUT_KEYPAD_4;        break;
-		case 'u': input->keypress.key = LIBTERMINPUT_KEYPAD_5;        break;
-		case 'v': input->keypress.key = LIBTERMINPUT_KEYPAD_6;        break;
-		case 'w': input->keypress.key = LIBTERMINPUT_KEYPAD_7;        break;
-		case 'x': input->keypress.key = LIBTERMINPUT_KEYPAD_8;        break;
-		case 'y': input->keypress.key = LIBTERMINPUT_KEYPAD_9;        break;
-		case 'k': input->keypress.key = LIBTERMINPUT_KEYPAD_PLUS;     break;
-		case 'm': input->keypress.key = LIBTERMINPUT_KEYPAD_MINUS;    break;
-		case 'j': input->keypress.key = LIBTERMINPUT_KEYPAD_TIMES;    break;
-		case 'o': input->keypress.key = LIBTERMINPUT_KEYPAD_DIVISION; break;
-		case 'n': input->keypress.key = LIBTERMINPUT_KEYPAD_DECIMAL;  break;
-		case 'l': input->keypress.key = LIBTERMINPUT_KEYPAD_COMMA;    break;
-		case 'b': input->keypress.key = LIBTERMINPUT_KEYPAD_POINT;    break;
-		case 'M': input->keypress.key = LIBTERMINPUT_KEYPAD_ENTER;    break;
-		default:
-			goto suppress;
-		}
-		break;
-
-	default:
-		/* This shouldn't happen (without goto) */
-	suppress:
-		input->type = LIBTERMINPUT_NONE;
-		break;
-	}		
-}
-
-
-static int
-read_bracketed_paste(int fd, union libterminput_input *input, struct libterminput_state *ctx)
-{
-	ssize_t r;
-	size_t n;
-
-	/* Unfortunately there is no standard for how to handle pasted ESC's,
-	 * not even ESC [201~ or ESC ESC. Terminates seem to just paste ESC as
-	 * is, so we cannot do anything about them, however, a good terminal
-	 * would stop the paste at the ~ in ESC [201~, send ~ as normal, and
-	 * then continue the brackated paste mode. */
-
-	if (ctx->stored_head - ctx->stored_tail) {
-		ctx->paused = 0;
-		n = ctx->stored_head - ctx->stored_tail;
-		if (!strncmp(&ctx->stored[ctx->stored_tail], "\033[201~", n < 6 ? n : 6)) {
-			if (n >= 6) {
-				ctx->stored_tail += 6;
-				if (ctx->stored_tail == ctx->stored_head)
-					ctx->stored_tail = ctx->stored_head = 0;
-				ctx->bracketed_paste = 0;
-				input->type = LIBTERMINPUT_BRACKETED_PASTE_END;
-				return 1;
-			}
-			input->text.nbytes = ctx->stored_head - ctx->stored_tail;
-			memcpy(input->text.bytes, &ctx->stored[ctx->stored_tail], input->text.nbytes);
-			r = read(fd, &input->text.bytes[input->text.nbytes], sizeof(input->text.bytes) - input->text.nbytes);
-			if (r <= 0)
-				return (int)r;
-			input->text.nbytes += (size_t)r;
-			ctx->stored_head = ctx->stored_tail = 0;
-			goto normal;
-		}
-		input->text.nbytes = ctx->stored_head - ctx->stored_tail;
-		memcpy(input->text.bytes, &ctx->stored[ctx->stored_tail], input->text.nbytes);
-		ctx->stored_head = ctx->stored_tail = 0;
-		goto normal;
-	}
-
-	r = read(fd, input->text.bytes, sizeof(input->text.bytes));
-	if (r <= 0)
-		return (int)r;
-	input->text.nbytes = (size_t)r;
-
-normal:
-	for (n = 0; n + 5 < input->text.nbytes; n++) {
-		if (input->text.bytes[n + 0] == '\033' && input->text.bytes[n + 1] == '[' && input->text.bytes[n + 2] == '2' &&
-		    input->text.bytes[n + 3] == '0'    && input->text.bytes[n + 4] == '1' && input->text.bytes[n + 5] == '~')
-			break;
-	}
-	do {
-		if (n + 4 < input->text.nbytes) {
-			if (input->text.bytes[n + 0] == '\033' && input->text.bytes[n + 1] == '[' && input->text.bytes[n + 2] == '2' &&
-			    input->text.bytes[n + 3] == '0'    && input->text.bytes[n + 4] == '1')
-				break;
-			n += 1;
-		}
-		if (n + 3 < input->text.nbytes) {
-			if (input->text.bytes[n + 0] == '\033' && input->text.bytes[n + 1] == '[' && input->text.bytes[n + 2] == '2' &&
-			    input->text.bytes[n + 3] == '0')
-				break;
-			n += 1;
-		}
-		if (n + 2 < input->text.nbytes) {
-			if (input->text.bytes[n + 0] == '\033' && input->text.bytes[n + 1] == '[' && input->text.bytes[n + 2] == '2')
-				break;
-			n += 1;
-		}
-		if (n + 1 < input->text.nbytes) {
-			if (input->text.bytes[n + 0] == '\033' && input->text.bytes[n + 1] == '[')
-				break;
-			n += 1;
-		}
-		if (n + 0 < input->text.nbytes) {
-			if (input->text.bytes[n + 0] == '\033')
-				break;
-			n += 1;
-		}
-	} while (0);
-	if (!n) {
-		if (input->text.nbytes < 6) {
-			input->text.type = LIBTERMINPUT_NONE;
-			memcpy(ctx->stored, input->text.bytes, input->text.nbytes);
-			ctx->stored_tail = 0;
-			ctx->stored_head = input->text.nbytes;
-			ctx->paused = 1;
-			return 1;
-		}
-		ctx->stored_tail = 0;
-		ctx->stored_head = input->text.nbytes - 6;
-		memcpy(ctx->stored, &input->text.bytes[6], ctx->stored_head);
-		if (ctx->stored_tail == ctx->stored_head)
-			ctx->stored_tail = ctx->stored_head = 0;
-		ctx->bracketed_paste = 0;
-		input->type = LIBTERMINPUT_BRACKETED_PASTE_END;
-		return 1;
-	}
-	ctx->stored_tail = 0;
-	ctx->stored_head = input->text.nbytes - n;
-	memcpy(ctx->stored, &input->text.bytes[n], ctx->stored_head);
-	input->text.nbytes = n;
-	input->text.type = LIBTERMINPUT_TEXT;
-	return 1;
-}
-
-
-int
-libterminput_read(int fd, union libterminput_input *input, struct libterminput_state *ctx)
-{
-	struct input ret;
-	size_t n, m;
-	char *p;
-	int r;
-	ssize_t rd;
-
-	if (!ctx->inited) {
-		ctx->inited = 1;
-		memset(input, 0, sizeof(*input));
-	} else if (input->type == LIBTERMINPUT_KEYPRESS && input->keypress.times > 1) {
-		input->keypress.times -= 1;
-		return 1;
-	}
-
-	if (ctx->bracketed_paste)
-		return read_bracketed_paste(fd, input, ctx);
-	if (!ctx->mouse_tracking) {
-		r = read_input(fd, &ret, ctx);
-		if (r <= 0)
-			return r;
-	} else if (ctx->mouse_tracking == 1) {
-		if (ctx->stored_tail == sizeof(ctx->stored)) {
-			memmove(ctx->stored, &ctx->stored[ctx->stored_tail], ctx->stored_head - ctx->stored_tail);
-			ctx->stored_tail -= ctx->stored_head;
-			ctx->stored_head = 0;
-		}
-		rd = read(fd, &ctx->stored[ctx->stored_head], 1);
-		if (rd <= 0)
-			return (int)rd;
-		ctx->stored_head += 1;
-		p = strchr(ctx->key, '\0');
-		goto continue_incomplete;
-	} else {
-		if (ctx->stored_tail > sizeof(ctx->stored) - (size_t)ctx->mouse_tracking) {
-			memmove(ctx->stored, &ctx->stored[ctx->stored_tail], ctx->stored_head - ctx->stored_tail);
-			ctx->stored_tail -= ctx->stored_head;
-			ctx->stored_head = 0;
-		}
-		rd = read(fd, &ctx->stored[ctx->stored_head], (size_t)ctx->mouse_tracking - (ctx->stored_head - ctx->stored_tail));
-		if (rd <= 0)
-			return (int)rd;
-		ctx->stored_head += (size_t)rd;
-		p = strchr(ctx->key, '\0');
-		goto continue_incomplete;
-	}
-
-again:
-	if (!*ret.symbol) {
-		/* Incomplete input */
-		if (ctx->meta < 3) {
-			/* Up to two Meta/ESC, wait until a third or something else is read */
-			input->type = LIBTERMINPUT_NONE;
-			return 1;
-		}
-		/* Three ESC's */
-		input->type = LIBTERMINPUT_KEYPRESS;
-		input->keypress.key = LIBTERMINPUT_ESC;
-		input->keypress.times = 3;
-		input->keypress.mods = 0;
-		input->keypress.symbol[0] = '\0';
-		ctx->meta -= 3;
-	} else if (*ctx->key) {
-		/* Special keys */
-		if (ret.mods) {
-			/* Special key was aborted, restart */
-			*ctx->key = '\0';
-			goto again;
-		}
-		/* Add new input to sequence */
-		n = strlen(ctx->key);
-		m = strlen(ret.symbol);
-		if (n + m >= sizeof(ctx->key)) {
-			/* Abort if too long */
-			input->type = LIBTERMINPUT_NONE;
-			return 1;
-		}
-		p = stpcpy(&ctx->key[n], ret.symbol);
-		/* Check if sequence is complete */
-	continue_incomplete:
-		if (!isalpha(p[-1]) && p[-1] != '~' && p[-1] != '@' && p[-1] != '^' && p[-1] != '$') {
-			input->type = LIBTERMINPUT_NONE;
-			return 1;
-		} else if (ctx->key[0] == '[' && ctx->key[1] == '<' && p == &ctx->key[2]) {
-			input->type = LIBTERMINPUT_NONE;
-			return 1;
-		} else if (ctx->key[0] == '[' && ctx->key[1] == 'M' && (ctx->flags & LIBTERMINPUT_MACRO_ON_CSI_M)) {
-			/* complete */
-		} else if (ctx->key[0] == '[' && ctx->key[1] == 'M' && (ctx->flags & LIBTERMINPUT_DECSET_1005)) {
-			ctx->mouse_tracking = 1;
-			if (ctx->stored_head == ctx->stored_tail) {
-				input->type = LIBTERMINPUT_NONE;
-				return 1;
-			}
-			n = ctx->stored_tail;
-			r = check_utf8_char(&ctx->stored[n], &m, ctx->stored_head - n);
-			if (r <= 0)
-				goto fallback_to_none_or_macro;
-			n += m;
-			r = check_utf8_char(&ctx->stored[n], &m, ctx->stored_head - n);
-			if (r <= 0)
-				goto fallback_to_none_or_macro;
-			n += m;
-			r = check_utf8_char(&ctx->stored[n], &m, ctx->stored_head - n);
-			if (r <= 0) {
-			fallback_to_none_or_macro:
-				if (!r) {
-					input->type = LIBTERMINPUT_NONE;
-					return 1;
-				}
-				ctx->mouse_tracking = 0;
-				input->type = LIBTERMINPUT_KEYPRESS;
-				input->keypress.key = LIBTERMINPUT_MACRO;
-				input->keypress.mods = ret.mods;
-				input->keypress.times = 1;
-				if (ctx->meta > 1)
-					input->keypress.mods |= LIBTERMINPUT_META;
-				ctx->meta = 0;
-				ctx->key[0] = '\0';
-				return 1;
-			}
-		} else if (ctx->key[0] == '[' && ctx->key[1] == 'M' && ctx->stored_head - ctx->stored_tail < 3) {
-			ctx->mouse_tracking = 3;
-			input->type = LIBTERMINPUT_NONE;
-			return 1;
-		} else if (ctx->key[0] == '[' && ctx->key[1] == 't' && ctx->stored_head - ctx->stored_tail < 2) {
-			ctx->mouse_tracking = 2;
-			input->type = LIBTERMINPUT_NONE;
-			return 1;
-		} else if (ctx->key[0] == '[' && ctx->key[1] == 'T' && ctx->stored_head - ctx->stored_tail < 6) {
-			ctx->mouse_tracking = 6;
-			input->type = LIBTERMINPUT_NONE;
-			return 1;
-		}
-		/* Parse the complete sequence */
-		parse_sequence(input, ctx);
-		/* Reset */
-		ctx->meta = 0;
-		ctx->key[0] = '\0';
-	} else if (ctx->meta && (!strcmp(ret.symbol, "[") || !strcmp(ret.symbol, "O"))) {
-		/* ESC [ or ESC 0 is used as the beginning of most special keys */
-		strcpy(ctx->key, ret.symbol);
-		input->type = LIBTERMINPUT_NONE;
-	} else {
-		/* Character input and single-byte special keys */
-		input->type = LIBTERMINPUT_KEYPRESS;
-		input->keypress.mods = ret.mods;
-		input->keypress.times = 1;
-		if (ctx->meta) {
-			/* Transfer meta modifier from state to input */
-			input->keypress.mods |= LIBTERMINPUT_META;
-			ctx->meta = 0;
-		}
-		switch (ret.symbol[1] ? 0 : ret.symbol[0]) {
-		case 127:
-		case '\b':
-			input->keypress.key = LIBTERMINPUT_ERASE;
-			input->keypress.symbol[0] = '\0';
-			break;
-		case '\t':
-			input->keypress.key = LIBTERMINPUT_TAB;
-			input->keypress.symbol[0] = '\0';
-			break;
-		case '\n':
-			input->keypress.key = LIBTERMINPUT_ENTER;
-			input->keypress.symbol[0] = '\0';
-			break;
-		case 033:
-			input->keypress.key = LIBTERMINPUT_ESC;
-			input->keypress.symbol[0] = '\0';
-			break;
-		default:
-			input->keypress.key = LIBTERMINPUT_SYMBOL;
-			strcpy(input->keypress.symbol, ret.symbol);
-			break;
-		}
-	}
-
-	return 1;
-}
-
-
-int
-libterminput_set_flags(struct libterminput_state *ctx, enum libterminput_flags flags)
-{
-	ctx->flags |= flags;
-	return 0;
-}
-
-
-int
-libterminput_clear_flags(struct libterminput_state *ctx, enum libterminput_flags flags)
-{
-	ctx->flags |= flags;
-	ctx->flags ^= flags;
-	return 0;
-}
-
-
-extern inline int libterminput_is_ready(const union libterminput_input *input, const struct libterminput_state *ctx);
diff --git a/libterminput_check_utf8_char__.c b/libterminput_check_utf8_char__.c
new file mode 100644
index 0000000..d2884cf
--- /dev/null
+++ b/libterminput_check_utf8_char__.c
@@ -0,0 +1,36 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+int
+libterminput_check_utf8_char__(const char *s, size_t size, size_t *len_out)
+{
+	size_t i;
+	*len_out = 0;
+	if (!size) {
+		return 0;
+	} else if ((*s & 0x80) == 0) {
+		*len_out = 1U;
+		return 1;
+	} else if ((*s & 0xE0) == 0xC0) {
+		*len_out = 2U;
+	} else if ((*s & 0xF0) == 0xE0) {
+		*len_out = 3U;
+	} else if ((*s & 0xF8) == 0xF0) {
+		*len_out = 4U;
+	} else if ((*s & 0xFC) == 0xF8) {
+		*len_out = 5U;
+	} else if ((*s & 0xFE) == 0xFC) {
+		*len_out = 6U;
+	} else {
+		*len_out = 0U;
+		return -1;
+	}
+	for (i = 1; i < *len_out; i++) {
+		if (i == size)
+			return 0;
+		if ((s[i] & 0xC0) != 0x80)
+			return -1;
+	}
+	return 1;
+}
diff --git a/libterminput_clear_flags.c b/libterminput_clear_flags.c
new file mode 100644
index 0000000..9eba361
--- /dev/null
+++ b/libterminput_clear_flags.c
@@ -0,0 +1,11 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+int
+libterminput_clear_flags(struct libterminput_state *ctx, enum libterminput_flags flags)
+{
+	ctx->flags |= flags;
+	ctx->flags ^= flags;
+	return 0;
+}
diff --git a/libterminput_encode_utf8__.c b/libterminput_encode_utf8__.c
new file mode 100644
index 0000000..7e83a04
--- /dev/null
+++ b/libterminput_encode_utf8__.c
@@ -0,0 +1,44 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+void
+libterminput_encode_utf8__(unsigned long long int codepoint, char buffer[7])
+{
+	static const char masks[6] = {
+		(char)0x00, /* 1 byte = 0 high set bits, */
+		(char)0xC0, /* 2 bytes = 2 high set bits, */
+		(char)0xE0, /* 3 bytes = 3 high set bits, ... */
+		(char)0xF0,
+		(char)0xF8,
+		(char)0xFC  /* 6 bytes = 3 high set bits */
+	};
+	static const unsigned long long int limits[6] = {
+		1ULL << (7 + 0 * 6), /* 1 byte has room for 7 codepoint encoding bits, */
+		1ULL << (5 + 1 * 6), /* 2 bytes has room for 5 bits in the first by and 6 bits the rest, */
+		1ULL << (4 + 2 * 6), /* 3 bytes has room for 4 bits in the first by and 6 bits the rest, ... */
+		1ULL << (3 + 3 * 6),
+		1ULL << (2 + 4 * 6),
+		1ULL << (1 + 5 * 6)  /* 6 bytes has room for 1 bits in the first by and 6 bits the rest */
+	};
+
+	size_t len;
+
+	/* Get encoding length for codepoint */
+	for (len = 0; codepoint >= limits[len]; len++);
+
+	/* Set the `len` (but 0 if 1) high bits in the first byte
+	 * to encode the encoding length of the codepoint */
+	buffer[0] = masks[len];
+
+	/* NUL terminate the encoding buffer,
+	 * to mark the encode of the encoding */
+	buffer[++len] = '\0';
+
+	/* Encode the bites representing the code point
+	 * and the length continuation marker bits in
+	 * the non-first bytes */
+	for (; --len; codepoint >>= 6)
+		buffer[len] = (char)((codepoint & 0x3FULL) | 0x80ULL);
+	buffer[0] |= (char)codepoint;
+}
diff --git a/libterminput_is_ready.c b/libterminput_is_ready.c
new file mode 100644
index 0000000..9c8af91
--- /dev/null
+++ b/libterminput_is_ready.c
@@ -0,0 +1,5 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+extern inline int libterminput_is_ready(const union libterminput_input *input, const struct libterminput_state *ctx);
diff --git a/libterminput_read.c b/libterminput_read.c
new file mode 100644
index 0000000..50095bd
--- /dev/null
+++ b/libterminput_read.c
@@ -0,0 +1,653 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+static int
+read_input(int fd, struct input *input, struct libterminput_state *ctx)
+{
+	unsigned char c, tc;
+	ssize_t r;
+
+	/* Get next byte from input */
+	if (ctx->stored_head != ctx->stored_tail) {
+		c = ((unsigned char *)ctx->stored)[ctx->stored_tail++];
+		if (ctx->stored_tail == ctx->stored_head)
+			ctx->stored_tail = ctx->stored_head = 0;
+	} else {
+		r = read(fd, ctx->stored, sizeof(ctx->stored));
+		if (r <= 0)
+			return (int)r;
+		c = (unsigned char)ctx->stored[0];
+		if (r > 1) {
+			ctx->stored_tail = 1;
+			ctx->stored_head = (size_t)r;
+		}
+	}
+
+again:
+	if (ctx->n) {
+		/* Continuation of multibyte-character */
+		if ((c & 0xC0) != 0x80) {
+			/* Short multibyte-character: return short and store read byte from next input */
+			input->mods = ctx->mods;
+			ctx->partial[(unsigned char)ctx->npartial] = '\0';
+			ctx->n = 0;
+			ctx->npartial = 0;
+			ctx->mods = 0;
+			ctx->stored[ctx->stored_head++] = (char)c;
+			strcpy(input->symbol, ctx->partial);
+			return 1;
+		} else {
+			/* Store byte, and if done, return */
+			ctx->partial[(unsigned char)ctx->npartial++] = (char)c;
+			if (ctx->npartial == ctx->n) {
+				ctx->partial[(unsigned char)ctx->npartial] = '\0';
+				input->mods = ctx->mods;
+				ctx->npartial = 0;
+				ctx->mods = 0;
+				ctx->n = 0;
+				strcpy(input->symbol, ctx->partial);
+				return 1;
+			}
+		}
+	} else if (c == 033 && !*ctx->key) {
+		/* ESC at the beginning, save as a Meta/ESC (for default behaviour) */
+		if ((ctx->flags & LIBTERMINPUT_ESC_ON_BLOCK) && ctx->stored_tail == ctx->stored_head) {
+			input->symbol[0] = (char)c;
+			input->symbol[1] = '\0';
+			input->mods = ctx->mods;
+			ctx->mods = 0;
+			return 1;
+		}
+		ctx->meta += 1;
+	} else if (c == 0) {
+		/* CTRL on Space */
+		input->symbol[0] = ' ';
+		input->symbol[1] = '\0';
+		input->mods = ctx->mods | LIBTERMINPUT_CTRL;
+		ctx->mods = 0;
+		return 1;
+	} else if (c < (unsigned char)' ' && (char)c != '\t' && (char)c != '\b' && (char)c != '\n') {
+		/* CTRL on some some character key */
+		input->symbol[0] = (char)c + '@';
+		input->symbol[1] = '\0';
+		input->mods = ctx->mods | LIBTERMINPUT_CTRL;
+		ctx->mods = 0;
+		return 1;
+	} else if ((c & 0xC0) == 0xC0 && c != 0xFF) {
+		/* Beginning of multibyte-character */
+		ctx->n = 0;
+		for (tc = c; tc & 0x80; tc <<= 1)
+			ctx->n++;
+		if (ctx->n > 6) {
+			/* If overlong, return first byte a single-byte-character */
+			input->symbol[0] = (char)c;
+			input->symbol[1] = '\0';
+			input->mods = ctx->mods;
+			ctx->mods = 0;
+			return 1;
+		}
+		ctx->partial[0] = (char)c;
+		ctx->npartial = 1;
+	} else if (c & 0x80) {
+		/* 8th bit set to signify META */
+		c ^= 0x80;
+		ctx->mods |= LIBTERMINPUT_META;
+		goto again;
+	} else {
+		/* Single-byte-character */
+		input->symbol[0] = (char)c;
+		input->symbol[1] = '\0';
+		input->mods = ctx->mods;
+		ctx->mods = 0;
+		return 1;
+	}
+
+	input->symbol[0] = '\0';
+	input->mods = -1;
+	return 1;
+}
+
+
+static void
+parse_sequence(union libterminput_input *input, struct libterminput_state *ctx)
+{
+	unsigned long long int *nums, numsbuf[6];
+	size_t keylen, n, nnums = 0, pos;
+	char *p;
+
+	/* Get number of numbers in the sequence, and allocate an array of at least 2 */
+	if (ctx->key[0] == '[' && (ctx->key[1] == '<' ? isdigit(ctx->key[2]) : isdigit(ctx->key[1])))
+		nnums += 1;
+	for (n = 2, p = ctx->key; *p; p++) {
+		if (*p == ';') {
+			n += 1;
+			nnums += 1;
+		}
+	}
+	nums = alloca(n * sizeof(*nums));
+	nums[0] = nums[1] = 0;
+
+	/* Read numbers and remove numbers and delimiters */
+	for (keylen = 0, n = 0, p = ctx->key; *p; p++) {
+		if (*p == ';') {
+			nums[++n] = 0; /* We made sure above to allocate one extra */
+		} else if (!isdigit(*p)) {
+			ctx->key[keylen++] = *p;
+		} else if (n < 3) {
+			if (nums[n] < (ULLONG_MAX - (*p & 15)) / 10)
+				nums[n] = nums[n] * 10 + (*p & 15);
+			else
+				nums[n] = ULLONG_MAX;
+		}
+	}
+	ctx->key[keylen] = '\0';
+
+	/* Get times and mods, and reset symbol, and more as keypress */
+	input->type = LIBTERMINPUT_KEYPRESS;
+	input->keypress.symbol[0] = '\0';
+	input->keypress.times = nums[0] + !nums[0];
+	input->keypress.mods = nums[1] > 1 ? nums[1] - 1 : 0;
+	input->keypress.mods |= ctx->meta > 1 ? LIBTERMINPUT_META : 0;
+
+	switch (ctx->key[0]) {
+	case '[':
+		switch (keylen) {
+		case 2:
+			switch (ctx->key[1]) {
+			case 'A': input->keypress.key = LIBTERMINPUT_UP;    break;
+			case 'B': input->keypress.key = LIBTERMINPUT_DOWN;  break;
+			case 'C': input->keypress.key = LIBTERMINPUT_RIGHT; break;
+			case 'D': input->keypress.key = LIBTERMINPUT_LEFT;  break;
+			case 'E': input->keypress.key = LIBTERMINPUT_BEGIN; break;
+			case 'F': input->keypress.key = LIBTERMINPUT_END;   break;
+			case 'G': input->keypress.key = LIBTERMINPUT_BEGIN; break;
+			case 'H': input->keypress.key = LIBTERMINPUT_HOME;  break;
+			case 'M':
+				if (ctx->flags & LIBTERMINPUT_MACRO_ON_CSI_M) {
+					input->keypress.key = LIBTERMINPUT_MACRO;
+				} else if (nnums >= 3) {
+					/* Parsing for \e[?1000;1015h output. */
+					nums[0] -= 32ULL;
+				decimal_mouse_tracking_set_press:
+					input->mouseevent.event = LIBTERMINPUT_PRESS;
+				decimal_mouse_tracking:
+					input->mouseevent.type = LIBTERMINPUT_MOUSEEVENT;
+					input->mouseevent.x = (size_t)nums[1] + (size_t)!nums[1];
+					input->mouseevent.y = (size_t)nums[2] + (size_t)!nums[2];
+					input->mouseevent.mods = (enum libterminput_mod)((nums[0] >> 2) & 7ULL);
+					if (nums[0] & 32)
+						input->mouseevent.event = LIBTERMINPUT_MOTION;
+					nums[0] = (nums[0] & 3ULL) | ((nums[0] >> 4) & ~3ULL);
+					if (nums[0] < 4) {
+						nums[0] = (nums[0] + 1) & 3;
+						if (!nums[0] && input->mouseevent.event == LIBTERMINPUT_PRESS) {
+							input->mouseevent.event = LIBTERMINPUT_RELEASE;
+							nums[0] = 1;
+						}
+					}
+					input->mouseevent.button = (enum libterminput_button)nums[0];
+				} else if (!nnums & !(ctx->flags & LIBTERMINPUT_DECSET_1005)) {
+					/* Parsing output for legacy mouse tracking output. */
+					ctx->mouse_tracking = 0;
+					nums = numsbuf;
+					nums[0] = (unsigned long long int)(unsigned char)ctx->stored[ctx->stored_tail++];
+					nums[1] = (unsigned long long int)(unsigned char)ctx->stored[ctx->stored_tail++];
+					nums[2] = (unsigned long long int)(unsigned char)ctx->stored[ctx->stored_tail++];
+					nums[0] = (nums[0] - 32ULL) & 255ULL;
+					nums[1] = (nums[1] - 32ULL) & 255ULL;
+					nums[2] = (nums[2] - 32ULL) & 255ULL;
+					if (ctx->stored_head == ctx->stored_tail)
+						ctx->stored_head = ctx->stored_tail = 0;
+					goto decimal_mouse_tracking_set_press;
+				} else if (!nnums) {
+					/* Parsing for semi-legacy \e[?1000;1005h output. */
+					ctx->mouse_tracking = 0;
+					nums = numsbuf;
+					pos = ctx->stored_tail;
+					if ((nums[0] = libterminput_utf8_decode__(ctx->stored, &ctx->stored_tail)) < 32 ||
+					    (nums[1] = libterminput_utf8_decode__(ctx->stored, &ctx->stored_tail)) < 32 ||
+					    (nums[2] = libterminput_utf8_decode__(ctx->stored, &ctx->stored_tail)) < 32) {
+						ctx->stored_tail = pos;
+						input->keypress.key = LIBTERMINPUT_MACRO;
+						return;
+					}
+					nums[0] = nums[0] - 32ULL;
+					nums[1] = nums[1] - 32ULL;
+					nums[2] = nums[2] - 32ULL;
+					if (ctx->stored_head == ctx->stored_tail)
+						ctx->stored_head = ctx->stored_tail = 0;
+					goto decimal_mouse_tracking_set_press;
+				} else {
+					goto suppress;
+				}
+				break;
+			case 'P':
+				input->keypress.key = LIBTERMINPUT_F1;
+				if (ctx->flags & LIBTERMINPUT_PAUSE_ON_CSI_P)
+					input->keypress.key = LIBTERMINPUT_PAUSE;
+				break;
+			case 'Q':
+				input->keypress.key = LIBTERMINPUT_F2;
+				break;
+			case 'R':
+				if ((ctx->flags & LIBTERMINPUT_AWAITING_CURSOR_POSITION) && nnums >= 2) {
+					input->position.type = LIBTERMINPUT_CURSOR_POSITION;
+					input->position.y = (size_t)nums[0] + (size_t)!nums[0];
+					input->position.x = (size_t)nums[1] + (size_t)!nums[1];
+				} else {
+					input->keypress.key = LIBTERMINPUT_F3;
+				}
+				break;
+			case 'S':
+				input->keypress.key = LIBTERMINPUT_F4;
+				break;
+			case 'T':
+				/* Parsing output for legacy mouse highlight tracking output. (\e[?1001h) */
+				ctx->mouse_tracking = 0;
+				nums = numsbuf;
+				nums[0] = (unsigned long long int)(unsigned char)ctx->stored[ctx->stored_tail++];
+				nums[1] = (unsigned long long int)(unsigned char)ctx->stored[ctx->stored_tail++];
+				nums[2] = (unsigned long long int)(unsigned char)ctx->stored[ctx->stored_tail++];
+				nums[3] = (unsigned long long int)(unsigned char)ctx->stored[ctx->stored_tail++];
+				nums[4] = (unsigned long long int)(unsigned char)ctx->stored[ctx->stored_tail++];
+				nums[5] = (unsigned long long int)(unsigned char)ctx->stored[ctx->stored_tail++];
+				nums[0] = (nums[0] - 32ULL) & 255ULL;
+				nums[1] = (nums[1] - 32ULL) & 255ULL;
+				nums[2] = (nums[2] - 32ULL) & 255ULL;
+				nums[3] = (nums[3] - 32ULL) & 255ULL;
+				nums[4] = (nums[4] - 32ULL) & 255ULL;
+				nums[5] = (nums[5] - 32ULL) & 255ULL;
+				if (ctx->stored_head == ctx->stored_tail)
+					ctx->stored_head = ctx->stored_tail = 0;
+				input->mouseevent.type = LIBTERMINPUT_MOUSEEVENT;
+				input->mouseevent.event = LIBTERMINPUT_HIGHLIGHT_OUTSIDE;
+				input->mouseevent.mods = 0;
+				input->mouseevent.button = LIBTERMINPUT_BUTTON1;
+				input->mouseevent.start_x = (size_t)nums[0] + (size_t)!nums[0];
+				input->mouseevent.start_y = (size_t)nums[1] + (size_t)!nums[1];
+				input->mouseevent.end_x = (size_t)nums[2] + (size_t)!nums[2];
+				input->mouseevent.end_y = (size_t)nums[3] + (size_t)!nums[3];
+				input->mouseevent.x = (size_t)nums[4] + (size_t)!nums[4];
+				input->mouseevent.y = (size_t)nums[5] + (size_t)!nums[5];
+				break;
+			case 'U': input->keypress.key = LIBTERMINPUT_NEXT;  break;
+			case 'V': input->keypress.key = LIBTERMINPUT_PRIOR; break;
+			case 'Z':
+				if (!(ctx->flags & LIBTERMINPUT_SEPARATE_BACKTAB)) {
+					input->keypress.key = LIBTERMINPUT_TAB;
+					input->keypress.mods |= LIBTERMINPUT_SHIFT;
+				} else {
+					input->keypress.key = LIBTERMINPUT_BACKTAB;
+				}
+				break;
+			case 'a':
+				input->keypress.key = LIBTERMINPUT_UP;
+				input->keypress.mods |= LIBTERMINPUT_SHIFT;
+				break;
+			case 'b':
+				input->keypress.key = LIBTERMINPUT_DOWN;
+				input->keypress.mods |= LIBTERMINPUT_SHIFT;
+				break;
+			case 'c':
+				input->keypress.key = LIBTERMINPUT_RIGHT;
+				input->keypress.mods |= LIBTERMINPUT_SHIFT;
+				break;
+			case 'd':
+				input->keypress.key = LIBTERMINPUT_LEFT;
+				input->keypress.mods |= LIBTERMINPUT_SHIFT;
+				break;
+			case 'n':
+				if (nnums == 1 && nums[0] == 0) {
+					input->type = LIBTERMINPUT_TERMINAL_IS_OK;
+				} else if (nnums == 1 && nums[0] == 3) {
+					input->type = LIBTERMINPUT_TERMINAL_IS_NOT_OK;
+				} else {
+					goto suppress;
+				}
+				break;
+			case 't':
+				/* Parsing output for legacy mouse highlight tracking output (\e[?1001h). */
+				ctx->mouse_tracking = 0;
+				nums = numsbuf;
+				nums[0] = (unsigned long long int)(unsigned char)ctx->stored[ctx->stored_tail++];
+				nums[1] = (unsigned long long int)(unsigned char)ctx->stored[ctx->stored_tail++];
+				nums[0] = (nums[0] - 32ULL) & 255ULL;
+				nums[1] = (nums[1] - 32ULL) & 255ULL;
+				if (ctx->stored_head == ctx->stored_tail)
+					ctx->stored_head = ctx->stored_tail = 0;
+				input->mouseevent.type = LIBTERMINPUT_MOUSEEVENT;
+				input->mouseevent.event = LIBTERMINPUT_HIGHLIGHT_INSIDE;
+				input->mouseevent.mods = 0;
+				input->mouseevent.button = LIBTERMINPUT_BUTTON1;
+				input->mouseevent.x = (size_t)nums[0] + (size_t)!nums[0];
+				input->mouseevent.y = (size_t)nums[1] + (size_t)!nums[1];
+				break;
+			case 'u':
+				if (nums[0] > 0x10FFFFULL || (nums[0] & 0xFFF800ULL) == 0xD800ULL) {
+					input->type = LIBTERMINPUT_NONE;
+					break;
+				}
+				libterminput_encode_utf8__(nums[0], input->keypress.symbol);
+				input->keypress.times = 1;
+				break;
+			case '$':
+				input->keypress.mods |= LIBTERMINPUT_SHIFT;
+				if (nums[0] >= 200)
+					goto suppress;
+				goto tilde_case;
+			case '@':
+				if (ctx->flags & LIBTERMINPUT_INS_ON_CSI_AT) {
+					input->keypress.key = LIBTERMINPUT_INS;
+					break;
+				}
+				input->keypress.mods |= LIBTERMINPUT_SHIFT;
+				/* fall through */
+			case '^':
+				input->keypress.mods |= LIBTERMINPUT_CTRL;
+				if (nums[0] >= 200)
+					goto suppress;
+				/* fall through */
+			case '~':
+			tilde_case:
+				input->keypress.times = 1;
+				switch (nums[0]) {
+				case  1: input->keypress.key = LIBTERMINPUT_HOME;  break;
+				case  2: input->keypress.key = LIBTERMINPUT_INS;   break;
+				case  3: input->keypress.key = LIBTERMINPUT_DEL;   break;
+				case  4: input->keypress.key = LIBTERMINPUT_END;   break;
+				case  5: input->keypress.key = LIBTERMINPUT_PRIOR; break;
+				case  6: input->keypress.key = LIBTERMINPUT_NEXT;  break;
+				case  7: input->keypress.key = LIBTERMINPUT_HOME;  break;
+				case  8: input->keypress.key = LIBTERMINPUT_END;   break;
+				case  9: input->keypress.key = LIBTERMINPUT_ESC;   break; /* just made this one up */
+				case 11: input->keypress.key = LIBTERMINPUT_F1;    break;
+				case 12: input->keypress.key = LIBTERMINPUT_F2;    break;
+				case 13: input->keypress.key = LIBTERMINPUT_F3;    break;
+				case 14: input->keypress.key = LIBTERMINPUT_F4;    break;
+				case 15: input->keypress.key = LIBTERMINPUT_F5;    break;
+				case 17: input->keypress.key = LIBTERMINPUT_F6;    break;
+				case 18: input->keypress.key = LIBTERMINPUT_F7;    break;
+				case 19: input->keypress.key = LIBTERMINPUT_F8;    break;
+				case 20: input->keypress.key = LIBTERMINPUT_F9;    break;
+				case 21: input->keypress.key = LIBTERMINPUT_F10;   break;
+				case 23: input->keypress.key = LIBTERMINPUT_F11;   break;
+				case 24: input->keypress.key = LIBTERMINPUT_F12;   break;
+				case 25: input->keypress.key = LIBTERMINPUT_F1;    break;
+				case 26: input->keypress.key = LIBTERMINPUT_F2;    break;
+				case 28: input->keypress.key = LIBTERMINPUT_F3;    break;
+				case 29: input->keypress.key = LIBTERMINPUT_F4;    break;
+				case 31: input->keypress.key = LIBTERMINPUT_F5;    break;
+				case 32: input->keypress.key = LIBTERMINPUT_F6;    break;
+				case 33: input->keypress.key = LIBTERMINPUT_F7;    break;
+				case 34: input->keypress.key = LIBTERMINPUT_F8;    break;
+				case 200:
+					ctx->bracketed_paste = 1;
+					input->type = LIBTERMINPUT_BRACKETED_PASTE_START;
+					return;
+				case 201:
+					ctx->bracketed_paste = 0;
+					input->type = LIBTERMINPUT_BRACKETED_PASTE_END;
+					return;
+				default:
+					goto suppress;
+				}
+				if (25 <= nums[0] && nums[0] <= 34)
+					input->keypress.mods |= LIBTERMINPUT_SHIFT;
+				break;
+			default:
+				goto suppress;
+			}
+			break;
+		case 3:
+			switch (ctx->key[1] == '[' ? ctx->key[2] : 0) {
+			case 'A': input->keypress.key = LIBTERMINPUT_F1; break;
+			case 'B': input->keypress.key = LIBTERMINPUT_F2; break;
+			case 'C': input->keypress.key = LIBTERMINPUT_F3; break;
+			case 'D': input->keypress.key = LIBTERMINPUT_F4; break;
+			case 'E': input->keypress.key = LIBTERMINPUT_F5; break;
+			default:
+				if (ctx->key[1] == '<' && (ctx->key[2] == 'M' || ctx->key[2] == 'm') && nnums >= 3) {
+					/* Parsing for \e[?1003;1006h output. */
+					input->mouseevent.event = LIBTERMINPUT_PRESS;
+					if (ctx->key[2] == 'm')
+						input->mouseevent.event = LIBTERMINPUT_RELEASE;
+					goto decimal_mouse_tracking;
+				} else {
+					goto suppress;
+				}
+			}
+			break;
+		default:
+			goto suppress;
+		}
+		break;
+
+	case 'O':
+		switch (!ctx->key[2] ? ctx->key[1] : 0) {
+		case 'A': input->keypress.key = LIBTERMINPUT_UP;              break;
+		case 'B': input->keypress.key = LIBTERMINPUT_DOWN;            break;
+		case 'C': input->keypress.key = LIBTERMINPUT_RIGHT;           break;
+		case 'D': input->keypress.key = LIBTERMINPUT_LEFT;            break;
+		case 'E': input->keypress.key = LIBTERMINPUT_BEGIN;           break; /* not attested */
+		case 'F': input->keypress.key = LIBTERMINPUT_END;             break;
+		case 'G': input->keypress.key = LIBTERMINPUT_BEGIN;           break; /* not attested */
+		case 'H': input->keypress.key = LIBTERMINPUT_HOME;            break;
+		case 'P': input->keypress.key = LIBTERMINPUT_F1;              break;
+		case 'Q': input->keypress.key = LIBTERMINPUT_F2;              break;
+		case 'R': input->keypress.key = LIBTERMINPUT_F3;              break;
+		case 'S': input->keypress.key = LIBTERMINPUT_F4;              break;
+		case 'p': input->keypress.key = LIBTERMINPUT_KEYPAD_0;        break;
+		case 'q': input->keypress.key = LIBTERMINPUT_KEYPAD_1;        break;
+		case 'r': input->keypress.key = LIBTERMINPUT_KEYPAD_2;        break;
+		case 's': input->keypress.key = LIBTERMINPUT_KEYPAD_3;        break;
+		case 't': input->keypress.key = LIBTERMINPUT_KEYPAD_4;        break;
+		case 'u': input->keypress.key = LIBTERMINPUT_KEYPAD_5;        break;
+		case 'v': input->keypress.key = LIBTERMINPUT_KEYPAD_6;        break;
+		case 'w': input->keypress.key = LIBTERMINPUT_KEYPAD_7;        break;
+		case 'x': input->keypress.key = LIBTERMINPUT_KEYPAD_8;        break;
+		case 'y': input->keypress.key = LIBTERMINPUT_KEYPAD_9;        break;
+		case 'k': input->keypress.key = LIBTERMINPUT_KEYPAD_PLUS;     break;
+		case 'm': input->keypress.key = LIBTERMINPUT_KEYPAD_MINUS;    break;
+		case 'j': input->keypress.key = LIBTERMINPUT_KEYPAD_TIMES;    break;
+		case 'o': input->keypress.key = LIBTERMINPUT_KEYPAD_DIVISION; break;
+		case 'n': input->keypress.key = LIBTERMINPUT_KEYPAD_DECIMAL;  break;
+		case 'l': input->keypress.key = LIBTERMINPUT_KEYPAD_COMMA;    break;
+		case 'b': input->keypress.key = LIBTERMINPUT_KEYPAD_POINT;    break;
+		case 'M': input->keypress.key = LIBTERMINPUT_KEYPAD_ENTER;    break;
+		default:
+			goto suppress;
+		}
+		break;
+
+	default:
+		/* This shouldn't happen (without goto) */
+	suppress:
+		input->type = LIBTERMINPUT_NONE;
+		break;
+	}		
+}
+
+
+int
+libterminput_read(int fd, union libterminput_input *input, struct libterminput_state *ctx)
+{
+	struct input ret;
+	size_t n, m;
+	char *p;
+	int r;
+	ssize_t rd;
+
+	if (!ctx->inited) {
+		ctx->inited = 1;
+		memset(input, 0, sizeof(*input));
+	} else if (input->type == LIBTERMINPUT_KEYPRESS && input->keypress.times > 1) {
+		input->keypress.times -= 1;
+		return 1;
+	}
+
+	if (ctx->bracketed_paste)
+		return libterminput_read_bracketed_paste__(fd, input, ctx);
+	if (!ctx->mouse_tracking) {
+		r = read_input(fd, &ret, ctx);
+		if (r <= 0)
+			return r;
+	} else if (ctx->mouse_tracking == 1) {
+		if (ctx->stored_tail == sizeof(ctx->stored)) {
+			memmove(ctx->stored, &ctx->stored[ctx->stored_tail], ctx->stored_head - ctx->stored_tail);
+			ctx->stored_tail -= ctx->stored_head;
+			ctx->stored_head = 0;
+		}
+		rd = read(fd, &ctx->stored[ctx->stored_head], 1);
+		if (rd <= 0)
+			return (int)rd;
+		ctx->stored_head += 1;
+		p = strchr(ctx->key, '\0');
+		goto continue_incomplete;
+	} else {
+		if (ctx->stored_tail > sizeof(ctx->stored) - (size_t)ctx->mouse_tracking) {
+			memmove(ctx->stored, &ctx->stored[ctx->stored_tail], ctx->stored_head - ctx->stored_tail);
+			ctx->stored_tail -= ctx->stored_head;
+			ctx->stored_head = 0;
+		}
+		rd = read(fd, &ctx->stored[ctx->stored_head], (size_t)ctx->mouse_tracking - (ctx->stored_head - ctx->stored_tail));
+		if (rd <= 0)
+			return (int)rd;
+		ctx->stored_head += (size_t)rd;
+		p = strchr(ctx->key, '\0');
+		goto continue_incomplete;
+	}
+
+again:
+	if (!*ret.symbol) {
+		/* Incomplete input */
+		if (ctx->meta < 3) {
+			/* Up to two Meta/ESC, wait until a third or something else is read */
+			input->type = LIBTERMINPUT_NONE;
+			return 1;
+		}
+		/* Three ESC's */
+		input->type = LIBTERMINPUT_KEYPRESS;
+		input->keypress.key = LIBTERMINPUT_ESC;
+		input->keypress.times = 3;
+		input->keypress.mods = 0;
+		input->keypress.symbol[0] = '\0';
+		ctx->meta -= 3;
+	} else if (*ctx->key) {
+		/* Special keys */
+		if (ret.mods) {
+			/* Special key was aborted, restart */
+			*ctx->key = '\0';
+			goto again;
+		}
+		/* Add new input to sequence */
+		n = strlen(ctx->key);
+		m = strlen(ret.symbol);
+		if (n + m >= sizeof(ctx->key)) {
+			/* Abort if too long */
+			input->type = LIBTERMINPUT_NONE;
+			return 1;
+		}
+		p = stpcpy(&ctx->key[n], ret.symbol);
+		/* Check if sequence is complete */
+	continue_incomplete:
+		if (!isalpha(p[-1]) && p[-1] != '~' && p[-1] != '@' && p[-1] != '^' && p[-1] != '$') {
+			input->type = LIBTERMINPUT_NONE;
+			return 1;
+		} else if (ctx->key[0] == '[' && ctx->key[1] == '<' && p == &ctx->key[2]) {
+			input->type = LIBTERMINPUT_NONE;
+			return 1;
+		} else if (ctx->key[0] == '[' && ctx->key[1] == 'M' && (ctx->flags & LIBTERMINPUT_MACRO_ON_CSI_M)) {
+			/* complete */
+		} else if (ctx->key[0] == '[' && ctx->key[1] == 'M' && (ctx->flags & LIBTERMINPUT_DECSET_1005)) {
+			ctx->mouse_tracking = 1;
+			if (ctx->stored_head == ctx->stored_tail) {
+				input->type = LIBTERMINPUT_NONE;
+				return 1;
+			}
+			n = ctx->stored_tail;
+			r = libterminput_check_utf8_char__(&ctx->stored[n], ctx->stored_head - n, &m);
+			if (r <= 0)
+				goto fallback_to_none_or_macro;
+			n += m;
+			r = libterminput_check_utf8_char__(&ctx->stored[n], ctx->stored_head - n, &m);
+			if (r <= 0)
+				goto fallback_to_none_or_macro;
+			n += m;
+			r = libterminput_check_utf8_char__(&ctx->stored[n], ctx->stored_head - n, &m);
+			if (r <= 0) {
+			fallback_to_none_or_macro:
+				if (!r) {
+					input->type = LIBTERMINPUT_NONE;
+					return 1;
+				}
+				ctx->mouse_tracking = 0;
+				input->type = LIBTERMINPUT_KEYPRESS;
+				input->keypress.key = LIBTERMINPUT_MACRO;
+				input->keypress.mods = ret.mods;
+				input->keypress.times = 1;
+				if (ctx->meta > 1)
+					input->keypress.mods |= LIBTERMINPUT_META;
+				ctx->meta = 0;
+				ctx->key[0] = '\0';
+				return 1;
+			}
+		} else if (ctx->key[0] == '[' && ctx->key[1] == 'M' && ctx->stored_head - ctx->stored_tail < 3) {
+			ctx->mouse_tracking = 3;
+			input->type = LIBTERMINPUT_NONE;
+			return 1;
+		} else if (ctx->key[0] == '[' && ctx->key[1] == 't' && ctx->stored_head - ctx->stored_tail < 2) {
+			ctx->mouse_tracking = 2;
+			input->type = LIBTERMINPUT_NONE;
+			return 1;
+		} else if (ctx->key[0] == '[' && ctx->key[1] == 'T' && ctx->stored_head - ctx->stored_tail < 6) {
+			ctx->mouse_tracking = 6;
+			input->type = LIBTERMINPUT_NONE;
+			return 1;
+		}
+		/* Parse the complete sequence */
+		parse_sequence(input, ctx);
+		/* Reset */
+		ctx->meta = 0;
+		ctx->key[0] = '\0';
+	} else if (ctx->meta && (!strcmp(ret.symbol, "[") || !strcmp(ret.symbol, "O"))) {
+		/* ESC [ or ESC 0 is used as the beginning of most special keys */
+		strcpy(ctx->key, ret.symbol);
+		input->type = LIBTERMINPUT_NONE;
+	} else {
+		/* Character input and single-byte special keys */
+		input->type = LIBTERMINPUT_KEYPRESS;
+		input->keypress.mods = ret.mods;
+		input->keypress.times = 1;
+		if (ctx->meta) {
+			/* Transfer meta modifier from state to input */
+			input->keypress.mods |= LIBTERMINPUT_META;
+			ctx->meta = 0;
+		}
+		switch (ret.symbol[1] ? 0 : ret.symbol[0]) {
+		case 127:
+		case '\b':
+			input->keypress.key = LIBTERMINPUT_ERASE;
+			input->keypress.symbol[0] = '\0';
+			break;
+		case '\t':
+			input->keypress.key = LIBTERMINPUT_TAB;
+			input->keypress.symbol[0] = '\0';
+			break;
+		case '\n':
+			input->keypress.key = LIBTERMINPUT_ENTER;
+			input->keypress.symbol[0] = '\0';
+			break;
+		case 033:
+			input->keypress.key = LIBTERMINPUT_ESC;
+			input->keypress.symbol[0] = '\0';
+			break;
+		default:
+			input->keypress.key = LIBTERMINPUT_SYMBOL;
+			strcpy(input->keypress.symbol, ret.symbol);
+			break;
+		}
+	}
+
+	return 1;
+}
diff --git a/libterminput_read_bracketed_paste__.c b/libterminput_read_bracketed_paste__.c
new file mode 100644
index 0000000..0c7a81f
--- /dev/null
+++ b/libterminput_read_bracketed_paste__.c
@@ -0,0 +1,108 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+int
+libterminput_read_bracketed_paste__(int fd, union libterminput_input *input, struct libterminput_state *ctx)
+{
+	ssize_t r;
+	size_t n;
+	size_t i;
+
+	/* Unfortunately there is no standard for how to handle pasted ESC's,
+	 * not even ESC [201~ or ESC ESC. Terminates seem to just paste ESC as
+	 * is, so we cannot do anything about them, however, a good terminal
+	 * would stop the paste at the ~ in ESC [201~, send ~ as normal, and
+	 * then continue the brackated paste mode. */
+
+	/* Check for bracketed paste end marker to output LIBTERMINPUT_BRACKETED_PASTE_END
+	 * and stop, and read more if we don't have it; the marker will be at the
+	 * beginning as the function will stop when it encounteres it and output the
+	 * text pasted before it */
+	if (ctx->stored_head - ctx->stored_tail) {
+		/* If we have input buffered, unpause and handle it */
+		ctx->paused = 0;
+		n = ctx->stored_head - ctx->stored_tail;
+		if (!strncmp(&ctx->stored[ctx->stored_tail], "\033[201~", n < 6U ? n : 6U)) {
+			/* If starting with bracketed paste end marker, output LIBTERMINPUT_BRACKETED_PASTE_END, */
+			if (n >= 6U) {
+				ctx->stored_tail += 6U;
+				if (ctx->stored_tail == ctx->stored_head)
+					ctx->stored_tail = ctx->stored_head = 0;
+				ctx->bracketed_paste = 0;
+				input->type = LIBTERMINPUT_BRACKETED_PASTE_END;
+				return 1;
+			}
+			/* otherwise, but if the buffered input is a truncating of the marker,
+			 * move over the data from the stored input buffer to the input buffer
+			 * and store continue reading input */
+			input->text.nbytes = ctx->stored_head - ctx->stored_tail;
+			memcpy(input->text.bytes, &ctx->stored[ctx->stored_tail], input->text.nbytes);
+			r = read(fd, &input->text.bytes[input->text.nbytes], sizeof(input->text.bytes) - input->text.nbytes);
+			if (r <= 0)
+				return (int)r;
+			input->text.nbytes += (size_t)r;
+			ctx->stored_head = ctx->stored_tail = 0;
+		} else {
+			/* If the buffered input does not begin with the bracketed paste end marker,
+			 * or a truncation of it, move over the data from the stored input buffer
+			 * to the input buffer */
+			input->text.nbytes = ctx->stored_head - ctx->stored_tail;
+			memcpy(input->text.bytes, &ctx->stored[ctx->stored_tail], input->text.nbytes);
+			ctx->stored_head = ctx->stored_tail = 0;
+		}
+	} else {
+		/* If we don't have any input buffered, read some */
+		r = read(fd, input->text.bytes, sizeof(input->text.bytes));
+		if (r <= 0)
+			return (int)r;
+		input->text.nbytes = (size_t)r;
+	}
+
+	/* Count the number of bytes available before a bracketed paste end
+	 * marker, or a truncation of it at the end of the input buffer */
+	for (n = 0; n + 5U < input->text.nbytes; n++) {
+		if (!strncmp(&input->text.bytes[n], "\033[201~", 6U))
+			break;
+	}
+	for (i = 5U; i--;) {
+		if (n + i < input->text.nbytes) {
+			if (!strncmp(&input->text.bytes[n], "\033[201~", i + 1U))
+				break;
+			n += 1;
+		}
+	}
+
+	/* Of there was pasted input, output it */
+	if (n) {
+		ctx->stored_tail = 0;
+		ctx->stored_head = input->text.nbytes - n;
+		memcpy(ctx->stored, &input->text.bytes[n], ctx->stored_head);
+		input->text.nbytes = n;
+		input->text.type = LIBTERMINPUT_TEXT;
+		return 1;
+	}
+
+	/* If the input is solely a truncation of the bracketed paste
+	 * end marker, output that we do not have any complete input */
+	if (input->text.nbytes < 6U) {
+		input->text.type = LIBTERMINPUT_NONE;
+		memcpy(ctx->stored, input->text.bytes, input->text.nbytes);
+		ctx->stored_tail = 0;
+		ctx->stored_head = input->text.nbytes;
+		ctx->paused = 1;
+		return 1;
+	}
+
+	/* If the input starts with a bracketed paste end marker,
+	 * output it and store the rest of the input buffer for
+	 * later processing */
+	ctx->stored_tail = 0;
+	ctx->stored_head = input->text.nbytes - 6U;
+	memcpy(ctx->stored, &input->text.bytes[6], ctx->stored_head);
+	if (ctx->stored_tail == ctx->stored_head)
+		ctx->stored_tail = ctx->stored_head = 0;
+	ctx->bracketed_paste = 0;
+	input->type = LIBTERMINPUT_BRACKETED_PASTE_END;
+	return 1;
+}
diff --git a/libterminput_set_flags.c b/libterminput_set_flags.c
new file mode 100644
index 0000000..508d0d6
--- /dev/null
+++ b/libterminput_set_flags.c
@@ -0,0 +1,10 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+int
+libterminput_set_flags(struct libterminput_state *ctx, enum libterminput_flags flags)
+{
+	ctx->flags |= flags;
+	return 0;
+}
diff --git a/libterminput_utf8_decode__.c b/libterminput_utf8_decode__.c
new file mode 100644
index 0000000..e4d0e75
--- /dev/null
+++ b/libterminput_utf8_decode__.c
@@ -0,0 +1,79 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+unsigned long long int
+libterminput_utf8_decode__(const char *s, size_t *ip)
+{
+	unsigned long long int cp = 0;
+	size_t len;
+
+	/* Parse the first byte, to get the highest codepoint bits and the encoding length */
+	if ((s[*ip] & 0x80) == 0) {
+		return (unsigned long long int)s[(*ip)++];
+	} else if ((s[*ip] & 0xE0) == 0xC0) {
+		cp = (unsigned long long int)((unsigned char)s[(*ip)++] ^ 0xC0U);
+		len = 2U;
+		goto need_1;
+	} else if ((s[*ip] & 0xF0) == 0xE0) {
+		cp = (unsigned long long int)((unsigned char)s[(*ip)++] ^ 0xE0U);
+		len = 3U;
+		goto need_2;
+	} else if ((s[*ip] & 0xF8) == 0xF0) {
+		cp = (unsigned long long int)((unsigned char)s[(*ip)++] ^ 0xF0U);
+		len = 4U;
+		goto need_3;
+	} else if ((s[*ip] & 0xFC) == 0xF8) {
+		cp = (unsigned long long int)((unsigned char)s[(*ip)++] ^ 0xF8U);
+		len = 5U;
+		goto need_4;
+	} else if ((s[*ip] & 0xFE) == 0xFC) {
+		cp = (unsigned long long int)((unsigned char)s[(*ip)++] ^ 0xFCU);
+		len = 6U;
+		goto need_5;
+	}
+
+	/* Parse continuation bytes; check marked as continuation the get codepoint bits */
+need_5:
+	if ((s[*ip] & 0xC0) != 0x80) return 0;
+	cp <<= 6;
+	cp |= (unsigned long long int)((unsigned char)s[(*ip)++] ^ 0x80U);
+
+need_4:
+	if ((s[*ip] & 0xC0) != 0x80) return 0;
+	cp <<= 6;
+	cp |= (unsigned long long int)((unsigned char)s[(*ip)++] ^ 0x80U);
+
+need_3:
+	if ((s[*ip] & 0xC0) != 0x80) return 0;
+	cp <<= 6;
+	cp |= (unsigned long long int)((unsigned char)s[(*ip)++] ^ 0x80U);
+
+need_2:
+	if ((s[*ip] & 0xC0) != 0x80) return 0;
+	cp <<= 6;
+	cp |= (unsigned long long int)((unsigned char)s[(*ip)++] ^ 0x80U);
+
+need_1:
+	if ((s[*ip] & 0xC0) != 0x80) return 0;
+	cp <<= 6;
+	cp |= (unsigned long long int)((unsigned char)s[(*ip)++] ^ 0x80U);
+
+	/* Check that encoded codepoint is encoded with the minimum possible length */
+	if (cp < 1ULL << (7 + 0 * 6))
+		return 0;
+	if (cp < 1ULL << (5 + 1 * 6))
+		return len > 2U ? 0ULL : cp;
+	if (cp < 1ULL << (4 + 2 * 6))
+		return len > 3U ? 0ULL : cp;
+	if (cp < 1ULL << (3 + 3 * 6))
+		return len > 4U ? 0ULL : cp;
+	if (cp < 1ULL << (2 + 4 * 6))
+		return len > 5U ? 0ULL : cp;
+	if (cp < 1ULL << (1 + 5 * 6))
+		return len > 6U ? 0ULL : cp;
+
+	/* (Let's ignore the 0x10FFFF upper bound.) */
+
+	return 0;
+}
-- 
cgit v1.2.3-70-g09d2