aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMattias Andrée <maandree@kth.se>2021-04-01 23:39:01 +0200
committerMattias Andrée <maandree@kth.se>2021-04-01 23:39:01 +0200
commitc120520578692ee2448ba057e470ac580fc01de8 (patch)
treee1a89a37ff4abe4578edd50c5eff6411cdad3a9b
downloadlibterminput-c120520578692ee2448ba057e470ac580fc01de8.tar.gz
libterminput-c120520578692ee2448ba057e470ac580fc01de8.tar.bz2
libterminput-c120520578692ee2448ba057e470ac580fc01de8.tar.xz
First commit
Signed-off-by: Mattias Andrée <maandree@kth.se>
-rw-r--r--.gitignore9
-rw-r--r--LICENSE15
-rw-r--r--Makefile44
-rw-r--r--config.mk8
-rw-r--r--interactive-test.c99
-rw-r--r--libterminput.c345
-rw-r--r--libterminput.h106
7 files changed, 626 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e41cec2
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+*~
+*\#*
+*.a
+*.o
+*.so
+*.su
+*.out
+/test
+/interactive-test
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..c44b2d9
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,15 @@
+ISC License
+
+© 2021 Mattias Andrée <maandree@kth.se>
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..b6913f6
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,44 @@
+.POSIX:
+
+CONFIGFILE = config.mk
+include $(CONFIGFILE)
+
+
+OBJ =\
+ interactive-test.o\
+ libterminput.o
+
+HDR =\
+ libterminput.h
+
+
+all: libterminput.a interactive-test
+$(OBJ): $(@:.o=.c) $(HDR)
+
+.c.o:
+ $(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS)
+
+.o.a:
+ $(AR) rc $@ $<
+ $(AR) -s $@
+
+interactive-test: interactive-test.o libterminput.a
+ $(CC) -o $@ interactive-test.o libterminput.a $(LDFLAGS)
+
+install: libterminput.a
+ mkdir -p -- "$(DESTDIR)$(PREFIX)/lib"
+ mkdir -p -- "$(DESTDIR)$(PREFIX)/include"
+ cp -- libterminput.a "$(DESTDIR)$(PREFIX)/lib/"
+ cp -- libterminput.h "$(DESTDIR)$(PREFIX)/include/"
+
+uninstall:
+ rm -- "$(DESTDIR)$(PREFIX)/lib/libterminput.a"
+ rm -- "$(DESTDIR)$(PREFIX)/include/libterminput.h"
+
+clean:
+ -rm -f -- *.o *.a interactive-test
+
+.SUFFIXES:
+.SUFFIXES: .a .o .c
+
+.PHONY: all install uninstall clean
diff --git a/config.mk b/config.mk
new file mode 100644
index 0000000..9404b9a
--- /dev/null
+++ b/config.mk
@@ -0,0 +1,8 @@
+PREFIX = /usr
+MANPREFIX = $(PREFIX)/share/man
+
+CC = cc
+
+CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700
+CFLAGS = -std=c99 -O2
+LDFLAGS = -s
diff --git a/interactive-test.c b/interactive-test.c
new file mode 100644
index 0000000..cc41231
--- /dev/null
+++ b/interactive-test.c
@@ -0,0 +1,99 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "libterminput.h"
+
+
+int
+main(void)
+{
+ struct libterminput_state ctx;
+ union libterminput_input input;
+ struct termios stty, saved_stty;
+ int r;
+
+ memset(&ctx, 0, sizeof(ctx));
+
+ if (tcgetattr(STDIN_FILENO, &stty)) {
+ perror("tcgetattr STDIN_FILENO");
+ return 1;
+ }
+ saved_stty = stty;
+ stty.c_lflag &= (tcflag_t)~(ECHO | ICANON);
+ if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &stty)) {
+ perror("tcsetattr STDIN_FILENO TCSAFLUSH");
+ return 1;
+ }
+
+ while ((r = libterminput_read(STDIN_FILENO, &input, &ctx)) > 0) {
+ if (input.type == LIBTERMINPUT_NONE) {
+ printf("none\n");
+ } else if (input.type == LIBTERMINPUT_KEYPRESS) {
+ printf("keypress:\n");
+ switch (input.keypress.key) {
+ case LIBTERMINPUT_SYMBOL:
+ printf("\t%s: %s\n", "key: symbol", input.keypress.symbol);
+ break;
+ case LIBTERMINPUT_UP: printf("\t%s: %s\n", "key", "up"); break;
+ case LIBTERMINPUT_DOWN: printf("\t%s: %s\n", "key", "down"); break;
+ case LIBTERMINPUT_RIGHT: printf("\t%s: %s\n", "key", "right"); break;
+ case LIBTERMINPUT_BEGIN: printf("\t%s: %s\n", "key", "begin"); break;
+ case LIBTERMINPUT_PAUSE: printf("\t%s: %s\n", "key", "pause"); break;
+ case LIBTERMINPUT_TAB: printf("\t%s: %s\n", "key", "tab"); break;
+ case LIBTERMINPUT_F1: printf("\t%s: %s\n", "key", "f1"); break;
+ case LIBTERMINPUT_F2: printf("\t%s: %s\n", "key", "f2"); break;
+ case LIBTERMINPUT_F3: printf("\t%s: %s\n", "key", "f3"); break;
+ case LIBTERMINPUT_F4: printf("\t%s: %s\n", "key", "f4"); break;
+ case LIBTERMINPUT_F5: printf("\t%s: %s\n", "key", "f5"); break;
+ case LIBTERMINPUT_F6: printf("\t%s: %s\n", "key", "f6"); break;
+ case LIBTERMINPUT_F7: printf("\t%s: %s\n", "key", "f7"); break;
+ case LIBTERMINPUT_F8: printf("\t%s: %s\n", "key", "f8"); break;
+ case LIBTERMINPUT_F9: printf("\t%s: %s\n", "key", "f9"); break;
+ case LIBTERMINPUT_F10: printf("\t%s: %s\n", "key", "f10"); break;
+ case LIBTERMINPUT_F11: printf("\t%s: %s\n", "key", "f11"); break;
+ case LIBTERMINPUT_F12: printf("\t%s: %s\n", "key", "f12"); break;
+ case LIBTERMINPUT_HOME: printf("\t%s: %s\n", "key", "home"); break;
+ case LIBTERMINPUT_INS: printf("\t%s: %s\n", "key", "ins"); break;
+ case LIBTERMINPUT_DEL: printf("\t%s: %s\n", "key", "del"); break;
+ case LIBTERMINPUT_END: printf("\t%s: %s\n", "key", "end"); break;
+ case LIBTERMINPUT_PRIOR: printf("\t%s: %s\n", "key", "prior"); break;
+ case LIBTERMINPUT_NEXT: printf("\t%s: %s\n", "key", "next"); break;
+ case LIBTERMINPUT_ERASE: printf("\t%s: %s\n", "key", "erase"); break;
+ case LIBTERMINPUT_ENTER: printf("\t%s: %s\n", "key", "enter"); break;
+ case LIBTERMINPUT_ESC: printf("\t%s: %s\n", "key", "esc"); break;
+ case LIBTERMINPUT_KEYPAD_0: printf("\t%s: %s\n", "key", "keypad 0"); break;
+ case LIBTERMINPUT_KEYPAD_1: printf("\t%s: %s\n", "key", "keypad 1"); break;
+ case LIBTERMINPUT_KEYPAD_2: printf("\t%s: %s\n", "key", "keypad 2"); break;
+ case LIBTERMINPUT_KEYPAD_3: printf("\t%s: %s\n", "key", "keypad 3"); break;
+ case LIBTERMINPUT_KEYPAD_4: printf("\t%s: %s\n", "key", "keypad 4"); break;
+ case LIBTERMINPUT_KEYPAD_5: printf("\t%s: %s\n", "key", "keypad 5"); break;
+ case LIBTERMINPUT_KEYPAD_6: printf("\t%s: %s\n", "key", "keypad 6"); break;
+ case LIBTERMINPUT_KEYPAD_7: printf("\t%s: %s\n", "key", "keypad 7"); break;
+ case LIBTERMINPUT_KEYPAD_8: printf("\t%s: %s\n", "key", "keypad 8"); break;
+ case LIBTERMINPUT_KEYPAD_9: printf("\t%s: %s\n", "key", "keypad 9"); break;
+ case LIBTERMINPUT_KEYPAD_MINUS: printf("\t%s: %s\n", "key", "keypad minus"); break;
+ case LIBTERMINPUT_KEYPAD_COMMA: printf("\t%s: %s\n", "key", "keypad comma"); break;
+ case LIBTERMINPUT_KEYPAD_POINT: printf("\t%s: %s\n", "key", "keypad point"); break;
+ case LIBTERMINPUT_KEYPAD_ENTER: printf("\t%s: %s\n", "key", "keypad enter"); break;
+ default:
+ printf("\t%s: %s\n", "key", "other");
+ break;
+ }
+ printf("\t%s: %s\n", "shift", (input.keypress.mods & LIBTERMINPUT_SHIFT) ? "yes" : "no");
+ printf("\t%s: %s\n", "meta", (input.keypress.mods & LIBTERMINPUT_META) ? "yes" : "no");
+ printf("\t%s: %s\n", "ctrl", (input.keypress.mods & LIBTERMINPUT_CTRL) ? "yes" : "no");
+ printf("\t%s: %s (%llu)\n", "will repeat", input.keypress.times > 1 ? "yes" : "no", input.keypress.times);
+ } else {
+ printf("other\n");
+ }
+ }
+
+ if (r < 0)
+ perror("libterminput_read STDIN_FILENO");
+
+ tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_stty);
+ return -r;
+}
diff --git a/libterminput.c b/libterminput.c
new file mode 100644
index 0000000..a255373
--- /dev/null
+++ b/libterminput.c
@@ -0,0 +1,345 @@
+/* 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;
+ int r;
+
+ /* Get next byte from input */
+ if (ctx->have_stored) {
+ ctx->have_stored = 0;
+ c = (unsigned char)ctx->stored;
+ } else {
+ r = read(fd, &c, 1);
+ if (r <= 0)
+ return r;
+ }
+
+ 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[ctx->npartial] = '\0';
+ ctx->n = 0;
+ ctx->npartial = 0;
+ ctx->mods = 0;
+ ctx->have_stored = 1;
+ ctx->stored = (char)c;
+ strcpy(input->symbol, ctx->partial);
+ return 1;
+ } else {
+ /* Store byte, and if done, return */
+ ctx->partial[ctx->npartial++] = c;
+ if (ctx->npartial == ctx->n) {
+ ctx->partial[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 */
+ 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) {
+ /* 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] = c;
+ input->symbol[1] = '\0';
+ input->mods = ctx->mods;
+ ctx->mods = 0;
+ return 1;
+ }
+ ctx->partial[0] = c;
+ ctx->npartial = 1;
+ } else {
+ /* Single-byte-character or stray multi-byte continuation byte */
+ input->symbol[0] = 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;
+ size_t keylen, n;
+ char *p;
+
+ /* Get number of numbers in the sequence, and allocate an array of at least 2 */
+ for (n = 2, p = ctx->key; *p; p++)
+ n += *p == ';';
+ 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 'G': input->keypress.key = LIBTERMINPUT_BEGIN; break;
+ case 'P': input->keypress.key = LIBTERMINPUT_PAUSE; break;
+ case 'Z':
+ input->keypress.key = LIBTERMINPUT_TAB;
+ input->keypress.mods |= LIBTERMINPUT_SHIFT;
+ break;
+ 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 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;
+ default:
+ input->type = LIBTERMINPUT_NONE;
+ return;
+ }
+ if (25 <= nums[0] && nums[0] <= 34)
+ input->keypress.mods |= LIBTERMINPUT_SHIFT;
+ break;
+ default:
+ input->type = LIBTERMINPUT_NONE;
+ break;
+ }
+ 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:
+ input->type = LIBTERMINPUT_NONE;
+ break;
+ }
+ break;
+ default:
+ input->type = LIBTERMINPUT_NONE;
+ break;
+ }
+ break;
+
+ case 'O':
+ switch (!ctx->key[2] ? ctx->key[1] : 0) {
+ case 'H': input->keypress.key = LIBTERMINPUT_HOME; break;
+ case 'F': input->keypress.key = LIBTERMINPUT_END; 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 'm': input->keypress.key = LIBTERMINPUT_KEYPAD_MINUS; 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:
+ input->type = LIBTERMINPUT_NONE;
+ break;
+ }
+ break;
+
+ default:
+ /* This shouldn't happen */
+ 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;
+
+ 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;
+ }
+
+ r = read_input(fd, &ret, ctx);
+ if (r <= 0)
+ return r;
+
+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 = ret.mods;
+ 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 */
+ if (!isalpha(p[-1]) && p[-1] != '~') {
+ 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;
+ default:
+ input->keypress.key = LIBTERMINPUT_SYMBOL;
+ strcpy(input->keypress.symbol, ret.symbol);
+ break;
+ }
+ }
+
+ return 1;
+}
diff --git a/libterminput.h b/libterminput.h
new file mode 100644
index 0000000..b86a2bd
--- /dev/null
+++ b/libterminput.h
@@ -0,0 +1,106 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef LIBTERMINPUT_H
+#define LIBTERMINPUT_H
+
+#include <stddef.h>
+
+
+enum libterminput_mod {
+ LIBTERMINPUT_SHIFT = 0x01,
+ LIBTERMINPUT_META = 0x02,
+ LIBTERMINPUT_CTRL = 0x04
+};
+
+enum libterminput_key {
+ LIBTERMINPUT_SYMBOL,
+ LIBTERMINPUT_UP,
+ LIBTERMINPUT_DOWN,
+ LIBTERMINPUT_RIGHT,
+ LIBTERMINPUT_LEFT,
+ LIBTERMINPUT_BEGIN, /* keypad 5 without numlock */
+ LIBTERMINPUT_PAUSE,
+ LIBTERMINPUT_TAB, /* backtab if with shift */
+ LIBTERMINPUT_F1,
+ LIBTERMINPUT_F2,
+ LIBTERMINPUT_F3,
+ LIBTERMINPUT_F4,
+ LIBTERMINPUT_F5,
+ LIBTERMINPUT_F6,
+ LIBTERMINPUT_F7,
+ LIBTERMINPUT_F8,
+ LIBTERMINPUT_F9,
+ LIBTERMINPUT_F10,
+ LIBTERMINPUT_F11,
+ LIBTERMINPUT_F12,
+ LIBTERMINPUT_HOME,
+ LIBTERMINPUT_INS,
+ LIBTERMINPUT_DEL,
+ LIBTERMINPUT_END,
+ LIBTERMINPUT_PRIOR, /* page up */
+ LIBTERMINPUT_NEXT, /* page down */
+ LIBTERMINPUT_ERASE, /* backspace */
+ LIBTERMINPUT_ENTER, /* return */
+ LIBTERMINPUT_ESC,
+ LIBTERMINPUT_KEYPAD_0,
+ LIBTERMINPUT_KEYPAD_1,
+ LIBTERMINPUT_KEYPAD_2,
+ LIBTERMINPUT_KEYPAD_3,
+ LIBTERMINPUT_KEYPAD_4,
+ LIBTERMINPUT_KEYPAD_5,
+ LIBTERMINPUT_KEYPAD_6,
+ LIBTERMINPUT_KEYPAD_7,
+ LIBTERMINPUT_KEYPAD_8,
+ LIBTERMINPUT_KEYPAD_9,
+ LIBTERMINPUT_KEYPAD_MINUS,
+ LIBTERMINPUT_KEYPAD_COMMA,
+ LIBTERMINPUT_KEYPAD_POINT,
+ LIBTERMINPUT_KEYPAD_ENTER
+};
+
+enum libterminput_type {
+ LIBTERMINPUT_NONE,
+ LIBTERMINPUT_KEYPRESS
+};
+
+struct libterminput_keypress {
+ enum libterminput_type type;
+ enum libterminput_key key;
+ unsigned long long int times; /* if .times > 1, next will be the same, but will .times -= 1 */
+ enum libterminput_mod mods;
+ char symbol[7]; /* use if .key == LIBTERMINPUT_SYMBOL */
+};
+
+union libterminput_input {
+ enum libterminput_type type;
+ struct libterminput_keypress keypress;
+};
+
+
+/**
+ * This struct should be considered opaque
+ */
+struct libterminput_state {
+ int inited; /* whether the input in initialised, not this struct */
+ enum libterminput_mod mods;
+ char meta;
+ char n;
+ char have_stored;
+ char npartial;
+ char stored;
+ char partial[7];
+ char key[44];
+};
+
+
+/**
+ * Get input from the terminal
+ *
+ * @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
+ */
+int libterminput_read(int fd, union libterminput_input *input, struct libterminput_state *ctx);
+
+
+#endif