diff options
Diffstat (limited to '')
47 files changed, 3279 insertions, 1006 deletions
@@ -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 @@ -6,20 +6,51 @@ include $(CONFIGFILE) OS = linux # Linux: linux # Mac OS: macos -# Windows: windows +# Windows: windows 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: @@ -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..b8b2801 --- /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); + + +/** + * Singular 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..58668fd --- /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_destroy \- 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..de1e919 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 @@ -15,15 +15,44 @@ Link with .SH DESCRIPTION The .BR libterminput_is_ready () -function check if a call to the +function checks if a call to the .BR libterminput_read (3) function will skip reading from the file descriptor passed to it because it already has read data buffered. However, if there is buffered data but the library knows it -it not enough to return something they, +is not enough to return anything yet, 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..7219066 100644 --- a/libterminput_set_flags.3 +++ b/libterminput_set_flags.3 @@ -8,8 +8,8 @@ libterminput_clear_flags \- Remove input parsing flags .nf #include <libterminput.h> -int libterminput_set_flags(struct libterminput_state *ctx *\fIctx\fP, enum libterminput_flags \fIflags\fP); -int libterminput_clear_flags(struct libterminput_state *ctx *\fIctx\fP, enum libterminput_flags \fIflags\fP); +int libterminput_set_flags(struct libterminput_state *\fIctx\fP, enum libterminput_flags \fIflags\fP); +int libterminput_clear_flags(struct libterminput_state *\fIctx\fP, enum libterminput_flags \fIflags\fP); .fi .PP Link with @@ -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; +} @@ -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; } |
