diff options
Diffstat (limited to 'input.c')
-rw-r--r-- | input.c | 2796 |
1 files changed, 2796 insertions, 0 deletions
@@ -0,0 +1,2796 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> + * + * 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 <netinet/in.h> + +#include <ctype.h> +#include <resolv.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include "tmux.h" + +/* + * Based on the description by Paul Williams at: + * + * https://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. + * + * - Special handling for ESC inside a DCS to allow arbitrary byte sequences to + * be passed to the underlying terminals. + */ + +/* Input parser cell. */ +struct input_cell { + struct grid_cell cell; + int set; + int g0set; /* 1 if ACS */ + int g1set; /* 1 if ACS */ +}; + +/* Input parser argument. */ +struct input_param { + enum { + INPUT_MISSING, + INPUT_NUMBER, + INPUT_STRING + } type; + union { + int num; + char *str; + }; +}; + +/* Input parser context. */ +struct input_ctx { + struct window_pane *wp; + struct bufferevent *event; + struct screen_write_ctx ctx; + struct colour_palette *palette; + + struct input_cell cell; + + struct input_cell old_cell; + u_int old_cx; + u_int old_cy; + int old_mode; + + u_char interm_buf[4]; + size_t interm_len; + + u_char param_buf[64]; + size_t param_len; + +#define INPUT_BUF_START 32 +#define INPUT_BUF_LIMIT 1048576 + u_char *input_buf; + size_t input_len; + size_t input_space; + enum { + INPUT_END_ST, + INPUT_END_BEL + } input_end; + + struct input_param param_list[24]; + u_int param_list_len; + + struct utf8_data utf8data; + int utf8started; + + int ch; + int last; + + int flags; +#define INPUT_DISCARD 0x1 + + const struct input_state *state; + + struct event timer; + + /* + * All input received since we were last in the ground state. Sent to + * control clients on connection. + */ + struct evbuffer *since_ground; +}; + +/* Helper functions. */ +struct input_transition; +static int input_split(struct input_ctx *); +static int input_get(struct input_ctx *, u_int, int, int); +static void printflike(2, 3) input_reply(struct input_ctx *, const char *, ...); +static void input_set_state(struct input_ctx *, + const struct input_transition *); +static void input_reset_cell(struct input_ctx *); + +static void input_osc_4(struct input_ctx *, const char *); +static void input_osc_10(struct input_ctx *, const char *); +static void input_osc_11(struct input_ctx *, const char *); +static void input_osc_12(struct input_ctx *, const char *); +static void input_osc_52(struct input_ctx *, const char *); +static void input_osc_104(struct input_ctx *, const char *); +static void input_osc_110(struct input_ctx *, const char *); +static void input_osc_111(struct input_ctx *, const char *); +static void input_osc_112(struct input_ctx *, const char *); + +/* Transition entry/exit handlers. */ +static void input_clear(struct input_ctx *); +static void input_ground(struct input_ctx *); +static void input_enter_dcs(struct input_ctx *); +static void input_enter_osc(struct input_ctx *); +static void input_exit_osc(struct input_ctx *); +static void input_enter_apc(struct input_ctx *); +static void input_exit_apc(struct input_ctx *); +static void input_enter_rename(struct input_ctx *); +static void input_exit_rename(struct input_ctx *); + +/* Input state handlers. */ +static int input_print(struct input_ctx *); +static int input_intermediate(struct input_ctx *); +static int input_parameter(struct input_ctx *); +static int input_input(struct input_ctx *); +static int input_c0_dispatch(struct input_ctx *); +static int input_esc_dispatch(struct input_ctx *); +static int input_csi_dispatch(struct input_ctx *); +static void input_csi_dispatch_rm(struct input_ctx *); +static void input_csi_dispatch_rm_private(struct input_ctx *); +static void input_csi_dispatch_sm(struct input_ctx *); +static void input_csi_dispatch_sm_private(struct input_ctx *); +static void input_csi_dispatch_winops(struct input_ctx *); +static void input_csi_dispatch_sgr_256(struct input_ctx *, int, u_int *); +static void input_csi_dispatch_sgr_rgb(struct input_ctx *, int, u_int *); +static void input_csi_dispatch_sgr(struct input_ctx *); +static int input_dcs_dispatch(struct input_ctx *); +static int input_top_bit_set(struct input_ctx *); +static int input_end_bel(struct input_ctx *); + +/* Command table comparison function. */ +static 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_SCSG0_OFF, + INPUT_ESC_SCSG0_ON, + INPUT_ESC_SCSG1_OFF, + INPUT_ESC_SCSG1_ON, + INPUT_ESC_ST, +}; + +/* Escape command table. */ +static const struct input_table_entry input_esc_table[] = { + { '0', "(", INPUT_ESC_SCSG0_ON }, + { '0', ")", INPUT_ESC_SCSG1_ON }, + { '7', "", INPUT_ESC_DECSC }, + { '8', "", INPUT_ESC_DECRC }, + { '8', "#", INPUT_ESC_DECALN }, + { '=', "", INPUT_ESC_DECKPAM }, + { '>', "", INPUT_ESC_DECKPNM }, + { 'B', "(", INPUT_ESC_SCSG0_OFF }, + { 'B', ")", INPUT_ESC_SCSG1_OFF }, + { 'D', "", INPUT_ESC_IND }, + { 'E', "", INPUT_ESC_NEL }, + { 'H', "", INPUT_ESC_HTS }, + { 'M', "", INPUT_ESC_RI }, + { '\\', "", INPUT_ESC_ST }, + { 'c', "", INPUT_ESC_RIS }, +}; + +/* Control (CSI) commands. */ +enum input_csi_type { + INPUT_CSI_CBT, + INPUT_CSI_CNL, + INPUT_CSI_CPL, + INPUT_CSI_CUB, + INPUT_CSI_CUD, + INPUT_CSI_CUF, + INPUT_CSI_CUP, + INPUT_CSI_CUU, + INPUT_CSI_DA, + INPUT_CSI_DA_TWO, + INPUT_CSI_DCH, + INPUT_CSI_DECSCUSR, + INPUT_CSI_DECSTBM, + INPUT_CSI_DL, + INPUT_CSI_DSR, + INPUT_CSI_ECH, + INPUT_CSI_ED, + INPUT_CSI_EL, + INPUT_CSI_HPA, + INPUT_CSI_ICH, + INPUT_CSI_IL, + INPUT_CSI_MODOFF, + INPUT_CSI_MODSET, + INPUT_CSI_RCP, + INPUT_CSI_REP, + INPUT_CSI_RM, + INPUT_CSI_RM_PRIVATE, + INPUT_CSI_SCP, + INPUT_CSI_SD, + INPUT_CSI_SGR, + INPUT_CSI_SM, + INPUT_CSI_SM_PRIVATE, + INPUT_CSI_SU, + INPUT_CSI_TBC, + INPUT_CSI_VPA, + INPUT_CSI_WINOPS, + INPUT_CSI_XDA, +}; + +/* Control (CSI) command table. */ +static 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 }, + { 'E', "", INPUT_CSI_CNL }, + { 'F', "", INPUT_CSI_CPL }, + { '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 }, + { 'S', "", INPUT_CSI_SU }, + { 'T', "", INPUT_CSI_SD }, + { 'X', "", INPUT_CSI_ECH }, + { 'Z', "", INPUT_CSI_CBT }, + { '`', "", INPUT_CSI_HPA }, + { 'b', "", INPUT_CSI_REP }, + { 'c', "", INPUT_CSI_DA }, + { 'c', ">", INPUT_CSI_DA_TWO }, + { '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 }, + { 'm', ">", INPUT_CSI_MODSET }, + { 'n', "", INPUT_CSI_DSR }, + { 'n', ">", INPUT_CSI_MODOFF }, + { 'q', " ", INPUT_CSI_DECSCUSR }, + { 'q', ">", INPUT_CSI_XDA }, + { 'r', "", INPUT_CSI_DECSTBM }, + { 's', "", INPUT_CSI_SCP }, + { 't', "", INPUT_CSI_WINOPS }, + { 'u', "", INPUT_CSI_RCP }, +}; + +/* 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. */ +static const struct input_transition input_state_ground_table[]; +static const struct input_transition input_state_esc_enter_table[]; +static const struct input_transition input_state_esc_intermediate_table[]; +static const struct input_transition input_state_csi_enter_table[]; +static const struct input_transition input_state_csi_parameter_table[]; +static const struct input_transition input_state_csi_intermediate_table[]; +static const struct input_transition input_state_csi_ignore_table[]; +static const struct input_transition input_state_dcs_enter_table[]; +static const struct input_transition input_state_dcs_parameter_table[]; +static const struct input_transition input_state_dcs_intermediate_table[]; +static const struct input_transition input_state_dcs_handler_table[]; +static const struct input_transition input_state_dcs_escape_table[]; +static const struct input_transition input_state_dcs_ignore_table[]; +static const struct input_transition input_state_osc_string_table[]; +static const struct input_transition input_state_apc_string_table[]; +static const struct input_transition input_state_rename_string_table[]; +static const struct input_transition input_state_consume_st_table[]; + +/* ground state definition. */ +static const struct input_state input_state_ground = { + "ground", + input_ground, NULL, + input_state_ground_table +}; + +/* esc_enter state definition. */ +static const struct input_state input_state_esc_enter = { + "esc_enter", + input_clear, NULL, + input_state_esc_enter_table +}; + +/* esc_intermediate state definition. */ +static const struct input_state input_state_esc_intermediate = { + "esc_intermediate", + NULL, NULL, + input_state_esc_intermediate_table +}; + +/* csi_enter state definition. */ +static const struct input_state input_state_csi_enter = { + "csi_enter", + input_clear, NULL, + input_state_csi_enter_table +}; + +/* csi_parameter state definition. */ +static const struct input_state input_state_csi_parameter = { + "csi_parameter", + NULL, NULL, + input_state_csi_parameter_table +}; + +/* csi_intermediate state definition. */ +static const struct input_state input_state_csi_intermediate = { + "csi_intermediate", + NULL, NULL, + input_state_csi_intermediate_table +}; + +/* csi_ignore state definition. */ +static const struct input_state input_state_csi_ignore = { + "csi_ignore", + NULL, NULL, + input_state_csi_ignore_table +}; + +/* dcs_enter state definition. */ +static const struct input_state input_state_dcs_enter = { + "dcs_enter", + input_enter_dcs, NULL, + input_state_dcs_enter_table +}; + +/* dcs_parameter state definition. */ +static const struct input_state input_state_dcs_parameter = { + "dcs_parameter", + NULL, NULL, + input_state_dcs_parameter_table +}; + +/* dcs_intermediate state definition. */ +static const struct input_state input_state_dcs_intermediate = { + "dcs_intermediate", + NULL, NULL, + input_state_dcs_intermediate_table +}; + +/* dcs_handler state definition. */ +static const struct input_state input_state_dcs_handler = { + "dcs_handler", + NULL, NULL, + input_state_dcs_handler_table +}; + +/* dcs_escape state definition. */ +static const struct input_state input_state_dcs_escape = { + "dcs_escape", + NULL, NULL, + input_state_dcs_escape_table +}; + +/* dcs_ignore state definition. */ +static const struct input_state input_state_dcs_ignore = { + "dcs_ignore", + NULL, NULL, + input_state_dcs_ignore_table +}; + +/* osc_string state definition. */ +static 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. */ +static 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. */ +static 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. */ +static const struct input_state input_state_consume_st = { + "consume_st", + input_enter_rename, NULL, /* rename also waits for ST */ + input_state_consume_st_table +}; + +/* ground state table. */ +static 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, 0xff, input_top_bit_set, NULL }, + + { -1, -1, NULL, NULL } +}; + +/* esc_enter state table. */ +static 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_intermediate state table. */ +static 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. */ +static 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, input_parameter, &input_state_csi_parameter }, + { 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. */ +static 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, input_parameter, NULL }, + { 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. */ +static 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. */ +static 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. */ +static 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, input_input, &input_state_dcs_handler }, + { 0x7f, 0xff, NULL, NULL }, + + { -1, -1, NULL, NULL } +}; + +/* dcs_parameter state table. */ +static 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, input_input, &input_state_dcs_handler }, + { 0x7f, 0xff, NULL, NULL }, + + { -1, -1, NULL, NULL } +}; + +/* dcs_intermediate state table. */ +static 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, input_input, &input_state_dcs_handler }, + { 0x7f, 0xff, NULL, NULL }, + + { -1, -1, NULL, NULL } +}; + +/* dcs_handler state table. */ +static const struct input_transition input_state_dcs_handler_table[] = { + /* No INPUT_STATE_ANYWHERE */ + + { 0x00, 0x1a, input_input, NULL }, + { 0x1b, 0x1b, NULL, &input_state_dcs_escape }, + { 0x1c, 0xff, input_input, NULL }, + + { -1, -1, NULL, NULL } +}; + +/* dcs_escape state table. */ +static const struct input_transition input_state_dcs_escape_table[] = { + /* No INPUT_STATE_ANYWHERE */ + + { 0x00, 0x5b, input_input, &input_state_dcs_handler }, + { 0x5c, 0x5c, input_dcs_dispatch, &input_state_ground }, + { 0x5d, 0xff, input_input, &input_state_dcs_handler }, + + { -1, -1, NULL, NULL } +}; + +/* dcs_ignore state table. */ +static 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. */ +static const struct input_transition input_state_osc_string_table[] = { + INPUT_STATE_ANYWHERE, + + { 0x00, 0x06, NULL, NULL }, + { 0x07, 0x07, input_end_bel, &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. */ +static 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. */ +static 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. */ +static 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 } +}; + +/* Input table compare. */ +static 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)); +} + +/* + * Timer - if this expires then have been waiting for a terminator for too + * long, so reset to ground. + */ +static void +input_timer_callback(__unused int fd, __unused short events, void *arg) +{ + struct input_ctx *ictx = arg; + + log_debug("%s: %s expired" , __func__, ictx->state->name); + input_reset(ictx, 0); +} + +/* Start the timer. */ +static void +input_start_timer(struct input_ctx *ictx) +{ + struct timeval tv = { .tv_sec = 5, .tv_usec = 0 }; + + event_del(&ictx->timer); + event_add(&ictx->timer, &tv); +} + +/* Reset cell state to default. */ +static void +input_reset_cell(struct input_ctx *ictx) +{ + memcpy(&ictx->cell.cell, &grid_default_cell, sizeof ictx->cell.cell); + ictx->cell.set = 0; + ictx->cell.g0set = ictx->cell.g1set = 0; + + memcpy(&ictx->old_cell, &ictx->cell, sizeof ictx->old_cell); + ictx->old_cx = 0; + ictx->old_cy = 0; +} + +/* Save screen state. */ +static void +input_save_state(struct input_ctx *ictx) +{ + struct screen_write_ctx *sctx = &ictx->ctx; + struct screen *s = sctx->s; + + memcpy(&ictx->old_cell, &ictx->cell, sizeof ictx->old_cell); + ictx->old_cx = s->cx; + ictx->old_cy = s->cy; + ictx->old_mode = s->mode; +} + +/* Restore screen state. */ +static void +input_restore_state(struct input_ctx *ictx) +{ + struct screen_write_ctx *sctx = &ictx->ctx; + + memcpy(&ictx->cell, &ictx->old_cell, sizeof ictx->cell); + if (ictx->old_mode & MODE_ORIGIN) + screen_write_mode_set(sctx, MODE_ORIGIN); + else + screen_write_mode_clear(sctx, MODE_ORIGIN); + screen_write_cursormove(sctx, ictx->old_cx, ictx->old_cy, 0); +} + +/* Initialise input parser. */ +struct input_ctx * +input_init(struct window_pane *wp, struct bufferevent *bev, + struct colour_palette *palette) +{ + struct input_ctx *ictx; + + ictx = xcalloc(1, sizeof *ictx); + ictx->wp = wp; + ictx->event = bev; + ictx->palette = palette; + + ictx->input_space = INPUT_BUF_START; + ictx->input_buf = xmalloc(INPUT_BUF_START); + + ictx->since_ground = evbuffer_new(); + if (ictx->since_ground == NULL) + fatalx("out of memory"); + + evtimer_set(&ictx->timer, input_timer_callback, ictx); + + input_reset(ictx, 0); + return (ictx); +} + +/* Destroy input parser. */ +void +input_free(struct input_ctx *ictx) +{ + u_int i; + + for (i = 0; i < ictx->param_list_len; i++) { + if (ictx->param_list[i].type == INPUT_STRING) + free(ictx->param_list[i].str); + } + + event_del(&ictx->timer); + + free(ictx->input_buf); + evbuffer_free(ictx->since_ground); + + free(ictx); +} + +/* Reset input state and clear screen. */ +void +input_reset(struct input_ctx *ictx, int clear) +{ + struct screen_write_ctx *sctx = &ictx->ctx; + struct window_pane *wp = ictx->wp; + + input_reset_cell(ictx); + + if (clear && wp != NULL) { + if (TAILQ_EMPTY(&wp->modes)) + screen_write_start_pane(sctx, wp, &wp->base); + else + screen_write_start(sctx, &wp->base); + screen_write_reset(sctx); + screen_write_stop(sctx); + } + + input_clear(ictx); + + ictx->last = -1; + + ictx->state = &input_state_ground; + ictx->flags = 0; +} + +/* Return pending data. */ +struct evbuffer * +input_pending(struct input_ctx *ictx) +{ + return (ictx->since_ground); +} + +/* Change input state. */ +static void +input_set_state(struct input_ctx *ictx, const struct input_transition *itr) +{ + if (ictx->state->exit != NULL) + ictx->state->exit(ictx); + ictx->state = itr->state; + if (ictx->state->enter != NULL) + ictx->state->enter(ictx); +} + +/* Parse data. */ +static void +input_parse(struct input_ctx *ictx, u_char *buf, size_t len) +{ + struct screen_write_ctx *sctx = &ictx->ctx; + const struct input_state *state = NULL; + const struct input_transition *itr = NULL; + size_t off = 0; + + /* Parse the input. */ + while (off < len) { + ictx->ch = buf[off++]; + + /* Find the transition. */ + if (ictx->state != state || + itr == NULL || + ictx->ch < itr->first || + ictx->ch > itr->last) { + 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"); + } + } + state = ictx->state; + + /* + * Any state except print stops the current collection. This is + * an optimization to avoid checking if the attributes have + * changed for every character. It will stop unnecessarily for + * sequences that don't make a terminal change, but they should + * be the minority. + */ + if (itr->handler != input_print) + screen_write_collect_end(sctx); + + /* + * 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) + input_set_state(ictx, itr); + + /* If not in ground state, save input. */ + if (ictx->state != &input_state_ground) + evbuffer_add(ictx->since_ground, &ictx->ch, 1); + } +} + +/* Parse input from pane. */ +void +input_parse_pane(struct window_pane *wp) +{ + void *new_data; + size_t new_size; + + new_data = window_pane_get_new_data(wp, &wp->offset, &new_size); + input_parse_buffer(wp, new_data, new_size); + window_pane_update_used_data(wp, &wp->offset, new_size); +} + +/* Parse given input. */ +void +input_parse_buffer(struct window_pane *wp, u_char *buf, size_t len) +{ + struct input_ctx *ictx = wp->ictx; + struct screen_write_ctx *sctx = &ictx->ctx; + + if (len == 0) + return; + + window_update_activity(wp->window); + wp->flags |= PANE_CHANGED; + + /* NULL wp if there is a mode set as don't want to update the tty. */ + if (TAILQ_EMPTY(&wp->modes)) + screen_write_start_pane(sctx, wp, &wp->base); + else + screen_write_start(sctx, &wp->base); + + log_debug("%s: %%%u %s, %zu bytes: %.*s", __func__, wp->id, + ictx->state->name, len, (int)len, buf); + + input_parse(ictx, buf, len); + screen_write_stop(sctx); +} + +/* Parse given input for screen. */ +void +input_parse_screen(struct input_ctx *ictx, struct screen *s, + screen_write_init_ctx_cb cb, void *arg, u_char *buf, size_t len) +{ + struct screen_write_ctx *sctx = &ictx->ctx; + + if (len == 0) + return; + + screen_write_start_callback(sctx, s, cb, arg); + input_parse(ictx, buf, len); + screen_write_stop(sctx); +} + +/* Split the parameter list (if any). */ +static int +input_split(struct input_ctx *ictx) +{ + const char *errstr; + char *ptr, *out; + struct input_param *ip; + u_int i; + + for (i = 0; i < ictx->param_list_len; i++) { + if (ictx->param_list[i].type == INPUT_STRING) + free(ictx->param_list[i].str); + } + ictx->param_list_len = 0; + + if (ictx->param_len == 0) + return (0); + ip = &ictx->param_list[0]; + + ptr = ictx->param_buf; + while ((out = strsep(&ptr, ";")) != NULL) { + if (*out == '\0') + ip->type = INPUT_MISSING; + else { + if (strchr(out, ':') != NULL) { + ip->type = INPUT_STRING; + ip->str = xstrdup(out); + } else { + ip->type = INPUT_NUMBER; + ip->num = strtonum(out, 0, INT_MAX, &errstr); + if (errstr != NULL) + return (-1); + } + } + ip = &ictx->param_list[++ictx->param_list_len]; + if (ictx->param_list_len == nitems(ictx->param_list)) + return (-1); + } + + for (i = 0; i < ictx->param_list_len; i++) { + ip = &ictx->param_list[i]; + if (ip->type == INPUT_MISSING) + log_debug("parameter %u: missing", i); + else if (ip->type == INPUT_STRING) + log_debug("parameter %u: string %s", i, ip->str); + else if (ip->type == INPUT_NUMBER) + log_debug("parameter %u: number %d", i, ip->num); + } + + return (0); +} + +/* Get an argument or return default value. */ +static int +input_get(struct input_ctx *ictx, u_int validx, int minval, int defval) +{ + struct input_param *ip; + int retval; + + if (validx >= ictx->param_list_len) + return (defval); + ip = &ictx->param_list[validx]; + if (ip->type == INPUT_MISSING) + return (defval); + if (ip->type == INPUT_STRING) + return (-1); + retval = ip->num; + if (retval < minval) + return (minval); + return (retval); +} + +/* Reply to terminal query. */ +static void +input_reply(struct input_ctx *ictx, const char *fmt, ...) +{ + struct bufferevent *bev = ictx->event; + va_list ap; + char *reply; + + if (bev == NULL) + return; + + va_start(ap, fmt); + xvasprintf(&reply, fmt, ap); + va_end(ap); + + bufferevent_write(bev, reply, strlen(reply)); + free(reply); +} + +/* Clear saved state. */ +static void +input_clear(struct input_ctx *ictx) +{ + event_del(&ictx->timer); + + *ictx->interm_buf = '\0'; + ictx->interm_len = 0; + + *ictx->param_buf = '\0'; + ictx->param_len = 0; + + *ictx->input_buf = '\0'; + ictx->input_len = 0; + + ictx->input_end = INPUT_END_ST; + + ictx->flags &= ~INPUT_DISCARD; +} + +/* Reset for ground state. */ +static void +input_ground(struct input_ctx *ictx) +{ + event_del(&ictx->timer); + evbuffer_drain(ictx->since_ground, EVBUFFER_LENGTH(ictx->since_ground)); + + if (ictx->input_space > INPUT_BUF_START) { + ictx->input_space = INPUT_BUF_START; + ictx->input_buf = xrealloc(ictx->input_buf, INPUT_BUF_START); + } +} + +/* Output this character to the screen. */ +static int +input_print(struct input_ctx *ictx) +{ + struct screen_write_ctx *sctx = &ictx->ctx; + int set; + + ictx->utf8started = 0; /* can't be valid UTF-8 */ + + set = ictx->cell.set == 0 ? ictx->cell.g0set : ictx->cell.g1set; + if (set == 1) + ictx->cell.cell.attr |= GRID_ATTR_CHARSET; + else + ictx->cell.cell.attr &= ~GRID_ATTR_CHARSET; + + utf8_set(&ictx->cell.cell.data, ictx->ch); + screen_write_collect_add(sctx, &ictx->cell.cell); + ictx->last = ictx->ch; + + ictx->cell.cell.attr &= ~GRID_ATTR_CHARSET; + + return (0); +} + +/* Collect intermediate string. */ +static 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. */ +static 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. */ +static int +input_input(struct input_ctx *ictx) +{ + size_t available; + + available = ictx->input_space; + while (ictx->input_len + 1 >= available) { + available *= 2; + if (available > INPUT_BUF_LIMIT) { + ictx->flags |= INPUT_DISCARD; + return (0); + } + ictx->input_buf = xrealloc(ictx->input_buf, available); + ictx->input_space = available; + } + ictx->input_buf[ictx->input_len++] = ictx->ch; + ictx->input_buf[ictx->input_len] = '\0'; + + return (0); +} + +/* Execute C0 control sequence. */ +static 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; + + ictx->utf8started = 0; /* can't be valid UTF-8 */ + + log_debug("%s: '%c'", __func__, ictx->ch); + + switch (ictx->ch) { + case '\000': /* NUL */ + break; + case '\007': /* BEL */ + if (wp != NULL) + alerts_queue(wp->window, 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, ictx->cell.cell.bg); + if (s->mode & MODE_CRLF) + screen_write_carriagereturn(sctx); + break; + case '\015': /* CR */ + screen_write_carriagereturn(sctx); + break; + case '\016': /* SO */ + ictx->cell.set = 1; + break; + case '\017': /* SI */ + ictx->cell.set = 0; + break; + default: + log_debug("%s: unknown '%c'", __func__, ictx->ch); + break; + } + + ictx->last = -1; + return (0); +} + +/* Execute escape sequence. */ +static 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: + colour_palette_clear(ictx->palette); + input_reset_cell(ictx); + screen_write_reset(sctx); + screen_write_fullredraw(sctx); + break; + case INPUT_ESC_IND: + screen_write_linefeed(sctx, 0, ictx->cell.cell.bg); + break; + case INPUT_ESC_NEL: + screen_write_carriagereturn(sctx); + screen_write_linefeed(sctx, 0, ictx->cell.cell.bg); + 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, ictx->cell.cell.bg); + break; + case INPUT_ESC_DECKPAM: + screen_write_mode_set(sctx, MODE_KKEYPAD); + break; + case INPUT_ESC_DECKPNM: + screen_write_mode_clear(sctx, MODE_KKEYPAD); + break; + case INPUT_ESC_DECSC: + input_save_state(ictx); + break; + case INPUT_ESC_DECRC: + input_restore_state(ictx); + break; + case INPUT_ESC_DECALN: + screen_write_alignmenttest(sctx); + break; + case INPUT_ESC_SCSG0_ON: + ictx->cell.g0set = 1; + break; + case INPUT_ESC_SCSG0_OFF: + ictx->cell.g0set = 0; + break; + case INPUT_ESC_SCSG1_ON: + ictx->cell.g1set = 1; + break; + case INPUT_ESC_SCSG1_OFF: + ictx->cell.g1set = 0; + break; + case INPUT_ESC_ST: + /* ST terminates OSC but the state transition already did it. */ + break; + } + + ictx->last = -1; + return (0); +} + +/* Execute control sequence. */ +static int +input_csi_dispatch(struct input_ctx *ictx) +{ + struct screen_write_ctx *sctx = &ictx->ctx; + struct screen *s = sctx->s; + struct input_table_entry *entry; + int i, n, m; + u_int cx, bg = ictx->cell.cell.bg; + + if (ictx->flags & INPUT_DISCARD) + return (0); + + log_debug("%s: '%c' \"%s\" \"%s\"", + __func__, ictx->ch, ictx->interm_buf, ictx->param_buf); + + if (input_split(ictx) != 0) + return (0); + + 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. */ + cx = s->cx; + if (cx > screen_size_x(s) - 1) + cx = screen_size_x(s) - 1; + n = input_get(ictx, 0, 1, 1); + if (n == -1) + break; + while (cx > 0 && n-- > 0) { + do + cx--; + while (cx > 0 && !bit_test(s->tabs, cx)); + } + s->cx = cx; + break; + case INPUT_CSI_CUB: + n = input_get(ictx, 0, 1, 1); + if (n != -1) + screen_write_cursorleft(sctx, n); + break; + case INPUT_CSI_CUD: + n = input_get(ictx, 0, 1, 1); + if (n != -1) + screen_write_cursordown(sctx, n); + break; + case INPUT_CSI_CUF: + n = input_get(ictx, 0, 1, 1); + if (n != -1) + screen_write_cursorright(sctx, n); + break; + case INPUT_CSI_CUP: + n = input_get(ictx, 0, 1, 1); + m = input_get(ictx, 1, 1, 1); + if (n != -1 && m != -1) + screen_write_cursormove(sctx, m - 1, n - 1, 1); + break; + case INPUT_CSI_MODSET: + n = input_get(ictx, 0, 0, 0); + m = input_get(ictx, 1, 0, 0); + if (options_get_number(global_options, "extended-keys") == 2) + break; + if (n == 0 || (n == 4 && m == 0)) + screen_write_mode_clear(sctx, MODE_KEXTENDED); + else if (n == 4 && (m == 1 || m == 2)) + screen_write_mode_set(sctx, MODE_KEXTENDED); + break; + case INPUT_CSI_MODOFF: + n = input_get(ictx, 0, 0, 0); + if (n == 4) + screen_write_mode_clear(sctx, MODE_KEXTENDED); + break; + case INPUT_CSI_WINOPS: + input_csi_dispatch_winops(ictx); + break; + case INPUT_CSI_CUU: + n = input_get(ictx, 0, 1, 1); + if (n != -1) + screen_write_cursorup(sctx, n); + break; + case INPUT_CSI_CNL: + n = input_get(ictx, 0, 1, 1); + if (n != -1) { + screen_write_carriagereturn(sctx); + screen_write_cursordown(sctx, n); + } + break; + case INPUT_CSI_CPL: + n = input_get(ictx, 0, 1, 1); + if (n != -1) { + screen_write_carriagereturn(sctx); + screen_write_cursorup(sctx, n); + } + break; + case INPUT_CSI_DA: + switch (input_get(ictx, 0, 0, 0)) { + case -1: + break; + case 0: + input_reply(ictx, "\033[?1;2c"); + break; + default: + log_debug("%s: unknown '%c'", __func__, ictx->ch); + break; + } + break; + case INPUT_CSI_DA_TWO: + switch (input_get(ictx, 0, 0, 0)) { + case -1: + break; + case 0: + input_reply(ictx, "\033[>84;0;0c"); + break; + default: + log_debug("%s: unknown '%c'", __func__, ictx->ch); + break; + } + break; + case INPUT_CSI_ECH: + n = input_get(ictx, 0, 1, 1); + if (n != -1) + screen_write_clearcharacter(sctx, n, bg); + break; + case INPUT_CSI_DCH: + n = input_get(ictx, 0, 1, 1); + if (n != -1) + screen_write_deletecharacter(sctx, n, bg); + break; + case INPUT_CSI_DECSTBM: + n = input_get(ictx, 0, 1, 1); + m = input_get(ictx, 1, 1, screen_size_y(s)); + if (n != -1 && m != -1) + screen_write_scrollregion(sctx, n - 1, m - 1); + break; + case INPUT_CSI_DL: + n = input_get(ictx, 0, 1, 1); + if (n != -1) + screen_write_deleteline(sctx, n, bg); + break; + case INPUT_CSI_DSR: + switch (input_get(ictx, 0, 0, 0)) { + case -1: + break; + 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 -1: + break; + case 0: + screen_write_clearendofscreen(sctx, bg); + break; + case 1: + screen_write_clearstartofscreen(sctx, bg); + break; + case 2: + screen_write_clearscreen(sctx, bg); + break; + case 3: + if (input_get(ictx, 1, 0, 0) == 0) { + /* + * Linux console extension to clear history + * (for example before locking the screen). + */ + screen_write_clearhistory(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 -1: + break; + case 0: + screen_write_clearendofline(sctx, bg); + break; + case 1: + screen_write_clearstartofline(sctx, bg); + break; + case 2: + screen_write_clearline(sctx, bg); + break; + default: + log_debug("%s: unknown '%c'", __func__, ictx->ch); + break; + } + break; + case INPUT_CSI_HPA: + n = input_get(ictx, 0, 1, 1); + if (n != -1) + screen_write_cursormove(sctx, n - 1, -1, 1); + break; + case INPUT_CSI_ICH: + n = input_get(ictx, 0, 1, 1); + if (n != -1) + screen_write_insertcharacter(sctx, n, bg); + break; + case INPUT_CSI_IL: + n = input_get(ictx, 0, 1, 1); + if (n != -1) + screen_write_insertline(sctx, n, bg); + break; + case INPUT_CSI_REP: + n = input_get(ictx, 0, 1, 1); + if (n == -1) + break; + + m = screen_size_x(s) - s->cx; + if (n > m) + n = m; + + if (ictx->last == -1) + break; + ictx->ch = ictx->last; + + for (i = 0; i < n; i++) + input_print(ictx); + break; + case INPUT_CSI_RCP: + input_restore_state(ictx); + break; + case INPUT_CSI_RM: + input_csi_dispatch_rm(ictx); + break; + case INPUT_CSI_RM_PRIVATE: + input_csi_dispatch_rm_private(ictx); + break; + case INPUT_CSI_SCP: + input_save_state(ictx); + break; + case INPUT_CSI_SGR: + input_csi_dispatch_sgr(ictx); + break; + case INPUT_CSI_SM: + input_csi_dispatch_sm(ictx); + break; + case INPUT_CSI_SM_PRIVATE: + input_csi_dispatch_sm_private(ictx); + break; + case INPUT_CSI_SU: + n = input_get(ictx, 0, 1, 1); + if (n != -1) + screen_write_scrollup(sctx, n, bg); + break; + case INPUT_CSI_SD: + n = input_get(ictx, 0, 1, 1); + if (n != -1) + screen_write_scrolldown(sctx, n, bg); + break; + case INPUT_CSI_TBC: + switch (input_get(ictx, 0, 0, 0)) { + case -1: + break; + 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); + if (n != -1) + screen_write_cursormove(sctx, -1, n - 1, 1); + break; + case INPUT_CSI_DECSCUSR: + n = input_get(ictx, 0, 0, 0); + if (n != -1) + screen_set_cursor_style(n, &s->cstyle, &s->mode); + break; + case INPUT_CSI_XDA: + n = input_get(ictx, 0, 0, 0); + if (n == 0) + input_reply(ictx, "\033P>|tmux %s\033\\", getversion()); + break; + + } + + ictx->last = -1; + return (0); +} + +/* Handle CSI RM. */ +static void +input_csi_dispatch_rm(struct input_ctx *ictx) +{ + struct screen_write_ctx *sctx = &ictx->ctx; + u_int i; + + for (i = 0; i < ictx->param_list_len; i++) { + switch (input_get(ictx, i, 0, -1)) { + case -1: + break; + case 4: /* IRM */ + screen_write_mode_clear(sctx, MODE_INSERT); + break; + case 34: + screen_write_mode_set(sctx, MODE_CURSOR_VERY_VISIBLE); + break; + default: + log_debug("%s: unknown '%c'", __func__, ictx->ch); + break; + } + } +} + +/* Handle CSI private RM. */ +static void +input_csi_dispatch_rm_private(struct input_ctx *ictx) +{ + struct screen_write_ctx *sctx = &ictx->ctx; + struct grid_cell *gc = &ictx->cell.cell; + u_int i; + + for (i = 0; i < ictx->param_list_len; i++) { + switch (input_get(ictx, i, 0, -1)) { + case -1: + break; + case 1: /* DECCKM */ + screen_write_mode_clear(sctx, MODE_KCURSOR); + break; + case 3: /* DECCOLM */ + screen_write_cursormove(sctx, 0, 0, 1); + screen_write_clearscreen(sctx, gc->bg); + break; + case 6: /* DECOM */ + screen_write_mode_clear(sctx, MODE_ORIGIN); + screen_write_cursormove(sctx, 0, 0, 1); + break; + case 7: /* DECAWM */ + screen_write_mode_clear(sctx, MODE_WRAP); + break; + case 12: + screen_write_mode_clear(sctx, MODE_CURSOR_BLINKING); + screen_write_mode_set(sctx, MODE_CURSOR_BLINKING_SET); + break; + case 25: /* TCEM */ + screen_write_mode_clear(sctx, MODE_CURSOR); + break; + case 1000: + case 1001: + case 1002: + case 1003: + screen_write_mode_clear(sctx, ALL_MOUSE_MODES); + break; + case 1004: + screen_write_mode_clear(sctx, MODE_FOCUSON); + break; + case 1005: + screen_write_mode_clear(sctx, MODE_MOUSE_UTF8); + break; + case 1006: + screen_write_mode_clear(sctx, MODE_MOUSE_SGR); + break; + case 47: + case 1047: + screen_write_alternateoff(sctx, gc, 0); + break; + case 1049: + screen_write_alternateoff(sctx, gc, 1); + break; + case 2004: + screen_write_mode_clear(sctx, MODE_BRACKETPASTE); + break; + default: + log_debug("%s: unknown '%c'", __func__, ictx->ch); + break; + } + } +} + +/* Handle CSI SM. */ +static void +input_csi_dispatch_sm(struct input_ctx *ictx) +{ + struct screen_write_ctx *sctx = &ictx->ctx; + u_int i; + + for (i = 0; i < ictx->param_list_len; i++) { + switch (input_get(ictx, i, 0, -1)) { + case -1: + break; + case 4: /* IRM */ + screen_write_mode_set(sctx, MODE_INSERT); + break; + case 34: + screen_write_mode_clear(sctx, MODE_CURSOR_VERY_VISIBLE); + break; + default: + log_debug("%s: unknown '%c'", __func__, ictx->ch); + break; + } + } +} + +/* Handle CSI private SM. */ +static void +input_csi_dispatch_sm_private(struct input_ctx *ictx) +{ + struct screen_write_ctx *sctx = &ictx->ctx; + struct window_pane *wp = ictx->wp; + struct grid_cell *gc = &ictx->cell.cell; + u_int i; + + for (i = 0; i < ictx->param_list_len; i++) { + switch (input_get(ictx, i, 0, -1)) { + case -1: + break; + case 1: /* DECCKM */ + screen_write_mode_set(sctx, MODE_KCURSOR); + break; + case 3: /* DECCOLM */ + screen_write_cursormove(sctx, 0, 0, 1); + screen_write_clearscreen(sctx, ictx->cell.cell.bg); + break; + case 6: /* DECOM */ + screen_write_mode_set(sctx, MODE_ORIGIN); + screen_write_cursormove(sctx, 0, 0, 1); + break; + case 7: /* DECAWM */ + screen_write_mode_set(sctx, MODE_WRAP); + break; + case 12: + screen_write_mode_set(sctx, MODE_CURSOR_BLINKING); + screen_write_mode_set(sctx, MODE_CURSOR_BLINKING_SET); + break; + case 25: /* TCEM */ + screen_write_mode_set(sctx, MODE_CURSOR); + break; + case 1000: + screen_write_mode_clear(sctx, ALL_MOUSE_MODES); + screen_write_mode_set(sctx, MODE_MOUSE_STANDARD); + break; + case 1002: + screen_write_mode_clear(sctx, ALL_MOUSE_MODES); + screen_write_mode_set(sctx, MODE_MOUSE_BUTTON); + break; + case 1003: + screen_write_mode_clear(sctx, ALL_MOUSE_MODES); + screen_write_mode_set(sctx, MODE_MOUSE_ALL); + break; + case 1004: + if (sctx->s->mode & MODE_FOCUSON) + break; + screen_write_mode_set(sctx, MODE_FOCUSON); + if (wp == NULL) + break; + if (!options_get_number(global_options, "focus-events")) + break; + if (wp->flags & PANE_FOCUSED) + bufferevent_write(wp->event, "\033[I", 3); + else + bufferevent_write(wp->event, "\033[O", 3); + break; + case 1005: + screen_write_mode_set(sctx, MODE_MOUSE_UTF8); + break; + case 1006: + screen_write_mode_set(sctx, MODE_MOUSE_SGR); + break; + case 47: + case 1047: + screen_write_alternateon(sctx, gc, 0); + break; + case 1049: + screen_write_alternateon(sctx, gc, 1); + break; + case 2004: + screen_write_mode_set(sctx, MODE_BRACKETPASTE); + break; + default: + log_debug("%s: unknown '%c'", __func__, ictx->ch); + break; + } + } +} + +/* Handle CSI window operations. */ +static void +input_csi_dispatch_winops(struct input_ctx *ictx) +{ + struct screen_write_ctx *sctx = &ictx->ctx; + struct screen *s = sctx->s; + struct window_pane *wp = ictx->wp; + u_int x = screen_size_x(s), y = screen_size_y(s); + int n, m; + + m = 0; + while ((n = input_get(ictx, m, 0, -1)) != -1) { + switch (n) { + case 1: + case 2: + case 5: + case 6: + case 7: + case 11: + case 13: + case 14: + case 19: + case 20: + case 21: + case 24: + break; + case 3: + case 4: + case 8: + m++; + if (input_get(ictx, m, 0, -1) == -1) + return; + /* FALLTHROUGH */ + case 9: + case 10: + m++; + if (input_get(ictx, m, 0, -1) == -1) + return; + break; + case 22: + m++; + switch (input_get(ictx, m, 0, -1)) { + case -1: + return; + case 0: + case 2: + screen_push_title(sctx->s); + break; + } + break; + case 23: + m++; + switch (input_get(ictx, m, 0, -1)) { + case -1: + return; + case 0: + case 2: + screen_pop_title(sctx->s); + if (wp == NULL) + break; + notify_pane("pane-title-changed", wp); + server_redraw_window_borders(wp->window); + server_status_window(wp->window); + break; + } + break; + case 18: + input_reply(ictx, "\033[8;%u;%ut", x, y); + break; + default: + log_debug("%s: unknown '%c'", __func__, ictx->ch); + break; + } + m++; + } +} + +/* Helper for 256 colour SGR. */ +static int +input_csi_dispatch_sgr_256_do(struct input_ctx *ictx, int fgbg, int c) +{ + struct grid_cell *gc = &ictx->cell.cell; + + if (c == -1 || c > 255) { + if (fgbg == 38) + gc->fg = 8; + else if (fgbg == 48) + gc->bg = 8; + } else { + if (fgbg == 38) + gc->fg = c | COLOUR_FLAG_256; + else if (fgbg == 48) + gc->bg = c | COLOUR_FLAG_256; + else if (fgbg == 58) + gc->us = c | COLOUR_FLAG_256; + } + return (1); +} + +/* Handle CSI SGR for 256 colours. */ +static void +input_csi_dispatch_sgr_256(struct input_ctx *ictx, int fgbg, u_int *i) +{ + int c; + + c = input_get(ictx, (*i) + 1, 0, -1); + if (input_csi_dispatch_sgr_256_do(ictx, fgbg, c)) + (*i)++; +} + +/* Helper for RGB colour SGR. */ +static int +input_csi_dispatch_sgr_rgb_do(struct input_ctx *ictx, int fgbg, int r, int g, + int b) +{ + struct grid_cell *gc = &ictx->cell.cell; + + if (r == -1 || r > 255) + return (0); + if (g == -1 || g > 255) + return (0); + if (b == -1 || b > 255) + return (0); + + if (fgbg == 38) + gc->fg = colour_join_rgb(r, g, b); + else if (fgbg == 48) + gc->bg = colour_join_rgb(r, g, b); + else if (fgbg == 58) + gc->us = colour_join_rgb(r, g, b); + return (1); +} + +/* Handle CSI SGR for RGB colours. */ +static void +input_csi_dispatch_sgr_rgb(struct input_ctx *ictx, int fgbg, u_int *i) +{ + int r, g, b; + + r = input_get(ictx, (*i) + 1, 0, -1); + g = input_get(ictx, (*i) + 2, 0, -1); + b = input_get(ictx, (*i) + 3, 0, -1); + if (input_csi_dispatch_sgr_rgb_do(ictx, fgbg, r, g, b)) + (*i) += 3; +} + +/* Handle CSI SGR with a ISO parameter. */ +static void +input_csi_dispatch_sgr_colon(struct input_ctx *ictx, u_int i) +{ + struct grid_cell *gc = &ictx->cell.cell; + char *s = ictx->param_list[i].str, *copy, *ptr, *out; + int p[8]; + u_int n; + const char *errstr; + + for (n = 0; n < nitems(p); n++) + p[n] = -1; + n = 0; + + ptr = copy = xstrdup(s); + while ((out = strsep(&ptr, ":")) != NULL) { + if (*out != '\0') { + p[n++] = strtonum(out, 0, INT_MAX, &errstr); + if (errstr != NULL || n == nitems(p)) { + free(copy); + return; + } + } else { + n++; + if (n == nitems(p)) { + free(copy); + return; + } + } + log_debug("%s: %u = %d", __func__, n - 1, p[n - 1]); + } + free(copy); + + if (n == 0) + return; + if (p[0] == 4) { + if (n != 2) + return; + switch (p[1]) { + case 0: + gc->attr &= ~GRID_ATTR_ALL_UNDERSCORE; + break; + case 1: + gc->attr &= ~GRID_ATTR_ALL_UNDERSCORE; + gc->attr |= GRID_ATTR_UNDERSCORE; + break; + case 2: + gc->attr &= ~GRID_ATTR_ALL_UNDERSCORE; + gc->attr |= GRID_ATTR_UNDERSCORE_2; + break; + case 3: + gc->attr &= ~GRID_ATTR_ALL_UNDERSCORE; + gc->attr |= GRID_ATTR_UNDERSCORE_3; + break; + case 4: + gc->attr &= ~GRID_ATTR_ALL_UNDERSCORE; + gc->attr |= GRID_ATTR_UNDERSCORE_4; + break; + case 5: + gc->attr &= ~GRID_ATTR_ALL_UNDERSCORE; + gc->attr |= GRID_ATTR_UNDERSCORE_5; + break; + } + return; + } + if (n < 2 || (p[0] != 38 && p[0] != 48 && p[0] != 58)) + return; + switch (p[1]) { + case 2: + if (n < 3) + break; + if (n == 5) + i = 2; + else + i = 3; + if (n < i + 3) + break; + input_csi_dispatch_sgr_rgb_do(ictx, p[0], p[i], p[i + 1], + p[i + 2]); + break; + case 5: + if (n < 3) + break; + input_csi_dispatch_sgr_256_do(ictx, p[0], p[2]); + break; + } +} + +/* Handle CSI SGR. */ +static void +input_csi_dispatch_sgr(struct input_ctx *ictx) +{ + struct grid_cell *gc = &ictx->cell.cell; + u_int i; + int n; + + if (ictx->param_list_len == 0) { + memcpy(gc, &grid_default_cell, sizeof *gc); + return; + } + + for (i = 0; i < ictx->param_list_len; i++) { + if (ictx->param_list[i].type == INPUT_STRING) { + input_csi_dispatch_sgr_colon(ictx, i); + continue; + } + n = input_get(ictx, i, 0, 0); + if (n == -1) + continue; + + if (n == 38 || n == 48 || n == 58) { + i++; + switch (input_get(ictx, i, 0, -1)) { + case 2: + input_csi_dispatch_sgr_rgb(ictx, n, &i); + break; + case 5: + input_csi_dispatch_sgr_256(ictx, n, &i); + break; + } + continue; + } + + switch (n) { + case 0: + memcpy(gc, &grid_default_cell, sizeof *gc); + 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_ALL_UNDERSCORE; + gc->attr |= GRID_ATTR_UNDERSCORE; + break; + case 5: + case 6: + gc->attr |= GRID_ATTR_BLINK; + break; + case 7: + gc->attr |= GRID_ATTR_REVERSE; + break; + case 8: + gc->attr |= GRID_ATTR_HIDDEN; + break; + case 9: + gc->attr |= GRID_ATTR_STRIKETHROUGH; + break; + case 21: + gc->attr &= ~GRID_ATTR_ALL_UNDERSCORE; + gc->attr |= GRID_ATTR_UNDERSCORE_2; + 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_ALL_UNDERSCORE; + break; + case 25: + gc->attr &= ~GRID_ATTR_BLINK; + break; + case 27: + gc->attr &= ~GRID_ATTR_REVERSE; + break; + case 28: + gc->attr &= ~GRID_ATTR_HIDDEN; + break; + case 29: + gc->attr &= ~GRID_ATTR_STRIKETHROUGH; + break; + case 30: + case 31: + case 32: + case 33: + case 34: + case 35: + case 36: + case 37: + gc->fg = n - 30; + break; + case 39: + gc->fg = 8; + break; + case 40: + case 41: + case 42: + case 43: + case 44: + case 45: + case 46: + case 47: + gc->bg = n - 40; + break; + case 49: + gc->bg = 8; + break; + case 53: + gc->attr |= GRID_ATTR_OVERLINE; + break; + case 55: + gc->attr &= ~GRID_ATTR_OVERLINE; + break; + case 59: + gc->us = 0; + break; + case 90: + case 91: + case 92: + case 93: + case 94: + case 95: + case 96: + case 97: + gc->fg = n; + break; + case 100: + case 101: + case 102: + case 103: + case 104: + case 105: + case 106: + case 107: + gc->bg = n - 10; + break; + } + } +} + +/* End of input with BEL. */ +static int +input_end_bel(struct input_ctx *ictx) +{ + log_debug("%s", __func__); + + ictx->input_end = INPUT_END_BEL; + + return (0); +} + +/* DCS string started. */ +static void +input_enter_dcs(struct input_ctx *ictx) +{ + log_debug("%s", __func__); + + input_clear(ictx); + input_start_timer(ictx); + ictx->last = -1; +} + +/* DCS terminator (ST) received. */ +static int +input_dcs_dispatch(struct input_ctx *ictx) +{ + struct window_pane *wp = ictx->wp; + struct screen_write_ctx *sctx = &ictx->ctx; + u_char *buf = ictx->input_buf; + size_t len = ictx->input_len; + const char prefix[] = "tmux;"; + const u_int prefixlen = (sizeof prefix) - 1; + + if (wp == NULL) + return (0); + if (ictx->flags & INPUT_DISCARD) + return (0); + if (!options_get_number(ictx->wp->options, "allow-passthrough")) + return (0); + log_debug("%s: \"%s\"", __func__, buf); + + if (len >= prefixlen && strncmp(buf, prefix, prefixlen) == 0) + screen_write_rawstring(sctx, buf + prefixlen, len - prefixlen); + + return (0); +} + +/* OSC string started. */ +static void +input_enter_osc(struct input_ctx *ictx) +{ + log_debug("%s", __func__); + + input_clear(ictx); + input_start_timer(ictx); + ictx->last = -1; +} + +/* OSC terminator (ST) received. */ +static void +input_exit_osc(struct input_ctx *ictx) +{ + struct screen_write_ctx *sctx = &ictx->ctx; + struct window_pane *wp = ictx->wp; + u_char *p = ictx->input_buf; + u_int option; + + if (ictx->flags & INPUT_DISCARD) + return; + if (ictx->input_len < 1 || *p < '0' || *p > '9') + return; + + log_debug("%s: \"%s\" (end %s)", __func__, p, + ictx->input_end == INPUT_END_ST ? "ST" : "BEL"); + + option = 0; + while (*p >= '0' && *p <= '9') + option = option * 10 + *p++ - '0'; + if (*p == ';') + p++; + + switch (option) { + case 0: + case 2: + if (screen_set_title(sctx->s, p) && wp != NULL) { + notify_pane("pane-title-changed", wp); + server_redraw_window_borders(wp->window); + server_status_window(wp->window); + } + break; + case 4: + input_osc_4(ictx, p); + break; + case 7: + if (utf8_isvalid(p)) { + screen_set_path(sctx->s, p); + if (wp != NULL) { + server_redraw_window_borders(wp->window); + server_status_window(wp->window); + } + } + break; + case 10: + input_osc_10(ictx, p); + break; + case 11: + input_osc_11(ictx, p); + break; + case 12: + input_osc_12(ictx, p); + break; + case 52: + input_osc_52(ictx, p); + break; + case 104: + input_osc_104(ictx, p); + break; + case 110: + input_osc_110(ictx, p); + break; + case 111: + input_osc_111(ictx, p); + break; + case 112: + input_osc_112(ictx, p); + break; + default: + log_debug("%s: unknown '%u'", __func__, option); + break; + } +} + +/* APC string started. */ +static void +input_enter_apc(struct input_ctx *ictx) +{ + log_debug("%s", __func__); + + input_clear(ictx); + input_start_timer(ictx); + ictx->last = -1; +} + +/* APC terminator (ST) received. */ +static void +input_exit_apc(struct input_ctx *ictx) +{ + struct screen_write_ctx *sctx = &ictx->ctx; + struct window_pane *wp = ictx->wp; + + if (ictx->flags & INPUT_DISCARD) + return; + log_debug("%s: \"%s\"", __func__, ictx->input_buf); + + if (screen_set_title(sctx->s, ictx->input_buf) && wp != NULL) { + notify_pane("pane-title-changed", wp); + server_redraw_window_borders(wp->window); + server_status_window(wp->window); + } +} + +/* Rename string started. */ +static void +input_enter_rename(struct input_ctx *ictx) +{ + log_debug("%s", __func__); + + input_clear(ictx); + input_start_timer(ictx); + ictx->last = -1; +} + +/* Rename terminator (ST) received. */ +static void +input_exit_rename(struct input_ctx *ictx) +{ + struct window_pane *wp = ictx->wp; + struct window *w; + struct options_entry *o; + + if (wp == NULL) + return; + if (ictx->flags & INPUT_DISCARD) + return; + if (!options_get_number(ictx->wp->options, "allow-rename")) + return; + log_debug("%s: \"%s\"", __func__, ictx->input_buf); + + if (!utf8_isvalid(ictx->input_buf)) + return; + w = wp->window; + + if (ictx->input_len == 0) { + o = options_get_only(w->options, "automatic-rename"); + if (o != NULL) + options_remove_or_default(o, -1, NULL); + if (!options_get_number(w->options, "automatic-rename")) + window_set_name(w, ""); + } else { + options_set_number(w->options, "automatic-rename", 0); + window_set_name(w, ictx->input_buf); + } + server_redraw_window_borders(w); + server_status_window(w); +} + +/* Open UTF-8 character. */ +static int +input_top_bit_set(struct input_ctx *ictx) +{ + struct screen_write_ctx *sctx = &ictx->ctx; + struct utf8_data *ud = &ictx->utf8data; + + ictx->last = -1; + + if (!ictx->utf8started) { + if (utf8_open(ud, ictx->ch) != UTF8_MORE) + return (0); + ictx->utf8started = 1; + return (0); + } + + switch (utf8_append(ud, ictx->ch)) { + case UTF8_MORE: + return (0); + case UTF8_ERROR: + ictx->utf8started = 0; + return (0); + case UTF8_DONE: + break; + } + ictx->utf8started = 0; + + log_debug("%s %hhu '%*s' (width %hhu)", __func__, ud->size, + (int)ud->size, ud->data, ud->width); + + utf8_copy(&ictx->cell.cell.data, ud); + screen_write_collect_add(sctx, &ictx->cell.cell); + + return (0); +} + +/* Parse colour from OSC. */ +static int +input_osc_parse_colour(const char *p) +{ + double c, m, y, k = 0; + u_int r, g, b; + size_t len = strlen(p); + int colour = -1; + char *copy; + + if ((len == 12 && sscanf(p, "rgb:%02x/%02x/%02x", &r, &g, &b) == 3) || + (len == 7 && sscanf(p, "#%02x%02x%02x", &r, &g, &b) == 3) || + sscanf(p, "%d,%d,%d", &r, &g, &b) == 3) + colour = colour_join_rgb(r, g, b); + else if ((len == 18 && + sscanf(p, "rgb:%04x/%04x/%04x", &r, &g, &b) == 3) || + (len == 13 && sscanf(p, "#%04x%04x%04x", &r, &g, &b) == 3)) + colour = colour_join_rgb(r >> 8, g >> 8, b >> 8); + else if ((sscanf(p, "cmyk:%lf/%lf/%lf/%lf", &c, &m, &y, &k) == 4 || + sscanf(p, "cmy:%lf/%lf/%lf", &c, &m, &y) == 3) && + c >= 0 && c <= 1 && m >= 0 && m <= 1 && + y >= 0 && y <= 1 && k >= 0 && k <= 1) { + colour = colour_join_rgb( + (1 - c) * (1 - k) * 255, + (1 - m) * (1 - k) * 255, + (1 - y) * (1 - k) * 255); + } else { + while (len != 0 && *p == ' ') { + p++; + len--; + } + while (len != 0 && p[len - 1] == ' ') + len--; + copy = xstrndup(p, len); + colour = colour_byname(copy); + free(copy); + } + log_debug("%s: %s = %s", __func__, p, colour_tostring(colour)); + return (colour); +} + +/* Reply to a colour request. */ +static void +input_osc_colour_reply(struct input_ctx *ictx, u_int n, int c) +{ + u_char r, g, b; + const char *end; + + if (c != -1) + c = colour_force_rgb(c); + if (c == -1) + return; + colour_split_rgb(c, &r, &g, &b); + + if (ictx->input_end == INPUT_END_BEL) + end = "\007"; + else + end = "\033\\"; + input_reply(ictx, "\033]%u;rgb:%02hhx%02hhx/%02hhx%02hhx/%02hhx%02hhx%s", + n, r, r, g, g, b, b, end); +} + +/* Handle the OSC 4 sequence for setting (multiple) palette entries. */ +static void +input_osc_4(struct input_ctx *ictx, const char *p) +{ + char *copy, *s, *next = NULL; + long idx; + int c, bad = 0, redraw = 0; + + copy = s = xstrdup(p); + while (s != NULL && *s != '\0') { + idx = strtol(s, &next, 10); + if (*next++ != ';') { + bad = 1; + break; + } + if (idx < 0 || idx >= 256) { + bad = 1; + break; + } + + s = strsep(&next, ";"); + if (strcmp(s, "?") == 0) { + c = colour_palette_get(ictx->palette, idx); + if (c != -1) + input_osc_colour_reply(ictx, 4, c); + continue; + } + if ((c = input_osc_parse_colour(s)) == -1) { + s = next; + continue; + } + if (colour_palette_set(ictx->palette, idx, c)) + redraw = 1; + s = next; + } + if (bad) + log_debug("bad OSC 4: %s", p); + if (redraw) + screen_write_fullredraw(&ictx->ctx); + free(copy); +} + +/* Handle the OSC 10 sequence for setting and querying foreground colour. */ +static void +input_osc_10(struct input_ctx *ictx, const char *p) +{ + struct window_pane *wp = ictx->wp; + struct grid_cell defaults; + int c; + + if (strcmp(p, "?") == 0) { + if (wp != NULL) { + tty_default_colours(&defaults, wp); + input_osc_colour_reply(ictx, 10, defaults.fg); + } + return; + } + + if ((c = input_osc_parse_colour(p)) == -1) { + log_debug("bad OSC 10: %s", p); + return; + } + if (ictx->palette != NULL) { + ictx->palette->fg = c; + if (wp != NULL) + wp->flags |= PANE_STYLECHANGED; + screen_write_fullredraw(&ictx->ctx); + } +} + +/* Handle the OSC 110 sequence for resetting foreground colour. */ +static void +input_osc_110(struct input_ctx *ictx, const char *p) +{ + struct window_pane *wp = ictx->wp; + + if (*p != '\0') + return; + if (ictx->palette != NULL) { + ictx->palette->fg = 8; + if (wp != NULL) + wp->flags |= PANE_STYLECHANGED; + screen_write_fullredraw(&ictx->ctx); + } +} + +/* Handle the OSC 11 sequence for setting and querying background colour. */ +static void +input_osc_11(struct input_ctx *ictx, const char *p) +{ + struct window_pane *wp = ictx->wp; + struct grid_cell defaults; + int c; + + if (strcmp(p, "?") == 0) { + if (wp != NULL) { + tty_default_colours(&defaults, wp); + input_osc_colour_reply(ictx, 11, defaults.bg); + } + return; + } + + if ((c = input_osc_parse_colour(p)) == -1) { + log_debug("bad OSC 11: %s", p); + return; + } + if (ictx->palette != NULL) { + ictx->palette->bg = c; + if (wp != NULL) + wp->flags |= PANE_STYLECHANGED; + screen_write_fullredraw(&ictx->ctx); + } +} + +/* Handle the OSC 111 sequence for resetting background colour. */ +static void +input_osc_111(struct input_ctx *ictx, const char *p) +{ + struct window_pane *wp = ictx->wp; + + if (*p != '\0') + return; + if (ictx->palette != NULL) { + ictx->palette->bg = 8; + if (wp != NULL) + wp->flags |= PANE_STYLECHANGED; + screen_write_fullredraw(&ictx->ctx); + } +} + +/* Handle the OSC 12 sequence for setting and querying cursor colour. */ +static void +input_osc_12(struct input_ctx *ictx, const char *p) +{ + struct window_pane *wp = ictx->wp; + int c; + + if (strcmp(p, "?") == 0) { + if (wp != NULL) { + c = ictx->ctx.s->ccolour; + if (c == -1) + c = ictx->ctx.s->default_ccolour; + input_osc_colour_reply(ictx, 12, c); + } + return; + } + + if ((c = input_osc_parse_colour(p)) == -1) { + log_debug("bad OSC 12: %s", p); + return; + } + screen_set_cursor_colour(ictx->ctx.s, c); +} + +/* Handle the OSC 112 sequence for resetting cursor colour. */ +static void +input_osc_112(struct input_ctx *ictx, const char *p) +{ + if (*p == '\0') /* no arguments allowed */ + screen_set_cursor_colour(ictx->ctx.s, -1); +} + + +/* Handle the OSC 52 sequence for setting the clipboard. */ +static void +input_osc_52(struct input_ctx *ictx, const char *p) +{ + struct window_pane *wp = ictx->wp; + char *end; + const char *buf = NULL; + size_t len = 0; + u_char *out; + int outlen, state; + struct screen_write_ctx ctx; + struct paste_buffer *pb; + + if (wp == NULL) + return; + state = options_get_number(global_options, "set-clipboard"); + if (state != 2) + return; + + if ((end = strchr(p, ';')) == NULL) + return; + end++; + if (*end == '\0') + return; + log_debug("%s: %s", __func__, end); + + if (strcmp(end, "?") == 0) { + if ((pb = paste_get_top(NULL)) != NULL) + buf = paste_buffer_data(pb, &len); + if (ictx->input_end == INPUT_END_BEL) + input_reply_clipboard(ictx->event, buf, len, "\007"); + else + input_reply_clipboard(ictx->event, buf, len, "\033\\"); + return; + } + + len = (strlen(end) / 4) * 3; + if (len == 0) + return; + + out = xmalloc(len); + if ((outlen = b64_pton(end, out, len)) == -1) { + free(out); + return; + } + + screen_write_start_pane(&ctx, wp, NULL); + screen_write_setselection(&ctx, out, outlen); + screen_write_stop(&ctx); + notify_pane("pane-set-clipboard", wp); + + paste_add(NULL, out, outlen); +} + +/* Handle the OSC 104 sequence for unsetting (multiple) palette entries. */ +static void +input_osc_104(struct input_ctx *ictx, const char *p) +{ + char *copy, *s; + long idx; + int bad = 0, redraw = 0; + + if (*p == '\0') { + colour_palette_clear(ictx->palette); + screen_write_fullredraw(&ictx->ctx); + return; + } + + copy = s = xstrdup(p); + while (*s != '\0') { + idx = strtol(s, &s, 10); + if (*s != '\0' && *s != ';') { + bad = 1; + break; + } + if (idx < 0 || idx >= 256) { + bad = 1; + break; + } + if (colour_palette_set(ictx->palette, idx, -1)) + redraw = 1; + if (*s == ';') + s++; + } + if (bad) + log_debug("bad OSC 104: %s", p); + if (redraw) + screen_write_fullredraw(&ictx->ctx); + free(copy); +} + +void +input_reply_clipboard(struct bufferevent *bev, const char *buf, size_t len, + const char *end) +{ + char *out = NULL; + size_t outlen = 0; + + if (buf != NULL && len != 0) { + outlen = 4 * ((len + 2) / 3) + 1; + out = xmalloc(outlen); + if ((outlen = b64_ntop(buf, len, out, outlen)) == -1) { + free(out); + return; + } + } + + bufferevent_write(bev, "\033]52;;", 6); + if (outlen != 0) + bufferevent_write(bev, out, outlen); + bufferevent_write(bev, end, strlen(end)); + free(out); +} |