diff options
author | Mattias Andrée <m@maandree.se> | 2025-02-19 19:53:53 +0100 |
---|---|---|
committer | Mattias Andrée <m@maandree.se> | 2025-02-19 19:55:30 +0100 |
commit | a40d93ab5a064f8f2f9696acd1b57ef3603cd2fe (patch) | |
tree | 288671ad6f1b0d8c57a00b18b04f21b827eab24d | |
parent | minor readability improvement (diff) | |
download | libterminput-a40d93ab5a064f8f2f9696acd1b57ef3603cd2fe.tar.gz libterminput-a40d93ab5a064f8f2f9696acd1b57ef3603cd2fe.tar.bz2 libterminput-a40d93ab5a064f8f2f9696acd1b57ef3603cd2fe.tar.xz |
misc cleanup, fixes, and clarifications, and escape sequence
Signed-off-by: Mattias Andrée <m@maandree.se>
-rw-r--r-- | Makefile | 6 | ||||
-rw-r--r-- | common.h | 66 | ||||
-rw-r--r-- | interactive-test.c | 47 | ||||
-rw-r--r-- | libterminput.h | 44 | ||||
-rw-r--r-- | libterminput_is_ready.3 | 30 | ||||
-rw-r--r-- | libterminput_parse_csi_m_mouse_tracking__.c | 54 | ||||
-rw-r--r-- | libterminput_parse_csi_small_t_mouse_tracking__.c | 24 | ||||
-rw-r--r-- | libterminput_parse_csi_t_mouse_tracking__.c | 36 | ||||
-rw-r--r-- | libterminput_parse_decimal_mouse_tracking__.c | 23 | ||||
-rw-r--r-- | libterminput_parse_sequence__.c | 274 | ||||
-rw-r--r-- | libterminput_read.c | 615 | ||||
-rw-r--r-- | libterminput_read_bracketed_paste__.c | 3 | ||||
-rw-r--r-- | libterminput_read_symbol__.c | 110 | ||||
-rw-r--r-- | test.c | 38 |
14 files changed, 801 insertions, 569 deletions
@@ -24,6 +24,12 @@ OBJ =\ 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 HDR =\ libterminput.h\ @@ -3,6 +3,7 @@ #include <alloca.h> #include <ctype.h> +#include <errno.h> #include <limits.h> #include <string.h> #include <unistd.h> @@ -15,8 +16,18 @@ #endif +/** + * Singlar read symbol + */ struct input { + /** + * Applied modifier keys + */ enum libterminput_mod mods; + + /** + * The read symbol; NUL-byte terminated + */ char symbol[7]; }; @@ -64,5 +75,60 @@ HIDDEN unsigned long long int libterminput_utf8_decode__(const char *s, size_t * */ 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); + #undef HIDDEN diff --git a/interactive-test.c b/interactive-test.c index d982ec5..687e391 100644 --- a/interactive-test.c +++ b/interactive-test.c @@ -1,4 +1,7 @@ /* See LICENSE file for copyright and license details. */ +#include <errno.h> +#include <fcntl.h> +#include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -8,16 +11,32 @@ #include "libterminput.h" +static volatile sig_atomic_t interrupted = 0; + + +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, print_state; + int r, print_state, flags; + struct sigaction sa; memset(&ctx, 0, sizeof(ctx)); + 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"); libterminput_set_flags(&ctx, LIBTERMINPUT_DECSET_1005); @@ -60,6 +79,18 @@ 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) { if (input.type == LIBTERMINPUT_NONE) { printf("none\n"); @@ -198,9 +229,19 @@ main(void) } } - 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; + return -r && !interrupted; } diff --git a/libterminput.h b/libterminput.h index e39befb..b0c55af 100644 --- a/libterminput.h +++ b/libterminput.h @@ -554,25 +554,28 @@ union libterminput_input { /** * The current input state and configurations * - * This struct should be considered opaque + * NB! This struct should be considered opaque * * Initialised with by setting all bytes to 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 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 */ }; @@ -583,7 +586,7 @@ struct libterminput_state { * @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) */ int libterminput_read(int fd, union libterminput_input *input, struct libterminput_state *ctx); @@ -593,6 +596,15 @@ int libterminput_read(int fd, union libterminput_input *input, struct libterminp * 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 diff --git a/libterminput_is_ready.3 b/libterminput_is_ready.3 index 08e6fd6..27b98f6 100644 --- a/libterminput_is_ready.3 +++ b/libterminput_is_ready.3 @@ -24,6 +24,33 @@ 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 flag +.I LIBTERMINPUT_ESC_ON_BLOCK +is 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 +84,5 @@ None. None. .SH SEE ALSO -.BR libterminput_read (3) +.BR libterminput_read (3), +.BR fcntl (3) diff --git a/libterminput_parse_csi_m_mouse_tracking__.c b/libterminput_parse_csi_m_mouse_tracking__.c new file mode 100644 index 0000000..f943c04 --- /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 { + input->type = LIBTERMINPUT_NONE; + 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..aaf0fb4 --- /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) { + input->type = LIBTERMINPUT_NONE; + break; + } + libterminput_encode_utf8__(nums[0], input->keypress.symbol); + input->keypress.times = 1; + break; + case '$': + input->keypress.mods |= LIBTERMINPUT_SHIFT; + if (nums[0] >= 200) + goto suppress; + goto tilde_case; + case '@': + if (ctx->flags & LIBTERMINPUT_INS_ON_CSI_AT) { + input->keypress.key = LIBTERMINPUT_INS; + break; + } + input->keypress.mods |= LIBTERMINPUT_SHIFT; + /* fall through */ + case '^': + input->keypress.mods |= LIBTERMINPUT_CTRL; + if (nums[0] >= 200) + goto suppress; + /* fall through */ + case '~': + tilde_case: + input->keypress.times = 1; + switch (nums[0]) { + case 1: input->keypress.key = LIBTERMINPUT_HOME; break; + case 2: input->keypress.key = LIBTERMINPUT_INS; break; + case 3: input->keypress.key = LIBTERMINPUT_DEL; break; + case 4: input->keypress.key = LIBTERMINPUT_END; break; + case 5: input->keypress.key = LIBTERMINPUT_PRIOR; break; + case 6: input->keypress.key = LIBTERMINPUT_NEXT; break; + case 7: input->keypress.key = LIBTERMINPUT_HOME; break; + case 8: input->keypress.key = LIBTERMINPUT_END; break; + case 9: input->keypress.key = LIBTERMINPUT_ESC; break; /* just made this one up */ + case 11: input->keypress.key = LIBTERMINPUT_F1; break; + case 12: input->keypress.key = LIBTERMINPUT_F2; break; + case 13: input->keypress.key = LIBTERMINPUT_F3; break; + case 14: input->keypress.key = LIBTERMINPUT_F4; break; + case 15: input->keypress.key = LIBTERMINPUT_F5; break; + case 17: input->keypress.key = LIBTERMINPUT_F6; break; + case 18: input->keypress.key = LIBTERMINPUT_F7; break; + case 19: input->keypress.key = LIBTERMINPUT_F8; break; + case 20: input->keypress.key = LIBTERMINPUT_F9; break; + case 21: input->keypress.key = LIBTERMINPUT_F10; break; + case 23: input->keypress.key = LIBTERMINPUT_F11; break; + case 24: input->keypress.key = LIBTERMINPUT_F12; break; + case 25: input->keypress.key = LIBTERMINPUT_F1; 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: + input->type = LIBTERMINPUT_NONE; + break; + } +} diff --git a/libterminput_read.c b/libterminput_read.c index 0c46585..0ca3c36 100644 --- a/libterminput_read.c +++ b/libterminput_read.c @@ -2,515 +2,56 @@ #include "common.h" -static int -read_input(int fd, struct input *input, struct libterminput_state *ctx) -{ - unsigned char c, tc; - ssize_t r; - - /* Get next byte from input */ - if (ctx->stored_head != ctx->stored_tail) { - c = ((unsigned char *)ctx->stored)[ctx->stored_tail++]; - if (ctx->stored_tail == ctx->stored_head) - ctx->stored_tail = ctx->stored_head = 0; - } else { - r = read(fd, ctx->stored, sizeof(ctx->stored)); - if (r <= 0) - return (int)r; - c = (unsigned char)ctx->stored[0]; - if (r > 1) { - ctx->stored_tail = 1; - ctx->stored_head = (size_t)r; - } - } - -again: - if (ctx->n) { - /* Continuation of multibyte-character */ - if ((c & 0xC0) != 0x80) { - /* Short multibyte-character: return short and store read byte from next input */ - input->mods = ctx->mods; - ctx->partial[(unsigned char)ctx->npartial] = '\0'; - ctx->n = 0; - ctx->npartial = 0; - ctx->mods = 0; - ctx->stored[ctx->stored_head++] = (char)c; - strcpy(input->symbol, ctx->partial); - return 1; - } else { - /* Store byte, and if done, return */ - ctx->partial[(unsigned char)ctx->npartial++] = (char)c; - if (ctx->npartial == ctx->n) { - ctx->partial[(unsigned char)ctx->npartial] = '\0'; - input->mods = ctx->mods; - ctx->npartial = 0; - ctx->mods = 0; - ctx->n = 0; - strcpy(input->symbol, ctx->partial); - return 1; - } - } - } else if (c == 033 && !*ctx->key) { - /* ESC at the beginning, save as a Meta/ESC (for default behaviour) */ - if ((ctx->flags & LIBTERMINPUT_ESC_ON_BLOCK) && ctx->stored_tail == ctx->stored_head) { - input->symbol[0] = (char)c; - input->symbol[1] = '\0'; - input->mods = ctx->mods; - ctx->mods = 0; - return 1; - } - ctx->meta += 1; - } else if (c == 0) { - /* CTRL on Space */ - input->symbol[0] = ' '; - input->symbol[1] = '\0'; - input->mods = ctx->mods | LIBTERMINPUT_CTRL; - ctx->mods = 0; - return 1; - } else if (c < (unsigned char)' ' && (char)c != '\t' && (char)c != '\b' && (char)c != '\n') { - /* CTRL on some some character key */ - input->symbol[0] = (char)c + '@'; - input->symbol[1] = '\0'; - input->mods = ctx->mods | LIBTERMINPUT_CTRL; - ctx->mods = 0; - return 1; - } else if ((c & 0xC0) == 0xC0 && c != 0xFF) { - /* Beginning of multibyte-character */ - ctx->n = 0; - for (tc = c; tc & 0x80; tc <<= 1) - ctx->n++; - if (ctx->n > 6) { - /* If overlong, return first byte a single-byte-character */ - input->symbol[0] = (char)c; - input->symbol[1] = '\0'; - input->mods = ctx->mods; - ctx->mods = 0; - return 1; - } - ctx->partial[0] = (char)c; - ctx->npartial = 1; - } else if (c & 0x80) { - /* 8th bit set to signify META */ - c ^= 0x80; - ctx->mods |= LIBTERMINPUT_META; - goto again; - } else { - /* Single-byte-character */ - input->symbol[0] = (char)c; - input->symbol[1] = '\0'; - input->mods = ctx->mods; - ctx->mods = 0; - return 1; - } - - input->symbol[0] = '\0'; - input->mods = -1; - return 1; -} - - -static void -parse_sequence(union libterminput_input *input, struct libterminput_state *ctx) -{ - unsigned long long int *nums, numsbuf[6]; - size_t keylen, n, nnums = 0, pos; - char *p; - - /* Get number of numbers in the sequence, and allocate an array of at least 2 */ - if (ctx->key[0] == '[' && (ctx->key[1] == '<' ? isdigit(ctx->key[2]) : isdigit(ctx->key[1]))) - nnums += 1; - for (n = 2, p = ctx->key; *p; p++) { - if (*p == ';') { - n += 1; - nnums += 1; - } - } - nums = alloca(n * sizeof(*nums)); - nums[0] = nums[1] = 0; - - /* Read numbers and remove numbers and delimiters */ - for (keylen = 0, n = 0, p = ctx->key; *p; p++) { - if (*p == ';') { - nums[++n] = 0; /* We made sure above to allocate one extra */ - } else if (!isdigit(*p)) { - ctx->key[keylen++] = *p; - } else if (n < 3) { - if (nums[n] < (ULLONG_MAX - (*p & 15)) / 10) - nums[n] = nums[n] * 10 + (*p & 15); - else - nums[n] = ULLONG_MAX; - } - } - ctx->key[keylen] = '\0'; - - /* Get times and mods, and reset symbol, and more as keypress */ - input->type = LIBTERMINPUT_KEYPRESS; - input->keypress.symbol[0] = '\0'; - input->keypress.times = nums[0] + !nums[0]; - input->keypress.mods = nums[1] > 1 ? nums[1] - 1 : 0; - input->keypress.mods |= ctx->meta > 1 ? LIBTERMINPUT_META : 0; - - switch (ctx->key[0]) { - case '[': - switch (keylen) { - case 2: - switch (ctx->key[1]) { - case 'A': input->keypress.key = LIBTERMINPUT_UP; break; - case 'B': input->keypress.key = LIBTERMINPUT_DOWN; break; - case 'C': input->keypress.key = LIBTERMINPUT_RIGHT; break; - case 'D': input->keypress.key = LIBTERMINPUT_LEFT; break; - case 'E': input->keypress.key = LIBTERMINPUT_BEGIN; break; - case 'F': input->keypress.key = LIBTERMINPUT_END; break; - case 'G': input->keypress.key = LIBTERMINPUT_BEGIN; break; - case 'H': input->keypress.key = LIBTERMINPUT_HOME; break; - case 'M': - if (ctx->flags & LIBTERMINPUT_MACRO_ON_CSI_M) { - input->keypress.key = LIBTERMINPUT_MACRO; - } else if (nnums >= 3) { - /* Parsing for \e[?1000;1015h output. */ - nums[0] -= 32ULL; - decimal_mouse_tracking_set_press: - input->mouseevent.event = LIBTERMINPUT_PRESS; - decimal_mouse_tracking: - input->mouseevent.type = LIBTERMINPUT_MOUSEEVENT; - input->mouseevent.x = (size_t)nums[1] + (size_t)!nums[1]; - input->mouseevent.y = (size_t)nums[2] + (size_t)!nums[2]; - input->mouseevent.mods = (enum libterminput_mod)((nums[0] >> 2) & 7ULL); - if (nums[0] & 32) - input->mouseevent.event = LIBTERMINPUT_MOTION; - nums[0] = (nums[0] & 3ULL) | ((nums[0] >> 4) & ~3ULL); - if (nums[0] < 4) { - nums[0] = (nums[0] + 1) & 3; - if (!nums[0] && input->mouseevent.event == LIBTERMINPUT_PRESS) { - input->mouseevent.event = LIBTERMINPUT_RELEASE; - nums[0] = 1; - } - } - input->mouseevent.button = (enum libterminput_button)nums[0]; - } else if (!nnums && (ctx->flags & LIBTERMINPUT_DECSET_1005)) { - /* Parsing for semi-legacy \e[?1000;1005h output. */ - ctx->mouse_tracking = 0; - nums = numsbuf; - pos = ctx->stored_tail; - if ((nums[0] = libterminput_utf8_decode__(ctx->stored, &ctx->stored_tail)) < 32 || - (nums[1] = libterminput_utf8_decode__(ctx->stored, &ctx->stored_tail)) < 32 || - (nums[2] = libterminput_utf8_decode__(ctx->stored, &ctx->stored_tail)) < 32) { - ctx->stored_tail = pos; - input->keypress.key = LIBTERMINPUT_MACRO; - return; - } - nums[0] = nums[0] - 32ULL; - nums[1] = nums[1] - 32ULL; - nums[2] = nums[2] - 32ULL; - if (ctx->stored_head == ctx->stored_tail) - ctx->stored_head = ctx->stored_tail = 0; - goto decimal_mouse_tracking_set_press; - } else 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; - goto decimal_mouse_tracking_set_press; - } else { - goto suppress; - } - break; - case 'P': - input->keypress.key = LIBTERMINPUT_F1; - if (ctx->flags & LIBTERMINPUT_PAUSE_ON_CSI_P) - input->keypress.key = LIBTERMINPUT_PAUSE; - break; - case 'Q': - input->keypress.key = LIBTERMINPUT_F2; - break; - case 'R': - if ((ctx->flags & LIBTERMINPUT_AWAITING_CURSOR_POSITION) && nnums >= 2) { - input->position.type = LIBTERMINPUT_CURSOR_POSITION; - input->position.y = (size_t)nums[0] + (size_t)!nums[0]; - input->position.x = (size_t)nums[1] + (size_t)!nums[1]; - } else { - input->keypress.key = LIBTERMINPUT_F3; - } - break; - case 'S': - input->keypress.key = LIBTERMINPUT_F4; - break; - case 'T': - /* Parsing output for legacy mouse highlight tracking output. (\e[?1001h) */ - ctx->mouse_tracking = 0; - nums = numsbuf; - nums[0] = (unsigned long long int)(unsigned char)ctx->stored[ctx->stored_tail++]; - nums[1] = (unsigned long long int)(unsigned char)ctx->stored[ctx->stored_tail++]; - nums[2] = (unsigned long long int)(unsigned char)ctx->stored[ctx->stored_tail++]; - nums[3] = (unsigned long long int)(unsigned char)ctx->stored[ctx->stored_tail++]; - nums[4] = (unsigned long long int)(unsigned char)ctx->stored[ctx->stored_tail++]; - nums[5] = (unsigned long long int)(unsigned char)ctx->stored[ctx->stored_tail++]; - nums[0] = (nums[0] - 32ULL) & 255ULL; - nums[1] = (nums[1] - 32ULL) & 255ULL; - nums[2] = (nums[2] - 32ULL) & 255ULL; - nums[3] = (nums[3] - 32ULL) & 255ULL; - nums[4] = (nums[4] - 32ULL) & 255ULL; - nums[5] = (nums[5] - 32ULL) & 255ULL; - if (ctx->stored_head == ctx->stored_tail) - ctx->stored_head = ctx->stored_tail = 0; - input->mouseevent.type = LIBTERMINPUT_MOUSEEVENT; - input->mouseevent.event = LIBTERMINPUT_HIGHLIGHT_OUTSIDE; - input->mouseevent.mods = 0; - input->mouseevent.button = LIBTERMINPUT_BUTTON1; - input->mouseevent.start_x = (size_t)nums[0] + (size_t)!nums[0]; - input->mouseevent.start_y = (size_t)nums[1] + (size_t)!nums[1]; - input->mouseevent.end_x = (size_t)nums[2] + (size_t)!nums[2]; - input->mouseevent.end_y = (size_t)nums[3] + (size_t)!nums[3]; - input->mouseevent.x = (size_t)nums[4] + (size_t)!nums[4]; - input->mouseevent.y = (size_t)nums[5] + (size_t)!nums[5]; - break; - case 'U': input->keypress.key = LIBTERMINPUT_NEXT; break; - case 'V': input->keypress.key = LIBTERMINPUT_PRIOR; break; - case 'Z': - if (!(ctx->flags & LIBTERMINPUT_SEPARATE_BACKTAB)) { - input->keypress.key = LIBTERMINPUT_TAB; - input->keypress.mods |= LIBTERMINPUT_SHIFT; - } else { - input->keypress.key = LIBTERMINPUT_BACKTAB; - } - break; - case 'a': - input->keypress.key = LIBTERMINPUT_UP; - input->keypress.mods |= LIBTERMINPUT_SHIFT; - break; - case 'b': - input->keypress.key = LIBTERMINPUT_DOWN; - input->keypress.mods |= LIBTERMINPUT_SHIFT; - break; - case 'c': - input->keypress.key = LIBTERMINPUT_RIGHT; - input->keypress.mods |= LIBTERMINPUT_SHIFT; - break; - case 'd': - input->keypress.key = LIBTERMINPUT_LEFT; - input->keypress.mods |= LIBTERMINPUT_SHIFT; - break; - case 'n': - if (nnums == 1 && nums[0] == 0) { - input->type = LIBTERMINPUT_TERMINAL_IS_OK; - } else if (nnums == 1 && nums[0] == 3) { - input->type = LIBTERMINPUT_TERMINAL_IS_NOT_OK; - } else { - goto suppress; - } - break; - case 't': - /* Parsing output for legacy mouse highlight tracking output (\e[?1001h). */ - ctx->mouse_tracking = 0; - nums = numsbuf; - nums[0] = (unsigned long long int)(unsigned char)ctx->stored[ctx->stored_tail++]; - nums[1] = (unsigned long long int)(unsigned char)ctx->stored[ctx->stored_tail++]; - nums[0] = (nums[0] - 32ULL) & 255ULL; - nums[1] = (nums[1] - 32ULL) & 255ULL; - if (ctx->stored_head == ctx->stored_tail) - ctx->stored_head = ctx->stored_tail = 0; - input->mouseevent.type = LIBTERMINPUT_MOUSEEVENT; - input->mouseevent.event = LIBTERMINPUT_HIGHLIGHT_INSIDE; - input->mouseevent.mods = 0; - input->mouseevent.button = LIBTERMINPUT_BUTTON1; - input->mouseevent.x = (size_t)nums[0] + (size_t)!nums[0]; - input->mouseevent.y = (size_t)nums[1] + (size_t)!nums[1]; - break; - case 'u': - if (nums[0] > 0x10FFFFULL || (nums[0] & 0xFFF800ULL) == 0xD800ULL) { - input->type = LIBTERMINPUT_NONE; - break; - } - libterminput_encode_utf8__(nums[0], input->keypress.symbol); - input->keypress.times = 1; - break; - case '$': - input->keypress.mods |= LIBTERMINPUT_SHIFT; - if (nums[0] >= 200) - goto suppress; - goto tilde_case; - case '@': - if (ctx->flags & LIBTERMINPUT_INS_ON_CSI_AT) { - input->keypress.key = LIBTERMINPUT_INS; - break; - } - input->keypress.mods |= LIBTERMINPUT_SHIFT; - /* fall through */ - case '^': - input->keypress.mods |= LIBTERMINPUT_CTRL; - if (nums[0] >= 200) - goto suppress; - /* fall through */ - case '~': - tilde_case: - input->keypress.times = 1; - switch (nums[0]) { - case 1: input->keypress.key = LIBTERMINPUT_HOME; break; - case 2: input->keypress.key = LIBTERMINPUT_INS; break; - case 3: input->keypress.key = LIBTERMINPUT_DEL; break; - case 4: input->keypress.key = LIBTERMINPUT_END; break; - case 5: input->keypress.key = LIBTERMINPUT_PRIOR; break; - case 6: input->keypress.key = LIBTERMINPUT_NEXT; break; - case 7: input->keypress.key = LIBTERMINPUT_HOME; break; - case 8: input->keypress.key = LIBTERMINPUT_END; break; - case 9: input->keypress.key = LIBTERMINPUT_ESC; break; /* just made this one up */ - case 11: input->keypress.key = LIBTERMINPUT_F1; break; - case 12: input->keypress.key = LIBTERMINPUT_F2; break; - case 13: input->keypress.key = LIBTERMINPUT_F3; break; - case 14: input->keypress.key = LIBTERMINPUT_F4; break; - case 15: input->keypress.key = LIBTERMINPUT_F5; break; - case 17: input->keypress.key = LIBTERMINPUT_F6; break; - case 18: input->keypress.key = LIBTERMINPUT_F7; break; - case 19: input->keypress.key = LIBTERMINPUT_F8; break; - case 20: input->keypress.key = LIBTERMINPUT_F9; break; - case 21: input->keypress.key = LIBTERMINPUT_F10; break; - case 23: input->keypress.key = LIBTERMINPUT_F11; break; - case 24: input->keypress.key = LIBTERMINPUT_F12; break; - case 25: input->keypress.key = LIBTERMINPUT_F1; break; - case 26: input->keypress.key = LIBTERMINPUT_F2; break; - case 28: input->keypress.key = LIBTERMINPUT_F3; break; - case 29: input->keypress.key = LIBTERMINPUT_F4; break; - case 31: input->keypress.key = LIBTERMINPUT_F5; break; - case 32: input->keypress.key = LIBTERMINPUT_F6; break; - case 33: input->keypress.key = LIBTERMINPUT_F7; break; - case 34: input->keypress.key = LIBTERMINPUT_F8; break; - case 200: - ctx->bracketed_paste = 1; - input->type = LIBTERMINPUT_BRACKETED_PASTE_START; - return; - case 201: - ctx->bracketed_paste = 0; - input->type = LIBTERMINPUT_BRACKETED_PASTE_END; - return; - default: - goto suppress; - } - if (25 <= nums[0] && nums[0] <= 34) - input->keypress.mods |= LIBTERMINPUT_SHIFT; - break; - default: - goto suppress; - } - break; - case 3: - switch (ctx->key[1] == '[' ? ctx->key[2] : 0) { - case 'A': input->keypress.key = LIBTERMINPUT_F1; break; - case 'B': input->keypress.key = LIBTERMINPUT_F2; break; - case 'C': input->keypress.key = LIBTERMINPUT_F3; break; - case 'D': input->keypress.key = LIBTERMINPUT_F4; break; - case 'E': input->keypress.key = LIBTERMINPUT_F5; break; - default: - if (ctx->key[1] == '<' && (ctx->key[2] == 'M' || ctx->key[2] == 'm') && nnums >= 3) { - /* Parsing for \e[?1003;1006h output. */ - input->mouseevent.event = LIBTERMINPUT_PRESS; - if (ctx->key[2] == 'm') - input->mouseevent.event = LIBTERMINPUT_RELEASE; - goto decimal_mouse_tracking; - } else { - goto suppress; - } - } - break; - default: - goto suppress; - } - break; - - case 'O': - switch (!ctx->key[2] ? ctx->key[1] : 0) { - case 'A': input->keypress.key = LIBTERMINPUT_UP; break; - case 'B': input->keypress.key = LIBTERMINPUT_DOWN; break; - case 'C': input->keypress.key = LIBTERMINPUT_RIGHT; break; - case 'D': input->keypress.key = LIBTERMINPUT_LEFT; break; - case 'E': input->keypress.key = LIBTERMINPUT_BEGIN; break; /* not attested */ - case 'F': input->keypress.key = LIBTERMINPUT_END; break; - case 'G': input->keypress.key = LIBTERMINPUT_BEGIN; break; /* not attested */ - case 'H': input->keypress.key = LIBTERMINPUT_HOME; break; - case 'P': input->keypress.key = LIBTERMINPUT_F1; break; - case 'Q': input->keypress.key = LIBTERMINPUT_F2; break; - case 'R': input->keypress.key = LIBTERMINPUT_F3; break; - case 'S': input->keypress.key = LIBTERMINPUT_F4; break; - case 'p': input->keypress.key = LIBTERMINPUT_KEYPAD_0; break; - case 'q': input->keypress.key = LIBTERMINPUT_KEYPAD_1; break; - case 'r': input->keypress.key = LIBTERMINPUT_KEYPAD_2; break; - case 's': input->keypress.key = LIBTERMINPUT_KEYPAD_3; break; - case 't': input->keypress.key = LIBTERMINPUT_KEYPAD_4; break; - case 'u': input->keypress.key = LIBTERMINPUT_KEYPAD_5; break; - case 'v': input->keypress.key = LIBTERMINPUT_KEYPAD_6; break; - case 'w': input->keypress.key = LIBTERMINPUT_KEYPAD_7; break; - case 'x': input->keypress.key = LIBTERMINPUT_KEYPAD_8; break; - case 'y': input->keypress.key = LIBTERMINPUT_KEYPAD_9; break; - case 'k': input->keypress.key = LIBTERMINPUT_KEYPAD_PLUS; break; - case 'm': input->keypress.key = LIBTERMINPUT_KEYPAD_MINUS; break; - case 'j': input->keypress.key = LIBTERMINPUT_KEYPAD_TIMES; break; - case 'o': input->keypress.key = LIBTERMINPUT_KEYPAD_DIVISION; break; - case 'n': input->keypress.key = LIBTERMINPUT_KEYPAD_DECIMAL; break; - case 'l': input->keypress.key = LIBTERMINPUT_KEYPAD_COMMA; break; - case 'b': input->keypress.key = LIBTERMINPUT_KEYPAD_POINT; break; - case 'M': input->keypress.key = LIBTERMINPUT_KEYPAD_ENTER; break; - default: - goto suppress; - } - break; - - default: - /* This shouldn't happen (without goto) */ - suppress: - input->type = LIBTERMINPUT_NONE; - break; - } -} - - int libterminput_read(int fd, union libterminput_input *input, struct libterminput_state *ctx) { struct input ret; size_t n, m; char *p; - int r; + 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 = 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; + 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; + return 1; } - 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; + 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], (size_t)ctx->mouse_tracking - (ctx->stored_head - ctx->stored_tail)); + 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; @@ -520,11 +61,21 @@ libterminput_read(int fd, union libterminput_input *input, struct libterminput_s again: if (!*ret.symbol) { - /* Incomplete input */ + /* 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 */ - input->type = LIBTERMINPUT_NONE; - return 1; + /* 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; + goto none; + } + goto none; } /* Three ESC's */ input->type = LIBTERMINPUT_KEYPRESS; @@ -533,6 +84,7 @@ again: input->keypress.mods = 0; input->keypress.symbol[0] = '\0'; ctx->meta -= 3; + } else if (*ctx->key) { /* Special keys */ if (ret.mods) { @@ -545,42 +97,29 @@ again: m = strlen(ret.symbol); if (n + m >= sizeof(ctx->key)) { /* Abort if too long */ - input->type = LIBTERMINPUT_NONE; - return 1; + 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] != '$') { - input->type = LIBTERMINPUT_NONE; - return 1; + goto none; } else if (ctx->key[0] == '[' && ctx->key[1] == '<' && p == &ctx->key[2]) { - input->type = LIBTERMINPUT_NONE; - return 1; + goto none; } else if (ctx->key[0] == '[' && ctx->key[1] == 'M' && (ctx->flags & LIBTERMINPUT_MACRO_ON_CSI_M)) { /* complete */ + /* TODO optionally this should also be the case if blocked */ } else if (ctx->key[0] == '[' && ctx->key[1] == 'M' && (ctx->flags & LIBTERMINPUT_DECSET_1005)) { ctx->mouse_tracking = 1; - if (ctx->stored_head == ctx->stored_tail) { - input->type = LIBTERMINPUT_NONE; - return 1; - } n = ctx->stored_tail; - r = libterminput_check_utf8_char__(&ctx->stored[n], ctx->stored_head - n, &m); - if (r <= 0) - goto fallback_to_none_or_macro; - n += m; - r = libterminput_check_utf8_char__(&ctx->stored[n], ctx->stored_head - n, &m); - if (r <= 0) - goto fallback_to_none_or_macro; - n += m; - r = libterminput_check_utf8_char__(&ctx->stored[n], ctx->stored_head - n, &m); - if (r <= 0) { - fallback_to_none_or_macro: - if (!r) { - input->type = LIBTERMINPUT_NONE; - return 1; + 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; @@ -592,28 +131,27 @@ again: ctx->key[0] = '\0'; return 1; } - } else if (ctx->key[0] == '[' && ctx->key[1] == 'M' && ctx->stored_head - ctx->stored_tail < 3) { + } else if (ctx->key[0] == '[' && ctx->key[1] == 'M' && ctx->stored_head - ctx->stored_tail < 3U) { 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) { + goto none; + } else if (ctx->key[0] == '[' && ctx->key[1] == 't' && ctx->stored_head - ctx->stored_tail < 2U) { 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) { + goto none; + } else if (ctx->key[0] == '[' && ctx->key[1] == 'T' && ctx->stored_head - ctx->stored_tail < 6U) { ctx->mouse_tracking = 6; - input->type = LIBTERMINPUT_NONE; - return 1; + goto none; } /* Parse the complete sequence */ - parse_sequence(input, ctx); + libterminput_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 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; @@ -624,30 +162,23 @@ again: 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; - 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; + 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; - strcpy(input->keypress.symbol, ret.symbol); + stpcpy(input->keypress.symbol, ret.symbol); break; } } return 1; + +none: + input->type = LIBTERMINPUT_NONE; + return 1; } diff --git a/libterminput_read_bracketed_paste__.c b/libterminput_read_bracketed_paste__.c index 0c7a81f..02fecc5 100644 --- a/libterminput_read_bracketed_paste__.c +++ b/libterminput_read_bracketed_paste__.c @@ -84,7 +84,8 @@ libterminput_read_bracketed_paste__(int fd, union libterminput_input *input, str } /* If the input is solely a truncation of the bracketed paste - * end marker, output that we do not have any complete input */ + * 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) { input->text.type = LIBTERMINPUT_NONE; memcpy(ctx->stored, input->text.bytes, input->text.nbytes); 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; +} @@ -1,5 +1,6 @@ /* See LICENSE file for copyright and license details. */ #include <errno.h> +#include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -15,6 +16,7 @@ static const struct keypress { enum libterminput_mod mods; enum libterminput_flags flags; } keypresses[] = { + /* TODO test new ESC O keys and test replacing ESC O with ESC ? */ {"\033[[", "A", LIBTERMINPUT_F1, 0, 0}, {"\033[[", "B", LIBTERMINPUT_F2, 0, 0}, {"\033[[", "C", LIBTERMINPUT_F3, 0, 0}, @@ -335,12 +337,21 @@ static int fds[2]; 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 { + r = libterminput_read(fds[0], &input, &ctx); + TEST(r == 1 || (r == -1 && errno == EAGAIN)); + } while (input.type == LIBTERMINPUT_NONE && r > 0); + } else { + do { + TEST(libterminput_read(fds[0], &input, &ctx) == 1); + } while (input.type == LIBTERMINPUT_NONE && libterminput_is_ready(&input, &ctx)); + } TEST(input.type == type); } @@ -348,16 +359,25 @@ 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 { + r = libterminput_read(fds[0], &input, &ctx); + TEST(r == 1 || (r == -1 && errno == EAGAIN)); + } while (input.type == LIBTERMINPUT_NONE && r > 0); + } else { + do { + TEST(libterminput_read(fds[0], &input, &ctx) == 1); + } while (input.type == LIBTERMINPUT_NONE && libterminput_is_ready(&input, &ctx)); + } TEST(input.type == LIBTERMINPUT_KEYPRESS); TEST(input.keypress.key == key); TEST(input.keypress.mods == mods); @@ -497,10 +517,14 @@ int main(void) { size_t i; + int flags; 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); @@ -598,7 +622,9 @@ main(void) KEYPRESS_SPECIAL_CHAR('\t', LIBTERMINPUT_TAB); KEYPRESS_SPECIAL_CHAR('\n', LIBTERMINPUT_ENTER); libterminput_set_flags(&ctx, LIBTERMINPUT_ESC_ON_BLOCK); + TEST(fcntl(fds[0], F_SETFL, flags | O_NONBLOCK) >= 0); KEYPRESS_SPECIAL_CHAR('\033', LIBTERMINPUT_ESC); + TEST(fcntl(fds[0], F_SETFL, flags) >= 0); libterminput_clear_flags(&ctx, LIBTERMINPUT_ESC_ON_BLOCK); TYPE("text", LIBTERMINPUT_KEYPRESS); |