root/usr.bin/tmux/input.c
/* $OpenBSD: input.c,v 1.257 2026/04/03 09:14:27 nicm Exp $ */

/*
 * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/types.h>

#include <netinet/in.h>

#include <ctype.h>
#include <resolv.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include "tmux.h"

/*
 * Based on the description by Paul Williams at:
 *
 * https://vt100.net/emu/dec_ansi_parser
 *
 * With the following changes:
 *
 * - 7-bit only.
 *
 * - Support for UTF-8.
 *
 * - OSC (but not APC) may be terminated by \007 as well as ST.
 *
 * - A state for APC similar to OSC. Some terminals appear to use this to set
 *   the title.
 *
 * - A state for the screen \033k...\033\\ sequence to rename a window. This is
 *   pretty stupid but not supporting it is more trouble than it is worth.
 *
 * - Special handling for ESC inside a DCS to allow arbitrary byte sequences to
 *   be passed to the underlying terminals.
 */

/* Type of terminator. */
enum input_end_type {
        INPUT_END_ST,
        INPUT_END_BEL
};

/* Request sent by a pane. */
struct input_request {
        struct client                   *c;
        struct input_ctx                *ictx;

        enum input_request_type          type;
        uint64_t                         t;
        enum input_end_type              end;

        int                              idx;
        void                            *data;

        TAILQ_ENTRY(input_request)       entry;
        TAILQ_ENTRY(input_request)       centry;
};
#define INPUT_REQUEST_TIMEOUT 500

/* 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 client                  *c;

        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
        u_char                         *input_buf;
        size_t                          input_len;
        size_t                          input_space;
        enum input_end_type             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;

        const struct input_state       *state;
        int                             flags;
#define INPUT_DISCARD 0x1
#define INPUT_LAST 0x2

        struct input_requests            requests;
        u_int                            request_count;
        struct event                     request_timer;

        /*
         * All input received since we were last in the ground state. Sent to
         * control clients on connection.
         */
        struct evbuffer                 *since_ground;
        struct event                     ground_timer;
};

/* Helper functions. */
struct input_transition;
static void     input_request_timer_callback(int, short, void *);
static void     input_start_request_timer(struct input_ctx *);
static struct input_request *input_make_request(struct input_ctx *,
                    enum input_request_type);
static void     input_free_request(struct input_request *);
static int      input_add_request(struct input_ctx *, enum input_request_type,
                    int);
static int      input_split(struct input_ctx *);
static int      input_get(struct input_ctx *, u_int, int, int);
static void     input_set_state(struct input_ctx *,
                    const struct input_transition *);
static void     input_reset_cell(struct input_ctx *);
static void     input_report_current_theme(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_9(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_DSR_PRIVATE,
        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_QUERY_PRIVATE,
        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_GRAPHICS,
        INPUT_CSI_SM_PRIVATE,
        INPUT_CSI_SU,
        INPUT_CSI_TBC,
        INPUT_CSI_VPA,
        INPUT_CSI_WINOPS,
        INPUT_CSI_XDA
};

/* Control (CSI) command table. */
static const struct input_table_entry input_csi_table[] = {
        { '@', "",  INPUT_CSI_ICH },
        { 'A', "",  INPUT_CSI_CUU },
        { 'B', "",  INPUT_CSI_CUD },
        { 'C', "",  INPUT_CSI_CUF },
        { 'D', "",  INPUT_CSI_CUB },
        { 'E', "",  INPUT_CSI_CNL },
        { 'F', "",  INPUT_CSI_CPL },
        { 'G', "",  INPUT_CSI_HPA },
        { 'H', "",  INPUT_CSI_CUP },
        { 'J', "",  INPUT_CSI_ED },
        { 'K', "",  INPUT_CSI_EL },
        { 'L', "",  INPUT_CSI_IL },
        { 'M', "",  INPUT_CSI_DL },
        { 'P', "",  INPUT_CSI_DCH },
        { 'S', "",  INPUT_CSI_SU },
        { '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 },
        { 'n', "?", INPUT_CSI_DSR_PRIVATE },
        { 'p', "?$", INPUT_CSI_QUERY_PRIVATE },
        { '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 }
};

/* Maximum of bytes allowed to read in a single input. */
static size_t input_buffer_size = INPUT_BUF_DEFAULT_SIZE;

/* Input table compare. */
static int
input_table_compare(const void *key, const void *value)
{
        const struct input_ctx          *ictx = key;
        const struct input_table_entry  *entry = value;

        if (ictx->ch != entry->ch)
                return (ictx->ch - entry->ch);
        return (strcmp(ictx->interm_buf, entry->interm));
}

/* Stop UTF-8 and enter an invalid character. */
static void
input_stop_utf8(struct input_ctx *ictx)
{
        struct screen_write_ctx *sctx = &ictx->ctx;
        static struct utf8_data  rc = { "\357\277\275", 3, 3, 1 };

        if (ictx->utf8started) {
                utf8_copy(&ictx->cell.cell.data, &rc);
                screen_write_collect_add(sctx, &ictx->cell.cell);
        }
        ictx->utf8started = 0;
}

/*
 * Timer - if this expires then have been waiting for a terminator for too
 * long, so reset to ground.
 */
static void
input_ground_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_ground_timer(struct input_ctx *ictx)
{
        struct timeval  tv = { .tv_sec = 5, .tv_usec = 0 };

        event_del(&ictx->ground_timer);
        event_add(&ictx->ground_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 client *c)
{
        struct input_ctx        *ictx;

        ictx = xcalloc(1, sizeof *ictx);
        ictx->wp = wp;
        ictx->event = bev;
        ictx->palette = palette;
        ictx->c = c;

        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->ground_timer, input_ground_timer_callback, ictx);

        TAILQ_INIT(&ictx->requests);
        evtimer_set(&ictx->request_timer, input_request_timer_callback, ictx);

        input_reset(ictx, 0);
        return (ictx);
}

/* Destroy input parser. */
void
input_free(struct input_ctx *ictx)
{
        struct input_request    *ir, *ir1;
        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);
        }

        TAILQ_FOREACH_SAFE(ir, &ictx->requests, entry, ir1)
                input_free_request(ir);
        event_del(&ictx->request_timer);

        free(ictx->input_buf);
        evbuffer_free(ictx->since_ground);
        event_del(&ictx->ground_timer);

        screen_write_stop_sync(ictx->wp);

        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, const 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, const 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, const u_char *buf, size_t len)
{
        struct screen_write_ctx *sctx = &ictx->ctx;

        if (len == 0)
                return;

        screen_write_start_callback(sctx, s, cb, arg);
        input_parse(ictx, buf, len);
        screen_write_stop(sctx);
}

/* Split the parameter list (if any). */
static int
input_split(struct input_ctx *ictx)
{
        const char              *errstr;
        char                    *ptr, *out;
        struct input_param      *ip;
        u_int                    i;

        for (i = 0; i < ictx->param_list_len; i++) {
                if (ictx->param_list[i].type == INPUT_STRING)
                        free(ictx->param_list[i].str);
        }
        ictx->param_list_len = 0;

        if (ictx->param_len == 0)
                return (0);
        ip = &ictx->param_list[0];

        ptr = ictx->param_buf;
        while ((out = strsep(&ptr, ";")) != NULL) {
                if (*out == '\0')
                        ip->type = INPUT_MISSING;
                else {
                        if (strchr(out, ':') != NULL) {
                                ip->type = INPUT_STRING;
                                ip->str = xstrdup(out);
                        } else {
                                ip->type = INPUT_NUMBER;
                                ip->num = strtonum(out, 0, INT_MAX, &errstr);
                                if (errstr != NULL)
                                        return (-1);
                        }
                }
                ip = &ictx->param_list[++ictx->param_list_len];
                if (ictx->param_list_len == nitems(ictx->param_list))
                        return (-1);
        }

        for (i = 0; i < ictx->param_list_len; i++) {
                ip = &ictx->param_list[i];
                if (ip->type == INPUT_MISSING)
                        log_debug("parameter %u: missing", i);
                else if (ip->type == INPUT_STRING)
                        log_debug("parameter %u: string %s", i, ip->str);
                else if (ip->type == INPUT_NUMBER)
                        log_debug("parameter %u: number %d", i, ip->num);
        }

        return (0);
}

/* Get an argument or return default value. */
static int
input_get(struct input_ctx *ictx, u_int validx, int minval, int defval)
{
        struct input_param      *ip;
        int                      retval;

        if (validx >= ictx->param_list_len)
            return (defval);
        ip = &ictx->param_list[validx];
        if (ip->type == INPUT_MISSING)
                return (defval);
        if (ip->type == INPUT_STRING)
                return (-1);
        retval = ip->num;
        if (retval < minval)
                return (minval);
        return (retval);
}

/* Send reply. */
static void
input_send_reply(struct input_ctx *ictx, const char *reply)
{
        if (ictx->event != NULL) {
                log_debug("%s: %s", __func__, reply);
                bufferevent_write(ictx->event, reply, strlen(reply));
        }
}

/* Reply to terminal query. */
static void printflike(3, 4)
input_reply(struct input_ctx *ictx, int add, const char *fmt, ...)
{
        struct input_request    *ir;
        va_list                  ap;
        char                    *reply;

        va_start(ap, fmt);
        xvasprintf(&reply, fmt, ap);
        va_end(ap);

        if (add && !TAILQ_EMPTY(&ictx->requests)) {
                ir = input_make_request(ictx, INPUT_REQUEST_QUEUE);
                ir->data = reply;
        } else {
                input_send_reply(ictx, reply);
                free(reply);
        }
}

/* Clear saved state. */
static void
input_clear(struct input_ctx *ictx)
{
        event_del(&ictx->ground_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->ground_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;

        input_stop_utf8(ictx); /* 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_buffer_size) {
                        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;
        struct grid_cell         gc, first_gc;
        u_int                    cx, line;
        u_int                    width;
        int                      has_content = 0;

        input_stop_utf8(ictx); /* 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. */
                cx = s->cx;
                if (cx >= screen_size_x(s) - 1)
                        break;

                /* Find the next tab point, or use the last column if none. */
                line = s->cy + s->grid->hsize;
                grid_get_cell(s->grid, cx, line, &first_gc);
                do {
                        if (!has_content) {
                                grid_get_cell(s->grid, cx, line, &gc);
                                if (gc.data.size != 1 ||
                                    *gc.data.data != ' ' ||
                                    !grid_cells_look_equal(&gc, &first_gc))
                                        has_content = 1;
                        }
                        cx++;
                        if (bit_test(s->tabs, cx))
                                break;
                } while (cx < screen_size_x(s) - 1);

                width = cx - s->cx;
                if (has_content || width > sizeof gc.data.data)
                        s->cx = cx;
                else {
                        grid_get_cell(s->grid, s->cx, line, &gc);
                        grid_set_tab(&gc, width);
                        screen_write_collect_add(sctx, &gc);
                }
                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;
        struct options                 *oo;
        int                             i, n, m, ek, set, p;
        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:
                        input_reply(ictx, 1, "\033[?1;2c");
                        break;
                default:
                        log_debug("%s: unknown '%c'", __func__, ictx->ch);
                        break;
                }
                break;
        case INPUT_CSI_DA_TWO:
                switch (input_get(ictx, 0, 0, 0)) {
                case -1:
                        break;
                case 0:
                        input_reply(ictx, 1, "\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_PRIVATE:
                switch (input_get(ictx, 0, 0, 0)) {
                case 996:
                        input_report_current_theme(ictx);
                        break;
                }
                break;
        case INPUT_CSI_QUERY_PRIVATE:
                switch (input_get(ictx, 0, 0, 0)) {
                case 12: /* cursor blink: 1 = blink, 2 = steady */
                        if (s->cstyle != SCREEN_CURSOR_DEFAULT ||
                            s->mode & MODE_CURSOR_BLINKING_SET)
                                n = (s->mode & MODE_CURSOR_BLINKING) ? 1 : 2;
                        else {
                                if (ictx->wp != NULL)
                                        oo = ictx->wp->options;
                                else
                                        oo = global_w_options;
                                p = options_get_number(oo, "cursor-style");

                                /* blink for 1,3,5; steady for 0,2,4,6 */
                                n = (p == 1 || p == 3 || p == 5) ? 1 : 2;
                        }
                        input_reply(ictx, 1, "\033[?12;%d$y", n);
                        break;
                case 1004: /* focus reporting */
                        n = (s->mode & MODE_FOCUSON) ? 1 : 2;
                        input_reply(ictx, 1, "\033[?1004;%d$y", n);
                        break;
                case 1006: /* SGR mouse */
                        n = (s->mode & MODE_MOUSE_SGR) ? 1 : 2;
                        input_reply(ictx, 1, "\033[?1006;%d$y", n);
                        break;
                case 2004: /* bracketed paste */
                        n = (s->mode & MODE_BRACKETPASTE) ? 1 : 2;
                        input_reply(ictx, 1, "\033[?2004;%d$y", n);
                        break;
                case 2026: /* synchronized output */
                        n = (s->mode & MODE_SYNC) ? 1 : 2;
                        input_reply(ictx, 1, "\033[?2026;%d$y", n);
                        break;
                case 2031:
                        input_reply(ictx, 1, "\033[?2031;2$y");
                        break;
                }
                break;
        case INPUT_CSI_DSR:
                switch (input_get(ictx, 0, 0, 0)) {
                case -1:
                        break;
                case 5:
                        input_reply(ictx, 1, "\033[0n");
                        break;
                case 6:
                        input_reply(ictx, 1, "\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;

                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_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)
                        break;
                screen_set_cursor_style(n, &s->cstyle, &s->mode);
                if (n == 0) {
                        /* Go back to default blinking state. */
                        screen_write_mode_clear(sctx, MODE_CURSOR_BLINKING_SET);
                }
                break;
        case INPUT_CSI_XDA:
                n = input_get(ictx, 0, 0, 0);
                if (n == 0) {
                        input_reply(ictx, 1, "\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;
                case 2031:
                        screen_write_mode_clear(sctx, MODE_THEME_UPDATES);
                        if (ictx->wp != NULL)
                                ictx->wp->flags &= ~PANE_THEMECHANGED;
                        break;
                case 2026:      /* synchronized output */
                        screen_write_stop_sync(ictx->wp);
                        if (ictx->wp != NULL)
                                ictx->wp->flags |= PANE_REDRAW;
                        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;
                case 2031:
                        screen_write_mode_set(sctx, MODE_THEME_UPDATES);
                        if (ictx->wp != NULL) {
                                ictx->wp->last_theme = window_pane_get_theme(ictx->wp);
                                ictx->wp->flags &= ~PANE_THEMECHANGED;
                        }
                        break;
                case 2026:      /* synchronized output */
                        screen_write_start_sync(ictx->wp);
                        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)
{
}

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

/* Handle DECRQSS query. */
static int
input_handle_decrqss(struct input_ctx *ictx)
{
        struct window_pane      *wp = ictx->wp;
        struct options          *oo;
        struct screen_write_ctx *sctx = &ictx->ctx;
        u_char                  *buf = ictx->input_buf;
        size_t                   len = ictx->input_len;
        struct screen           *s = sctx->s;
        int                      ps, opt_ps, blinking;

        if (len < 3 || buf[1] != ' ' || buf[2] != 'q')
                goto not_recognized;

        /*
         * Cursor style query: DCS $ q SP q
         * Reply: DCS 1 $ r SP q <Ps> SP q ST
         */
        if (s->cstyle == SCREEN_CURSOR_BLOCK ||
            s->cstyle == SCREEN_CURSOR_UNDERLINE ||
            s->cstyle == SCREEN_CURSOR_BAR) {
                blinking = (s->mode & MODE_CURSOR_BLINKING) != 0;
                switch (s->cstyle) {
                case SCREEN_CURSOR_BLOCK:
                        ps = blinking ? 1 : 2;
                        break;
                case SCREEN_CURSOR_UNDERLINE:
                        ps = blinking ? 3 : 4;
                        break;
                case SCREEN_CURSOR_BAR:
                        ps = blinking ? 5 : 6;
                        break;
                default:
                        ps = 0;
                        break;
                }
        } else {
                /*
                 * No explicit runtime style: fall back to the configured
                 * cursor-style option (integer Ps 0..6). Pane options inherit.
                 */
                if (wp != NULL)
                        oo = wp->options;
                else
                        oo = global_w_options;
                opt_ps = options_get_number(oo, "cursor-style");

                /* Sanity clamp: valid Ps are 0..6 per DECSCUSR. */
                if (opt_ps < 0 || opt_ps > 6)
                        opt_ps = 0;
                ps = opt_ps;
        }

        log_debug("%s: DECRQSS cursor -> Ps=%d (cstyle=%d mode=%#x)", __func__,
            ps, s->cstyle, s->mode);

        input_reply(ictx, 1, "\033P1$r q%d q\033\\", ps);
        return (0);

not_recognized:
        /* Unrecognized DECRQSS: send DCS 0 $ r Pt ST. */
        input_reply(ictx, 1, "\033P0$r\033\\");
        return (0);
}

/* DCS terminator (ST) received. */
static int
input_dcs_dispatch(struct input_ctx *ictx)
{
        struct window_pane      *wp = ictx->wp;
        struct options          *oo;
        struct screen_write_ctx *sctx = &ictx->ctx;
        u_char                  *buf = ictx->input_buf;
        size_t                   len = ictx->input_len;
        const char               prefix[] = "tmux;";
        const u_int              prefixlen = (sizeof prefix) - 1;
        long long                allow_passthrough = 0;

        if (wp == NULL)
                oo = global_w_options;
        else
                oo = wp->options;

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

        /* DCS sequences with intermediate byte '$' (includes DECRQSS). */
        if (ictx->interm_len == 1 && ictx->interm_buf[0] == '$') {
                /* DECRQSS is DCS $ q Pt ST. */
                if (len >= 1 && buf[0] == 'q')
                        return (input_handle_decrqss(ictx));

                /*
                 * Not DECRQSS. DCS '$' is currently only used by DECRQSS, but
                 * leave other '$' DCS (if any appear in future) to existing
                 * handlers.
                 */
        }

        allow_passthrough = options_get_number(oo, "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, 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_ground_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;
        u_char                  *p = ictx->input_buf;
        u_int                    option;

        if (ictx->flags & INPUT_DISCARD)
                return;
        if (ictx->input_len < 1 || *p < '0' || *p > '9')
                return;

        log_debug("%s: \"%s\" (end %s)", __func__, p,
            ictx->input_end == INPUT_END_ST ? "ST" : "BEL");

        option = 0;
        while (*p >= '0' && *p <= '9')
                option = option * 10 + *p++ - '0';
        if (*p != ';' && *p != '\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 9:
                input_osc_9(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_ground_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;

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

        if (wp != NULL &&
            options_get_number(wp->options, "allow-set-title") &&
            screen_set_title(sctx->s, ictx->input_buf)) {
                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_ground_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;

        if (wp == NULL)
                return;
        if (ictx->flags & INPUT_DISCARD)
                return;
        if (!options_get_number(ictx->wp->options, "allow-rename"))
                return;
        log_debug("%s: \"%s\"", __func__, ictx->input_buf);

        if (!utf8_isvalid(ictx->input_buf))
                return;
        w = wp->window;

        if (ictx->input_len == 0) {
                o = options_get_only(w->options, "automatic-rename");
                if (o != NULL)
                        options_remove_or_default(o, -1, NULL);
                if (!options_get_number(w->options, "automatic-rename"))
                        window_set_name(w, "");
        } else {
                options_set_number(w->options, "automatic-rename", 0);
                window_set_name(w, ictx->input_buf);
        }
        server_redraw_window_borders(w);
        server_status_window(w);
}

/* Open UTF-8 character. */
static int
input_top_bit_set(struct input_ctx *ictx)
{
        struct screen_write_ctx *sctx = &ictx->ctx;
        struct utf8_data        *ud = &ictx->utf8data;

        ictx->flags &= ~INPUT_LAST;

        if (!ictx->utf8started) {
                ictx->utf8started = 1;
                if (utf8_open(ud, ictx->ch) != UTF8_MORE)
                        input_stop_utf8(ictx);
                return (0);
        }

        switch (utf8_append(ud, ictx->ch)) {
        case UTF8_MORE:
                return (0);
        case UTF8_ERROR:
                input_stop_utf8(ictx);
                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, int add, u_int n, int idx, int c,
    enum input_end_type end_type)
{
        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 (end_type == INPUT_END_BEL)
                end = "\007";
        else
                end = "\033\\";

        if (n == 4) {
                input_reply(ictx, add,
                    "\033]%u;%d;rgb:%02hhx%02hhx/%02hhx%02hhx/%02hhx%02hhx%s",
                    n, idx, r, r, g, g, b, b, end);
        } else {
                input_reply(ictx, add,
                    "\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;
        struct colour_palette   *palette = ictx->palette;

        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(palette, idx|COLOUR_FLAG_256);
                        if (c != -1) {
                                input_osc_colour_reply(ictx, 1, 4, idx, c,
                                    ictx->input_end);
                                s = next;
                                continue;
                        }
                        input_add_request(ictx, INPUT_REQUEST_PALETTE, idx);
                        s = next;
                        continue;
                }
                if ((c = colour_parseX11(s)) == -1) {
                        s = next;
                        continue;
                }
                if (colour_palette_set(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);
}

/* Helper to handle setting the progress bar and redrawing. */
static void
input_set_progress_bar(struct input_ctx *ictx, enum progress_bar_state state,
    int p)
{
        screen_set_progress_bar(ictx->ctx.s, state, p);
        if (ictx->wp != NULL) {
                server_redraw_window_borders(ictx->wp->window);
                server_status_window(ictx->wp->window);
        }
}

/* Handle the OSC 9;4 sequence for progress bars. */
static void
input_osc_9(struct input_ctx *ictx, const char *p)
{
        const char              *pb = p;
        enum progress_bar_state  state;
        int                      progress = 0;

        if (*pb++ != '4')
                return;
        if (*pb == '\0' || (*pb == ';' && pb[1] == '\0'))
                return;

        if (*pb++ != ';')
                return;
        if (*pb < '0' || *pb > '4')
                goto bad;
        state = *pb++ - '0';

        if (*pb == '\0' || (*pb == ';' && pb[1] == '\0')) {
                input_set_progress_bar(ictx, state, -1);
                return;
        }

        if (*pb++ != ';')
                goto bad;
        while (*pb >= '0' && *pb <= '9') {
                if (progress > 100)
                        goto bad;
                progress = progress * 10 + *pb++ - '0';
        }
        if (*pb != '\0' || progress < 0 || progress > 100)
                goto bad;
        input_set_progress_bar(ictx, state, progress);
        return;

bad:
        log_debug("bad OSC 9;4 %s", p);
}

/* 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 = window_pane_get_fg_control_client(wp);
                if (c == -1) {
                        tty_default_colours(&defaults, wp);
                        if (COLOUR_DEFAULT(defaults.fg))
                                c = window_pane_get_fg(wp);
                        else
                                c = defaults.fg;
                }
                input_osc_colour_reply(ictx, 1, 10, 0, c, ictx->input_end);
                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;
        int                      c;

        if (strcmp(p, "?") == 0) {
                if (wp == NULL)
                        return;
                c = window_pane_get_bg(wp);
                input_osc_colour_reply(ictx, 1, 11, 0, c, ictx->input_end);
                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|PANE_THEMECHANGED);
                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|PANE_THEMECHANGED);
                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, 1, 12, 0, c, ictx->input_end);
                }
                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 OSC 52 reply. */
static void
input_osc_52_reply(struct input_ctx *ictx, char clip)
{
        struct bufferevent      *ev = ictx->event;
        struct paste_buffer     *pb;
        int                      state;
        const char              *buf;
        size_t                   len;

        state = options_get_number(global_options, "get-clipboard");
        if (state == 0)
                return;
        if (state == 1) {
                if ((pb = paste_get_top(NULL)) == NULL)
                        return;
                buf = paste_buffer_data(pb, &len);
                if (ictx->input_end == INPUT_END_BEL)
                        input_reply_clipboard(ev, buf, len, "\007", clip);
                else
                        input_reply_clipboard(ev, buf, len, "\033\\", clip);
                return;
        }
        input_add_request(ictx, INPUT_REQUEST_CLIPBOARD, ictx->input_end);
}

/*
 * Parse and decode OSC 52 clipboard data. Returns 0 on failure or if handled
 * as a query. On success, returns 1 and sets *out, *outlen, and *flags (caller
 * must free *out).
 */
static int
input_osc_52_parse(struct input_ctx *ictx, const char *p, u_char **out,
    int *outlen, char *clip)
{
        char            *end;
        size_t           len;
        const char      *allow = "cpqs01234567";
        u_int            i, j = 0;

        if (options_get_number(global_options, "set-clipboard") != 2)
                return (0);

        if ((end = strchr(p, ';')) == NULL)
                return (0);
        end++;
        if (*end == '\0')
                return (0);
        log_debug("%s: %s", __func__, end);

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

        if (strcmp(end, "?") == 0) {
                input_osc_52_reply(ictx, *clip);
                return (0);
        }

        len = ((strlen(end) + 3) / 4) * 3;
        if (len == 0)
                return (0);

        *out = xmalloc(len);
        if ((*outlen = b64_pton(end, *out, len)) == -1) {
                free(*out);
                *out = NULL;
                return (0);
        }

        return (1);
}

/* Handle the OSC 52 sequence for setting the clipboard. */
static void
input_osc_52(struct input_ctx *ictx, const char *p)
{
        struct window_pane      *wp = ictx->wp;
        struct screen_write_ctx  ctx;
        u_char                  *out;
        int                      outlen;
        char                     clip[sizeof "cpqs01234567"] = "";

        if (!input_osc_52_parse(ictx, p, &out, &outlen, clip))
                return;

        if (wp == NULL) {
                /* Popup window. */
                if (ictx->c == NULL) {
                        free(out);
                        return;
                }
                tty_set_selection(&ictx->c->tty, clip, out, outlen);
                paste_add(NULL, out, outlen);
        } else {
                /* Normal window. */
                screen_write_start_pane(&ctx, wp, NULL);
                screen_write_setselection(&ctx, clip, out, outlen);
                screen_write_stop(&ctx);
                notify_pane("pane-set-clipboard", wp);
                paste_add(NULL, out, outlen);
        }
}

/* Handle the OSC 104 sequence for unsetting (multiple) palette entries. */
static void
input_osc_104(struct input_ctx *ictx, const char *p)
{
        char    *copy, *s;
        long     idx;
        int      bad = 0, redraw = 0;

        if (*p == '\0') {
                colour_palette_clear(ictx->palette);
                screen_write_fullredraw(&ictx->ctx);
                return;
        }

        copy = s = xstrdup(p);
        while (*s != '\0') {
                idx = strtol(s, &s, 10);
                if (*s != '\0' && *s != ';') {
                        bad = 1;
                        break;
                }
                if (idx < 0 || idx >= 256) {
                        bad = 1;
                        break;
                }
                if (colour_palette_set(ictx->palette, idx, -1))
                        redraw = 1;
                if (*s == ';')
                        s++;
        }
        if (bad)
                log_debug("bad OSC 104: %s", p);
        if (redraw)
                screen_write_fullredraw(&ictx->ctx);
        free(copy);
}

/* Send a clipboard reply. */
void
input_reply_clipboard(struct bufferevent *bev, const char *buf, size_t len,
    const char *end, char clip)
{
        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;", 5);
        if (clip != 0)
                bufferevent_write(bev, &clip, 1);
        bufferevent_write(bev, ";", 1);
        if (outlen != 0)
                bufferevent_write(bev, out, outlen);
        bufferevent_write(bev, end, strlen(end));
        free(out);
}

/* Set input buffer size. */
void
input_set_buffer_size(size_t buffer_size)
{
        log_debug("%s: %lu -> %lu", __func__, input_buffer_size, buffer_size);
        input_buffer_size = buffer_size;
}

/* Request timer. Remove any requests that are too old. */
static void
input_request_timer_callback(__unused int fd, __unused short events, void *arg)
{
        struct input_ctx        *ictx = arg;
        struct input_request    *ir, *ir1;
        uint64_t                 t = get_timer();

        TAILQ_FOREACH_SAFE(ir, &ictx->requests, entry, ir1) {
                if (ir->t >= t - INPUT_REQUEST_TIMEOUT)
                        continue;
                if (ir->type == INPUT_REQUEST_QUEUE)
                        input_send_reply(ir->ictx, ir->data);
                input_free_request(ir);
        }
        if (ictx->request_count != 0)
                input_start_request_timer(ictx);
}

/* Start the request timer. */
static void
input_start_request_timer(struct input_ctx *ictx)
{
        struct timeval  tv = { .tv_sec = 0, .tv_usec = 100000 };

        event_del(&ictx->request_timer);
        event_add(&ictx->request_timer, &tv);
}

/* Create a request. */
static struct input_request *
input_make_request(struct input_ctx *ictx, enum input_request_type type)
{
        struct input_request    *ir;

        ir = xcalloc (1, sizeof *ir);
        ir->type = type;
        ir->ictx = ictx;
        ir->t = get_timer();

        if (++ictx->request_count == 1)
                input_start_request_timer(ictx);
        TAILQ_INSERT_TAIL(&ictx->requests, ir, entry);

        return (ir);
}

/* Free a request. */
static void
input_free_request(struct input_request *ir)
{
        struct input_ctx        *ictx = ir->ictx;

        if (ir->c != NULL)
                TAILQ_REMOVE(&ir->c->input_requests, ir, centry);

        ictx->request_count--;
        TAILQ_REMOVE(&ictx->requests, ir, entry);

        free(ir->data);
        free(ir);
}

/* Add a request. */
static int
input_add_request(struct input_ctx *ictx, enum input_request_type type, int idx)
{
        struct window_pane      *wp = ictx->wp;
        struct window           *w;
        struct client           *c = NULL, *loop;
        struct input_request    *ir;
        char                     s[64];

        if (wp == NULL)
                return (-1);
        w = wp->window;

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

        ir = input_make_request(ictx, type);
        ir->c = c;
        ir->idx = idx;
        ir->end = ictx->input_end;
        TAILQ_INSERT_TAIL(&c->input_requests, ir, centry);

        switch (type) {
        case INPUT_REQUEST_PALETTE:
                xsnprintf(s, sizeof s, "\033]4;%d;?\033\\", idx);
                tty_puts(&c->tty, s);
                break;
        case INPUT_REQUEST_CLIPBOARD:
                tty_putcode_ss(&c->tty, TTYC_MS, "", "?");
                break;
        case INPUT_REQUEST_QUEUE:
                break;
        }

        return (0);
}

/* Handle a palette reply. */
static void
input_request_palette_reply(struct input_request *ir, void *data)
{
        struct input_request_palette_data       *pd = data;

        input_osc_colour_reply(ir->ictx, 0, 4, pd->idx, pd->c, ir->end);
}

/* Handle a clipboard reply. */
static void
input_request_clipboard_reply(struct input_request *ir, void *data)
{
        struct input_ctx                        *ictx = ir->ictx;
        struct bufferevent                      *ev = ictx->event;
        struct input_request_clipboard_data     *cd = data;
        int                                      state;
        char                                    *copy;

        state = options_get_number(global_options, "get-clipboard");
        if (state == 0 || state == 1)
                return;
        if (state == 3) {
                copy = xmalloc(cd->len);
                memcpy(copy, cd->buf, cd->len);
                paste_add(NULL, copy, cd->len);
        }

        if (ir->idx == INPUT_END_BEL)
                input_reply_clipboard(ev, cd->buf, cd->len, "\007", cd->clip);
        else
                input_reply_clipboard(ev, cd->buf, cd->len, "\033\\", cd->clip);
}

/* Handle a reply to a request. */
void
input_request_reply(struct client *c, enum input_request_type type, void *data)
{
        struct input_request                    *ir, *ir1, *found = NULL;
        struct input_request_palette_data       *pd = data;
        int                                      complete = 0;

        TAILQ_FOREACH_SAFE(ir, &c->input_requests, centry, ir1) {
                if (ir->type != type) {
                        input_free_request(ir);
                        continue;
                }
                if (type == INPUT_REQUEST_PALETTE) {
                        if (pd->idx != ir->idx) {
                                input_free_request(ir);
                                continue;
                        }
                        found = ir;
                        break;
                }
                if (type == INPUT_REQUEST_CLIPBOARD) {
                        found = ir;
                        break;
                }
        }
        if (found == NULL)
                return;

        TAILQ_FOREACH_SAFE(ir, &found->ictx->requests, entry, ir1) {
                if (complete && ir->type != INPUT_REQUEST_QUEUE)
                        break;
                if (ir->type == INPUT_REQUEST_QUEUE)
                        input_send_reply(ir->ictx, ir->data);
                else if (ir == found) {
                        if (ir->type == INPUT_REQUEST_PALETTE)
                                input_request_palette_reply(ir, data);
                        else if (ir->type == INPUT_REQUEST_CLIPBOARD)
                                input_request_clipboard_reply(ir, data);
                        complete = 1;
                }
                input_free_request(ir);
        }
}

/* Cancel pending requests for client. */
void
input_cancel_requests(struct client *c)
{
        struct input_request    *ir, *ir1;

        TAILQ_FOREACH_SAFE(ir, &c->input_requests, centry, ir1)
                input_free_request(ir);
}

/* Report current theme. */
static void
input_report_current_theme(struct input_ctx *ictx)
{
        struct window_pane      *wp = ictx->wp;

        if (wp != NULL) {
                wp->last_theme = window_pane_get_theme(wp);
                wp->flags &= ~PANE_THEMECHANGED;

                switch (wp->last_theme) {
                case THEME_DARK:
                        log_debug("%s: %%%u dark theme", __func__, wp->id);
                        input_reply(ictx, 0, "\033[?997;1n");
                        break;
                case THEME_LIGHT:
                        log_debug("%s: %%%u light theme", __func__, wp->id);
                        input_reply(ictx, 0, "\033[?997;2n");
                        break;
                case THEME_UNKNOWN:
                        log_debug("%s: %%%u unknown theme", __func__, wp->id);
                        break;
                }
        }
}