From a41ece5ff0d3ce7a0b7d987baa9759f8a012b48b Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Mon, 9 Jul 2007 19:04:12 +0000 Subject: Initial import to CVS. Basic functions are working, albeit with a couple of showstopper memory bugs and many missing features. Detaching, reattaching, creating new sessions, listing sessions work acceptably for using with shells. Simple curses programs (top, systat, tetris) and more complicated ones (mutt, emacs) that don't require scrolling regions (ESC[r) mostly work fine (including mutt, emacs). No status bar yet and no key remapping or other customisation. --- input.c | 825 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 825 insertions(+) create mode 100644 input.c (limited to 'input.c') diff --git a/input.c b/input.c new file mode 100644 index 00000000..663ef2a3 --- /dev/null +++ b/input.c @@ -0,0 +1,825 @@ +/* $Id: input.c,v 1.1.1.1 2007-07-09 19:03:45 nicm Exp $ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include +#include +#include + +#include "tmux.h" + +size_t input_sequence( + u_char *, size_t, u_char *, u_char *, uint16_t **, u_int *); +int input_control( + u_char **, size_t *, struct buffer *, struct screen *, u_char); +int input_pair_private( + u_char **, size_t *, struct buffer *, struct screen *, u_char); +int input_pair_standard( + u_char **, size_t *, struct buffer *, struct screen *, u_char); +int input_pair_control( + u_char **, size_t *, struct buffer *, struct screen *, u_char); +int input_control_sequence( + u_char **, size_t *, struct buffer *, struct screen *); +int input_check_one(uint16_t *, u_int, uint16_t *, uint16_t); +int input_check_one2( + uint16_t *, u_int, uint16_t *, uint16_t, uint16_t, uint16_t); +int input_check_two( + uint16_t *, u_int, uint16_t *, uint16_t, uint16_t *, uint16_t); + +struct input_key { + int key; + const char *string; +}; + +struct input_key input_keys[] = { + { KEYC_BACKSPACE, "" }, + { KEYC_DC, "[3~" }, + { KEYC_DOWN, "OB" }, + { KEYC_F1, "OP" }, + { KEYC_F10, "[21~" }, + { KEYC_F11, "[23~" }, + { KEYC_F12, "[24~" }, + { KEYC_F2, "OQ" }, + { KEYC_F3, "OR" }, + { KEYC_F4, "OS" }, + { KEYC_F5, "[15~" }, + { KEYC_F6, "[17~" }, + { KEYC_F7, "[18~" }, + { KEYC_F8, "[19~" }, + { KEYC_F9, "[20~" }, + { KEYC_HOME, "[1~" }, + { KEYC_IC, "[2~" }, + { KEYC_LEFT, "OD" }, + { KEYC_LL, "[4~" }, + { KEYC_NPAGE, "[6~" }, + { KEYC_PPAGE, "[5~" }, + { KEYC_RIGHT, "OC" }, + { KEYC_UP, "OA" } +}; + +/* + * This parses CSI escape sequences into a code and a block of uint16_t + * arguments. buf must be the next byte after the \e[ and len the remaining + * data. + */ +size_t +input_sequence(u_char *buf, size_t len, + u_char *code, u_char *private, uint16_t **argv, u_int *argc) +{ + char ch; + u_char *ptr, *saved; + const char *errstr; + + *code = 0; + + *argc = 0; + *argv = NULL; + + if (len == 0) + return (0); + saved = buf; + + /* + * 0x3c (<) to 0x3f (?) mark private sequences when appear as the first + * character. + */ + *private = '\0'; + if (*buf >= '<' && *buf <= '?') { + *private = *buf; + buf++; len--; + } else if (*buf < '0' || *buf > ';') + goto complete; + + while (len > 0) { + /* + * Every parameter substring is bytes from 0x30 (0) to 0x3a (:), + * terminated by 0x3b (;). 0x3a is an internal seperator. + */ + + /* Find the end of the substring. */ + ptr = buf; + while (len != 0 && *ptr >= '0' && *ptr <= '9') { + ptr++; + len--; + } + if (len == 0) + break; + + /* An 0x3a is unsupported. */ + if (*ptr == ':') + goto invalid; + + /* Create a new argument. */ + (*argc)++; + *argv = xrealloc(*argv, *argc, sizeof **argv); + + /* Fill in argument value. */ + errstr = NULL; + if (ptr == buf) + (*argv)[*argc - 1] = UINT16_MAX; + else { + ch = *ptr; *ptr = '\0'; + (*argv)[*argc - 1] = + strtonum(buf, 0, UINT16_MAX - 1, &errstr); + *ptr = ch; + } + buf = ptr; + + /* If the conversion had errors, abort now. */ + if (errstr != NULL) + goto invalid; + + /* Break for any non-terminator. */ + if (*buf != ';') + goto complete; + buf++; len--; + } + if (len == 0) + goto incomplete; + +complete: + /* Valid final characters are 0x40 (@) to 0x7e (~). */ + if (*buf < '@' || *buf > '~') + goto invalid; + + *code = *buf; + return (buf - saved + 1); + +invalid: + if (*argv != NULL) { + xfree(*argv); + *argv = NULL; + } + *argc = 0; + + /* Invalid. Consume until a valid terminator. */ + while (len > 0) { + if (*buf >= '@' && *buf <= '~') + break; + buf++; len--; + } + if (len == 0) + goto incomplete; + + *code = '\0'; + return (buf - saved + 1); + +incomplete: + if (*argv != NULL) { + xfree(*argv); + *argv = NULL; + } + *argc = 0; + + *code = '\0'; + return (0); +} + +/* Translate a key code into an output key sequence. */ +void +input_key(struct buffer *b, int key) +{ + struct input_key *ak; + u_int i; + + log_debug("writing key %d", key); + if (key != KEYC_NONE && key >= 0) { + input_store8(b, key); + return; + } + + for (i = 0; i < (sizeof input_keys / sizeof input_keys[0]); i++) { + ak = input_keys + i; + if (ak->key == key) { + log_debug("found key %d: \"%s\"", key, ak->string); + buffer_write(b, ak->string, strlen(ak->string)); + return; + } + } +} + +/* + * Parse a block of data and normalise escape sequences into a \e, a single + * character code and the correct number of arguments. This includes adding + * missing arguments and default values, and enforcing limits. Returns the + * number of bytes consumed. The screen is updated with the data and used + * to fill in current cursor positions and sizes. + */ +size_t +input_parse(u_char *buf, size_t len, struct buffer *b, struct screen *s) +{ + u_char *saved, ch; + size_t size; + FILE *f; + + saved = buf; + + if (debug_level > 1) { + f = fopen("tmux-in.log", "a+"); + fwrite(buf, len, 1, f); + fclose(f); + } + + while (len > 0) { + ch = *buf++; len--; + + /* Handle control characters. */ + if (ch != '\e') { + if (ch < ' ') { + if (input_control(&buf, &len, b, s, ch) == 1) { + *--buf = ch; + break; + } + } else { + log_debug("character: %c (%hhu)", ch, ch); + screen_character(s, ch); + input_store8(b, ch); + } + continue; + } + if (len == 0) { + *--buf = '\e'; + break; + } + + /* Read the first character. */ + ch = *buf++; len--; + + /* Ignore delete. */ + if (ch == '\177') { + if (len == 0) { + *--buf = '\e'; + break; + } + ch = *buf++; len--; + } + + /* Interpret C0 immediately. */ + if (ch < ' ') { + if (input_control(&buf, &len, b, s, ch) == 1) { + *--buf = ch; + break; + } + + if (len == 0) { + *--buf = '\e'; + break; + } + ch = *buf++; len--; + } + + /* + * Save used size to work out how much to pass to + * screen_sequence later. + */ + size = BUFFER_USED(b); + + /* Skip until the end of intermediate strings. */ + if (ch >= ' ' && ch <= '/') { + while (len != 0) { + if (ch >= 0x30 && ch <= 0x3f) + break; + if (ch >= 0x40 && ch <= 0x5f) + break; + ch = *buf++; len--; + } + continue; + } + + /* Handle two-character sequences. */ + if (ch >= '0' && ch <= '?') { + if (input_pair_private(&buf, &len, b, s, ch) == 1) + goto incomplete; + goto next; + } + if (ch >= '`' && ch <= '~') { + if (input_pair_standard(&buf, &len, b, s, ch) == 1) + goto incomplete; + goto next; + } + if (ch >= '@' && ch <= '_' && ch != '[') { + if (input_pair_control(&buf, &len, b, s, ch) == 1) + goto incomplete; + goto next; + } + + /* If not CSI at this point, invalid. */ + if (ch != '[') + continue; + + if (input_control_sequence(&buf, &len, b, s) == 1) + goto incomplete; + + next: + size = BUFFER_USED(b) - size; + log_debug("output is %zu bytes", size); + if (size > 0) /* XXX only one command? */ + screen_sequence(s, BUFFER_IN(b) - size); + log_debug("remaining data %zu bytes", len); + } + + return (buf - saved); + +incomplete: + *--buf = ch; + *--buf = '\e'; + return (buf - saved); +} + +/* Handle single control characters. */ +int +input_control(unused u_char **buf, unused size_t *len, + struct buffer *b, struct screen *s, u_char ch) +{ + switch (ch) { + case '\0': /* NUL */ + break; + case '\n': /* LF */ + case '\r': /* CR */ + case '\010': /* BS */ + log_debug("control: %hhu", ch); + screen_character(s, ch); + input_store8(b, ch); + break; + default: + log_debug("unknown control: %c (%hhu)", ch, ch); + break; + } + + return (0); +} + +/* Translate a private two-character sequence. */ +int +input_pair_private(unused u_char **buf, unused size_t *len, + unused struct buffer *b, unused struct screen *s, unused u_char ch) +{ + log_debug("private2: %c (%hhu)", ch, ch); + + switch (ch) { + case '=': /* DECKPAM */ + input_store_zero(b, CODE_KKEYPADON); + break; + case '>': /* DECKPNM*/ + input_store_zero(b, CODE_KKEYPADOFF); + break; + default: + log_debug("unknown private2: %c (%hhu)", ch, ch); + break; + } + + return (0); +} + +/* Translate a standard two-character sequence. */ +int +input_pair_standard(unused u_char **buf, unused size_t *len, + unused struct buffer *b, unused struct screen *s, u_char ch) +{ + log_debug("unknown standard2: %c (%hhu)", ch, ch); + + return (0); +} + +/* Translate a control two-character sequence. */ +int +input_pair_control(u_char **buf, size_t *len, + struct buffer *b, unused struct screen *s, u_char ch) +{ + u_char *ptr; + size_t size; + + log_debug("control2: %c (%hhu)", ch, ch); + + switch (ch) { + case ']': /* window title */ + if (*len < 3) + return (1); + ch = *(*buf)++; (*len)--; + + /* + * Search MAXTITLELEN + 1 to allow space for the ;. The + * \007 is also included, but space is needed for a \0 so + * it doesn't need to be compensated for. + */ + size = *len > MAXTITLELEN + 1 ? MAXTITLELEN + 1 : *len; + if ((ptr = memchr(*buf, '\007', size)) == NULL) { + log_debug("title not found in %zu bytes", size); + if (*len >= MAXTITLELEN + 1) + break; + (*buf)--; (*len)++; + return (1); + } + size = ptr - *buf; + + /* A zero size means no ;, just skip the \007 and return. */ + if (size == 0) { + (*buf)++; (*len)--; + break; + } + + /* Set the title if appropriate. */ + if (**buf == ';' && (ch == '0' || ch == '1')) { + log_debug("title found, length %zu bytes: %.*s", + size - 1, (int) size - 1, *buf + 1); + input_store_one(b, CODE_TITLE, size - 1); + buffer_write(b, *buf + 1, size - 1); + } + + /* Skip the title; add one for the \007. */ + (*buf) += size + 1; + (*len) -= size + 1; + break; + case 'M': /* RI */ + input_store_one(b, CODE_CURSORUPSCROLL, 1); + break; + default: + log_debug("unknown control2: %c (%hhu)", ch, ch); + break; + } + + return (0); +} + +/* Translate a control sequence. */ +int +input_control_sequence( + u_char **buf, size_t *len, struct buffer *b, struct screen *s) +{ + u_char code, private; + size_t used; + uint16_t *argv, ua, ub; + u_int argc, i; + + used = input_sequence(*buf, *len, &code, &private, &argv, &argc); + if (used == 0) /* incomplete */ + return (1); + + (*buf) += used; + (*len) -= used; + + if (code == '\0') /* invalid */ + return (-1); + + log_debug( + "sequence: %c (%hhu) [%c] [cx %u, cy %u, sx %u, sy %u]: %u", code, + code, private, s->cx + 1, s->cy + 1, s->sx + 1, s->sy + 1, argc); + for (i = 0; i < argc; i++) + log_debug("\targument %u: %u", i, argv[i]); + + switch (code) { + case 'A': /* CUU */ + if (private != '\0') + break; + if (input_check_one(argv, argc, &ua, 1) != 0) + break; + if (ua == 0) + break; + input_store_one(b, CODE_CURSORUP, ua); + break; + case 'B': /* CUD */ + if (private != '\0') + break; + if (input_check_one(argv, argc, &ua, 1) != 0) + break; + if (ua == 0) + break; + input_store_one(b, CODE_CURSORDOWN, ua); + break; + case 'C': /* CUF */ + if (private != '\0') + break; + if (input_check_one(argv, argc, &ua, 1) != 0) + break; + if (ua == 0) + break; + input_store_one(b, CODE_CURSORRIGHT, ua); + break; + case 'D': /* CUB */ + if (private != '\0') + break; + if (input_check_one(argv, argc, &ua, 1) != 0) + break; + if (ua == 0) + break; + input_store_one(b, CODE_CURSORLEFT, ua); + break; + case 'P': /* DCH */ + if (private != '\0') + break; + if (input_check_one(argv, argc, &ua, 1) != 0) + break; + if (ua == 0) + break; + input_store_one(b, CODE_DELETECHARACTER, ua); + break; + case 'M': /* DL */ + if (private != '\0') + break; + if (input_check_one(argv, argc, &ua, 1) != 0) + break; + if (ua == 0) + break; + input_store_one(b, CODE_DELETELINE, ua); + break; + case '@': /* ICH */ + if (private != '\0') + break; + if (input_check_one(argv, argc, &ua, 1) != 0) + break; + if (ua == 0) + break; + input_store_one(b, CODE_INSERTCHARACTER, ua); + break; + case 'L': /* IL */ + if (private != '\0') + break; + if (input_check_one(argv, argc, &ua, 1) != 0) + break; + if (ua == 0) + break; + input_store_one(b, CODE_INSERTLINE, ua); + break; + case 'd': /* VPA */ + if (private != '\0') + break; + if (input_check_one(argv, argc, &ua, 1) != 0) + break; + if (ua == 0) + break; + input_store_two(b, CODE_CURSORMOVE, ua, s->cx + 1); + break; + case 'G': /* HPA */ + if (private != '\0') + break; + if (input_check_one(argv, argc, &ua, 1) != 0) + break; + if (ua == 0) + break; + input_store_two(b, CODE_CURSORMOVE, s->cy + 1, ua); + break; + case 'H': /* CUP */ + case 'f': /* HVP */ + if (private != '\0') + break; + if (input_check_two(argv, argc, &ua, 1, &ub, 1) != 0) + break; + if (ua == 0 || ub == 0) + break; + input_store_two(b, CODE_CURSORMOVE, ua, ub); + break; + case 'J': /* ED */ + if (private != '\0') + break; + if (input_check_one2(argv, argc, &ua, 0, 0, 2) != 0) + break; + switch (ua) { + case 0: + input_store_zero(b, CODE_CLEARENDOFSCREEN); + break; + case 2: + input_store_zero(b, CODE_CLEARSCREEN); + break; + } + break; + case 'K': /* EL */ + if (private != '\0') + break; + if (input_check_one2(argv, argc, &ua, 0, 0, 2) != 0) + break; + switch (ua) { + case 0: + input_store_zero(b, CODE_CLEARENDOFLINE); + break; + case 1: + input_store_zero(b, CODE_CLEARSTARTOFLINE); + break; + case 2: + input_store_zero(b, CODE_CLEARLINE); + break; + } + break; + case 'h': /* SM */ + if (input_check_one(argv, argc, &ua, 0) != 0) + break; + switch (private) { + case '?': + switch (ua) { + case 1: /* GATM */ + input_store_zero(b, CODE_KCURSORON); + break; + case 25: /* TCEM */ + input_store_zero(b, CODE_CURSORON); + break; + default: + log_debug("unknown SM [%d]: %u", private, ua); + } + break; + case '\0': + switch (ua) { + case 4: /* IRM */ + input_store_zero(b, CODE_INSERTON); + break; + case 34: + /* Cursor high visibility not supported. */ + break; + default: + log_debug("unknown SM [%d]: %u", private, ua); + break; + } + break; + } + break; + case 'l': /* RM */ + if (input_check_one(argv, argc, &ua, 0) != 0) + break; + switch (private) { + case '?': + switch (ua) { + case 1: /* GATM */ + input_store_zero(b, CODE_KCURSOROFF); + break; + case 25: /* TCEM */ + input_store_zero(b, CODE_CURSOROFF); + break; + default: + log_debug("unknown RM [%d]: %u", private, ua); + } + break; + case '\0': + switch (ua) { + case 4: /* IRM */ + input_store_zero(b, CODE_INSERTOFF); + break; + case 34: + /* Cursor high visibility not supported. */ + break; + default: + log_debug("unknown RM [%d]: %u", private, ua); + break; + } + break; + } + break; + case 'r': /* DECSTBM */ + if (private != '\0') + break; + if (input_check_two(argv, argc, + &ua, s->ry_upper + 1, &ub, s->ry_lower + 1) != 0) + break; + if (ua == 0 || ub == 0 || ub < ua) + break; + input_store_two(b, CODE_SCROLLREGION, ua, ub); + break; + case 'm': /* SGR */ + input_store_zero(b, CODE_ATTRIBUTES); + if (argc == 0) { + input_store16(b, 1); + input_store16(b, 0); + } else { + input_store16(b, argc); + for (i = 0; i < argc; i++) { + if (argv[i] == UINT16_MAX) + argv[i] = 0; + input_store16(b, argv[i]); + } + } + break; + default: + log_debug("unknown sequence: %c (%hhu)", code, code); + break; + } + + if (argv != NULL) { + xfree(argv); + argv = NULL; + } + + return (0); +} + +/* Check for one argument. */ +int +input_check_one(uint16_t *argv, u_int argc, uint16_t *a, uint16_t ad) +{ + *a = ad; + if (argc == 1) { + if (argv[0] != UINT16_MAX) + *a = argv[0]; + } else if (argc != 0) + return (-1); + return (0); +} + +/* Check for one argument with limits. */ +int +input_check_one2(uint16_t *argv, u_int argc, + uint16_t *a, uint16_t ad, uint16_t al, uint16_t au) +{ + *a = ad; + if (argc == 1) { + if (argv[0] != UINT16_MAX) + *a = argv[0]; + } else if (argc != 0) + return (-1); + if (*a < al || *a > au) + return (-1); + return (0); +} + +/* Check for two arguments. */ +int +input_check_two(uint16_t *argv, u_int argc, + uint16_t *a, uint16_t ad, uint16_t *b, uint16_t bd) +{ + *a = ad; + *b = bd; + if (argc == 1) { + if (argv[0] != UINT16_MAX) + *a = argv[0]; + } else if (argc == 2) { + if (argv[0] != UINT16_MAX) + *a = argv[0]; + if (argv[1] != UINT16_MAX) + *b = argv[1]; + } else if (argc != 0) + return (-1); + return (0); +} + +/* Store a code without arguments. */ +void +input_store_zero(struct buffer *b, u_char code) +{ + input_store8(b, '\e'); + input_store8(b, code); +} + +/* Store a code with a single argument. */ +void +input_store_one(struct buffer *b, u_char code, uint16_t ua) +{ + input_store8(b, '\e'); + input_store8(b, code); + input_store16(b, ua); +} + +/* Store a code with two arguments. */ +void +input_store_two(struct buffer *b, u_char code, uint16_t ua, uint16_t ub) +{ + input_store8(b, '\e'); + input_store8(b, code); + input_store16(b, ua); + input_store16(b, ub); +} + +/* Write an 8-bit quantity to a buffer. */ +void +input_store8(struct buffer *b, uint8_t n) +{ + buffer_write(b, &n, sizeof n); +} + +/* Write a 16-bit argument to a buffer. */ +void +input_store16(struct buffer *b, uint16_t n) +{ + buffer_write(b, &n, sizeof n); +} + +/* Extract an 8-bit quantity from a buffer. */ +uint8_t +input_extract8(struct buffer *b) +{ + uint8_t n; + + buffer_read(b, &n, sizeof n); + return (n); +} + +/* Extract a 16-bit argument from a pointer. */ +uint16_t +input_extract16(struct buffer *b) +{ + uint16_t n; + + buffer_read(b, &n, sizeof n); + return (n); +} -- cgit