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