/* $OpenBSD$ */

/*
* Copyright (c) 2007 Nicholas Marriott <[email protected]>
*
* 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;
       struct utf8_data        last;

       int                     flags;
#define INPUT_DISCARD 0x1
#define INPUT_LAST 0x2

       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_8(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 *);
static void     input_osc_133(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_sm_graphics(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_SM_GRAPHICS,
       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 },
       { 'S', "?", INPUT_CSI_SM_GRAPHICS },
       { '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((const char *)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->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;

       /* Flag new input while in a mode. */
       if (!TAILQ_EMPTY(&wp->modes))
               wp->flags |= PANE_UNSEENCHANGES;

       /* 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 = (char *)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);

       log_debug("%s: %s", __func__, reply);
       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);

       utf8_copy(&ictx->last, &ictx->cell.cell.data);
       ictx->flags |= INPUT_LAST;

       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->flags &= ~INPUT_LAST;
       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->flags &= ~INPUT_LAST;
       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, ek;
       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);
               if (n != 4)
                       break;
               m = input_get(ictx, 1, 0, 0);

               /*
                * Set the extended key reporting mode as per the client
                * request, unless "extended-keys" is set to "off".
                */
               ek = options_get_number(global_options, "extended-keys");
               if (ek == 0)
                       break;
               screen_write_mode_clear(sctx, EXTENDED_KEY_MODES);
               if (m == 2)
                       screen_write_mode_set(sctx, MODE_KEYS_EXTENDED_2);
               else if (m == 1 || ek == 2)
                       screen_write_mode_set(sctx, MODE_KEYS_EXTENDED);
               break;
       case INPUT_CSI_MODOFF:
               n = input_get(ictx, 0, 0, 0);
               if (n != 4)
                       break;

               /*
                * Clear the extended key reporting mode as per the client
                * request, unless "extended-keys always" forces into mode 1.
                */
               screen_write_mode_clear(sctx,
                   MODE_KEYS_EXTENDED|MODE_KEYS_EXTENDED_2);
               if (options_get_number(global_options, "extended-keys") == 2)
                       screen_write_mode_set(sctx, MODE_KEYS_EXTENDED);
               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:
#ifdef ENABLE_SIXEL
                       input_reply(ictx, "\033[?1;2;4c");
#else
                       input_reply(ictx, "\033[?1;2c");
#endif
                       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->flags & INPUT_LAST)
                       break;

               utf8_copy(&ictx->cell.cell.data, &ictx->last);
               for (i = 0; i < n; i++)
                       screen_write_collect_add(sctx, &ictx->cell.cell);
               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_SM_GRAPHICS:
               input_csi_dispatch_sm_graphics(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->flags &= ~INPUT_LAST;
       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 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:
                       screen_write_mode_set(sctx, MODE_FOCUSON);
                       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 graphics SM. */
static void
input_csi_dispatch_sm_graphics(__unused struct input_ctx *ictx)
{
#ifdef ENABLE_SIXEL
       int     n, m, o;

       if (ictx->param_list_len > 3)
               return;
       n = input_get(ictx, 0, 0, 0);
       m = input_get(ictx, 1, 0, 0);
       o = input_get(ictx, 2, 0, 0);

       if (n == 1 && (m == 1 || m == 2 || m == 4))
               input_reply(ictx, "\033[?%d;0;%uS", n, SIXEL_COLOUR_REGISTERS);
       else
               input_reply(ictx, "\033[?%d;3;%dS", n, o);
#endif
}

/* 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;
       struct window           *w = NULL;
       u_int                    x = screen_size_x(s), y = screen_size_y(s);
       int                      n, m;

       if (wp != NULL)
               w = wp->window;

       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 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 14:
                       if (w == NULL)
                               break;
                       input_reply(ictx, "\033[4;%u;%ut", y * w->ypixel,
                           x * w->xpixel);
                       break;
               case 15:
                       if (w == NULL)
                               break;
                       input_reply(ictx, "\033[5;%u;%ut", y * w->ypixel,
                           x * w->xpixel);
                       break;
               case 16:
                       if (w == NULL)
                               break;
                       input_reply(ictx, "\033[6;%u;%ut", w->ypixel,
                           w->xpixel);
                       break;
               case 18:
                       input_reply(ictx, "\033[8;%u;%ut", y, x);
                       break;
               case 19:
                       input_reply(ictx, "\033[9;%u;%ut", y, x);
                       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(w);
                               server_status_window(w);
                               break;
                       }
                       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, link;
       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:
                       link = gc->link;
                       memcpy(gc, &grid_default_cell, sizeof *gc);
                       gc->link = link;
                       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 = 8;
                       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->flags &= ~INPUT_LAST;
}

/* 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;
       char                    *buf = (char *)ictx->input_buf;
       size_t                   len = ictx->input_len;
       const char               prefix[] = "tmux;";
       const u_int              prefixlen = (sizeof prefix) - 1;
       long long                allow_passthrough = 0;
#ifdef ENABLE_SIXEL
       struct window           *w;
       struct sixel_image      *si;
#endif

       if (wp == NULL)
               return (0);

       if (ictx->flags & INPUT_DISCARD) {
               log_debug("%s: %zu bytes (discard)", __func__, len);
               return (0);
       }

#ifdef ENABLE_SIXEL
       w = wp->window;
       if (buf[0] == 'q') {
               si = sixel_parse(buf, len, w->xpixel, w->ypixel);
               if (si != NULL)
                       screen_write_sixelimage(sctx, si, ictx->cell.cell.bg);
       }
#endif

       allow_passthrough = options_get_number(wp->options, "allow-passthrough");
       if (!allow_passthrough)
               return (0);
       log_debug("%s: \"%s\"", __func__, buf);

       if (len >= prefixlen && strncmp(buf, prefix, prefixlen) == 0) {
               screen_write_rawstring(sctx, (u_char *)buf + prefixlen, len - prefixlen,
                   allow_passthrough == 2);
       }

       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->flags &= ~INPUT_LAST;
}

/* 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;
       char                    *p = (char *)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 != '\0')
               return;
       if (*p == ';')
               p++;

       switch (option) {
       case 0:
       case 2:
               if (wp != NULL &&
                   options_get_number(wp->options, "allow-set-title") &&
                   screen_set_title(sctx->s, p)) {
                       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 8:
               input_osc_8(ictx, p);
               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;
       case 133:
               input_osc_133(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->flags &= ~INPUT_LAST;
}

/* 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;
       char                    *p = (char *)ictx->input_buf;

       if (ictx->flags & INPUT_DISCARD)
               return;
       log_debug("%s: \"%s\"", __func__, p);

       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);
       }
}

/* Rename string started. */
static void
input_enter_rename(struct input_ctx *ictx)
{
       log_debug("%s", __func__);

       input_clear(ictx);
       input_start_timer(ictx);
       ictx->flags &= ~INPUT_LAST;
}

/* 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;
       char                    *p = (char *)ictx->input_buf;

       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__, p);

       if (!utf8_isvalid(p))
               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->flags &= ~INPUT_LAST;

       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);

       utf8_copy(&ictx->last, &ictx->cell.cell.data);
       ictx->flags |= INPUT_LAST;

       return (0);
}

/* 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 = colour_parseX11(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 8 sequence for embedding hyperlinks. */
static void
input_osc_8(struct input_ctx *ictx, const char *p)
{
       struct hyperlinks       *hl = ictx->ctx.s->hyperlinks;
       struct grid_cell        *gc = &ictx->cell.cell;
       const char              *start, *end, *uri;
       char                    *id = NULL;

       for (start = p; (end = strpbrk(start, ":;")) != NULL; start = end + 1) {
               if (end - start >= 4 && strncmp(start, "id=", 3) == 0) {
                       if (id != NULL)
                               goto bad;
                       id = xstrndup(start + 3, end - start - 3);
               }

               /* The first ; is the end of parameters and start of the URI. */
               if (*end == ';')
                       break;
       }
       if (end == NULL || *end != ';')
               goto bad;
       uri = end + 1;
       if (*uri == '\0') {
               gc->link = 0;
               free(id);
               return;
       }
       gc->link = hyperlinks_put(hl, uri, id);
       if (id == NULL)
               log_debug("hyperlink (anonymous) %s = %u", uri, gc->link);
       else
               log_debug("hyperlink (id=%s) %s = %u", id, uri, gc->link);
       free(id);
       return;

bad:
       log_debug("bad OSC 8 %s", p);
       free(id);
}

/*
* Get a client with a foreground for the pane. There isn't much to choose
* between them so just use the first.
*/
static int
input_get_fg_client(struct window_pane *wp)
{
       struct window   *w = wp->window;
       struct client   *loop;

       TAILQ_FOREACH(loop, &clients, entry) {
               if (loop->flags & CLIENT_UNATTACHEDFLAGS)
                       continue;
               if (loop->session == NULL || !session_has(loop->session, w))
                       continue;
               if (loop->tty.fg == -1)
                       continue;
               return (loop->tty.fg);
       }
       return (-1);
}

/* Get a client with a background for the pane. */
static int
input_get_bg_client(struct window_pane *wp)
{
       struct window   *w = wp->window;
       struct client   *loop;

       TAILQ_FOREACH(loop, &clients, entry) {
               if (loop->flags & CLIENT_UNATTACHEDFLAGS)
                       continue;
               if (loop->session == NULL || !session_has(loop->session, w))
                       continue;
               if (loop->tty.bg == -1)
                       continue;
               return (loop->tty.bg);
       }
       return (-1);
}

/*
* If any control mode client exists that has provided a bg color, return it.
* Otherwise, return -1.
*/
static int
input_get_bg_control_client(struct window_pane *wp)
{
       struct client   *c;

       if (wp->control_bg == -1)
               return (-1);

       TAILQ_FOREACH(c, &clients, entry) {
               if (c->flags & CLIENT_CONTROL)
                       return (wp->control_bg);
       }
       return (-1);
}

/*
* If any control mode client exists that has provided a fg color, return it.
* Otherwise, return -1.
*/
static int
input_get_fg_control_client(struct window_pane *wp)
{
       struct client   *c;

       if (wp->control_fg == -1)
               return (-1);

       TAILQ_FOREACH(c, &clients, entry) {
               if (c->flags & CLIENT_CONTROL)
                       return (wp->control_fg);
       }
       return (-1);
}

/* 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)
                       return;
               c = input_get_fg_control_client(wp);
               if (c == -1) {
                       tty_default_colours(&defaults, wp);
                       if (COLOUR_DEFAULT(defaults.fg))
                               c = input_get_fg_client(wp);
                       else
                               c = defaults.fg;
               }
               input_osc_colour_reply(ictx, 10, c);
               return;
       }

       if ((c = colour_parseX11(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)
                       return;
               c = input_get_bg_control_client(wp);
               if (c == -1) {
                       tty_default_colours(&defaults, wp);
                       if (COLOUR_DEFAULT(defaults.bg))
                               c = input_get_bg_client(wp);
                       else
                               c = defaults.bg;
               }
               input_osc_colour_reply(ictx, 11, c);
               return;
       }

       if ((c = colour_parseX11(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 = colour_parseX11(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 133 sequence. */
static void
input_osc_133(struct input_ctx *ictx, const char *p)
{
       struct grid             *gd = ictx->ctx.s->grid;
       u_int                    line = ictx->ctx.s->cy + gd->hsize;
       struct grid_line        *gl;

       if (line > gd->hsize + gd->sy - 1)
               return;
       gl = grid_get_line(gd, line);

       switch (*p) {
       case 'A':
               gl->flags |= GRID_LINE_START_PROMPT;
               break;
       case 'C':
               gl->flags |= GRID_LINE_START_OUTPUT;
               break;
       }
}

/* 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;
       const char*              allow = "cpqs01234567";
       char                     flags[sizeof "cpqs01234567"] = "";
       u_int                    i, j = 0;

       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);

       for (i = 0; p + i != end; i++) {
               if (strchr(allow, p[i]) != NULL && strchr(flags, p[i]) == NULL)
                       flags[j++] = p[i];
       }
       log_debug("%s: %.*s %s", __func__, (int)(end - p - 1), p, flags);

       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, flags, out, outlen);
       screen_write_stop(&ctx);
       notify_pane("pane-set-clipboard", wp);

       paste_add(NULL, (char *)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;
       int      outlen = 0;

       if (buf != NULL && len != 0) {
               if (len >= ((size_t)INT_MAX * 3 / 4) - 1)
                       return;
               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);
}