tmux/input.c

828 lines
17 KiB
C
Raw Blame History

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

/* $Id: input.c,v 1.4 2007-08-28 09:19:50 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
*
* 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 <sys/types.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#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);
if (size > 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_zero(b, CODE_REVERSEINDEX);
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, s->cy, s->sx, s->sy, 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);
}