aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--LICENSE2
-rw-r--r--Makefile47
-rw-r--r--README12
-rw-r--r--common.h245
-rw-r--r--interactive-test.c130
-rw-r--r--libterminput.717
-rw-r--r--libterminput.c922
-rw-r--r--libterminput.h735
-rw-r--r--libterminput_check_utf8_char__.c36
l---------libterminput_clear_flags.31
-rw-r--r--libterminput_clear_flags.c11
l---------libterminput_destroy.31
-rw-r--r--libterminput_destroy.c9
-rw-r--r--libterminput_encode_utf8__.c44
-rw-r--r--libterminput_init.3110
-rw-r--r--libterminput_init.c14
-rw-r--r--libterminput_is_ready.334
-rw-r--r--libterminput_is_ready.c5
l---------libterminput_marshal_input.31
-rw-r--r--libterminput_marshal_input.c27
-rw-r--r--libterminput_marshal_keypress__.c16
-rw-r--r--libterminput_marshal_mouseevent__.c20
-rw-r--r--libterminput_marshal_position__.c9
-rw-r--r--libterminput_marshal_state.3121
-rw-r--r--libterminput_marshal_state.c9
-rw-r--r--libterminput_marshal_text__.c14
-rw-r--r--libterminput_parse_csi_m_mouse_tracking__.c54
-rw-r--r--libterminput_parse_csi_small_t_mouse_tracking__.c24
-rw-r--r--libterminput_parse_csi_t_mouse_tracking__.c36
-rw-r--r--libterminput_parse_decimal_mouse_tracking__.c23
-rw-r--r--libterminput_parse_sequence__.c274
-rw-r--r--libterminput_read.316
-rw-r--r--libterminput_read.c196
-rw-r--r--libterminput_read_bracketed_paste__.c109
-rw-r--r--libterminput_read_symbol__.c110
-rw-r--r--libterminput_set_flags.315
-rw-r--r--libterminput_set_flags.c10
l---------libterminput_unmarshal_input.31
-rw-r--r--libterminput_unmarshal_input.c44
-rw-r--r--libterminput_unmarshal_keypress__.c23
-rw-r--r--libterminput_unmarshal_mouseevent__.c30
-rw-r--r--libterminput_unmarshal_position__.c10
-rw-r--r--libterminput_unmarshal_state.3122
-rw-r--r--libterminput_unmarshal_state.c34
-rw-r--r--libterminput_unmarshal_text__.c16
-rw-r--r--libterminput_utf8_decode__.c79
-rw-r--r--test.c457
47 files changed, 3274 insertions, 1001 deletions
diff --git a/LICENSE b/LICENSE
index c44b2d9..b2a7dfb 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
ISC License
-© 2021 Mattias Andrée <maandree@kth.se>
+© 2021, 2025 Mattias Andrée <m@maandree.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
diff --git a/Makefile b/Makefile
index 81f42ff..7ea07c4 100644
--- a/Makefile
+++ b/Makefile
@@ -11,15 +11,46 @@ include mk/$(OS).mk
LIB_MAJOR = 1
-LIB_MINOR = 0
+LIB_MINOR = 1
LIB_VERSION = $(LIB_MAJOR).$(LIB_MINOR)
+MAN3 =\
+ libterminput_read.3\
+ libterminput_is_ready.3\
+ libterminput_set_flags.3\
+ libterminput_clear_flags.3\
+ libterminput_marshal_input.3\
+ libterminput_marshal_state.3\
+ libterminput_unmarshal_input.3\
+ libterminput_unmarshal_state.3\
+ libterminput_init.3\
+ libterminput_destroy.3
+
OBJ =\
- libterminput.o
+ $(MAN3:.3=.o)\
+ libterminput_encode_utf8__.o\
+ libterminput_check_utf8_char__.o\
+ libterminput_utf8_decode__.o\
+ libterminput_read_bracketed_paste__.o\
+ libterminput_parse_decimal_mouse_tracking__.o\
+ libterminput_parse_csi_m_mouse_tracking__.o\
+ libterminput_parse_csi_t_mouse_tracking__.o\
+ libterminput_parse_csi_small_t_mouse_tracking__.o\
+ libterminput_parse_sequence__.o\
+ libterminput_read_symbol__.o\
+ libterminput_marshal_keypress__.o\
+ libterminput_marshal_text__.o\
+ libterminput_marshal_mouseevent__.o\
+ libterminput_marshal_position__.o\
+ libterminput_unmarshal_keypress__.o\
+ libterminput_unmarshal_text__.o\
+ libterminput_unmarshal_mouseevent__.o\
+ libterminput_unmarshal_position__.o
HDR =\
- libterminput.h
+ libterminput.h\
+ common.h
TESTS =\
interactive-test\
@@ -28,7 +59,7 @@ TESTS =\
LOBJ = $(OBJ:.o=.lo)
-all: libterminput.a libterminput.$(LIBEXT) $(TESTS)
+all: libterminput.a libterminput.$(LIBEXT) $(TESTS) interactive-test
$(OBJ): $(HDR)
$(LOBJ): $(HDR)
$(TESTS:=.o): $(HDR)
@@ -67,8 +98,7 @@ install: libterminput.a libterminput.$(LIBEXT)
$(FIX_INSTALL_NAME) "$(DESTDIR)$(PREFIX)/lib/libterminput.$(LIBMINOREXT)"
ln -sf -- libterminput.$(LIBMINOREXT) "$(DESTDIR)$(PREFIX)/lib/libterminput.$(LIBMAJOREXT)"
ln -sf -- libterminput.$(LIBMAJOREXT) "$(DESTDIR)$(PREFIX)/lib/libterminput.$(LIBEXT)"
- cp -- libterminput_read.3 libterminput_set_flags.3 libterminput_is_ready.3 "$(DESTDIR)$(MANPREFIX)/man3"
- ln -sf -- libterminput_set_flags.3 "$(DESTDIR)$(MANPREFIX)/man3/libterminput_clear_flags.3"
+ cp -P -- $(MAN3) "$(DESTDIR)$(MANPREFIX)/man3"
cp -- libterminput.7 "$(DESTDIR)$(MANPREFIX)/man7"
uninstall:
@@ -77,10 +107,7 @@ uninstall:
-rm -f -- "$(DESTDIR)$(PREFIX)/lib/libterminput.$(LIBEXT)"
-rm -f -- "$(DESTDIR)$(PREFIX)/lib/libterminput.a"
-rm -f -- "$(DESTDIR)$(PREFIX)/include/libterminput.h"
- -rm -f -- "$(DESTDIR)$(MANPREFIX)/man3/libterminput_read.3"
- -rm -f -- "$(DESTDIR)$(MANPREFIX)/man3/libterminput_set_flags.3"
- -rm -f -- "$(DESTDIR)$(MANPREFIX)/man3/libterminput_clear_flags.3"
- -rm -f -- "$(DESTDIR)$(MANPREFIX)/man3/libterminput_is_ready.3"
+ -cd -- "$(DESTDIR)$(MANPREFIX)/man3/" && rm -f -- $(MAN3)
-rm -f -- "$(DESTDIR)$(MANPREFIX)/man7/libterminput.7"
clean:
diff --git a/README b/README
index 22259df..6004f21 100644
--- a/README
+++ b/README
@@ -8,6 +8,12 @@ DESCRIPTION
libterminput provides the following functions:
+ libterminput_init(3)
+ Configure library for terminal quirks.
+
+ libterminput_destroy(3)
+ Deallocate library configuration resources.
+
libterminput_read(3)
Read and parse input from the terminal.
@@ -19,3 +25,9 @@ DESCRIPTION
libterminput_clear_flags(3)
Remove input parsing flags.
+
+ libterminput_marshal_state(3), libterminput_marshal_input(3)
+ Marshal library state.
+
+ libterminput_unmarshal_state(3), libterminput_unmarshal_input(3)
+ Unmarshal library state.
diff --git a/common.h b/common.h
new file mode 100644
index 0000000..6278b3e
--- /dev/null
+++ b/common.h
@@ -0,0 +1,245 @@
+/* See LICENSE file for copyright and license details. */
+#include "libterminput.h"
+
+#include <alloca.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+
+#if defined(__GNUC__)
+# define HIDDEN __attribute__((__visibility__("hidden")))
+#else
+# define HIDDEN
+#endif
+
+
+/**
+ * Mark that there is no input available or pending
+ *
+ * @param INPUT_OUT:union libterminput_input *input The input output
+ */
+#define NOTHING(INPUT_OUT)\
+ do {\
+ union libterminput_input *input__ = (INPUT_OUT);\
+ input__->type = LIBTERMINPUT_NONE;\
+ input__->keypress.key = LIBTERMINPUT_SYMBOL;\
+ } while (0);
+
+
+/**
+ * Singlar read symbol
+ */
+struct input {
+ /**
+ * Applied modifier keys
+ */
+ enum libterminput_mod mods;
+
+ /**
+ * The read symbol; NUL-byte terminated
+ */
+ 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);
+
+/**
+ * Parse mouse tracking event data
+ *
+ * @param input Output parameter for the parsed event
+ * @param nums The numbers assoicated with the event
+ */
+HIDDEN void libterminput_parse_decimal_mouse_tracking__(union libterminput_input *input, unsigned long long int nums[3]);
+
+/**
+ * Parse a CSI M mouse tracking event
+ *
+ * @param input Output parameter for the parsed event
+ * @param ctx State for the terminal, parts of the state may be stored in `input`
+ * @param nums Numbers insert reported for the event (between CSI and M)
+ * @param nnums Number of elements in `nums`
+ */
+HIDDEN void libterminput_parse_csi_m_mouse_tracking__(union libterminput_input *input, struct libterminput_state *ctx,
+ unsigned long long int *nums, size_t nnums);
+
+/**
+ * Parse a CSI T mouse tracking event
+ *
+ * @param input Output parameter for the parsed event
+ * @param ctx State for the terminal, parts of the state may be stored in `input`
+ */
+HIDDEN void libterminput_parse_csi_t_mouse_tracking__(union libterminput_input *input, struct libterminput_state *ctx);
+
+/**
+ * Parse a CSI t mouse tracking event
+ *
+ * @param input Output parameter for the parsed event
+ * @param ctx State for the terminal, parts of the state may be stored in `input`
+ */
+HIDDEN void libterminput_parse_csi_small_t_mouse_tracking__(union libterminput_input *input, struct libterminput_state *ctx);
+
+/**
+ * Parse a complete, atomic input sequence out side of a bracketed paste
+ *
+ * @param input Output parameter for the parsed event
+ * @param ctx State for the terminal, parts of the state may be stored in `input`
+ */
+HIDDEN void libterminput_parse_sequence__(union libterminput_input *input, struct libterminput_state *ctx);
+
+/**
+ * Read a singular symbol 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
+ *
+ * @throws Any reason specified for read(3)
+ */
+HIDDEN int libterminput_read_symbol__(int fd, struct input *input, struct libterminput_state *ctx);
+
+/**
+ * Marshal the parsed input
+ *
+ * @param how Object used to store the serialization
+ * @param what The input to marshal
+ * @return 0 on success, -1 on failure
+ *
+ * This function will fail for any reason `*how->store` fails
+ */
+HIDDEN int libterminput_marshal_keypress__(struct libterminput_marshaller *how, const struct libterminput_keypress *what);
+
+/**
+ * Marshal the parsed input
+ *
+ * @param how Object used to store the serialization
+ * @param what The input to marshal
+ * @return 0 on success, -1 on failure
+ *
+ * This function will fail for any reason `*how->store` fails
+ */
+HIDDEN int libterminput_marshal_text__(struct libterminput_marshaller *how, const struct libterminput_text *what);
+
+/**
+ * Marshal the parsed input
+ *
+ * @param how Object used to store the serialization
+ * @param what The input to marshal
+ * @return 0 on success, -1 on failure
+ *
+ * This function will fail for any reason `*how->store` fails
+ */
+HIDDEN int libterminput_marshal_mouseevent__(struct libterminput_marshaller *how, const struct libterminput_mouseevent *what);
+
+/**
+ * Marshal the parsed input
+ *
+ * @param how Object used to store the serialization
+ * @param what The input to marshal
+ * @return 0 on success, -1 on failure
+ *
+ * This function will fail for any reason `*how->store` fails
+ */
+HIDDEN int libterminput_marshal_position__(struct libterminput_marshaller *how, const struct libterminput_position *what);
+
+/**
+ * Unmarshal the parsed input
+ *
+ * @param how Object used to load the serialization
+ * @param what Output parameter for the unmarshalled input
+ * @return 0 on success, -1 on failure
+ *
+ * @throws EINVAL Invalid serialisation
+ *
+ * This function will fail for any reason `*how->load` fails
+ */
+HIDDEN int libterminput_unmarshal_keypress__(struct libterminput_unmarshaller *how, struct libterminput_keypress *what);
+
+/**
+ * Unmarshal the parsed input
+ *
+ * @param how Object used to load the serialization
+ * @param what Output parameter for the unmarshalled input
+ * @return 0 on success, -1 on failure
+ *
+ * @throws EINVAL Invalid serialisation
+ *
+ * This function will fail for any reason `*how->load` fails
+ */
+HIDDEN int libterminput_unmarshal_text__(struct libterminput_unmarshaller *how, struct libterminput_text *what);
+
+/**
+ * Unmarshal the parsed input
+ *
+ * @param how Object used to load the serialization
+ * @param what Output parameter for the unmarshalled input
+ * @return 0 on success, -1 on failure
+ *
+ * @throws EINVAL Invalid serialisation
+ *
+ * This function will fail for any reason `*how->load` fails
+ */
+HIDDEN int libterminput_unmarshal_mouseevent__(struct libterminput_unmarshaller *how, struct libterminput_mouseevent *what);
+
+/**
+ * Unmarshal the parsed input
+ *
+ * @param how Object used to load the serialization
+ * @param what Output parameter for the unmarshalled input
+ * @return 0 on success, -1 on failure
+ *
+ * @throws EINVAL Invalid serialisation
+ *
+ * This function will fail for any reason `*how->load` fails
+ */
+HIDDEN int libterminput_unmarshal_position__(struct libterminput_unmarshaller *how, struct libterminput_position *what);
+
+
+#undef HIDDEN
diff --git a/interactive-test.c b/interactive-test.c
index 2180de8..3945eb2 100644
--- a/interactive-test.c
+++ b/interactive-test.c
@@ -1,4 +1,8 @@
/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -8,15 +12,94 @@
#include "libterminput.h"
+#define TEST(EXPR)\
+ do {\
+ if (EXPR)\
+ break;\
+ fprintf(stderr, "Failure at line %i, with errno = %i (%s): %s\n",\
+ __LINE__, errno, strerror(errno), #EXPR);\
+ exit(1);\
+ } while (0)
+
+
+static volatile sig_atomic_t interrupted = 0;
+
+static struct libterminput_state ctx;
+
+static char *marshalled = NULL;
+static size_t nmarshalled = 0;
+static size_t nunmarshalled = 0;
+static size_t marshalled_size = 0;
+
+
+static int
+store(struct libterminput_marshaller *this, const void *data, size_t size)
+{
+ (void) this;
+ if (size > marshalled_size - nmarshalled) {
+ TEST(size < SIZE_MAX - nmarshalled);
+ marshalled_size = nmarshalled + size;
+ TEST((marshalled = realloc(marshalled, marshalled_size)));
+ }
+ memcpy(&marshalled[nmarshalled], data, size);
+ nmarshalled += size;
+ return 0;
+}
+
+
+static int
+load(struct libterminput_unmarshaller *this, void *data, size_t size)
+{
+ (void) this;
+ TEST(nunmarshalled <= nmarshalled);
+ TEST(size <= nmarshalled - nunmarshalled);
+ memcpy(data, &marshalled[nunmarshalled], size);
+ nunmarshalled += size;
+ return 0;
+}
+
+
+static struct libterminput_marshaller marshaller = {.store = &store};
+static struct libterminput_unmarshaller unmarshaller = {.load = &load};
+
+
+static void
+check_ctx_marshal(void)
+{
+ struct libterminput_state old_ctx;
+ memcpy(&old_ctx, &ctx, sizeof(ctx));
+ TEST(!libterminput_marshal_state(&marshaller, &ctx));
+ memset(&ctx, 255, sizeof(ctx));
+ TEST(!libterminput_unmarshal_state(&unmarshaller, &ctx));
+ TEST(!memcmp(&old_ctx, &ctx, sizeof(ctx)));
+}
+
+
+static void
+sigint_handler(int signo)
+{
+ (void) signo;
+ interrupted = 1;
+}
+
+
int
main(void)
{
- struct libterminput_state ctx;
union libterminput_input input;
struct termios stty, saved_stty;
- int r;
+ int r, print_state, flags;
+ struct sigaction sa;
memset(&ctx, 0, sizeof(ctx));
+ if (libterminput_init(&ctx, STDIN_FILENO)) {
+ perror("libterminput_init STDIN_FILENO");
+ return 1;
+ }
+
+ memset(&sa, 0, sizeof(sa)); /* importantly, SA_RESTART is cleared from sa.sa_flags */
+ sa.sa_handler = &sigint_handler;
+ sigaction(SIGINT, &sa, NULL);
if (getenv("TEST_LIBTERMINPUT_DECSET_1005")) {
fprintf(stderr, "LIBTERMINPUT_DECSET_1005 set\n");
@@ -47,6 +130,8 @@ main(void)
libterminput_set_flags(&ctx, LIBTERMINPUT_AWAITING_CURSOR_POSITION);
}
+ print_state = !!getenv("TEST_LIBTERMINPUT_PRINT_STATE");
+
if (tcgetattr(STDERR_FILENO, &stty)) {
perror("tcgetattr STDERR_FILENO");
return 1;
@@ -58,7 +143,20 @@ main(void)
return 1;
}
+ flags = fcntl(STDIN_FILENO, F_GETFL);
+ if (flags < 0) {
+ perror("fcntl STDIN_FILENO F_GETFL");
+ return 1;
+ } else if (!(flags & O_NONBLOCK)) {
+ if (fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK) < 0) {
+ perror("fcntl STDIN_FILENO F_SETFL <old>|O_NONBLOCK");
+ return 1;
+ }
+ }
+
+again:
while ((r = libterminput_read(STDIN_FILENO, &input, &ctx)) > 0) {
+ check_ctx_marshal();
if (input.type == LIBTERMINPUT_NONE) {
printf("none\n");
} else if (input.type == LIBTERMINPUT_KEYPRESS) {
@@ -184,11 +282,35 @@ main(void)
} else {
printf("other\n");
}
+ if (print_state) {
+ printf("(state):\n"
+ "\tinited=%i, mods=%#x, flags=%#x, bracketed_paste=%i, mouse_tracking=%i, meta=%i,\n"
+ "\tn=%i, stored_head=%zu, stored_tail=%zu, paused=%i, npartial=%i, partial=\"%.*s\",\n"
+ "\tkey=\"%s\", stored=\"%.*s\"\n",
+ (int)ctx.inited, (unsigned)ctx.mods, (unsigned)ctx.flags, (int)ctx.bracketed_paste,
+ (int)ctx.mouse_tracking, (int)ctx.meta, (int)ctx.n, ctx.stored_head, ctx.stored_tail,
+ (int)ctx.paused, (int)ctx.npartial, (int)ctx.npartial, ctx.partial, ctx.key,
+ (int)(ctx.stored_tail - ctx.stored_head), &ctx.stored[ctx.stored_head]);
+ }
}
+ check_ctx_marshal();
- if (r < 0)
+ if (r < 0 && !interrupted) {
+ if (errno == EAGAIN) {
+ fd_set fdset;
+ FD_ZERO(&fdset);
+ FD_SET(STDIN_FILENO, &fdset);
+ select(1, &fdset, NULL, NULL, NULL);
+ goto again;
+ }
perror("libterminput_read STDIN_FILENO");
+ }
+ if (!(flags & O_NONBLOCK))
+ fcntl(STDIN_FILENO, F_SETFL, flags);
tcsetattr(STDERR_FILENO, TCSAFLUSH, &saved_stty);
- return -r;
+
+ libterminput_destroy(&ctx);
+ free(marshalled);
+ return -r && !interrupted;
}
diff --git a/libterminput.7 b/libterminput.7
index d56a6d3..287b71b 100644
--- a/libterminput.7
+++ b/libterminput.7
@@ -10,6 +10,12 @@ does not use terminfo, but recognises common sequences.
.PP
libterminput provides the following functions:
.TP
+.BR libterminput_init (3)
+Configure library for terminal quirks.
+.TP
+.BR libterminput_destroy (3)
+Deallocate library configuration resources.
+.TP
.BR libterminput_read (3)
Read and parse input from the terminal.
.TP
@@ -21,8 +27,17 @@ Add input parsing flags.
.TP
.BR libterminput_clear_flags (3)
Remove input parsing flags.
+.TP
+.BR libterminput_marshal_state "(3), " libterminput_marshal_input (3)
+Marshal library state.
+.TP
+.BR libterminput_unmarshal_state "(3), " libterminput_unmarshal_input (3)
+Unmarshal library state.
.SH SEE ALSO
+.BR libterminput_init (3),
.BR libterminput_is_ready (3),
+.BR libterminput_marshal_state (3),
.BR libterminput_read (3),
-.BR libterminput_set_flags (3)
+.BR libterminput_set_flags (3),
+.BR libterminput_unmarshal_state (3)
diff --git a/libterminput.c b/libterminput.c
deleted file mode 100644
index cb946d0..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(union libterminput_input *input, struct libterminput_state *ctx);
diff --git a/libterminput.h b/libterminput.h
index d5c5439..464850f 100644
--- a/libterminput.h
+++ b/libterminput.h
@@ -9,13 +9,49 @@
* Flags for supporting incompatible input; the user must
* set or clear his flag after setting or clearing it on
* the terminal, and the user must make sure that the
- * terminal support this flag if set.
+ * terminal support this flag if set
+ *
+ * @since 1.0 (applies to all members unless stated otherwise)
*/
enum libterminput_flags {
+ /**
+ * The sequence CSI M shall be parsed be parse as a DECSET 1005
+ * sequence which is incompatible with legacy mouse tracking
+ *
+ * This flag shall only be set if DECSET 1005 has sent to the
+ * terminal and the user is sure it is supported by the terminal
+ */
LIBTERMINPUT_DECSET_1005 = 0x0001,
+
+ /**
+ * Parse CSI M as Macro key presses rather than mouse
+ * tracking events
+ *
+ * This is incompatible with all mouse tracking modes except
+ * DECSET 1006
+ */
LIBTERMINPUT_MACRO_ON_CSI_M = 0x0002,
+
+ /**
+ * Parse CSI P as Pause key presses rather than F1 key presses
+ */
LIBTERMINPUT_PAUSE_ON_CSI_P = 0x0004,
+
+ /**
+ * Parse CSI @ as Insert key presses rather than a number of
+ * possible special keys combined with the control and shift
+ * modifiers
+ */
LIBTERMINPUT_INS_ON_CSI_AT = 0x0008,
+
+ /**
+ * Backtab shall be treated as a separate key, and not be
+ * reported as tab with the shift modifier. This flag is just
+ * a usability issue. Keyboards put backtab on shift+tab,
+ * which is why the tab keycap has both a backward arrow
+ * (backtab) and a forward arrow (tab); but most users are
+ * unfamiliar with backtab, and just see it as shift+tab.
+ */
LIBTERMINPUT_SEPARATE_BACKTAB = 0x0010,
/**
@@ -27,17 +63,81 @@ enum libterminput_flags {
*/
LIBTERMINPUT_ESC_ON_BLOCK = 0x0020,
- LIBTERMINPUT_AWAITING_CURSOR_POSITION = 0x0040
+ /**
+ * This flag should be set, by the application, once
+ * the application sends an escape sequence requesting
+ * the terminal to report the cursor's position, and
+ * cleared once the position has been sent by the
+ * terminal and retreived by application
+ *
+ * This is required for distinguishing cursor position
+ * reports from F3 key presses
+ */
+ LIBTERMINPUT_AWAITING_CURSOR_POSITION = 0x0040,
+
+ /**
+ * If CSI M is received without anything after it,
+ * return Macro keypress. Since the user probably
+ * does not have the Macro key, it seems having this
+ * as the default behaviour introduces an unncessary
+ * risk of misparsing input. However, if mouse tracking
+ * is not activated, it makes since to enable this
+ * flag.
+ *
+ * @since 1.1
+ */
+ LIBTERMINPUT_MACRO_ON_BLOCK = 0x0080
};
+
+/**
+ * Modifier keys
+ *
+ * These are the commonly reported modifiers,
+ * but additional modifiers are possible
+ *
+ * @since 1.0 (applies to all members unless stated otherwise)
+ */
enum libterminput_mod {
+ /**
+ * Shift modifier
+ */
LIBTERMINPUT_SHIFT = 0x01,
+
+ /**
+ * Meta/Alternative modifier
+ */
LIBTERMINPUT_META = 0x02,
+
+ /**
+ * Control modifier
+ */
LIBTERMINPUT_CTRL = 0x04
};
+
+/**
+ * Keyboard buttons
+ *
+ * Only listed values can be reported, however the value
+ * must be listed for the version the application is linked
+ * against, not compiled against, so other values can be
+ * reported the application is linked against a newer version
+ * of the library than it is compiled against
+ *
+ * @since 1.0 (applies to all members unless stated otherwise)
+ */
enum libterminput_key {
+ /**
+ * Non-special key
+ *
+ * Each code point that is generated as a keypress
+ * is reported separately, meaning that keypresses
+ * that generate multiple code points appear as
+ * multiple keypresses
+ */
LIBTERMINPUT_SYMBOL,
+
LIBTERMINPUT_UP,
LIBTERMINPUT_DOWN,
LIBTERMINPUT_RIGHT,
@@ -85,119 +185,588 @@ enum libterminput_key {
LIBTERMINPUT_KEYPAD_DECIMAL,
LIBTERMINPUT_KEYPAD_COMMA,
LIBTERMINPUT_KEYPAD_POINT,
- LIBTERMINPUT_KEYPAD_ENTER,
+ LIBTERMINPUT_KEYPAD_ENTER
+
+#define LIBTERMINPUT_LAST_KEY__ LIBTERMINPUT_KEYPAD_ENTER /* for internal use */
};
+
+/**
+ * Mouse buttons
+ *
+ * It is possible that non-listed buttons are
+ * reported in events
+ *
+ * @since 1.0 (applies to all members unless stated otherwise)
+ */
enum libterminput_button {
+ /**
+ * No mouse button is held down
+ */
LIBTERMINPUT_NO_BUTTON,
- LIBTERMINPUT_BUTTON1, /* left (assuming right-handed) */
- LIBTERMINPUT_BUTTON2, /* middle */
- LIBTERMINPUT_BUTTON3, /* right (assuming right-handed) */
- LIBTERMINPUT_SCROLL_UP, /* no corresponding release event shall be generated */
- LIBTERMINPUT_SCROLL_DOWN, /* no corresponding release event shall be generated */
- LIBTERMINPUT_SCROLL_LEFT, /* may or may not have a corresponding release event */
- LIBTERMINPUT_SCROLL_RIGHT, /* may or may not have a corresponding release event */
- LIBTERMINPUT_XBUTTON1, /* extended button 1, also known as backward */
- LIBTERMINPUT_XBUTTON2, /* extended button 2, also known as forward */
- LIBTERMINPUT_XBUTTON3, /* extended button 3, you probably don't have this button */
- LIBTERMINPUT_XBUTTON4 /* extended button 4, you probably don't have this button */
+
+ /**
+ * Primary button
+ *
+ * Left button if right-handed,
+ * right button if left-handed
+ */
+ LIBTERMINPUT_BUTTON1,
+
+ /**
+ * Middle button
+ */
+ LIBTERMINPUT_BUTTON2,
+
+ /**
+ * Secondary button
+ *
+ * Right button if right-handed,
+ * left button if left-handed
+ */
+ LIBTERMINPUT_BUTTON3,
+
+ /**
+ * Wheel scrolled up
+ *
+ * No corresponding release event shall be generated
+ */
+ LIBTERMINPUT_SCROLL_UP,
+
+ /**
+ * Wheel scrolled down
+ *
+ * No corresponding release event shall be generated
+ */
+ LIBTERMINPUT_SCROLL_DOWN,
+
+ /**
+ * Left-scroll button or wheel scrolled left
+ *
+ * May or may not have a corresponding release event
+ */
+ LIBTERMINPUT_SCROLL_LEFT,
+
+ /**
+ * Right-scroll button or wheel scrolled right
+ *
+ * May or may not have a corresponding release event
+ */
+ LIBTERMINPUT_SCROLL_RIGHT,
+
+ /**
+ * Extended button 1, also known as backward
+ */
+ LIBTERMINPUT_XBUTTON1,
+
+ /**
+ * Extended button 2, also known as farward
+ */
+ LIBTERMINPUT_XBUTTON2,
+
+ /**
+ * Extended button 3
+ *
+ * You probably don't have this button
+ */
+ LIBTERMINPUT_XBUTTON3,
+
+ /**
+ * Extended button 4
+ *
+ * You probably don't have this button
+ */
+ LIBTERMINPUT_XBUTTON4
};
+
+/**
+ * Input event type
+ *
+ * @since 1.0 (applies to all members unless stated otherwise)
+ */
enum libterminput_type {
+ /**
+ * A special value to mark that the input was either
+ * discard or not yet completed
+ */
LIBTERMINPUT_NONE,
+
+ /**
+ * Normal key press
+ */
LIBTERMINPUT_KEYPRESS,
+
+ /**
+ * Pseudo-event that marks that beginning of a bracketed paste
+ */
LIBTERMINPUT_BRACKETED_PASTE_START,
+
+ /**
+ * Pseudo-event that marks that end of a bracketed paste
+ */
LIBTERMINPUT_BRACKETED_PASTE_END,
+
+ /**
+ * Bracketed paste
+ */
LIBTERMINPUT_TEXT,
+
+ /**
+ * Mouse event
+ */
LIBTERMINPUT_MOUSEEVENT,
+
+ /**
+ * OK response for a device status query
+ */
LIBTERMINPUT_TERMINAL_IS_OK, /* response to CSI 5 n */
+
+ /**
+ * Not-OK response for a device status query
+ */
LIBTERMINPUT_TERMINAL_IS_NOT_OK, /* response to CSI 5 n */
+
+ /**
+ * Cursor position report event as a response
+ * to a cursor position query
+ */
LIBTERMINPUT_CURSOR_POSITION /* response to CSI 6 n */
};
+
+/**
+ * Mouse event subtype
+ *
+ * @since 1.0 (applies to all members unless stated otherwise)
+ */
enum libterminput_event {
+ /**
+ * Mouse button pressed
+ */
LIBTERMINPUT_PRESS,
+
+ /**
+ * Mouse button released
+ */
LIBTERMINPUT_RELEASE,
+
+ /**
+ * Mouse moved, possibly with dragging
+ */
LIBTERMINPUT_MOTION,
+
+ /**
+ * Highlight ended inside of selected region
+ */
LIBTERMINPUT_HIGHLIGHT_INSIDE,
+
+ /**
+ * Highlight ended outside of selected region
+ */
LIBTERMINPUT_HIGHLIGHT_OUTSIDE
};
+
+/**
+ * Keypress event
+ *
+ * Some key presses may actually be mouse events,
+ * particularly when mouse tracking is disabled.
+ * In particular, mouse scrolling may appear as
+ * repeated Up key or Down key pesses
+ *
+ * @since 1.0 (applies to all members unless stated otherwise)
+ */
struct libterminput_keypress {
+ /**
+ * Should be `LIBTERMINPUT_KEYPRESS`
+ */
enum libterminput_type type;
+
+ /**
+ * Which key was pressed
+ */
enum libterminput_key key;
- unsigned long long int times; /* if .times > 1, next will be the same, but will .times -= 1 */
+
+ /**
+ * This number of types the key was
+ * pressed
+ *
+ * Normally this would be 1, some mouse
+ * events generate arrow key presses that
+ * are reported, not multiple times, but
+ * as clicked multiple times
+ *
+ * For the next `.times - 1` reads (if
+ * this value is not modified by the user)
+ * will reported as the same event except
+ * that this value will be decreased by 1
+ * each time
+ */
+ unsigned long long int times;
+
+ /**
+ * OR of active modifier keys
+ */
enum libterminput_mod mods;
- char symbol[7]; /* use if .key == LIBTERMINPUT_SYMBOL */
+
+ /**
+ * The symbol generated by the pressed key
+ *
+ * Only set if `.key == LIBTERMINPUT_SYMBOL`
+ */
+ char symbol[7];
};
+
+/**
+ * Text from a bracketed paste
+ *
+ * @since 1.0 (applies to all members unless stated otherwise)
+ */
struct libterminput_text {
+ /**
+ * Should be `LIBTERMINPUT_TEXT`
+ */
enum libterminput_type type;
+
+ /**
+ * The number of bytes available in `.bytes`
+ */
size_t nbytes;
+
+ /**
+ * The section of the paste included in this
+ * event report
+ *
+ * If the text is longer than this buffer, it
+ * is split into multiple events, however they
+ * will all be between the same
+ * `LIBTERMINPUT_BRACKETED_PASTE_START` and
+ * `LIBTERMINPUT_BRACKETED_PASTE_END`, so it is
+ * possible to determine which events are actually
+ * the same paste event
+ */
char bytes[512];
};
+
+/**
+ * Mouse event
+ *
+ * @since 1.0 (applies to all members unless stated otherwise)
+ */
struct libterminput_mouseevent {
+ /**
+ * Should be `LIBTERMINPUT_MOUSEEVENT`
+ */
enum libterminput_type type;
- enum libterminput_mod mods; /* Set to 0 for LIBTERMINPUT_HIGHLIGHT_INSIDE and LIBTERMINPUT_HIGHLIGHT_OUTSIDE */
- enum libterminput_button button; /* Set to 1 for LIBTERMINPUT_HIGHLIGHT_INSIDE and LIBTERMINPUT_HIGHLIGHT_OUTSIDE */
+
+ /**
+ * Active modifier keys
+ *
+ * Set to 0 for `LIBTERMINPUT_HIGHLIGHT_INSIDE`
+ * and `LIBTERMINPUT_HIGHLIGHT_OUTSIDE`
+ */
+ enum libterminput_mod mods;
+
+ /**
+ * The mouse button used in the event
+ *
+ * Set to 1 (LIBTERMINPUT_BUTTON1) for
+ * `LIBTERMINPUT_HIGHLIGHT_INSIDE` and
+ * `LIBTERMINPUT_HIGHLIGHT_OUTSIDE`
+ */
+ enum libterminput_button button;
+
+ /**
+ * Mouse event sub type
+ */
enum libterminput_event event;
+
+ /**
+ * Horizontal pointer position
+ *
+ * The number of cells offset right from the left edge, plus 1
+ */
size_t x;
+
+ /**
+ * Vertical pointer position
+ *
+ * The number of cells offset down from the top edge, plus 1
+ */
size_t y;
- size_t start_x; /* Only set for LIBTERMINPUT_HIGHLIGHT_OUTSIDE */
- size_t start_y; /* Only set for LIBTERMINPUT_HIGHLIGHT_OUTSIDE */
- size_t end_x; /* Only set for LIBTERMINPUT_HIGHLIGHT_OUTSIDE */
- size_t end_y; /* Only set for LIBTERMINPUT_HIGHLIGHT_OUTSIDE */
+
+ /**
+ * Horizontal beginning of the selection region
+ *
+ * Only set for `LIBTERMINPUT_HIGHLIGHT_OUTSIDE`
+ */
+ size_t start_x;
+
+ /**
+ * Vertical beginning of the selection region
+ *
+ * Only set for `LIBTERMINPUT_HIGHLIGHT_OUTSIDE`
+ */
+ size_t start_y;
+
+ /**
+ * Horizontal end of the selection region
+ *
+ * Only set for `LIBTERMINPUT_HIGHLIGHT_OUTSIDE`
+ */
+ size_t end_x;
+
+ /**
+ * Vertical end of the selection region
+ *
+ * Only set for `LIBTERMINPUT_HIGHLIGHT_OUTSIDE`
+ */
+ size_t end_y;
};
+
+/**
+ * Cursor position response
+ *
+ * @since 1.0 (applies to all members unless stated otherwise)
+ */
struct libterminput_position {
+ /**
+ * Should be `LIBTERMINPUT_CURSOR_POSITION`
+ */
enum libterminput_type type;
+
+ /**
+ * Horizontal cursor position
+ *
+ * The number of cells offset right from the left edge, plus 1
+ */
size_t x;
+
+ /**
+ * Vertical cursor position
+ *
+ * The number of cells offset down from the top edge, plus 1
+ */
size_t y;
};
+
+/**
+ * Input event
+ *
+ * @since 1.0 (applies to all members unless stated otherwise)
+ */
union libterminput_input {
+ /**
+ * Input event type, used to determine which
+ * other member to read
+ *
+ * The following values have no corresponding
+ * member to read data from:
+ * `LIBTERMINPUT_NONE`,
+ * `LIBTERMINPUT_BRACKETED_PASTE_START`,
+ * `LIBTERMINPUT_BRACKETED_PASTE_END`,
+ * `LIBTERMINPUT_TERMINAL_IS_OK`,
+ * `LIBTERMINPUT_TERMINAL_IS_NOT_OK`
+ *
+ * Internal comment:
+ * When `.type == LIBTERMINPUT_NONE`, `.keypress.key`
+ * is normally set to `LIBTERMINPUT_SYMBOL`, however
+ * if it is set to anything else, there is a queued
+ * keypress
+ */
enum libterminput_type type;
- struct libterminput_keypress keypress; /* use if .type == LIBTERMINPUT_KEYPRESS */
- struct libterminput_text text; /* use if .type == LIBTERMINPUT_TEXT */
- struct libterminput_mouseevent mouseevent; /* use if .type == LIBTERMINPUT_MOUSEEVENT */
- struct libterminput_position position; /* use if .type == LIBTERMINPUT_CURSOR_POSITION */
+
+ /**
+ * Use if `.type == LIBTERMINPUT_KEYPRESS`
+ */
+ struct libterminput_keypress keypress;
+
+ /**
+ * Use if `.type == LIBTERMINPUT_TEXT`
+ */
+ struct libterminput_text text;
+
+ /**
+ * Use if `.type == LIBTERMINPUT_MOUSEEVENT`
+ */
+ struct libterminput_mouseevent mouseevent;
+
+ /**
+ * Use if `.type == LIBTERMINPUT_CURSOR_POSITION`
+ */
+ struct libterminput_position position;
};
/**
- * This struct should be considered opaque
+ * The current input state and configurations
+ *
+ * NB! This struct should be considered opaque
+ *
+ * Initialised with by setting all bytes to 0
+ *
+ * @since 1.0
*/
struct libterminput_state {
- int inited; /* whether the input in initialised, not this struct */
- enum libterminput_mod mods;
- enum libterminput_flags flags;
- char bracketed_paste;
- char mouse_tracking;
- char meta;
- char n;
- size_t stored_head;
- size_t stored_tail;
- char paused;
- char npartial;
- char partial[7];
- char key[44];
- char stored[512];
+ int inited; /* whether the input in initialised, not this struct */
+ enum libterminput_mod mods; /* currently active modifier keys */
+ enum libterminput_flags flags; /* applied behaviour flags */
+ unsigned char bracketed_paste; /* 1 if in bracketed paste, 0 otherwise */
+ unsigned char mouse_tracking; /* length of mouse tracking data, 0 if not processing an mouse tracking event,
+ * 1 if undetermined */
+ unsigned char meta; /* number of captured meta/escape bytes */
+ unsigned char n; /* bytes length of partially read character */
+ size_t stored_head; /* index of first available byte in `.stored` */
+ size_t stored_tail; /* index of next byte to write in `.stored` */
+ unsigned char paused : 1; /* 1 if the available buffered input is incomplete */
+ unsigned char blocked : 1; /* 1 if the available no data was available for read(2) */
+ unsigned char queued : 1; /* 1 if a keypress is queued for output if there is no more data at next read(2) */
+ unsigned char unused_bits : 5; /* should be zero due to how the struct should be initialised */
+ unsigned char npartial; /* number of bytes available in `.partial` */
+ char partial[7]; /* partially read character */
+ char key[44]; /* processed bytes the identify the pressed key or non-key-press event */
+ char stored[512]; /* buffered input not yet processed */
};
/**
+ * User provided function and associated binary
+ * data used to store the state
+ *
+ * @since 1.1 (applies to all members unless stated otherwise)
+ */
+struct libterminput_marshaller {
+ /**
+ * Callback function to store data
+ *
+ * @param this Pointer to the object containing this function
+ * @param data The data to store
+ * @param size The number of bytes in `data`
+ * @return 0 on success, -1 on failure
+ */
+ int (*store)(struct libterminput_marshaller *this, const void *data, size_t size);
+
+ /**
+ * User-defined data
+ */
+ union {
+ void *ptr;
+ int i;
+ size_t zu;
+ } user;
+};
+
+
+/**
+ * User provided function and associated binary
+ * data used to load the state
+ *
+ * @since 1.1 (applies to all members unless stated otherwise)
+ */
+struct libterminput_unmarshaller {
+ /**
+ * Callback function to load data
+ *
+ * @param this Pointer to the object containing this function
+ * @param data Output buffer for the data
+ * @param size The number of bytes to load into `data`
+ * @return 0 on success, -1 on failure
+ */
+ int (*load)(struct libterminput_unmarshaller *this, void *data, size_t size);
+
+ /**
+ * User-defined data
+ */
+ union {
+ void *ptr;
+ int i;
+ size_t zu;
+ } user;
+};
+
+
+/**
+ * Initialises the parser
+ *
+ * Calling this function is optional
+ *
+ * Before calling this function, it is important to call
+ * `memset(ctx, 0, sizeof(*ctx))` so that `ctx` is properly
+ * marked as fully uninitialised (this is needed because
+ * the execution of the function is resumed by calling it
+ * again after EINTR or EAGAIN failure)
+ *
+ * In the current version, this function doesn't do anything,
+ * however future versions (or other implementations) may
+ * read to and write from the terminal, read the process's
+ * environment variables, or read from the filesystem in
+ * order to detect the particular quirks of the terminal
+ *
+ * @param ctx Parser state
+ * @param fd The file descriptor to the terminal
+ * @return 0 on success, -1 on failure
+ *
+ * It is unspecified which errors will cause this function
+ * to fail, however it should avoid failing for any other
+ * reason than EINTR and EAGAIN
+ *
+ * @since 1.1
+ */
+int libterminput_init(struct libterminput_state *ctx, int fd);
+
+/**
+ * Deallocate any resources allocated by `libterminput_init`
+ *
+ * `ctx` is no longer usable after this function returns
+ *
+ * @param ctx Parser state
+ *
+ * @since 1.1
+ */
+void libterminput_destroy(struct libterminput_state *ctx);
+
+/**
* 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
+ *
+ * @throws Any reason specified for read(3)
+ *
+ * @since 1.0
*/
int libterminput_read(int fd, union libterminput_input *input, struct libterminput_state *ctx);
+/**
+ * Check if more input available that can be processed
+ * by `libterminput_read` without performing any additional
+ * read(3) operation
+ *
+ * This function should only be used if using blocking
+ * read(3) operations. The flag LIBTERMINPUT_ESC_ON_BLOCK
+ * is only meaningful for non-blocking read. With non-blocking
+ * read, the application should call libterminput_read(3)
+ * and check for EAGAIN error rather than using this function,
+ * as EAGAIN errors on read can cause libterminput_read(3) to
+ * output keypresses it would otherwise not output, but in
+ * doing so not report EAGAIN.
+ *
+ * @param input Input gathered by `libterminput_read`
+ * @param ctx State for the terminal
+ * @return 1 if there there is more input available, 0 otherwise
+ *
+ * @since 1.0
+ */
inline int
-libterminput_is_ready(union libterminput_input *input, struct libterminput_state *ctx)
+libterminput_is_ready(const union libterminput_input *input, const struct libterminput_state *ctx)
{
if (!ctx->inited || ctx->paused || ctx->mouse_tracking)
return 0;
@@ -206,8 +775,90 @@ libterminput_is_ready(union libterminput_input *input, struct libterminput_state
return ctx->stored_head > ctx->stored_tail;
}
+/**
+ * Set a behavioural flags
+ *
+ * @param ctx Argument pasted as the last parameter to `libterminput_read`
+ * @param flags The OR of the flags to set
+ * @return 0 on success, -1 on failure
+ *
+ * The current version of this function cannot fail
+ *
+ * @since 1.0
+ */
int libterminput_set_flags(struct libterminput_state *ctx, enum libterminput_flags flags);
+
+/**
+ * Clear a behavioural flags
+ *
+ * @param ctx Argument pasted as the last parameter to `libterminput_read`
+ * @param flags The OR of the flags to clear
+ * @return 0 on success, -1 on failure
+ *
+ * The current version of this function cannot fail
+ *
+ * @since 1.0
+ */
int libterminput_clear_flags(struct libterminput_state *ctx, enum libterminput_flags flags);
+/**
+ * Marshal the parsed input
+ *
+ * @param how Object used to store the serialisation
+ * @param what The input to marshal
+ * @return 0 on success, -1 on failure
+ *
+ * This function will fail for any reason `*how->store` fails
+ *
+ * @since 1.1
+ */
+int libterminput_marshal_input(struct libterminput_marshaller *how, const union libterminput_input *what);
+
+/**
+ * Marshal the input parsing state
+ *
+ * It's important to also use `libterminput_marshal_input`
+ * as it may contain part of the state
+ *
+ * @param how Object used to store the serialisation
+ * @param what The state to marshal
+ * @return 0 on success, -1 on failure
+ *
+ * This function will fail for any reason `*how->store` fails
+ *
+ * @since 1.1
+ */
+int libterminput_marshal_state(struct libterminput_marshaller *how, const struct libterminput_state *what);
+
+/**
+ * Unmarshal the parsed input
+ *
+ * @param how Object used to load the serialisation
+ * @param what Output parameter for the unmarshalled input
+ * @return 0 on success, -1 on failure
+ *
+ * @throws EINVAL Invalid serialisation
+ *
+ * This function will also fail for any reason `*how->load` fails
+ *
+ * @since 1.1
+ */
+int libterminput_unmarshal_input(struct libterminput_unmarshaller *how, union libterminput_input *what);
+
+/**
+ * Unmarshal the input parsing state
+ *
+ * @param how Object used to load the serialisation
+ * @param what Output parameter for the unmarshalled state
+ * @return 0 on success, -1 on failure
+ *
+ * @throws EINVAL Invalid serialisation
+ *
+ * This function will also fail for any reason `*how->load` fails
+ *
+ * @since 1.1
+ */
+int libterminput_unmarshal_state(struct libterminput_unmarshaller *how, struct libterminput_state *what);
+
#endif
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.3 b/libterminput_clear_flags.3
new file mode 120000
index 0000000..62881e4
--- /dev/null
+++ b/libterminput_clear_flags.3
@@ -0,0 +1 @@
+libterminput_set_flags.3 \ No newline at end of file
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_destroy.3 b/libterminput_destroy.3
new file mode 120000
index 0000000..81850ed
--- /dev/null
+++ b/libterminput_destroy.3
@@ -0,0 +1 @@
+libterminput_init.3 \ No newline at end of file
diff --git a/libterminput_destroy.c b/libterminput_destroy.c
new file mode 100644
index 0000000..628032f
--- /dev/null
+++ b/libterminput_destroy.c
@@ -0,0 +1,9 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+void
+libterminput_destroy(struct libterminput_state *ctx)
+{
+ (void) ctx;
+}
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_init.3 b/libterminput_init.3
new file mode 100644
index 0000000..fa739a0
--- /dev/null
+++ b/libterminput_init.3
@@ -0,0 +1,110 @@
+.TH LIBTERMINPUT_INIT 3 LIBTERMINPUT
+.SH NAME
+libterminput_init \- Configure library for terminal quirks
+.br
+libterminput_destory \- Deallocate library configuration resources
+
+.SH SYNOPSIS
+.nf
+#include <libterminput.h>
+
+int libterminput_init(struct libterminput_state *\fIctx\fP, int \fIfd\fP);
+void libterminput_destroy(struct libterminput_state *\fIctx\fP);
+.fi
+.PP
+Link with
+.IR \-lterminput .
+
+.SH DESCRIPTION
+The current version of the
+.BR libterminput_init ()
+function doesn't do anything. However, future versions
+may communicate with the terminal, inspect the process's
+environment variables, and read the filesystem to determine
+the quirks of the terminal.
+.PP
+Before calling this,
+.I *ctx
+must be initialised by running
+.IR "memset(ctx, 0, sizeof(*ctx))" .
+.PP
+.I fd
+shall be a file descriptor to the terminal with both
+read and write access.
+.PP
+Calling the
+.BR libterminput_init ()
+function will always be optional. However, if the
+function has failed the function must be called again
+(until successful completion), or
+.I *ctx
+must be destroyed using
+.BR libterminput_destroy ()
+and reset with
+.BR memset (3).
+.PP
+The
+.BR libterminput_destroy ()
+function deallocates resources in
+.I *ctx
+allocated by the
+.BR libterminput_init ()
+function.
+
+.SH RETURN VALUE
+The
+.BR libterminput_init ()
+function returns 0 upon successful completion.
+On failure, it returns
+.B -1
+and sets
+.I errno
+to indicate the error.
+.PP
+The
+.BR libterminput_destroy ()
+function does not have a return value.
+
+.SH ERRORS
+The
+.BR libterminput_init ()
+function may fail for any reason, although it should
+avoid failing for any reason other than:
+.TP
+.B EINTR
+Default.
+.TP
+.B AGAIN
+Default.
+.PP
+The
+.BR libterminput_destroy ()
+function cannot fail.
+
+.SH EXAMPLES
+None.
+
+.SH APPLICATION USAGE
+None.
+
+.SH RATIONALE
+None.
+
+.SH FUTURE DIRECTIONS
+None.
+
+.SH HISTORY
+The
+.BR libterminput_init ()
+and
+.BR libterminput_destroy ()
+functions were added in version 1.1 of libterminput.
+
+.SH NOTES
+None.
+
+.SH BUGS
+None.
+
+.SH SEE ALSO
+.BR libterminput_read (3)
diff --git a/libterminput_init.c b/libterminput_init.c
new file mode 100644
index 0000000..06b93a6
--- /dev/null
+++ b/libterminput_init.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+#if defined(__GNUC__)
+__attribute__((__const__))
+#endif
+int
+libterminput_init(struct libterminput_state *ctx, int fd)
+{
+ (void) ctx;
+ (void) fd;
+ return 0;
+}
diff --git a/libterminput_is_ready.3 b/libterminput_is_ready.3
index 0739e11..c624008 100644
--- a/libterminput_is_ready.3
+++ b/libterminput_is_ready.3
@@ -6,7 +6,7 @@ libterminput_is_ready \- Check if there is read data buffered
.nf
#include <libterminput.h>
-inline int libterminput_is_ready(union libterminput_input *input, struct libterminput_state *ctx);
+inline int libterminput_is_ready(const union libterminput_input *\fIinput\fP, const struct libterminput_state *\fIctx\fP);
.fi
.PP
Link with
@@ -24,6 +24,35 @@ is buffered data but the library knows it
it not enough to return something they,
it will also return that there is nothing
buffered.
+.PP
+The
+.BR libterminput_is_ready ()
+function should only be used if using blocking
+read operations (terminal file descriptor configured with
+.I O_NONBLOCK
+cleared). The flags
+.I LIBTERMINPUT_ESC_ON_BLOCK
+and
+.I LIBTERMINPUT_MACRO_ON_BLOCK
+are only meaningful for non-blocking reads. With non-blocking
+reads, the application should call
+.BR libterminput_read (3)
+and check for
+.I EAGAIN
+error rather than using the
+.BR libterminput_is_ready ()
+function, as
+.I EAGAIN
+errors on read can cause the
+.BR libterminput_read (3)
+function to output keypresses it would otherwise not output,
+but in doing so not report
+.I EAGAIN
+(although
+.I errno
+will still be set to
+.IR EAGAIN ,
+so the application can still check if this occurred).
.SH RETURN VALUE
The
@@ -57,4 +86,5 @@ None.
None.
.SH SEE ALSO
-.BR libterminput_read (3)
+.BR libterminput_read (3),
+.BR fcntl (3)
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_marshal_input.3 b/libterminput_marshal_input.3
new file mode 120000
index 0000000..0656462
--- /dev/null
+++ b/libterminput_marshal_input.3
@@ -0,0 +1 @@
+libterminput_marshal_state.3 \ No newline at end of file
diff --git a/libterminput_marshal_input.c b/libterminput_marshal_input.c
new file mode 100644
index 0000000..4064956
--- /dev/null
+++ b/libterminput_marshal_input.c
@@ -0,0 +1,27 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+int
+libterminput_marshal_input(struct libterminput_marshaller *how, const union libterminput_input *what)
+{
+ enum libterminput_type type = what->type;
+ if (how->store(how, &type, sizeof(type)))
+ return -1;
+ if (type == LIBTERMINPUT_NONE) {
+ if (what->keypress.key != LIBTERMINPUT_SYMBOL)
+ type = LIBTERMINPUT_KEYPRESS;
+ if (how->store(how, &type, sizeof(type)))
+ return -1;
+ }
+ if (type == LIBTERMINPUT_KEYPRESS)
+ return libterminput_marshal_keypress__(how, &what->keypress);
+ else if (type == LIBTERMINPUT_TEXT)
+ return libterminput_marshal_text__(how, &what->text);
+ else if (type == LIBTERMINPUT_MOUSEEVENT)
+ return libterminput_marshal_mouseevent__(how, &what->mouseevent);
+ else if (type == LIBTERMINPUT_CURSOR_POSITION)
+ return libterminput_marshal_position__(how, &what->position);
+ else
+ return 0;
+}
diff --git a/libterminput_marshal_keypress__.c b/libterminput_marshal_keypress__.c
new file mode 100644
index 0000000..1ffba01
--- /dev/null
+++ b/libterminput_marshal_keypress__.c
@@ -0,0 +1,16 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+int
+libterminput_marshal_keypress__(struct libterminput_marshaller *how, const struct libterminput_keypress *what)
+{
+ if (how->store(how, &what->key, sizeof(what->key)) ||
+ how->store(how, &what->times, sizeof(what->times)) ||
+ how->store(how, &what->mods, sizeof(what->mods)))
+ return -1;
+ if (what->key == LIBTERMINPUT_SYMBOL)
+ return how->store(how, what->symbol, sizeof(what->symbol));
+ else
+ return 0;
+}
diff --git a/libterminput_marshal_mouseevent__.c b/libterminput_marshal_mouseevent__.c
new file mode 100644
index 0000000..8fe64c7
--- /dev/null
+++ b/libterminput_marshal_mouseevent__.c
@@ -0,0 +1,20 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+int
+libterminput_marshal_mouseevent__(struct libterminput_marshaller *how, const struct libterminput_mouseevent *what)
+{
+ if (how->store(how, &what->event, sizeof(what->event)) ||
+ how->store(how, &what->x, sizeof(size_t) * 2U))
+ return -1;
+ if (what->event == LIBTERMINPUT_HIGHLIGHT_OUTSIDE) {
+ if (how->store(how, &what->start_x, sizeof(size_t) * 4U))
+ return -1;
+ } else if (what->event != LIBTERMINPUT_HIGHLIGHT_INSIDE) {
+ if (how->store(how, &what->mods, sizeof(what->mods)) ||
+ how->store(how, &what->button, sizeof(what->button)))
+ return -1;
+ }
+ return 0;
+}
diff --git a/libterminput_marshal_position__.c b/libterminput_marshal_position__.c
new file mode 100644
index 0000000..59fc20e
--- /dev/null
+++ b/libterminput_marshal_position__.c
@@ -0,0 +1,9 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+int
+libterminput_marshal_position__(struct libterminput_marshaller *how, const struct libterminput_position *what)
+{
+ return how->store(how, &what->x, sizeof(size_t) * 2U);
+}
diff --git a/libterminput_marshal_state.3 b/libterminput_marshal_state.3
new file mode 100644
index 0000000..26f7a49
--- /dev/null
+++ b/libterminput_marshal_state.3
@@ -0,0 +1,121 @@
+.TH LIBTERMINPUT_MARSHAL_STATE 3 LIBTERMINPUT
+.SH NAME
+libterminput_marshal_state \- Marshal the input parsing state
+.br
+libterminput_marshal_input \- Marshal the parsed input
+
+.SH SYNOPSIS
+.nf
+#include <libterminput.h>
+
+struct libterminput_marshaller {
+ int (*store)(struct libterminput_marshaller *\fIthis\fP, const void *\fIdata\fP, size_t \fIsize\fP);
+ union {
+ void *ptr;
+ int i;
+ size_t zu;
+ } user;
+};
+
+int libterminput_marshal_state(struct libterminput_marshaller *\fIhow\fP, const struct libterminput_state *\fIwhat\fP);
+int libterminput_marshal_input(struct libterminput_marshaller *\fIhow\fP, const union libterminput_input *\fIwhat\fP);
+.fi
+.PP
+Link with
+.IR \-lterminput .
+
+.SH DESCRIPTION
+The
+.BR libterminput_marshal_state ()
+and
+.BR libterminput_marshal_input ()
+function marshals the contents of
+.I what
+using
+.IR *how->store .
+.PP
+It's important to also use the
+.BR libterminput_marshal_input ()
+function in addition to the
+.BR libterminput_marshal_state ()
+function as part of the state may be stored in the
+.IR "union libterminput_input" .
+.PP
+.I *how->store
+must return 0 on success and -1 on failure, and may set
+.I errno
+to indicate the error.
+.I how
+is fed back into
+.I *how->store
+(as
+.IR this )
+so that the
+.I *how->store
+function can use and modify
+.I how->user
+which contains application-defined data
+(typically a buffer, a file descriptor, or
+the size of the serialisation).
+.I data
+will be set to the binary data to store, and
+.I size
+will be set to the number of bytes from
+.I data
+to store.
+
+.SH RETURN VALUE
+The
+.BR libterminput_marshal_state ()
+and
+.BR libterminput_marshal_input ()
+functions return 0 upon successful completion.
+On failure,
+.B -1
+is returnes, but
+.I errno
+remains unmodified, however any changes to
+.I errno
+by
+.I *how->store
+will remain.
+
+.SH ERRORS
+The
+.BR libterminput_marshal_state ()
+and
+.BR libterminput_marshal_input ()
+functions fail, without further modifying
+.IR errno ,
+if
+.I *how->store
+fails.
+
+.SH EXAMPLES
+None.
+
+.SH APPLICATION USAGE
+None.
+
+.SH RATIONALE
+None.
+
+.SH FUTURE DIRECTIONS
+None.
+
+.SH HISTORY
+The
+.BR libterminput_marshal_state ()
+and
+.BR libterminput_marshal_input ()
+functions were added in version 1.1 of libterminput.
+
+.SH NOTES
+None.
+
+.SH BUGS
+None.
+
+.SH SEE ALSO
+.BR libterminput_unmarshal_state (3),
+.BR libterminput_read (3)
diff --git a/libterminput_marshal_state.c b/libterminput_marshal_state.c
new file mode 100644
index 0000000..d27966e
--- /dev/null
+++ b/libterminput_marshal_state.c
@@ -0,0 +1,9 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+int
+libterminput_marshal_state(struct libterminput_marshaller *how, const struct libterminput_state *what)
+{
+ return how->store(how, what, sizeof(*what));
+}
diff --git a/libterminput_marshal_text__.c b/libterminput_marshal_text__.c
new file mode 100644
index 0000000..5fa1325
--- /dev/null
+++ b/libterminput_marshal_text__.c
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+int
+libterminput_marshal_text__(struct libterminput_marshaller *how, const struct libterminput_text *what)
+{
+ if (what->nbytes > sizeof(what->bytes))
+ abort();
+ if (how->store(how, &what->nbytes, sizeof(what->nbytes)) ||
+ how->store(how, what->bytes, what->nbytes))
+ return -1;
+ return 0;
+}
diff --git a/libterminput_parse_csi_m_mouse_tracking__.c b/libterminput_parse_csi_m_mouse_tracking__.c
new file mode 100644
index 0000000..988a665
--- /dev/null
+++ b/libterminput_parse_csi_m_mouse_tracking__.c
@@ -0,0 +1,54 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+void
+libterminput_parse_csi_m_mouse_tracking__(union libterminput_input *input, struct libterminput_state *ctx,
+ unsigned long long int *nums, size_t nnums)
+{
+ unsigned long long int numsbuf[3];
+ size_t pos;
+
+ if (nnums >= 3U) {
+ /* Parsing for \e[?1000;1015h output */
+ nums[0] -= 32ULL;
+
+ } else if (!nnums && (ctx->flags & LIBTERMINPUT_DECSET_1005)) {
+ /* 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;
+
+ } else if (!nnums) {
+ /* 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;
+
+ } else {
+ NOTHING(input);
+ return;
+ }
+
+ input->mouseevent.event = LIBTERMINPUT_PRESS;
+ libterminput_parse_decimal_mouse_tracking__(input, nums);
+}
diff --git a/libterminput_parse_csi_small_t_mouse_tracking__.c b/libterminput_parse_csi_small_t_mouse_tracking__.c
new file mode 100644
index 0000000..14a1cf7
--- /dev/null
+++ b/libterminput_parse_csi_small_t_mouse_tracking__.c
@@ -0,0 +1,24 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+void
+libterminput_parse_csi_small_t_mouse_tracking__(union libterminput_input *input, struct libterminput_state *ctx)
+{
+ unsigned long long int nums[2];
+
+ /* Parsing output for legacy mouse highlight tracking output (\e[?1001h) */
+ ctx->mouse_tracking = 0;
+ 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];
+}
diff --git a/libterminput_parse_csi_t_mouse_tracking__.c b/libterminput_parse_csi_t_mouse_tracking__.c
new file mode 100644
index 0000000..47e4505
--- /dev/null
+++ b/libterminput_parse_csi_t_mouse_tracking__.c
@@ -0,0 +1,36 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+void
+libterminput_parse_csi_t_mouse_tracking__(union libterminput_input *input, struct libterminput_state *ctx)
+{
+ unsigned long long int nums[6];
+
+ /* Parsing output for legacy mouse highlight tracking output. (\e[?1001h) */
+ ctx->mouse_tracking = 0;
+ 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];
+}
diff --git a/libterminput_parse_decimal_mouse_tracking__.c b/libterminput_parse_decimal_mouse_tracking__.c
new file mode 100644
index 0000000..01b6040
--- /dev/null
+++ b/libterminput_parse_decimal_mouse_tracking__.c
@@ -0,0 +1,23 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+void
+libterminput_parse_decimal_mouse_tracking__(union libterminput_input *input, unsigned long long int nums[3])
+{
+ 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];
+}
diff --git a/libterminput_parse_sequence__.c b/libterminput_parse_sequence__.c
new file mode 100644
index 0000000..78f0161
--- /dev/null
+++ b/libterminput_parse_sequence__.c
@@ -0,0 +1,274 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+void
+libterminput_parse_sequence__(union libterminput_input *input, struct libterminput_state *ctx)
+{
+ unsigned long long int *nums;
+ size_t keylen, n, nnums = 0;
+ 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 = 2U, p = ctx->key; *p; p++) {
+ if (*p == ';') {
+ n += 1U;
+ nnums += 1U;
+ }
+ }
+ 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 < 3U) {
+ 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
+ libterminput_parse_csi_m_mouse_tracking__(input, ctx, nums, nnums);
+ 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 >= 2U) {
+ 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':
+ libterminput_parse_csi_t_mouse_tracking__(input, ctx);
+ 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_BACKTAB;
+ } else {
+ input->keypress.key = LIBTERMINPUT_TAB;
+ input->keypress.mods |= LIBTERMINPUT_SHIFT;
+ }
+ break;
+ case 'a': input->keypress.key = LIBTERMINPUT_UP; goto shift;
+ case 'b': input->keypress.key = LIBTERMINPUT_DOWN; goto shift;
+ case 'c': input->keypress.key = LIBTERMINPUT_RIGHT; goto shift;
+ case 'd': input->keypress.key = LIBTERMINPUT_LEFT; goto shift;
+ case 'n':
+ if (nnums == 1U && nums[0] == 0)
+ input->type = LIBTERMINPUT_TERMINAL_IS_OK;
+ else if (nnums == 1U && nums[0] == 3)
+ input->type = LIBTERMINPUT_TERMINAL_IS_NOT_OK;
+ else
+ goto suppress;
+ break;
+ case 't':
+ libterminput_parse_csi_small_t_mouse_tracking__(input, ctx);
+ break;
+ case 'u':
+ if (nums[0] > 0x10FFFFULL || (nums[0] & 0xFFF800ULL) == 0xD800ULL) {
+ NOTHING(input);
+ 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; goto shift;
+ case 26: input->keypress.key = LIBTERMINPUT_F2; goto shift;
+ case 28: input->keypress.key = LIBTERMINPUT_F3; goto shift;
+ case 29: input->keypress.key = LIBTERMINPUT_F4; goto shift;
+ case 31: input->keypress.key = LIBTERMINPUT_F5; goto shift;
+ case 32: input->keypress.key = LIBTERMINPUT_F6; goto shift;
+ case 33: input->keypress.key = LIBTERMINPUT_F7; goto shift;
+ case 34: input->keypress.key = LIBTERMINPUT_F8; goto shift;
+ 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;
+ }
+ 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 >= 3U) {
+ /* Parsing for \e[?1003;1006h output. */
+ input->mouseevent.event = LIBTERMINPUT_PRESS;
+ if (ctx->key[2] == 'm')
+ input->mouseevent.event = LIBTERMINPUT_RELEASE;
+ libterminput_parse_decimal_mouse_tracking__(input, nums);
+ } else {
+ goto suppress;
+ }
+ }
+ break;
+
+ default:
+ goto suppress;
+ }
+ break;
+
+ case '?': /* '?' is attested as synonym for 'O' when the next character is in "pqrstuvwxymlnM" */
+ 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 'I': input->keypress.key = LIBTERMINPUT_F12; break;
+ case 'J': input->keypress.key = LIBTERMINPUT_F1; goto shift;
+ case 'K': input->keypress.key = LIBTERMINPUT_F2; goto shift;
+ case 'L': input->keypress.key = LIBTERMINPUT_F3; goto shift;
+ case 'M': input->keypress.key = LIBTERMINPUT_KEYPAD_ENTER; break;
+ case 'N': input->keypress.key = LIBTERMINPUT_F4; goto shift;
+ 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 'T': input->keypress.key = LIBTERMINPUT_F5; break;
+ case 'U': input->keypress.key = LIBTERMINPUT_F6; break;
+ case 'V': input->keypress.key = LIBTERMINPUT_F7; break; /* not attested */
+ case 'W': input->keypress.key = LIBTERMINPUT_F8; break; /* not attested */
+ case 'X': input->keypress.key = LIBTERMINPUT_F9; break; /* not attested */
+ case 'Y': input->keypress.key = LIBTERMINPUT_F10; break;
+ case 'Z': input->keypress.key = LIBTERMINPUT_F11; break; /* not attested */
+ case 'b': input->keypress.key = LIBTERMINPUT_KEYPAD_POINT; break;
+ case 'e': input->keypress.key = LIBTERMINPUT_F7; break;
+ case 'f': input->keypress.key = LIBTERMINPUT_F8; break;
+ case 'j': input->keypress.key = LIBTERMINPUT_KEYPAD_TIMES; break;
+ case 'k': input->keypress.key = LIBTERMINPUT_KEYPAD_PLUS; break;
+ case 'l': input->keypress.key = LIBTERMINPUT_KEYPAD_COMMA; break;
+ case 'm': input->keypress.key = LIBTERMINPUT_KEYPAD_MINUS; break;
+ case 'n': input->keypress.key = LIBTERMINPUT_KEYPAD_DECIMAL; break;
+ case 'o': input->keypress.key = LIBTERMINPUT_KEYPAD_DIVISION; 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;
+ default:
+ goto suppress;
+ }
+ break;
+ shift:
+ input->keypress.mods |= LIBTERMINPUT_SHIFT;
+ break;
+
+ default:
+ /* This shouldn't happen (without goto) */
+ suppress:
+ NOTHING(input);
+ break;
+ }
+}
diff --git a/libterminput_read.3 b/libterminput_read.3
index 2c95be5..7a30072 100644
--- a/libterminput_read.3
+++ b/libterminput_read.3
@@ -137,6 +137,7 @@ union libterminput_input {
struct libterminput_keypress keypress;
struct libterminput_text text;
struct libterminput_mouseevent mouseevent;
+ struct libterminput_position position;
};
int libterminput_read(int \fIfd\fP, union libterminput_input *\fIinput\fP, struct libterminput_state *\fIctx\fP);
@@ -157,13 +158,15 @@ returns the result in the
.I ctx
must have been zero-initialised, e.g. with
.BR memset (3)
-function.
+function, and optionally with
+.BR libterminput_init (3)
+afterwards.
.PP
.I input
shall be the same pointer every time the
.BR libterminput_read ()
function is called with the same
-.I ctx ,
+.IR ctx ,
as should
.IR fd ,
except the user may choose to use a negative
@@ -410,7 +413,7 @@ OK response for a device status query.
Not-OK response for a device status query.
.TP
.B LIBTERMINPUT_CURSOR_POSITION
-Cursor position report even as a response to a
+Cursor position report event as a response to a
cursor position query. The line (indexed starting
with 1 at the top) the cursor is on will be
stored in
@@ -425,6 +428,7 @@ therefore the
flag must be set with the
.BR libterminput_set_flags (3)
function.
+
.SH RETURN VALUE
The
.BR libterminput_read ()
@@ -437,7 +441,7 @@ function returns
.B -1
and set
.I errno
-it indicate the error.
+to indicate the error.
.SH ERRORS
The
@@ -465,5 +469,7 @@ None.
None.
.SH SEE ALSO
+.BR libterminput_init (3),
.BR libterminput_is_ready (3),
-.BR libterminput_set_flags (3)
+.BR libterminput_set_flags (3),
+.BR libterminput_marshal_state (3)
diff --git a/libterminput_read.c b/libterminput_read.c
new file mode 100644
index 0000000..9324573
--- /dev/null
+++ b/libterminput_read.c
@@ -0,0 +1,196 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+int
+libterminput_read(int fd, union libterminput_input *input, struct libterminput_state *ctx)
+{
+ struct input ret;
+ size_t n, m;
+ char *p;
+ int r, i;
+ ssize_t rd;
+
+ if (!ctx->inited) {
+ /* Initialise structures */
+ ctx->inited = 1;
+ memset(input, 0, sizeof(*input));
+ /* The user should have set all bytes in `*ctx` to 0, and it doesn't
+ * contain any pointers, and in fact all initial values should be 0,
+ * so we do not need to modify `*ctx` except mark it as initialised */
+ } else if (input->type == LIBTERMINPUT_KEYPRESS && input->keypress.times > 1) {
+ /* For repeated input, unless counter is reset by user, report
+ * the same event again (and decrease the counter) */
+ input->keypress.times -= 1;
+ return 1;
+ }
+
+ /* If in a bracketed paste, use the reading function for that mode */
+ if (ctx->bracketed_paste)
+ return libterminput_read_bracketed_paste__(fd, input, ctx);
+
+ /* If we are on an incomplete mouse tracking event, read more raw input,
+ * otherwise read one symbol */
+ if (!ctx->mouse_tracking) {
+ r = libterminput_read_symbol__(fd, &ret, ctx);
+ if (r <= 0) {
+ if (!r || ctx->blocked || !ctx->queued || errno != EAGAIN)
+ return r;
+ ctx->blocked = 1;
+ ctx->queued = 0;
+ input->type = LIBTERMINPUT_KEYPRESS;
+ ctx->mods = 0;
+ ctx->meta = 0;
+ ctx->key[0] = '\0';
+ return 1;
+ }
+ ctx->blocked = 0;
+ ctx->queued = 0;
+ } 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], sizeof(ctx->stored) - ctx->stored_head);
+ 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, specifically only Meta/ESC's */
+ if (ctx->meta < 3) {
+ /* Up to two Meta/ESC, wait until a third or something else is read,
+ * or if ESC on block, report as ESC or Meta+ESC */
+ if (ctx->flags & LIBTERMINPUT_ESC_ON_BLOCK) {
+ input->keypress.key = LIBTERMINPUT_ESC;
+ input->keypress.times = 1;
+ input->keypress.mods = ctx->mods;
+ input->keypress.symbol[0] = '\0';
+ if (ctx->meta > 1)
+ input->keypress.mods |= LIBTERMINPUT_META;
+ ctx->queued = 1;
+ input->type = LIBTERMINPUT_NONE;
+ return 1;
+ }
+ goto none;
+ }
+ /* 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 */
+ goto none;
+ }
+ 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] != '$') {
+ goto none;
+ } else if (ctx->key[0] == '[' && ctx->key[1] == '<' && p == &ctx->key[2]) {
+ goto none;
+ } 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_MACRO_ON_BLOCK) &&
+ ctx->stored_head - ctx->stored_tail == 0) {
+ input->keypress.key = LIBTERMINPUT_MACRO;
+ input->keypress.times = 1;
+ input->keypress.mods = ctx->mods;
+ input->keypress.symbol[0] = '\0';
+ if (ctx->meta > 1)
+ input->keypress.mods |= LIBTERMINPUT_META;
+ ctx->queued = 1;
+ input->type = LIBTERMINPUT_NONE;
+ return 1;
+ } else if (ctx->key[0] == '[' && ctx->key[1] == 'M' && (ctx->flags & LIBTERMINPUT_DECSET_1005)) {
+ ctx->mouse_tracking = 1;
+ n = ctx->stored_tail;
+ for (i = 0; i < 3; i++) {
+ r = libterminput_check_utf8_char__(&ctx->stored[n], ctx->stored_head - n, &m);
+ if (r > 0) {
+ n += m;
+ continue;
+ }
+ if (!r)
+ goto none;
+ 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 < 3U) {
+ ctx->mouse_tracking = 3;
+ goto none;
+ } else if (ctx->key[0] == '[' && ctx->key[1] == 't' && ctx->stored_head - ctx->stored_tail < 2U) {
+ ctx->mouse_tracking = 2;
+ goto none;
+ } else if (ctx->key[0] == '[' && ctx->key[1] == 'T' && ctx->stored_head - ctx->stored_tail < 6U) {
+ ctx->mouse_tracking = 6;
+ goto none;
+ }
+ /* Parse the complete sequence */
+ libterminput_parse_sequence__(input, ctx);
+ /* Reset */
+ ctx->meta = 0;
+ ctx->key[0] = '\0';
+
+ } else if (ctx->meta && (!strcmp(ret.symbol, "[") || !strcmp(ret.symbol, "O") || !strcmp(ret.symbol, "?"))) {
+ /* ESC [, ESC O, or ESC ? is used as the beginning of most special keys */
+ stpcpy(ctx->key, ret.symbol);
+ goto 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;
+ }
+ input->keypress.symbol[0] = '\0';
+ switch (ret.symbol[1] ? 0 : ret.symbol[0]) {
+ case 127:
+ case '\b': input->keypress.key = LIBTERMINPUT_ERASE; break;
+ case '\t': input->keypress.key = LIBTERMINPUT_TAB; break;
+ case '\n': input->keypress.key = LIBTERMINPUT_ENTER; break;
+ case 033: input->keypress.key = LIBTERMINPUT_ESC; break;
+ default:
+ input->keypress.key = LIBTERMINPUT_SYMBOL;
+ stpcpy(input->keypress.symbol, ret.symbol);
+ break;
+ }
+ }
+
+ return 1;
+
+none:
+ NOTHING(input);
+ return 1;
+}
diff --git a/libterminput_read_bracketed_paste__.c b/libterminput_read_bracketed_paste__.c
new file mode 100644
index 0000000..b580b70
--- /dev/null
+++ b/libterminput_read_bracketed_paste__.c
@@ -0,0 +1,109 @@
+/* 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,
+ * and pause as the available buffered input is incomplete */
+ if (input->text.nbytes < 6U) {
+ memcpy(ctx->stored, input->text.bytes, input->text.nbytes);
+ ctx->stored_tail = 0;
+ ctx->stored_head = input->text.nbytes;
+ ctx->paused = 1;
+ NOTHING(input);
+ 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_read_symbol__.c b/libterminput_read_symbol__.c
new file mode 100644
index 0000000..34f4b21
--- /dev/null
+++ b/libterminput_read_symbol__.c
@@ -0,0 +1,110 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+int
+libterminput_read_symbol__(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 = 1U;
+ ctx->stored_head = (size_t)r;
+ }
+ }
+
+ /* Check if symbol is complete or can be completed, and split out modifier */
+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 */
+ 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;
+ }
+
+ /* Incomplete symbol */
+ input->symbol[0] = '\0';
+ input->mods = -1;
+ return 1;
+}
diff --git a/libterminput_set_flags.3 b/libterminput_set_flags.3
index af1908a..fd1df68 100644
--- a/libterminput_set_flags.3
+++ b/libterminput_set_flags.3
@@ -88,6 +88,15 @@ The sequence
.BI "CSI " Ps " ; " Rs " R"
shall be parsed as a cursor position report rather
than as an F3 key press.
+.TP
+.B LIBTERMINPUT_MACRO_ON_BLOCK
+If CSI M is received without anything after it,
+return Macro keypress. Since the user probably
+does not have the Macro key, it seems having this
+as the default behaviour introduces an unncessary
+risk of misparsing input. However, if mouse tracking
+is not activated, it makes since to enable this
+flag.
.PP
.I ctx
must have been zero-initialised, e.g. with
@@ -108,7 +117,7 @@ functions return
.B -1
and set
.I errno
-it indicate the error.
+to indicate the error.
.SH ERRORS
Current versions of the
@@ -129,6 +138,10 @@ None.
.SH FUTURE DIRECTIONS
None.
+.SH HISTORY
+.B LIBTERMINPUT_MACRO_ON_BLOCK
+was added in version 1.1 of libterminput.
+
.SH NOTES
None.
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_unmarshal_input.3 b/libterminput_unmarshal_input.3
new file mode 120000
index 0000000..2490adb
--- /dev/null
+++ b/libterminput_unmarshal_input.3
@@ -0,0 +1 @@
+libterminput_unmarshal_state.3 \ No newline at end of file
diff --git a/libterminput_unmarshal_input.c b/libterminput_unmarshal_input.c
new file mode 100644
index 0000000..c8c20a2
--- /dev/null
+++ b/libterminput_unmarshal_input.c
@@ -0,0 +1,44 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+int
+libterminput_unmarshal_input(struct libterminput_unmarshaller *how, union libterminput_input *what)
+{
+ enum libterminput_type type;
+ int r;
+ if (how->load(how, &what->type, sizeof(what->type)))
+ return -1;
+ type = what->type;
+ if (what->type == LIBTERMINPUT_NONE) {
+ what->keypress.key = LIBTERMINPUT_SYMBOL;
+ if (how->load(how, &what->type, sizeof(what->type)))
+ return -1;
+ }
+ switch ((int)what->type) {
+ case LIBTERMINPUT_KEYPRESS:
+ r = libterminput_unmarshal_keypress__(how, &what->keypress);
+ break;
+ case LIBTERMINPUT_TEXT:
+ r = libterminput_unmarshal_text__(how, &what->text);
+ break;
+ case LIBTERMINPUT_MOUSEEVENT:
+ r = libterminput_unmarshal_mouseevent__(how, &what->mouseevent);
+ break;
+ case LIBTERMINPUT_CURSOR_POSITION:
+ r = libterminput_unmarshal_position__(how, &what->position);
+ break;
+ case LIBTERMINPUT_NONE:
+ case LIBTERMINPUT_BRACKETED_PASTE_START:
+ case LIBTERMINPUT_BRACKETED_PASTE_END:
+ case LIBTERMINPUT_TERMINAL_IS_OK:
+ case LIBTERMINPUT_TERMINAL_IS_NOT_OK:
+ r = 0;
+ break;
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+ what->type = type;
+ return r;
+}
diff --git a/libterminput_unmarshal_keypress__.c b/libterminput_unmarshal_keypress__.c
new file mode 100644
index 0000000..bd67e1e
--- /dev/null
+++ b/libterminput_unmarshal_keypress__.c
@@ -0,0 +1,23 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+int
+libterminput_unmarshal_keypress__(struct libterminput_unmarshaller *how, struct libterminput_keypress *what)
+{
+ what->type = LIBTERMINPUT_KEYPRESS;
+ if (how->load(how, &what->key, sizeof(what->key)) ||
+ how->load(how, &what->times, sizeof(what->times)) ||
+ how->load(how, &what->mods, sizeof(what->mods)))
+ return -1;
+ if ((uintmax_t)what->key > (uintmax_t)LIBTERMINPUT_LAST_KEY__) {
+ errno = EINVAL;
+ return -1;
+ }
+ if (what->key == LIBTERMINPUT_SYMBOL) {
+ return how->load(how, what->symbol, sizeof(what->symbol));
+ } else {
+ memset(what->symbol, 0, sizeof(what->symbol));
+ return 0;
+ }
+}
diff --git a/libterminput_unmarshal_mouseevent__.c b/libterminput_unmarshal_mouseevent__.c
new file mode 100644
index 0000000..ab35b95
--- /dev/null
+++ b/libterminput_unmarshal_mouseevent__.c
@@ -0,0 +1,30 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+int
+libterminput_unmarshal_mouseevent__(struct libterminput_unmarshaller *how, struct libterminput_mouseevent *what)
+{
+ what->type = LIBTERMINPUT_MOUSEEVENT;
+ if (how->load(how, &what->event, sizeof(what->event)) ||
+ how->load(how, &what->x, sizeof(size_t) * 2U))
+ return -1;
+ what->mods = 0;
+ what->button = LIBTERMINPUT_BUTTON1;
+ switch ((int)what->event) {
+ case LIBTERMINPUT_PRESS:
+ case LIBTERMINPUT_RELEASE:
+ case LIBTERMINPUT_MOTION:
+ if (how->load(how, &what->mods, sizeof(what->mods)) ||
+ how->load(how, &what->button, sizeof(what->button)))
+ return -1;
+ /* fall through */
+ case LIBTERMINPUT_HIGHLIGHT_INSIDE:
+ return 0;
+ case LIBTERMINPUT_HIGHLIGHT_OUTSIDE:
+ return how->load(how, &what->start_x, sizeof(size_t) * 4U);
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+}
diff --git a/libterminput_unmarshal_position__.c b/libterminput_unmarshal_position__.c
new file mode 100644
index 0000000..9040c50
--- /dev/null
+++ b/libterminput_unmarshal_position__.c
@@ -0,0 +1,10 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+int
+libterminput_unmarshal_position__(struct libterminput_unmarshaller *how, struct libterminput_position *what)
+{
+ what->type = LIBTERMINPUT_CURSOR_POSITION;
+ return how->load(how, &what->x, sizeof(size_t) * 2U);
+}
diff --git a/libterminput_unmarshal_state.3 b/libterminput_unmarshal_state.3
new file mode 100644
index 0000000..dd498fa
--- /dev/null
+++ b/libterminput_unmarshal_state.3
@@ -0,0 +1,122 @@
+.TH LIBTERMINPUT_UNMARSHAL_STATE 3 LIBTERMINPUT
+.SH NAME
+libterminput_unmarshal_state \- Unmarshal the input parsing state
+.br
+libterminput_unmarshal_input \- Unmarshal the parsed input
+
+.SH SYNOPSIS
+.nf
+#include <libterminput.h>
+
+struct libterminput_unmarshaller {
+ int (*load)(struct libterminput_unmarshaller *\fIthis\fP, void *\fIdata\fP, size_t \fIsize\fP);
+ union {
+ void *ptr;
+ int i;
+ size_t zu;
+ } user;
+};
+
+int libterminput_unmarshal_state(struct libterminput_unmarshaller *\fIhow\fP, struct libterminput_state *\fIwhat\fP);
+int libterminput_unmarshal_input(struct libterminput_unmarshaller *\fIhow\fP, union libterminput_input *\fIwhat\fP);
+.fi
+.PP
+Link with
+.IR \-lterminput .
+
+.SH DESCRIPTION
+The
+.BR libterminput_unmarshal_state ()
+and
+.BR libterminput_unmarshal_input ()
+function unmarshals state into
+.I *what
+using
+.IR *how->load .
+.PP
+.I *how->load
+must return 0 on success and -1 on failure, and may set
+.I errno
+to indicate the error.
+.I how
+is fed back into
+.I *how->load
+(as
+.IR this )
+so that the
+.I *how->load
+function can use and modify
+.I how->user
+which contains application-defined data
+(typically a buffer, a file descriptor, or
+the size of the serialisation).
+.I data
+will be set to the output buffer for the data
+read by
+.I *how-load
+and
+.I size
+will be set to the number of bytes to read
+into from
+.IR data .
+
+.SH RETURN VALUE
+The
+.BR libterminput_unmarshal_state ()
+and
+.BR libterminput_unmarshal_input ()
+functions return 0 upon successful completion.
+On failure,
+.B -1
+is returnes, but
+.I errno
+remains unmodified, however any changes to
+.I errno
+by
+.I *how->load
+will remain.
+
+.SH ERRORS
+The
+.BR libterminput_unmarshal_state ()
+and
+.BR libterminput_unmarshal_input ()
+functions fail, without further modifying
+.IR errno ,
+if
+.I *how->load
+fails. The functions will also fail if:
+.TP
+.B EINVAL
+The read serialisation was invalid for the used
+version of the library (not a proper serialisation
+or containing data from a newer version of the
+library).
+
+.SH EXAMPLES
+None.
+
+.SH APPLICATION USAGE
+None.
+
+.SH RATIONALE
+None.
+
+.SH FUTURE DIRECTIONS
+None.
+
+.SH HISTORY
+The
+.BR libterminput_unmarshal_state ()
+and
+.BR libterminput_unmarshal_input ()
+functions were added in version 1.1 of libterminput.
+
+.SH NOTES
+None.
+
+.SH BUGS
+None.
+
+.SH SEE ALSO
+.BR libterminput_marshal_state (3)
diff --git a/libterminput_unmarshal_state.c b/libterminput_unmarshal_state.c
new file mode 100644
index 0000000..07dc625
--- /dev/null
+++ b/libterminput_unmarshal_state.c
@@ -0,0 +1,34 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+int
+libterminput_unmarshal_state(struct libterminput_unmarshaller *how, struct libterminput_state *what)
+{
+ if (how->load(how, what, sizeof(*what)))
+ return -1;
+ if (what->inited < 0 || what->inited > 1 ||
+ what->bracketed_paste > 1U ||
+ what->meta > 2U ||
+ what->n >= sizeof(what->partial) ||
+ what->npartial >= sizeof(what->partial) ||
+ what->stored_tail > what->stored_head ||
+ what->stored_head > sizeof(what->stored) ||
+ what->unused_bits)
+ goto einval;
+ switch (what->mouse_tracking) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 6:
+ break;
+ default:
+ goto einval;
+ }
+ return 0;
+
+einval:
+ errno = EINVAL;
+ return -1;
+}
diff --git a/libterminput_unmarshal_text__.c b/libterminput_unmarshal_text__.c
new file mode 100644
index 0000000..be9bb10
--- /dev/null
+++ b/libterminput_unmarshal_text__.c
@@ -0,0 +1,16 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+int
+libterminput_unmarshal_text__(struct libterminput_unmarshaller *how, struct libterminput_text *what)
+{
+ what->type = LIBTERMINPUT_TEXT;
+ if (how->load(how, &what->nbytes, sizeof(what->nbytes)))
+ return -1;
+ if (what->nbytes > sizeof(what->bytes)) {
+ errno = EINVAL;
+ return -1;
+ }
+ return how->load(how, what->bytes, what->nbytes);
+}
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;
+}
diff --git a/test.c b/test.c
index 4fd7b0f..eb007c3 100644
--- a/test.c
+++ b/test.c
@@ -1,5 +1,7 @@
/* See LICENSE file for copyright and license details. */
#include <errno.h>
+#include <fcntl.h>
+#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -28,28 +30,56 @@ static const struct keypress {
{"\033O", "F", LIBTERMINPUT_END, 0, 0},
{"\033O", "G", LIBTERMINPUT_BEGIN, 0, 0}, /* not attested */
{"\033O", "H", LIBTERMINPUT_HOME, 0, 0},
+ {"\033O", "I", LIBTERMINPUT_F12, 0, 0},
+ {"\033O", "J", LIBTERMINPUT_F1, LIBTERMINPUT_SHIFT, 0},
+ {"\033O", "K", LIBTERMINPUT_F2, LIBTERMINPUT_SHIFT, 0},
+ {"\033O", "L", LIBTERMINPUT_F3, LIBTERMINPUT_SHIFT, 0},
{"\033O", "M", LIBTERMINPUT_KEYPAD_ENTER, 0, 0},
+ {"\033?", "M", LIBTERMINPUT_KEYPAD_ENTER, 0, 0},
+ {"\033O", "N", LIBTERMINPUT_F4, LIBTERMINPUT_SHIFT, 0},
{"\033O", "P", LIBTERMINPUT_F1, 0, 0},
{"\033O", "Q", LIBTERMINPUT_F2, 0, 0},
{"\033O", "R", LIBTERMINPUT_F3, 0, 0},
{"\033O", "S", LIBTERMINPUT_F4, 0, 0},
+ {"\033O", "T", LIBTERMINPUT_F5, 0, 0},
+ {"\033O", "U", LIBTERMINPUT_F6, 0, 0},
+ {"\033O", "V", LIBTERMINPUT_F7, 0, 0}, /* not attested */
+ {"\033O", "W", LIBTERMINPUT_F8, 0, 0}, /* not attested */
+ {"\033O", "X", LIBTERMINPUT_F9, 0, 0}, /* not attested */
+ {"\033O", "Y", LIBTERMINPUT_F10, 0, 0},
+ {"\033O", "Z", LIBTERMINPUT_F11, 0, 0}, /* not attested */
+ {"\033O", "b", LIBTERMINPUT_KEYPAD_POINT, 0, 0},
+ {"\033O", "e", LIBTERMINPUT_F7, 0, 0},
+ {"\033O", "f", LIBTERMINPUT_F8, 0, 0},
+ {"\033O", "j", LIBTERMINPUT_KEYPAD_TIMES, 0, 0},
+ {"\033O", "k", LIBTERMINPUT_KEYPAD_PLUS, 0, 0},
+ {"\033O", "l", LIBTERMINPUT_KEYPAD_COMMA, 0, 0},
+ {"\033?", "l", LIBTERMINPUT_KEYPAD_COMMA, 0, 0},
+ {"\033O", "m", LIBTERMINPUT_KEYPAD_MINUS, 0, 0},
+ {"\033?", "m", LIBTERMINPUT_KEYPAD_MINUS, 0, 0},
+ {"\033O", "n", LIBTERMINPUT_KEYPAD_DECIMAL, 0, 0},
+ {"\033?", "n", LIBTERMINPUT_KEYPAD_DECIMAL, 0, 0},
+ {"\033O", "o", LIBTERMINPUT_KEYPAD_DIVISION, 0, 0},
{"\033O", "p", LIBTERMINPUT_KEYPAD_0, 0, 0},
+ {"\033?", "p", LIBTERMINPUT_KEYPAD_0, 0, 0},
{"\033O", "q", LIBTERMINPUT_KEYPAD_1, 0, 0},
+ {"\033?", "q", LIBTERMINPUT_KEYPAD_1, 0, 0},
{"\033O", "r", LIBTERMINPUT_KEYPAD_2, 0, 0},
+ {"\033?", "r", LIBTERMINPUT_KEYPAD_2, 0, 0},
{"\033O", "s", LIBTERMINPUT_KEYPAD_3, 0, 0},
+ {"\033?", "s", LIBTERMINPUT_KEYPAD_3, 0, 0},
{"\033O", "t", LIBTERMINPUT_KEYPAD_4, 0, 0},
+ {"\033?", "t", LIBTERMINPUT_KEYPAD_4, 0, 0},
{"\033O", "u", LIBTERMINPUT_KEYPAD_5, 0, 0},
+ {"\033?", "u", LIBTERMINPUT_KEYPAD_5, 0, 0},
{"\033O", "v", LIBTERMINPUT_KEYPAD_6, 0, 0},
+ {"\033?", "v", LIBTERMINPUT_KEYPAD_6, 0, 0},
{"\033O", "w", LIBTERMINPUT_KEYPAD_7, 0, 0},
+ {"\033?", "w", LIBTERMINPUT_KEYPAD_7, 0, 0},
{"\033O", "x", LIBTERMINPUT_KEYPAD_8, 0, 0},
+ {"\033?", "x", LIBTERMINPUT_KEYPAD_8, 0, 0},
{"\033O", "y", LIBTERMINPUT_KEYPAD_9, 0, 0},
- {"\033O", "k", LIBTERMINPUT_KEYPAD_PLUS, 0, 0},
- {"\033O", "m", LIBTERMINPUT_KEYPAD_MINUS, 0, 0},
- {"\033O", "j", LIBTERMINPUT_KEYPAD_TIMES, 0, 0},
- {"\033O", "o", LIBTERMINPUT_KEYPAD_DIVISION, 0, 0},
- {"\033O", "n", LIBTERMINPUT_KEYPAD_DECIMAL, 0, 0},
- {"\033O", "l", LIBTERMINPUT_KEYPAD_COMMA, 0, 0},
- {"\033O", "b", LIBTERMINPUT_KEYPAD_POINT, 0, 0},
+ {"\033?", "y", LIBTERMINPUT_KEYPAD_9, 0, 0},
{"\033[", "A", LIBTERMINPUT_UP, 0, 0},
{"\033[", "B", LIBTERMINPUT_DOWN, 0, 0},
{"\033[", "C", LIBTERMINPUT_RIGHT, 0, 0},
@@ -331,16 +361,76 @@ static struct libterminput_state ctx;
static union libterminput_input input;
static int fds[2];
+static char *marshalled = NULL;
+static size_t nmarshalled = 0;
+static size_t nunmarshalled = 0;
+static size_t marshalled_size = 0;
+
+
+static int
+store(struct libterminput_marshaller *this, const void *data, size_t size)
+{
+ (void) this;
+ if (size > marshalled_size - nmarshalled) {
+ TEST(size < SIZE_MAX - nmarshalled);
+ marshalled_size = nmarshalled + size;
+ TEST((marshalled = realloc(marshalled, marshalled_size)));
+ }
+ memcpy(&marshalled[nmarshalled], data, size);
+ nmarshalled += size;
+ return 0;
+}
+
+
+static int
+load(struct libterminput_unmarshaller *this, void *data, size_t size)
+{
+ (void) this;
+ TEST(nunmarshalled <= nmarshalled);
+ TEST(size <= nmarshalled - nunmarshalled);
+ memcpy(data, &marshalled[nunmarshalled], size);
+ nunmarshalled += size;
+ return 0;
+}
+
+
+static struct libterminput_marshaller marshaller = {.store = &store};
+static struct libterminput_unmarshaller unmarshaller = {.load = &load};
+
+
+static void
+check_ctx_marshal(void)
+{
+ struct libterminput_state old_ctx;
+ memcpy(&old_ctx, &ctx, sizeof(ctx));
+ TEST(!libterminput_marshal_state(&marshaller, &ctx));
+ memset(&ctx, 255, sizeof(ctx));
+ TEST(!libterminput_unmarshal_state(&unmarshaller, &ctx));
+ TEST(!memcmp(&old_ctx, &ctx, sizeof(ctx)));
+}
+
static void
type_mem(const char *str, size_t len, enum libterminput_type type)
{
+ int flags, r;
alarm(5);
if (len)
TEST(write(fds[1], str, len) == (ssize_t)len);
- do {
- TEST(libterminput_read(fds[0], &input, &ctx) == 1);
- } while (input.type == LIBTERMINPUT_NONE && libterminput_is_ready(&input, &ctx));
+ TEST((flags = fcntl(fds[0], F_GETFL)) >= 0);
+ if (flags & O_NONBLOCK) {
+ do {
+ check_ctx_marshal();
+ r = libterminput_read(fds[0], &input, &ctx);
+ TEST(r == 1 || (r == -1 && errno == EAGAIN));
+ } while (input.type == LIBTERMINPUT_NONE && r > 0);
+ } else {
+ do {
+ check_ctx_marshal();
+ TEST(libterminput_read(fds[0], &input, &ctx) == 1);
+ } while (input.type == LIBTERMINPUT_NONE && libterminput_is_ready(&input, &ctx));
+ }
+ check_ctx_marshal();
TEST(input.type == type);
}
@@ -348,16 +438,28 @@ static void
keypress_(const char *str1, const char *str2, const char *str3, const char *str4,
enum libterminput_key key, enum libterminput_mod mods, unsigned long long int times)
{
+ int flags, r;
unsigned long long int times_;
size_t i;
alarm(5);
stpcpy(stpcpy(stpcpy(stpcpy(buffer, str1), str2), str3), str4);
if (*buffer)
TEST(write(fds[1], buffer, strlen(buffer)) == (ssize_t)strlen(buffer));
+ TEST((flags = fcntl(fds[0], F_GETFL)) >= 0);
for (times_ = times; times_; times_--) {
- do {
- TEST(libterminput_read(fds[0], &input, &ctx) == 1);
- } while (input.type == LIBTERMINPUT_NONE && libterminput_is_ready(&input, &ctx));
+ if (flags & O_NONBLOCK) {
+ do {
+ check_ctx_marshal();
+ r = libterminput_read(fds[0], &input, &ctx);
+ TEST(r == 1 || (r == -1 && errno == EAGAIN));
+ } while (input.type == LIBTERMINPUT_NONE && r > 0);
+ } else {
+ do {
+ check_ctx_marshal();
+ TEST(libterminput_read(fds[0], &input, &ctx) == 1);
+ } while (input.type == LIBTERMINPUT_NONE && libterminput_is_ready(&input, &ctx));
+ }
+ check_ctx_marshal();
TEST(input.type == LIBTERMINPUT_KEYPRESS);
TEST(input.keypress.key == key);
TEST(input.keypress.mods == mods);
@@ -368,9 +470,11 @@ keypress_(const char *str1, const char *str2, const char *str3, const char *str4
TEST(write(fds[1], &buffer[i], 1) == 1);
TEST(libterminput_read(fds[0], &input, &ctx) == 1);
TEST(input.type == LIBTERMINPUT_NONE);
+ check_ctx_marshal();
}
TEST(write(fds[1], &buffer[i], 1) == 1);
TEST(libterminput_read(fds[0], &input, &ctx) == 1);
+ check_ctx_marshal();
TEST(input.keypress.key == key);
TEST(input.keypress.mods == mods);
TEST(input.keypress.times == times);
@@ -497,10 +601,327 @@ int
main(void)
{
size_t i;
+ int flags;
+
+ nunmarshalled = nmarshalled = 0;
+ input.type = LIBTERMINPUT_BRACKETED_PASTE_START;
+ TEST(!libterminput_marshal_input(&marshaller, &input));
+ input.type = LIBTERMINPUT_NONE;
+ TEST(!libterminput_unmarshal_input(&unmarshaller, &input));
+ TEST(nunmarshalled == nmarshalled);
+ TEST(input.type == LIBTERMINPUT_BRACKETED_PASTE_START);
+
+ nunmarshalled = nmarshalled = 0;
+ input.type = LIBTERMINPUT_BRACKETED_PASTE_END;
+ TEST(!libterminput_marshal_input(&marshaller, &input));
+ input.type = LIBTERMINPUT_NONE;
+ TEST(!libterminput_unmarshal_input(&unmarshaller, &input));
+ TEST(nunmarshalled == nmarshalled);
+ TEST(input.type == LIBTERMINPUT_BRACKETED_PASTE_END);
+
+ nunmarshalled = nmarshalled = 0;
+ input.type = LIBTERMINPUT_TERMINAL_IS_OK;
+ TEST(!libterminput_marshal_input(&marshaller, &input));
+ input.type = LIBTERMINPUT_NONE;
+ TEST(!libterminput_unmarshal_input(&unmarshaller, &input));
+ TEST(nunmarshalled == nmarshalled);
+ TEST(input.type == LIBTERMINPUT_TERMINAL_IS_OK);
+
+ nunmarshalled = nmarshalled = 0;
+ input.type = LIBTERMINPUT_TERMINAL_IS_NOT_OK;
+ TEST(!libterminput_marshal_input(&marshaller, &input));
+ input.type = LIBTERMINPUT_NONE;
+ TEST(!libterminput_unmarshal_input(&unmarshaller, &input));
+ TEST(nunmarshalled == nmarshalled);
+ TEST(input.type == LIBTERMINPUT_TERMINAL_IS_NOT_OK);
+
+ nunmarshalled = nmarshalled = 0;
+ input.type = LIBTERMINPUT_CURSOR_POSITION;
+ input.position.x = 5U;
+ input.position.y = 10U;
+ TEST(!libterminput_marshal_input(&marshaller, &input));
+ memset(&input, 0, sizeof(input));
+ TEST(!libterminput_unmarshal_input(&unmarshaller, &input));
+ TEST(nunmarshalled == nmarshalled);
+ TEST(input.type == LIBTERMINPUT_CURSOR_POSITION);
+ TEST(input.position.x == 5U);
+ TEST(input.position.y == 10U);
+
+ nunmarshalled = nmarshalled = 0;
+ input.type = LIBTERMINPUT_TEXT;
+ input.text.nbytes = 10U;
+ stpcpy(input.text.bytes, "1234567890");
+ TEST(!libterminput_marshal_input(&marshaller, &input));
+ memset(&input, 0, sizeof(input));
+ TEST(!libterminput_unmarshal_input(&unmarshaller, &input));
+ TEST(nunmarshalled == nmarshalled);
+ TEST(input.type == LIBTERMINPUT_TEXT);
+ TEST(input.text.nbytes == 10U);
+ TEST(!memcmp(input.text.bytes, "1234567890", 10U));
+
+ nunmarshalled = nmarshalled = 0;
+ input.type = LIBTERMINPUT_TEXT;
+ input.text.nbytes = sizeof(input.text.bytes);
+ memset(input.text.bytes, 1, input.text.nbytes);
+ TEST(!libterminput_marshal_input(&marshaller, &input));
+ memset(&input, 0, sizeof(input));
+ TEST(!libterminput_unmarshal_input(&unmarshaller, &input));
+ TEST(nunmarshalled == nmarshalled);
+ TEST(input.type == LIBTERMINPUT_TEXT);
+ TEST(input.text.nbytes == sizeof(input.text.bytes));
+ TEST(input.text.bytes[0] == 1);
+ TEST(input.text.bytes[input.text.nbytes - 1U] == 1);
+
+ errno = 0;
+ nunmarshalled = nmarshalled = 0;
+ input.type = LIBTERMINPUT_TEXT;
+ input.text.nbytes = sizeof(input.text.bytes);
+ memset(input.text.bytes, 2, sizeof(input.text.bytes));
+ TEST(!marshaller.store(&marshaller, &input.type, sizeof(input.type)));
+ TEST(!marshaller.store(&marshaller, &input.text.nbytes, sizeof(input.text.nbytes)));
+ TEST(!marshaller.store(&marshaller, input.text.bytes, sizeof(input.text.bytes)));
+ memset(&input, 0, sizeof(input));
+ TEST(!libterminput_unmarshal_input(&unmarshaller, &input));
+ TEST(nunmarshalled == nmarshalled);
+ TEST(input.type == LIBTERMINPUT_TEXT);
+ TEST(input.text.nbytes == sizeof(input.text.bytes));
+ TEST(input.text.bytes[0] == 2);
+ TEST(input.text.bytes[input.text.nbytes - 1U] == 2);
+
+ errno = 0;
+ nunmarshalled = nmarshalled = 0;
+ input.type = LIBTERMINPUT_TEXT;
+ input.text.nbytes = sizeof(input.text.bytes) + 1U;
+ TEST(!marshaller.store(&marshaller, &input.type, sizeof(input.type)));
+ TEST(!marshaller.store(&marshaller, &input.text.nbytes, sizeof(input.text.nbytes)));
+ TEST(!marshaller.store(&marshaller, input.text.bytes, sizeof(input.text.bytes)));
+ TEST(!marshaller.store(&marshaller, input.text.bytes, 1U));
+ TEST(libterminput_unmarshal_input(&unmarshaller, &input)== -1 && errno == EINVAL);
+
+ nunmarshalled = nmarshalled = 0;
+ input.type = LIBTERMINPUT_MOUSEEVENT;
+ input.mouseevent.mods = 9;
+ input.mouseevent.button = 7;
+ input.mouseevent.event = LIBTERMINPUT_PRESS;
+ input.mouseevent.x = 13U;
+ input.mouseevent.y = 21U;
+ TEST(!libterminput_marshal_input(&marshaller, &input));
+ memset(&input, 0, sizeof(input));
+ TEST(!libterminput_unmarshal_input(&unmarshaller, &input));
+ TEST(nunmarshalled == nmarshalled);
+ TEST(input.type == LIBTERMINPUT_MOUSEEVENT);
+ TEST(input.mouseevent.mods == 9);
+ TEST(input.mouseevent.button == 7);
+ TEST(input.mouseevent.event == LIBTERMINPUT_PRESS);
+ TEST(input.mouseevent.x == 13U);
+ TEST(input.mouseevent.y == 21U);
+
+ nunmarshalled = nmarshalled = 0;
+ input.type = LIBTERMINPUT_MOUSEEVENT;
+ input.mouseevent.mods = 77;
+ input.mouseevent.button = 88;
+ input.mouseevent.event = LIBTERMINPUT_RELEASE;
+ input.mouseevent.x = 41U;
+ input.mouseevent.y = 22U;
+ TEST(!libterminput_marshal_input(&marshaller, &input));
+ memset(&input, 0, sizeof(input));
+ TEST(!libterminput_unmarshal_input(&unmarshaller, &input));
+ TEST(nunmarshalled == nmarshalled);
+ TEST(input.type == LIBTERMINPUT_MOUSEEVENT);
+ TEST(input.mouseevent.mods == 77);
+ TEST(input.mouseevent.button == 88);
+ TEST(input.mouseevent.event == LIBTERMINPUT_RELEASE);
+ TEST(input.mouseevent.x == 41U);
+ TEST(input.mouseevent.y == 22U);
+
+ nunmarshalled = nmarshalled = 0;
+ input.type = LIBTERMINPUT_MOUSEEVENT;
+ input.mouseevent.mods = 12;
+ input.mouseevent.button = 14;
+ input.mouseevent.event = LIBTERMINPUT_MOTION;
+ input.mouseevent.x = 42U;
+ input.mouseevent.y = 23U;
+ TEST(!libterminput_marshal_input(&marshaller, &input));
+ memset(&input, 0, sizeof(input));
+ TEST(!libterminput_unmarshal_input(&unmarshaller, &input));
+ TEST(nunmarshalled == nmarshalled);
+ TEST(input.type == LIBTERMINPUT_MOUSEEVENT);
+ TEST(input.mouseevent.mods == 12);
+ TEST(input.mouseevent.button == 14);
+ TEST(input.mouseevent.event == LIBTERMINPUT_MOTION);
+ TEST(input.mouseevent.x == 42U);
+ TEST(input.mouseevent.y == 23U);
+
+ nunmarshalled = nmarshalled = 0;
+ input.type = LIBTERMINPUT_MOUSEEVENT;
+ input.mouseevent.mods = 99; /* invalid, will be set to 0 */
+ input.mouseevent.button = 99; /* invalid, will be set to 1 */
+ input.mouseevent.event = LIBTERMINPUT_HIGHLIGHT_INSIDE;
+ input.mouseevent.x = 52U;
+ input.mouseevent.y = 53U;
+ TEST(!libterminput_marshal_input(&marshaller, &input));
+ memset(&input, 0, sizeof(input));
+ TEST(!libterminput_unmarshal_input(&unmarshaller, &input));
+ TEST(nunmarshalled == nmarshalled);
+ TEST(input.type == LIBTERMINPUT_MOUSEEVENT);
+ TEST(input.mouseevent.mods == 0);
+ TEST(input.mouseevent.button == 1);
+ TEST(input.mouseevent.event == LIBTERMINPUT_HIGHLIGHT_INSIDE);
+ TEST(input.mouseevent.x == 52U);
+ TEST(input.mouseevent.y == 53U);
+
+ nunmarshalled = nmarshalled = 0;
+ input.type = LIBTERMINPUT_MOUSEEVENT;
+ input.mouseevent.mods = 99; /* invalid, will be set to 0 */
+ input.mouseevent.button = 99; /* invalid, will be set to 1 */
+ input.mouseevent.event = LIBTERMINPUT_HIGHLIGHT_OUTSIDE;
+ input.mouseevent.x = 62U;
+ input.mouseevent.y = 63U;
+ input.mouseevent.start_x = 12U;
+ input.mouseevent.start_y = 13U;
+ input.mouseevent.end_x = 22U;
+ input.mouseevent.end_y = 23U;
+ TEST(!libterminput_marshal_input(&marshaller, &input));
+ memset(&input, 0, sizeof(input));
+ TEST(!libterminput_unmarshal_input(&unmarshaller, &input));
+ TEST(nunmarshalled == nmarshalled);
+ TEST(input.type == LIBTERMINPUT_MOUSEEVENT);
+ TEST(input.mouseevent.mods == 0);
+ TEST(input.mouseevent.button == 1);
+ TEST(input.mouseevent.event == LIBTERMINPUT_HIGHLIGHT_OUTSIDE);
+ TEST(input.mouseevent.x == 62U);
+ TEST(input.mouseevent.y == 63U);
+ TEST(input.mouseevent.start_x == 12U);
+ TEST(input.mouseevent.start_y == 13U);
+ TEST(input.mouseevent.end_x == 22U);
+ TEST(input.mouseevent.end_y == 23U);
+
+ errno = 0;
+ nunmarshalled = nmarshalled = 0;
+ input.type = LIBTERMINPUT_MOUSEEVENT;
+ input.mouseevent.mods = 1;
+ input.mouseevent.button = 1;
+ input.mouseevent.event = (enum libterminput_event)99999;
+ input.mouseevent.x = 1U;
+ input.mouseevent.y = 1U;
+ TEST(!libterminput_marshal_input(&marshaller, &input));
+ TEST(libterminput_unmarshal_input(&unmarshaller, &input) == -1 && errno == EINVAL);
+
+ errno = 0;
+ nunmarshalled = nmarshalled = 0;
+ input.type = LIBTERMINPUT_MOUSEEVENT;
+ input.mouseevent.mods = 1;
+ input.mouseevent.button = 1;
+ input.mouseevent.event = (enum libterminput_event)-1;
+ input.mouseevent.x = 1U;
+ input.mouseevent.y = 1U;
+ TEST(!libterminput_marshal_input(&marshaller, &input));
+ TEST(libterminput_unmarshal_input(&unmarshaller, &input) == -1 && errno == EINVAL);
+
+ nunmarshalled = nmarshalled = 0;
+ input.type = LIBTERMINPUT_KEYPRESS;
+ input.keypress.key = LIBTERMINPUT_UP;
+ input.keypress.times = 0;
+ input.keypress.mods = 0;
+ TEST(!libterminput_marshal_input(&marshaller, &input));
+ memset(&input, 0, sizeof(input));
+ TEST(!libterminput_unmarshal_input(&unmarshaller, &input));
+ TEST(nunmarshalled == nmarshalled);
+ TEST(input.type == LIBTERMINPUT_KEYPRESS);
+ TEST(input.keypress.key == LIBTERMINPUT_UP);
+ TEST(input.keypress.times == 0);
+ TEST(input.keypress.mods == 0);
+
+ nunmarshalled = nmarshalled = 0;
+ input.type = LIBTERMINPUT_KEYPRESS;
+ input.keypress.key = LIBTERMINPUT_LAST_KEY__;
+ input.keypress.times = 1;
+ input.keypress.mods = 7;
+ TEST(!libterminput_marshal_input(&marshaller, &input));
+ memset(&input, 0, sizeof(input));
+ TEST(!libterminput_unmarshal_input(&unmarshaller, &input));
+ TEST(nunmarshalled == nmarshalled);
+ TEST(input.type == LIBTERMINPUT_KEYPRESS);
+ TEST(input.keypress.key == LIBTERMINPUT_LAST_KEY__);
+ TEST(input.keypress.times == 1);
+ TEST(input.keypress.mods == 7);
+
+ errno = 0;
+ nunmarshalled = nmarshalled = 0;
+ input.type = LIBTERMINPUT_KEYPRESS;
+ input.keypress.key = (enum libterminput_key)(LIBTERMINPUT_LAST_KEY__ + 1);
+ input.keypress.times = 1;
+ input.keypress.mods = 7;
+ TEST(!libterminput_marshal_input(&marshaller, &input));
+ TEST(libterminput_unmarshal_input(&unmarshaller, &input) == -1 && errno == EINVAL);
+
+ errno = 0;
+ nunmarshalled = nmarshalled = 0;
+ input.type = LIBTERMINPUT_KEYPRESS;
+ input.keypress.key = (enum libterminput_key)-1;
+ input.keypress.times = 1;
+ input.keypress.mods = 7;
+ TEST(!libterminput_marshal_input(&marshaller, &input));
+ TEST(libterminput_unmarshal_input(&unmarshaller, &input) == -1 && errno == EINVAL);
+
+ nunmarshalled = nmarshalled = 0;
+ input.type = LIBTERMINPUT_KEYPRESS;
+ input.keypress.key = LIBTERMINPUT_SYMBOL;
+ input.keypress.times = 5;
+ input.keypress.mods = 10;
+ stpcpy(input.keypress.symbol, "\xFD\x82\x83\x84\x85\x86");
+ TEST(!libterminput_marshal_input(&marshaller, &input));
+ memset(&input, 0, sizeof(input));
+ TEST(!libterminput_unmarshal_input(&unmarshaller, &input));
+ TEST(nunmarshalled == nmarshalled);
+ TEST(input.type == LIBTERMINPUT_KEYPRESS);
+ TEST(input.keypress.key == LIBTERMINPUT_SYMBOL);
+ TEST(input.keypress.times == 5);
+ TEST(input.keypress.mods == 10);
+ TEST(!memcmp(input.keypress.symbol, "\xFD\x82\x83\x84\x85\x86", 7U));
+
+ errno = 0;
+ nunmarshalled = nmarshalled = 0;
+ input.type = (enum libterminput_type)-1;
+ TEST(!libterminput_marshal_input(&marshaller, &input));
+ TEST(libterminput_unmarshal_input(&unmarshaller, &input) == -1 && errno == EINVAL);
+
+ errno = 0;
+ nunmarshalled = nmarshalled = 0;
+ input.type = (enum libterminput_type)99;
+ TEST(!libterminput_marshal_input(&marshaller, &input));
+ TEST(libterminput_unmarshal_input(&unmarshaller, &input) == -1 && errno == EINVAL);
+
+ nunmarshalled = nmarshalled = 0;
+ input.type = LIBTERMINPUT_NONE;
+ input.keypress.key = LIBTERMINPUT_LEFT;
+ input.keypress.times = 5;
+ input.keypress.mods = 8;
+ TEST(!libterminput_marshal_input(&marshaller, &input));
+ memset(&input, 0, sizeof(input));
+ TEST(!libterminput_unmarshal_input(&unmarshaller, &input));
+ TEST(nunmarshalled == nmarshalled);
+ TEST(input.type == LIBTERMINPUT_NONE);
+ TEST(input.keypress.key == LIBTERMINPUT_LEFT);
+ TEST(input.keypress.times == 5);
+ TEST(input.keypress.mods == 8);
+
+ nunmarshalled = nmarshalled = 0;
+ input.type = LIBTERMINPUT_NONE;
+ input.keypress.key = LIBTERMINPUT_SYMBOL;
+ TEST(!libterminput_marshal_input(&marshaller, &input));
+ memset(&input, 0, sizeof(input));
+ TEST(!libterminput_unmarshal_input(&unmarshaller, &input));
+ TEST(nunmarshalled == nmarshalled);
+ TEST(input.type == LIBTERMINPUT_NONE);
+ TEST(input.keypress.key == LIBTERMINPUT_SYMBOL);
memset(&ctx, 0, sizeof(ctx));
TEST(!pipe(fds));
+ flags = fcntl(fds[0], F_GETFL);
+ TEST(flags >= 0);
+
for (i = 0; keypresses[i].part1; i++) {
libterminput_set_flags(&ctx, keypresses[i].flags);
KEYPRESS(keypresses[i].part1, keypresses[i].part2, keypresses[i].key, keypresses[i].mods);
@@ -597,9 +1018,17 @@ main(void)
KEYPRESS_SPECIAL_CHAR('\b', LIBTERMINPUT_ERASE);
KEYPRESS_SPECIAL_CHAR('\t', LIBTERMINPUT_TAB);
KEYPRESS_SPECIAL_CHAR('\n', LIBTERMINPUT_ENTER);
+ TEST(fcntl(fds[0], F_SETFL, flags | O_NONBLOCK) >= 0);
libterminput_set_flags(&ctx, LIBTERMINPUT_ESC_ON_BLOCK);
KEYPRESS_SPECIAL_CHAR('\033', LIBTERMINPUT_ESC);
libterminput_clear_flags(&ctx, LIBTERMINPUT_ESC_ON_BLOCK);
+ libterminput_set_flags(&ctx, LIBTERMINPUT_MACRO_ON_BLOCK);
+ TYPE("\033[M", LIBTERMINPUT_KEYPRESS);
+ TEST(input.keypress.key == LIBTERMINPUT_MACRO);
+ TEST(input.keypress.mods == 0);
+ TEST(input.keypress.times == 1);
+ libterminput_clear_flags(&ctx, LIBTERMINPUT_MACRO_ON_BLOCK);
+ TEST(fcntl(fds[0], F_SETFL, flags) >= 0);
TYPE("text", LIBTERMINPUT_KEYPRESS);
TEST(input.keypress.key == LIBTERMINPUT_SYMBOL);
@@ -989,5 +1418,7 @@ main(void)
TEST(libterminput_read(fds[0], &input, &ctx) == 0);
close(fds[0]);
TEST(libterminput_read(fds[0], &input, &ctx) == -1 && errno == EBADF);
+
+ free(marshalled);
return 0;
}