tmux/input.c
2010-12-29 21:49:06 +00:00

1515 lines
36 KiB
C

/* $OpenBSD$ */
/*
* 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 <stdlib.h>
#include <string.h>
#include "tmux.h"
/*
* Based on the description by Paul Williams at:
*
* http://vt100.net/emu/dec_ansi_parser
*
* With the following changes:
*
* - 7-bit only.
*
* - Support for UTF-8.
*
* - OSC (but not APC) may be terminated by \007 as well as ST.
*
* - A state for APC similar to OSC. Some terminals appear to use this to set
* the title.
*
* - A state for the screen \033k...\033\\ sequence to rename a window. This is
* pretty stupid but not supporting it is more trouble than it is worth.
*/
/* Helper functions. */
int input_split(struct input_ctx *);
int input_get(struct input_ctx *, u_int, int, int);
void input_reply(struct input_ctx *, const char *, ...);
/* Transition entry/exit handlers. */
void input_clear(struct input_ctx *);
void input_enter_dcs(struct input_ctx *);
void input_exit_dcs(struct input_ctx *);
void input_enter_osc(struct input_ctx *);
void input_exit_osc(struct input_ctx *);
void input_enter_apc(struct input_ctx *);
void input_exit_apc(struct input_ctx *);
void input_enter_rename(struct input_ctx *);
void input_exit_rename(struct input_ctx *);
/* Input state handlers. */
int input_print(struct input_ctx *);
int input_intermediate(struct input_ctx *);
int input_parameter(struct input_ctx *);
int input_input(struct input_ctx *);
int input_c0_dispatch(struct input_ctx *);
int input_esc_dispatch(struct input_ctx *);
int input_csi_dispatch(struct input_ctx *);
void input_csi_dispatch_sgr(struct input_ctx *);
int input_utf8_open(struct input_ctx *);
int input_utf8_add(struct input_ctx *);
int input_utf8_close(struct input_ctx *);
/* Command table comparison function. */
int input_table_compare(const void *, const void *);
/* Command table entry. */
struct input_table_entry {
int ch;
const char *interm;
int type;
};
/* Escape commands. */
enum input_esc_type {
INPUT_ESC_DECALN,
INPUT_ESC_DECKPAM,
INPUT_ESC_DECKPNM,
INPUT_ESC_DECRC,
INPUT_ESC_DECSC,
INPUT_ESC_HTS,
INPUT_ESC_IND,
INPUT_ESC_NEL,
INPUT_ESC_RI,
INPUT_ESC_RIS,
INPUT_ESC_SCSOFF_G0,
INPUT_ESC_SCSON_G0,
};
/* Escape command table. */
const struct input_table_entry input_esc_table[] = {
{ '0', "(", INPUT_ESC_SCSOFF_G0 },
{ '7', "", INPUT_ESC_DECSC },
{ '8', "", INPUT_ESC_DECRC },
{ '8', "#", INPUT_ESC_DECALN },
{ '=', "", INPUT_ESC_DECKPAM },
{ '>', "", INPUT_ESC_DECKPNM },
{ 'B', "(", INPUT_ESC_SCSON_G0 },
{ 'D', "", INPUT_ESC_IND },
{ 'E', "", INPUT_ESC_NEL },
{ 'H', "", INPUT_ESC_HTS },
{ 'M', "", INPUT_ESC_RI },
{ 'c', "", INPUT_ESC_RIS },
};
/* Control (CSI) commands. */
enum input_csi_type {
INPUT_CSI_CBT,
INPUT_CSI_CUB,
INPUT_CSI_CUD,
INPUT_CSI_CUF,
INPUT_CSI_CUP,
INPUT_CSI_CUU,
INPUT_CSI_DA,
INPUT_CSI_DCH,
INPUT_CSI_DECSTBM,
INPUT_CSI_DL,
INPUT_CSI_DSR,
INPUT_CSI_ED,
INPUT_CSI_EL,
INPUT_CSI_HPA,
INPUT_CSI_ICH,
INPUT_CSI_IL,
INPUT_CSI_RM,
INPUT_CSI_RM_PRIVATE,
INPUT_CSI_SGR,
INPUT_CSI_SM,
INPUT_CSI_SM_PRIVATE,
INPUT_CSI_TBC,
INPUT_CSI_VPA,
};
/* Control (CSI) command table. */
const struct input_table_entry input_csi_table[] = {
{ '@', "", INPUT_CSI_ICH },
{ 'A', "", INPUT_CSI_CUU },
{ 'B', "", INPUT_CSI_CUD },
{ 'C', "", INPUT_CSI_CUF },
{ 'D', "", INPUT_CSI_CUB },
{ 'G', "", INPUT_CSI_HPA },
{ 'H', "", INPUT_CSI_CUP },
{ 'J', "", INPUT_CSI_ED },
{ 'K', "", INPUT_CSI_EL },
{ 'L', "", INPUT_CSI_IL },
{ 'M', "", INPUT_CSI_DL },
{ 'P', "", INPUT_CSI_DCH },
{ 'Z', "", INPUT_CSI_CBT },
{ 'c', "", INPUT_CSI_DA },
{ 'd', "", INPUT_CSI_VPA },
{ 'f', "", INPUT_CSI_CUP },
{ 'g', "", INPUT_CSI_TBC },
{ 'h', "", INPUT_CSI_SM },
{ 'h', "?", INPUT_CSI_SM_PRIVATE },
{ 'l', "", INPUT_CSI_RM },
{ 'l', "?", INPUT_CSI_RM_PRIVATE },
{ 'm', "", INPUT_CSI_SGR },
{ 'n', "", INPUT_CSI_DSR },
{ 'r', "", INPUT_CSI_DECSTBM },
};
/* Input transition. */
struct input_transition {
int first;
int last;
int (*handler)(struct input_ctx *);
const struct input_state *state;
};
/* Input state. */
struct input_state {
const char *name;
void (*enter)(struct input_ctx *);
void (*exit)(struct input_ctx *);
const struct input_transition *transitions;
};
/* State transitions available from all states. */
#define INPUT_STATE_ANYWHERE \
{ 0x18, 0x18, input_c0_dispatch, &input_state_ground }, \
{ 0x1a, 0x1a, input_c0_dispatch, &input_state_ground }, \
{ 0x1b, 0x1b, NULL, &input_state_esc_enter }
/* Forward declarations of state tables. */
const struct input_transition input_state_ground_table[];
const struct input_transition input_state_esc_enter_table[];
const struct input_transition input_state_esc_intermediate_table[];
const struct input_transition input_state_csi_enter_table[];
const struct input_transition input_state_csi_parameter_table[];
const struct input_transition input_state_csi_intermediate_table[];
const struct input_transition input_state_csi_ignore_table[];
const struct input_transition input_state_dcs_enter_table[];
const struct input_transition input_state_dcs_parameter_table[];
const struct input_transition input_state_dcs_intermediate_table[];
const struct input_transition input_state_dcs_handler_table[];
const struct input_transition input_state_dcs_ignore_table[];
const struct input_transition input_state_osc_string_table[];
const struct input_transition input_state_apc_string_table[];
const struct input_transition input_state_rename_string_table[];
const struct input_transition input_state_consume_st_table[];
const struct input_transition input_state_utf8_three_table[];
const struct input_transition input_state_utf8_two_table[];
const struct input_transition input_state_utf8_one_table[];
/* ground state definition. */
const struct input_state input_state_ground = {
"ground",
NULL, NULL,
input_state_ground_table
};
/* esc_enter state definition. */
const struct input_state input_state_esc_enter = {
"esc_enter",
input_clear, NULL,
input_state_esc_enter_table
};
/* esc_intermediate state definition. */
const struct input_state input_state_esc_intermediate = {
"esc_intermediate",
NULL, NULL,
input_state_esc_intermediate_table
};
/* csi_enter state definition. */
const struct input_state input_state_csi_enter = {
"csi_enter",
input_clear, NULL,
input_state_csi_enter_table
};
/* csi_parameter state definition. */
const struct input_state input_state_csi_parameter = {
"csi_parameter",
NULL, NULL,
input_state_csi_parameter_table
};
/* csi_intermediate state definition. */
const struct input_state input_state_csi_intermediate = {
"csi_intermediate",
NULL, NULL,
input_state_csi_intermediate_table
};
/* csi_ignore state definition. */
const struct input_state input_state_csi_ignore = {
"csi_ignore",
NULL, NULL,
input_state_csi_ignore_table
};
/* dcs_enter state definition. */
const struct input_state input_state_dcs_enter = {
"dcs_enter",
input_clear, NULL,
input_state_dcs_enter_table
};
/* dcs_parameter state definition. */
const struct input_state input_state_dcs_parameter = {
"dcs_parameter",
NULL, NULL,
input_state_dcs_parameter_table
};
/* dcs_intermediate state definition. */
const struct input_state input_state_dcs_intermediate = {
"dcs_intermediate",
NULL, NULL,
input_state_dcs_intermediate_table
};
/* dcs_handler state definition. */
const struct input_state input_state_dcs_handler = {
"dcs_handler",
input_enter_dcs, input_exit_dcs,
input_state_dcs_handler_table
};
/* dcs_ignore state definition. */
const struct input_state input_state_dcs_ignore = {
"dcs_ignore",
NULL, NULL,
input_state_dcs_ignore_table
};
/* osc_string state definition. */
const struct input_state input_state_osc_string = {
"osc_string",
input_enter_osc, input_exit_osc,
input_state_osc_string_table
};
/* apc_string state definition. */
const struct input_state input_state_apc_string = {
"apc_string",
input_enter_apc, input_exit_apc,
input_state_apc_string_table
};
/* rename_string state definition. */
const struct input_state input_state_rename_string = {
"rename_string",
input_enter_rename, input_exit_rename,
input_state_rename_string_table
};
/* consume_st state definition. */
const struct input_state input_state_consume_st = {
"consume_st",
NULL, NULL,
input_state_consume_st_table
};
/* utf8_three state definition. */
const struct input_state input_state_utf8_three = {
"utf8_three",
NULL, NULL,
input_state_utf8_three_table
};
/* utf8_two state definition. */
const struct input_state input_state_utf8_two = {
"utf8_two",
NULL, NULL,
input_state_utf8_two_table
};
/* utf8_one state definition. */
const struct input_state input_state_utf8_one = {
"utf8_one",
NULL, NULL,
input_state_utf8_one_table
};
/* ground state table. */
const struct input_transition input_state_ground_table[] = {
INPUT_STATE_ANYWHERE,
{ 0x00, 0x17, input_c0_dispatch, NULL },
{ 0x19, 0x19, input_c0_dispatch, NULL },
{ 0x1c, 0x1f, input_c0_dispatch, NULL },
{ 0x20, 0x7e, input_print, NULL },
{ 0x7f, 0x7f, NULL, NULL },
{ 0x80, 0xc1, input_print, NULL },
{ 0xc2, 0xdf, input_utf8_open, &input_state_utf8_one },
{ 0xe0, 0xef, input_utf8_open, &input_state_utf8_two },
{ 0xf0, 0xf4, input_utf8_open, &input_state_utf8_three },
{ 0xf5, 0xff, input_print, NULL },
{ -1, -1, NULL, NULL }
};
/* esc_enter state table. */
const struct input_transition input_state_esc_enter_table[] = {
INPUT_STATE_ANYWHERE,
{ 0x00, 0x17, input_c0_dispatch, NULL },
{ 0x19, 0x19, input_c0_dispatch, NULL },
{ 0x1c, 0x1f, input_c0_dispatch, NULL },
{ 0x20, 0x2f, input_intermediate, &input_state_esc_intermediate },
{ 0x30, 0x4f, input_esc_dispatch, &input_state_ground },
{ 0x50, 0x50, NULL, &input_state_dcs_enter },
{ 0x51, 0x57, input_esc_dispatch, &input_state_ground },
{ 0x58, 0x58, NULL, &input_state_consume_st },
{ 0x59, 0x59, input_esc_dispatch, &input_state_ground },
{ 0x5a, 0x5a, input_esc_dispatch, &input_state_ground },
{ 0x5b, 0x5b, NULL, &input_state_csi_enter },
{ 0x5c, 0x5c, input_esc_dispatch, &input_state_ground },
{ 0x5d, 0x5d, NULL, &input_state_osc_string },
{ 0x5e, 0x5e, NULL, &input_state_consume_st },
{ 0x5f, 0x5f, NULL, &input_state_apc_string },
{ 0x60, 0x6a, input_esc_dispatch, &input_state_ground },
{ 0x6b, 0x6b, NULL, &input_state_rename_string },
{ 0x6c, 0x7e, input_esc_dispatch, &input_state_ground },
{ 0x7f, 0xff, NULL, NULL },
{ -1, -1, NULL, NULL }
};
/* esc_interm state table. */
const struct input_transition input_state_esc_intermediate_table[] = {
INPUT_STATE_ANYWHERE,
{ 0x00, 0x17, input_c0_dispatch, NULL },
{ 0x19, 0x19, input_c0_dispatch, NULL },
{ 0x1c, 0x1f, input_c0_dispatch, NULL },
{ 0x20, 0x2f, input_intermediate, NULL },
{ 0x30, 0x7e, input_esc_dispatch, &input_state_ground },
{ 0x7f, 0xff, NULL, NULL },
{ -1, -1, NULL, NULL }
};
/* csi_enter state table. */
const struct input_transition input_state_csi_enter_table[] = {
INPUT_STATE_ANYWHERE,
{ 0x00, 0x17, input_c0_dispatch, NULL },
{ 0x19, 0x19, input_c0_dispatch, NULL },
{ 0x1c, 0x1f, input_c0_dispatch, NULL },
{ 0x20, 0x2f, input_intermediate, &input_state_csi_intermediate },
{ 0x30, 0x39, input_parameter, &input_state_csi_parameter },
{ 0x3a, 0x3a, NULL, &input_state_csi_ignore },
{ 0x3b, 0x3b, input_parameter, &input_state_csi_parameter },
{ 0x3c, 0x3f, input_intermediate, &input_state_csi_parameter },
{ 0x40, 0x7e, input_csi_dispatch, &input_state_ground },
{ 0x7f, 0xff, NULL, NULL },
{ -1, -1, NULL, NULL }
};
/* csi_parameter state table. */
const struct input_transition input_state_csi_parameter_table[] = {
INPUT_STATE_ANYWHERE,
{ 0x00, 0x17, input_c0_dispatch, NULL },
{ 0x19, 0x19, input_c0_dispatch, NULL },
{ 0x1c, 0x1f, input_c0_dispatch, NULL },
{ 0x20, 0x2f, input_intermediate, &input_state_csi_intermediate },
{ 0x30, 0x39, input_parameter, NULL },
{ 0x3a, 0x3a, NULL, &input_state_csi_ignore },
{ 0x3b, 0x3b, input_parameter, NULL },
{ 0x3c, 0x3f, NULL, &input_state_csi_ignore },
{ 0x40, 0x7e, input_csi_dispatch, &input_state_ground },
{ 0x7f, 0xff, NULL, NULL },
{ -1, -1, NULL, NULL }
};
/* csi_intermediate state table. */
const struct input_transition input_state_csi_intermediate_table[] = {
INPUT_STATE_ANYWHERE,
{ 0x00, 0x17, input_c0_dispatch, NULL },
{ 0x19, 0x19, input_c0_dispatch, NULL },
{ 0x1c, 0x1f, input_c0_dispatch, NULL },
{ 0x20, 0x2f, input_intermediate, NULL },
{ 0x30, 0x3f, NULL, &input_state_csi_ignore },
{ 0x40, 0x7e, input_csi_dispatch, &input_state_ground },
{ 0x7f, 0xff, NULL, NULL },
{ -1, -1, NULL, NULL }
};
/* csi_ignore state table. */
const struct input_transition input_state_csi_ignore_table[] = {
INPUT_STATE_ANYWHERE,
{ 0x00, 0x17, input_c0_dispatch, NULL },
{ 0x19, 0x19, input_c0_dispatch, NULL },
{ 0x1c, 0x1f, input_c0_dispatch, NULL },
{ 0x20, 0x3f, NULL, NULL },
{ 0x40, 0x7e, NULL, &input_state_ground },
{ 0x7f, 0xff, NULL, NULL },
{ -1, -1, NULL, NULL }
};
/* dcs_enter state table. */
const struct input_transition input_state_dcs_enter_table[] = {
INPUT_STATE_ANYWHERE,
{ 0x00, 0x17, NULL, NULL },
{ 0x19, 0x19, NULL, NULL },
{ 0x1c, 0x1f, NULL, NULL },
{ 0x20, 0x2f, input_intermediate, &input_state_dcs_intermediate },
{ 0x30, 0x39, input_parameter, &input_state_dcs_parameter },
{ 0x3a, 0x3a, NULL, &input_state_dcs_ignore },
{ 0x3b, 0x3b, input_parameter, &input_state_dcs_parameter },
{ 0x3c, 0x3f, input_intermediate, &input_state_dcs_parameter },
{ 0x40, 0x7e, NULL, &input_state_dcs_handler },
{ 0x7f, 0xff, NULL, NULL },
{ -1, -1, NULL, NULL }
};
/* dcs_parameter state table. */
const struct input_transition input_state_dcs_parameter_table[] = {
INPUT_STATE_ANYWHERE,
{ 0x00, 0x17, NULL, NULL },
{ 0x19, 0x19, NULL, NULL },
{ 0x1c, 0x1f, NULL, NULL },
{ 0x20, 0x2f, input_intermediate, &input_state_dcs_intermediate },
{ 0x30, 0x39, input_parameter, NULL },
{ 0x3a, 0x3a, NULL, &input_state_dcs_ignore },
{ 0x3b, 0x3b, input_parameter, NULL },
{ 0x3c, 0x3f, NULL, &input_state_dcs_ignore },
{ 0x40, 0x7e, NULL, &input_state_dcs_handler },
{ 0x7f, 0xff, NULL, NULL },
{ -1, -1, NULL, NULL }
};
/* dcs_interm state table. */
const struct input_transition input_state_dcs_intermediate_table[] = {
INPUT_STATE_ANYWHERE,
{ 0x00, 0x17, NULL, NULL },
{ 0x19, 0x19, NULL, NULL },
{ 0x1c, 0x1f, NULL, NULL },
{ 0x20, 0x2f, input_intermediate, NULL },
{ 0x30, 0x3f, NULL, &input_state_dcs_ignore },
{ 0x40, 0x7e, NULL, &input_state_dcs_handler },
{ 0x7f, 0xff, NULL, NULL },
{ -1, -1, NULL, NULL }
};
/* dcs_handler state table. */
const struct input_transition input_state_dcs_handler_table[] = {
INPUT_STATE_ANYWHERE,
{ 0x00, 0x17, NULL, NULL },
{ 0x19, 0x19, input_input, NULL },
{ 0x1c, 0x1f, input_input, NULL },
{ 0x20, 0x7e, input_input, NULL },
{ 0x7f, 0xff, NULL, NULL },
{ -1, -1, NULL, NULL }
};
/* device_ignore state table. */
const struct input_transition input_state_dcs_ignore_table[] = {
INPUT_STATE_ANYWHERE,
{ 0x00, 0x17, NULL, NULL },
{ 0x19, 0x19, NULL, NULL },
{ 0x1c, 0x1f, NULL, NULL },
{ 0x20, 0xff, NULL, NULL },
{ -1, -1, NULL, NULL }
};
/* osc_string state table. */
const struct input_transition input_state_osc_string_table[] = {
INPUT_STATE_ANYWHERE,
{ 0x00, 0x06, NULL, NULL },
{ 0x07, 0x07, NULL, &input_state_ground },
{ 0x08, 0x17, NULL, NULL },
{ 0x19, 0x19, NULL, NULL },
{ 0x1c, 0x1f, NULL, NULL },
{ 0x20, 0xff, input_input, NULL },
{ -1, -1, NULL, NULL }
};
/* apc_string state table. */
const struct input_transition input_state_apc_string_table[] = {
INPUT_STATE_ANYWHERE,
{ 0x00, 0x17, NULL, NULL },
{ 0x19, 0x19, NULL, NULL },
{ 0x1c, 0x1f, NULL, NULL },
{ 0x20, 0xff, input_input, NULL },
{ -1, -1, NULL, NULL }
};
/* rename_string state table. */
const struct input_transition input_state_rename_string_table[] = {
INPUT_STATE_ANYWHERE,
{ 0x00, 0x17, NULL, NULL },
{ 0x19, 0x19, NULL, NULL },
{ 0x1c, 0x1f, NULL, NULL },
{ 0x20, 0xff, input_input, NULL },
{ -1, -1, NULL, NULL }
};
/* consume_st state table. */
const struct input_transition input_state_consume_st_table[] = {
INPUT_STATE_ANYWHERE,
{ 0x00, 0x17, NULL, NULL },
{ 0x19, 0x19, NULL, NULL },
{ 0x1c, 0x1f, NULL, NULL },
{ 0x20, 0xff, NULL, NULL },
{ -1, -1, NULL, NULL }
};
/* utf8_three state table. */
const struct input_transition input_state_utf8_three_table[] = {
/* No INPUT_STATE_ANYWHERE */
{ 0x00, 0x7f, NULL, &input_state_ground },
{ 0x80, 0xbf, input_utf8_add, &input_state_utf8_two },
{ 0xc0, 0xff, NULL, &input_state_ground },
{ -1, -1, NULL, NULL }
};
/* utf8_two state table. */
const struct input_transition input_state_utf8_two_table[] = {
/* No INPUT_STATE_ANYWHERE */
{ 0x00, 0x7f, NULL, &input_state_ground },
{ 0x80, 0xbf, input_utf8_add, &input_state_utf8_one },
{ 0xc0, 0xff, NULL, &input_state_ground },
{ -1, -1, NULL, NULL }
};
/* utf8_one state table. */
const struct input_transition input_state_utf8_one_table[] = {
/* No INPUT_STATE_ANYWHERE */
{ 0x00, 0x7f, NULL, &input_state_ground },
{ 0x80, 0xbf, input_utf8_close, &input_state_ground },
{ 0xc0, 0xff, NULL, &input_state_ground },
{ -1, -1, NULL, NULL }
};
/* Input table compare. */
int
input_table_compare(const void *key, const void *value)
{
const struct input_ctx *ictx = key;
const struct input_table_entry *entry = value;
if (ictx->ch != entry->ch)
return (ictx->ch - entry->ch);
return (strcmp(ictx->interm_buf, entry->interm));
}
/* Initialise input parser. */
void
input_init(struct window_pane *wp)
{
struct input_ctx *ictx = &wp->ictx;
memcpy(&ictx->cell, &grid_default_cell, sizeof ictx->cell);
memcpy(&ictx->old_cell, &grid_default_cell, sizeof ictx->old_cell);
ictx->old_cx = 0;
ictx->old_cy = 0;
*ictx->interm_buf = '\0';
ictx->interm_len = 0;
*ictx->param_buf = '\0';
ictx->param_len = 0;
ictx->state = &input_state_ground;
ictx->flags = 0;
}
/* Destroy input parser. */
void
input_free(unused struct window_pane *wp)
{
}
/* Parse input. */
void
input_parse(struct window_pane *wp)
{
struct input_ctx *ictx = &wp->ictx;
const struct input_transition *itr;
struct evbuffer *evb = wp->event->input;
u_char *buf;
size_t len, off;
if (EVBUFFER_LENGTH(evb) == 0)
return;
wp->window->flags |= WINDOW_ACTIVITY;
wp->window->flags &= ~WINDOW_SILENCE;
/*
* Open the screen. Use NULL wp if there is a mode set as don't want to
* update the tty.
*/
if (wp->mode == NULL)
screen_write_start(&ictx->ctx, wp, &wp->base);
else
screen_write_start(&ictx->ctx, NULL, &wp->base);
ictx->wp = wp;
buf = EVBUFFER_DATA(evb);
len = EVBUFFER_LENGTH(evb);
off = 0;
/* Parse the input. */
while (off < len) {
ictx->ch = buf[off++];
log_debug("%s: '%c' %s", __func__, ictx->ch, ictx->state->name);
/* Find the transition. */
itr = ictx->state->transitions;
while (itr->first != -1 && itr->last != -1) {
if (ictx->ch >= itr->first && ictx->ch <= itr->last)
break;
itr++;
}
if (itr->first == -1 || itr->last == -1) {
/* No transition? Eh? */
fatalx("No transition from state!");
}
/*
* Execute the handler, if any. Don't switch state if it
* returns non-zero.
*/
if (itr->handler != NULL && itr->handler(ictx) != 0)
continue;
/* And switch state, if necessary. */
if (itr->state != NULL) {
if (ictx->state->exit != NULL)
ictx->state->exit(ictx);
ictx->state = itr->state;
if (ictx->state->enter != NULL)
ictx->state->enter(ictx);
}
}
/* Close the screen. */
screen_write_stop(&ictx->ctx);
evbuffer_drain(evb, len);
}
/* Split the parameter list (if any). */
int
input_split(struct input_ctx *ictx)
{
const char *errstr;
char *ptr, *out;
int n;
ictx->param_list_len = 0;
if (ictx->param_len == 0)
return (0);
ptr = ictx->param_buf;
while ((out = strsep(&ptr, ";")) != NULL) {
if (*out == '\0')
n = -1;
else {
n = strtonum(out, 0, INT_MAX, &errstr);
if (errstr != NULL)
return (-1);
}
ictx->param_list[ictx->param_list_len++] = n;
if (ictx->param_list_len == nitems(ictx->param_list))
return (-1);
}
return (0);
}
/* Get an argument or return default value..*/
int
input_get(struct input_ctx *ictx, u_int validx, int minval, int defval)
{
int retval;
if (validx >= ictx->param_list_len)
return (defval);
retval = ictx->param_list[validx];
if (retval == -1)
return (defval);
if (retval < minval)
return (minval);
return (retval);
}
/* Reply to terminal query. */
void
input_reply(struct input_ctx *ictx, const char *fmt, ...)
{
va_list ap;
char *reply;
va_start(ap, fmt);
vasprintf(&reply, fmt, ap);
va_end(ap);
bufferevent_write(ictx->wp->event, reply, strlen(reply));
xfree(reply);
}
/* Clear saved state. */
void
input_clear(struct input_ctx *ictx)
{
*ictx->interm_buf = '\0';
ictx->interm_len = 0;
*ictx->param_buf = '\0';
ictx->param_len = 0;
ictx->flags &= ~INPUT_DISCARD;
}
/* Output this character to the screen. */
int
input_print(struct input_ctx *ictx)
{
ictx->cell.data = ictx->ch;
screen_write_cell(&ictx->ctx, &ictx->cell, NULL);
return (0);
}
/* Collect intermediate string. */
int
input_intermediate(struct input_ctx *ictx)
{
if (ictx->interm_len == (sizeof ictx->interm_buf) - 1)
ictx->flags |= INPUT_DISCARD;
else {
ictx->interm_buf[ictx->interm_len++] = ictx->ch;
ictx->interm_buf[ictx->interm_len] = '\0';
}
return (0);
}
/* Collect parameter string. */
int
input_parameter(struct input_ctx *ictx)
{
if (ictx->param_len == (sizeof ictx->param_buf) - 1)
ictx->flags |= INPUT_DISCARD;
else {
ictx->param_buf[ictx->param_len++] = ictx->ch;
ictx->param_buf[ictx->param_len] = '\0';
}
return (0);
}
/* Collect input string. */
int
input_input(struct input_ctx *ictx)
{
if (ictx->input_len == (sizeof ictx->input_buf) - 1)
ictx->flags |= INPUT_DISCARD;
else {
ictx->input_buf[ictx->input_len++] = ictx->ch;
ictx->input_buf[ictx->input_len] = '\0';
}
return (0);
}
/* Execute C0 control sequence. */
int
input_c0_dispatch(struct input_ctx *ictx)
{
struct screen_write_ctx *sctx = &ictx->ctx;
struct window_pane *wp = ictx->wp;
struct screen *s = sctx->s;
log_debug("%s: '%c", __func__, ictx->ch);
switch (ictx->ch) {
case '\000': /* NUL */
break;
case '\007': /* BEL */
wp->window->flags |= WINDOW_BELL;
break;
case '\010': /* BS */
screen_write_backspace(sctx);
break;
case '\011': /* HT */
/* Don't tab beyond the end of the line. */
if (s->cx >= screen_size_x(s) - 1)
break;
/* Find the next tab point, or use the last column if none. */
do {
s->cx++;
if (bit_test(s->tabs, s->cx))
break;
} while (s->cx < screen_size_x(s) - 1);
break;
case '\012': /* LF */
case '\013': /* VT */
case '\014': /* FF */
screen_write_linefeed(sctx, 0);
break;
case '\015': /* CR */
screen_write_carriagereturn(sctx);
break;
case '\016': /* SO */
ictx->cell.attr |= GRID_ATTR_CHARSET;
break;
case '\017': /* SI */
ictx->cell.attr &= ~GRID_ATTR_CHARSET;
break;
default:
log_debug("%s: unknown '%c'", __func__, ictx->ch);
break;
}
return (0);
}
/* Execute escape sequence. */
int
input_esc_dispatch(struct input_ctx *ictx)
{
struct screen_write_ctx *sctx = &ictx->ctx;
struct screen *s = sctx->s;
struct input_table_entry *entry;
if (ictx->flags & INPUT_DISCARD)
return (0);
log_debug("%s: '%c', %s", __func__, ictx->ch, ictx->interm_buf);
entry = bsearch(ictx, input_esc_table, nitems(input_esc_table),
sizeof input_esc_table[0], input_table_compare);
if (entry == NULL) {
log_debug("%s: unknown '%c'", __func__, ictx->ch);
return (0);
}
switch (entry->type) {
case INPUT_ESC_RIS:
memcpy(&ictx->cell, &grid_default_cell, sizeof ictx->cell);
memcpy(&ictx->old_cell, &ictx->cell, sizeof ictx->old_cell);
ictx->old_cx = 0;
ictx->old_cy = 0;
screen_reset_tabs(sctx->s);
screen_write_scrollregion(sctx, 0, screen_size_y(sctx->s) - 1);
screen_write_insertmode(sctx, 0);
screen_write_kcursormode(sctx, 0);
screen_write_kkeypadmode(sctx, 0);
screen_write_mousemode_off(sctx);
screen_write_clearscreen(sctx);
screen_write_cursormove(sctx, 0, 0);
break;
case INPUT_ESC_IND:
screen_write_linefeed(sctx, 0);
break;
case INPUT_ESC_NEL:
screen_write_carriagereturn(sctx);
screen_write_linefeed(sctx, 0);
break;
case INPUT_ESC_HTS:
if (s->cx < screen_size_x(s))
bit_set(s->tabs, s->cx);
break;
case INPUT_ESC_RI:
screen_write_reverseindex(sctx);
break;
case INPUT_ESC_DECKPAM:
screen_write_kkeypadmode(sctx, 1);
break;
case INPUT_ESC_DECKPNM:
screen_write_kkeypadmode(sctx, 0);
break;
case INPUT_ESC_DECSC:
memcpy(&ictx->old_cell, &ictx->cell, sizeof ictx->old_cell);
ictx->old_cx = s->cx;
ictx->old_cy = s->cy;
break;
case INPUT_ESC_DECRC:
memcpy(&ictx->cell, &ictx->old_cell, sizeof ictx->cell);
screen_write_cursormove(sctx, ictx->old_cx, ictx->old_cy);
break;
case INPUT_ESC_DECALN:
screen_write_alignmenttest(sctx);
break;
case INPUT_ESC_SCSON_G0:
/*
* Not really supported, but fake it up enough for those that
* use it to switch character sets (by redefining G0 to
* graphics set, rather than switching to G1).
*/
ictx->cell.attr &= ~GRID_ATTR_CHARSET;
break;
case INPUT_ESC_SCSOFF_G0:
ictx->cell.attr |= GRID_ATTR_CHARSET;
break;
}
return (0);
}
/* Execute control sequence. */
int
input_csi_dispatch(struct input_ctx *ictx)
{
struct screen_write_ctx *sctx = &ictx->ctx;
struct window_pane *wp = ictx->wp;
struct screen *s = sctx->s;
struct input_table_entry *entry;
int n, m;
if (ictx->flags & INPUT_DISCARD)
return (0);
if (input_split(ictx) != 0)
return (0);
log_debug("%s: '%c' \"%s\" \"%s\"",
__func__, ictx->ch, ictx->interm_buf, ictx->param_buf);
entry = bsearch(ictx, input_csi_table, nitems(input_csi_table),
sizeof input_csi_table[0], input_table_compare);
if (entry == NULL) {
log_debug("%s: unknown '%c'", __func__, ictx->ch);
return (0);
}
switch (entry->type) {
case INPUT_CSI_CBT:
/* Find the previous tab point, n times. */
n = input_get(ictx, 0, 1, 1);
while (s->cx > 0 && n-- > 0) {
do
s->cx--;
while (s->cx > 0 && !bit_test(s->tabs, s->cx));
}
break;
case INPUT_CSI_CUB:
screen_write_cursorleft(sctx, input_get(ictx, 0, 1, 1));
break;
case INPUT_CSI_CUD:
screen_write_cursordown(sctx, input_get(ictx, 0, 1, 1));
break;
case INPUT_CSI_CUF:
screen_write_cursorright(sctx, input_get(ictx, 0, 1, 1));
break;
case INPUT_CSI_CUP:
n = input_get(ictx, 0, 1, 1);
m = input_get(ictx, 1, 1, 1);
screen_write_cursormove(sctx, m - 1, n - 1);
break;
case INPUT_CSI_CUU:
screen_write_cursorup(sctx, input_get(ictx, 0, 1, 1));
break;
case INPUT_CSI_DA:
switch (input_get(ictx, 0, 0, 0)) {
case 0:
input_reply(ictx, "\033[?1;2c");
break;
default:
log_debug("%s: unknown '%c'", __func__, ictx->ch);
break;
}
break;
case INPUT_CSI_DCH:
screen_write_deletecharacter(sctx, input_get(ictx, 0, 1, 1));
break;
case INPUT_CSI_DECSTBM:
n = input_get(ictx, 0, 1, 1);
m = input_get(ictx, 1, 1, screen_size_y(s));
screen_write_scrollregion(sctx, n - 1, m - 1);
break;
case INPUT_CSI_DL:
screen_write_deleteline(sctx, input_get(ictx, 0, 1, 1));
break;
case INPUT_CSI_DSR:
switch (input_get(ictx, 0, 0, 0)) {
case 5:
input_reply(ictx, "\033[0n");
break;
case 6:
input_reply(ictx, "\033[%u;%uR", s->cy + 1, s->cx + 1);
break;
default:
log_debug("%s: unknown '%c'", __func__, ictx->ch);
break;
}
break;
case INPUT_CSI_ED:
switch (input_get(ictx, 0, 0, 0)) {
case 0:
screen_write_clearendofscreen(sctx);
break;
case 1:
screen_write_clearstartofscreen(sctx);
break;
case 2:
screen_write_clearscreen(sctx);
break;
default:
log_debug("%s: unknown '%c'", __func__, ictx->ch);
break;
}
break;
case INPUT_CSI_EL:
switch (input_get(ictx, 0, 0, 0)) {
case 0:
screen_write_clearendofline(sctx);
break;
case 1:
screen_write_clearstartofline(sctx);
break;
case 2:
screen_write_clearline(sctx);
break;
default:
log_debug("%s: unknown '%c'", __func__, ictx->ch);
break;
}
break;
case INPUT_CSI_HPA:
n = input_get(ictx, 0, 1, 1);
screen_write_cursormove(sctx, n - 1, s->cy);
break;
case INPUT_CSI_ICH:
screen_write_insertcharacter(sctx, input_get(ictx, 0, 1, 1));
break;
case INPUT_CSI_IL:
screen_write_insertline(sctx, input_get(ictx, 0, 1, 1));
break;
case INPUT_CSI_RM:
switch (input_get(ictx, 0, 0, -1)) {
case 4: /* IRM */
screen_write_insertmode(&ictx->ctx, 0);
break;
default:
log_debug("%s: unknown '%c'", __func__, ictx->ch);
break;
}
break;
case INPUT_CSI_RM_PRIVATE:
switch (input_get(ictx, 0, 0, -1)) {
case 1: /* GATM */
screen_write_kcursormode(&ictx->ctx, 0);
break;
case 3: /* DECCOLM */
screen_write_cursormove(&ictx->ctx, 0, 0);
screen_write_clearscreen(&ictx->ctx);
break;
case 25: /* TCEM */
screen_write_cursormode(&ictx->ctx, 0);
break;
case 1000:
case 1001:
case 1002:
case 1003:
screen_write_mousemode_off(&ictx->ctx);
break;
case 1049:
window_pane_alternate_off(wp, &ictx->cell);
break;
default:
log_debug("%s: unknown '%c'", __func__, ictx->ch);
break;
}
break;
case INPUT_CSI_SGR:
input_csi_dispatch_sgr(ictx);
break;
case INPUT_CSI_SM:
switch (input_get(ictx, 0, 0, -1)) {
case 4: /* IRM */
screen_write_insertmode(&ictx->ctx, 1);
break;
default:
log_debug("%s: unknown '%c'", __func__, ictx->ch);
break;
}
break;
case INPUT_CSI_SM_PRIVATE:
switch (input_get(ictx, 0, 0, -1)) {
case 1: /* GATM */
screen_write_kcursormode(&ictx->ctx, 1);
break;
case 3: /* DECCOLM */
screen_write_cursormove(&ictx->ctx, 0, 0);
screen_write_clearscreen(&ictx->ctx);
break;
case 25: /* TCEM */
screen_write_cursormode(&ictx->ctx, 1);
break;
case 1000:
screen_write_mousemode_on(
&ictx->ctx, MODE_MOUSE_STANDARD);
break;
case 1001:
screen_write_mousemode_on(
&ictx->ctx, MODE_MOUSE_HIGHLIGHT);
break;
case 1002:
screen_write_mousemode_on(
&ictx->ctx, MODE_MOUSE_BUTTON);
break;
case 1003:
screen_write_mousemode_on(&ictx->ctx, MODE_MOUSE_ANY);
break;
case 1049:
window_pane_alternate_on(wp, &ictx->cell);
break;
default:
log_debug("%s: unknown '%c'", __func__, ictx->ch);
break;
}
break;
case INPUT_CSI_TBC:
switch (input_get(ictx, 0, 0, 0)) {
case 0:
if (s->cx < screen_size_x(s))
bit_clear(s->tabs, s->cx);
break;
case 3:
bit_nclear(s->tabs, 0, screen_size_x(s) - 1);
break;
default:
log_debug("%s: unknown '%c'", __func__, ictx->ch);
break;
}
break;
case INPUT_CSI_VPA:
n = input_get(ictx, 0, 1, 1);
screen_write_cursormove(sctx, s->cx, n - 1);
break;
}
return (0);
}
/* Handle CSI SGR. */
void
input_csi_dispatch_sgr(struct input_ctx *ictx)
{
struct grid_cell *gc = &ictx->cell;
u_int i;
int n, m;
u_char attr;
if (ictx->param_list_len == 0) {
attr = gc->attr;
memcpy(gc, &grid_default_cell, sizeof *gc);
gc->attr |= (attr & GRID_ATTR_CHARSET);
return;
}
for (i = 0; i < ictx->param_list_len; i++) {
n = input_get(ictx, i, 0, 0);
if (n == 38 || n == 48) {
i++;
if (input_get(ictx, i, 0, -1) != 5)
continue;
i++;
m = input_get(ictx, i, 0, -1);
if (m == -1) {
if (n == 38) {
gc->flags &= ~GRID_FLAG_FG256;
gc->fg = 8;
} else if (n == 48) {
gc->flags &= ~GRID_FLAG_BG256;
gc->fg = 8;
}
} else {
if (n == 38) {
gc->flags |= GRID_FLAG_FG256;
gc->fg = m;
} else if (n == 48) {
gc->flags |= GRID_FLAG_BG256;
gc->bg = m;
}
}
continue;
}
switch (n) {
case 0:
case 10:
attr = gc->attr;
memcpy(gc, &grid_default_cell, sizeof *gc);
gc->attr |= (attr & GRID_ATTR_CHARSET);
break;
case 1:
gc->attr |= GRID_ATTR_BRIGHT;
break;
case 2:
gc->attr |= GRID_ATTR_DIM;
break;
case 3:
gc->attr |= GRID_ATTR_ITALICS;
break;
case 4:
gc->attr |= GRID_ATTR_UNDERSCORE;
break;
case 5:
gc->attr |= GRID_ATTR_BLINK;
break;
case 7:
gc->attr |= GRID_ATTR_REVERSE;
break;
case 8:
gc->attr |= GRID_ATTR_HIDDEN;
break;
case 22:
gc->attr &= ~(GRID_ATTR_BRIGHT|GRID_ATTR_DIM);
break;
case 23:
gc->attr &= ~GRID_ATTR_ITALICS;
break;
case 24:
gc->attr &= ~GRID_ATTR_UNDERSCORE;
break;
case 25:
gc->attr &= ~GRID_ATTR_BLINK;
break;
case 27:
gc->attr &= ~GRID_ATTR_REVERSE;
break;
case 30:
case 31:
case 32:
case 33:
case 34:
case 35:
case 36:
case 37:
gc->flags &= ~GRID_FLAG_FG256;
gc->fg = n - 30;
break;
case 39:
gc->flags &= ~GRID_FLAG_FG256;
gc->fg = 8;
break;
case 40:
case 41:
case 42:
case 43:
case 44:
case 45:
case 46:
case 47:
gc->flags &= ~GRID_FLAG_BG256;
gc->bg = n - 40;
break;
case 49:
gc->flags &= ~GRID_FLAG_BG256;
gc->bg = 8;
break;
case 90:
case 91:
case 92:
case 93:
case 94:
case 95:
case 96:
case 97:
gc->flags &= ~GRID_FLAG_FG256;
gc->fg = n;
break;
case 100:
case 101:
case 102:
case 103:
case 104:
case 105:
case 106:
case 107:
gc->flags &= ~GRID_FLAG_BG256;
gc->bg = n;
break;
}
}
}
/* DCS string started. */
void
input_enter_dcs(struct input_ctx *ictx)
{
log_debug("%s", __func__);
ictx->input_len = 0;
}
/* DCS terminator (ST) received. */
void
input_exit_dcs(unused struct input_ctx *ictx)
{
log_debug("%s", __func__);
}
/* OSC string started. */
void
input_enter_osc(struct input_ctx *ictx)
{
log_debug("%s", __func__);
ictx->input_len = 0;
}
/* OSC terminator (ST) received. */
void
input_exit_osc(struct input_ctx *ictx)
{
if (ictx->flags & INPUT_DISCARD)
return;
log_debug("%s: \"%s\"", __func__, ictx->input_buf);
if (ictx->input_len < 2 || ictx->input_buf[1] != ';')
return;
if (ictx->input_buf[0] != '0' && ictx->input_buf[0] != '2')
return;
screen_set_title(ictx->ctx.s, ictx->input_buf + 2);
server_status_window(ictx->wp->window);
}
/* APC string started. */
void
input_enter_apc(struct input_ctx *ictx)
{
log_debug("%s", __func__);
ictx->input_len = 0;
}
/* APC terminator (ST) received. */
void
input_exit_apc(struct input_ctx *ictx)
{
if (ictx->flags & INPUT_DISCARD)
return;
log_debug("%s: \"%s\"", __func__, ictx->input_buf);
screen_set_title(ictx->ctx.s, ictx->input_buf);
server_status_window(ictx->wp->window);
}
/* Rename string started. */
void
input_enter_rename(struct input_ctx *ictx)
{
log_debug("%s", __func__);
ictx->input_len = 0;
}
/* Rename terminator (ST) received. */
void
input_exit_rename(struct input_ctx *ictx)
{
if (ictx->flags & INPUT_DISCARD)
return;
log_debug("%s: \"%s\"", __func__, ictx->input_buf);
xfree(ictx->wp->window->name);
ictx->wp->window->name = xstrdup(ictx->input_buf);
options_set_number(&ictx->wp->window->options, "automatic-rename", 0);
server_status_window(ictx->wp->window);
}
/* Open UTF-8 character. */
int
input_utf8_open(struct input_ctx *ictx)
{
if (!options_get_number(&ictx->wp->window->options, "utf8")) {
/* Print, and do not switch state. */
input_print(ictx);
return (-1);
}
log_debug("%s", __func__);
utf8_open(&ictx->utf8data, ictx->ch);
return (0);
}
/* Append to UTF-8 character. */
int
input_utf8_add(struct input_ctx *ictx)
{
log_debug("%s", __func__);
utf8_append(&ictx->utf8data, ictx->ch);
return (0);
}
/* Close UTF-8 string. */
int
input_utf8_close(struct input_ctx *ictx)
{
log_debug("%s", __func__);
utf8_append(&ictx->utf8data, ictx->ch);
ictx->cell.flags |= GRID_FLAG_UTF8;
screen_write_cell(&ictx->ctx, &ictx->cell, &ictx->utf8data);
ictx->cell.flags &= ~GRID_FLAG_UTF8;
return (0);
}