/* $Id: input.c,v 1.30 2007-11-09 17:06:01 nicm Exp $ */

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

#include <sys/types.h>

#include <stdint.h>
#include <stdlib.h>
#include <string.h>

#include "tmux.h"

#define INPUT_C0CONTROL(ch) 	(ch <= 0x1f)
#define INPUT_INTERMEDIATE(ch)	(ch == 0xa0 || (ch >= 0x20 && ch <= 0x2f))
#define INPUT_PARAMETER(ch)	(ch >= 0x30 && ch <= 0x3f)
#define INPUT_UPPERCASE(ch)	(ch >= 0x40 && ch <= 0x5f)
#define INPUT_LOWERCASE(ch)	(ch >= 0x60 && ch <= 0x7e)
#define INPUT_DELETE(ch)	(ch == 0x7f)
#define INPUT_C1CONTROL(ch)	(ch >= 0x80 && ch <= 0x9f)
#define INPUT_G1DISPLAYABLE(ch)	(ch >= 0xa1 && ch <= 0xfe)
#define INPUT_SPECIAL(ch)	(ch == 0xff)

int	 input_get_argument(struct input_ctx *, u_int, uint16_t *, uint16_t);
int	 input_new_argument(struct input_ctx *);
int	 input_add_argument(struct input_ctx *, u_char ch);

void	*input_state_first(u_char, struct input_ctx *);
void	*input_state_escape(u_char, struct input_ctx *);
void	*input_state_intermediate(u_char, struct input_ctx *);
void	*input_state_title_first(u_char, struct input_ctx *);
void	*input_state_title_second(u_char, struct input_ctx *);
void	*input_state_title_next(u_char, struct input_ctx *);
void	*input_state_sequence_first(u_char, struct input_ctx *);
void	*input_state_sequence_next(u_char, struct input_ctx *);
void	*input_state_sequence_intermediate(u_char, struct input_ctx *);

void	 input_handle_character(u_char, struct input_ctx *);
void	 input_handle_c0_control(u_char, struct input_ctx *);
void	 input_handle_c1_control(u_char, struct input_ctx *);
void	 input_handle_private_two(u_char, struct input_ctx *);
void	 input_handle_standard_two(u_char, struct input_ctx *);
void	 input_handle_sequence(u_char, struct input_ctx *);

void	 input_handle_sequence_cuu(struct input_ctx *);
void	 input_handle_sequence_cud(struct input_ctx *);
void	 input_handle_sequence_cuf(struct input_ctx *);
void	 input_handle_sequence_cub(struct input_ctx *);
void	 input_handle_sequence_dch(struct input_ctx *);
void	 input_handle_sequence_dl(struct input_ctx *);
void	 input_handle_sequence_ich(struct input_ctx *);
void	 input_handle_sequence_il(struct input_ctx *);
void	 input_handle_sequence_vpa(struct input_ctx *);
void	 input_handle_sequence_hpa(struct input_ctx *);
void	 input_handle_sequence_cup(struct input_ctx *);
void	 input_handle_sequence_cup(struct input_ctx *);
void	 input_handle_sequence_ed(struct input_ctx *);
void	 input_handle_sequence_el(struct input_ctx *);
void	 input_handle_sequence_sm(struct input_ctx *);
void	 input_handle_sequence_rm(struct input_ctx *);
void	 input_handle_sequence_decstbm(struct input_ctx *);
void	 input_handle_sequence_sgr(struct input_ctx *);
void	 input_handle_sequence_dsr(struct input_ctx *);

int
input_new_argument(struct input_ctx *ictx)
{
	struct input_arg       *arg;

	ARRAY_EXPAND(&ictx->args, 1);

	arg = &ARRAY_LAST(&ictx->args);
	arg->used = 0;

	return (0);
}

int
input_add_argument(struct input_ctx *ictx, u_char ch)
{
	struct input_arg       *arg;

	if (ARRAY_LENGTH(&ictx->args) == 0)
		return (0);

	arg = &ARRAY_LAST(&ictx->args);
	if (arg->used > (sizeof arg->data) - 1)
		return (-1);
	arg->data[arg->used++] = ch;

	return (0);
}

int
input_get_argument(struct input_ctx *ictx, u_int i, uint16_t *n, uint16_t d)
{
	struct input_arg	*arg;
	const char		*errstr;

	*n = d;
	if (i >= ARRAY_LENGTH(&ictx->args))
		return (0);

	arg = &ARRAY_ITEM(&ictx->args, i);
	if (*arg->data == '\0')
		return (0);

	*n = strtonum(arg->data, 0, UINT16_MAX, &errstr);
	if (errstr != NULL)
		return (-1);
	return (0);
}

void
input_init(struct window *w)
{
	ARRAY_INIT(&w->ictx.args);

	w->ictx.state = input_state_first;
}

void
input_free(struct window *w)
{
	ARRAY_FREE(&w->ictx.args);
}

void
input_parse(struct window *w, struct buffer *b)
{
	struct input_ctx	*ictx = &w->ictx;
	u_char			 ch;

	if (BUFFER_USED(w->in) == 0)
		return;

	ictx->buf = BUFFER_OUT(w->in);
	ictx->len = BUFFER_USED(w->in);
	ictx->off = 0;

	ictx->w = w;
	ictx->b = b;

	log_debug2("entry; buffer=%zu", ictx->len);

	while (ictx->off < ictx->len) {
		ch = ictx->buf[ictx->off++];
		ictx->state = ictx->state(ch, ictx);
	}

	buffer_remove(w->in, ictx->len);
}

void *
input_state_first(u_char ch, struct input_ctx *ictx)
{
	if (INPUT_C0CONTROL(ch)) {
		if (ch == 0x1b)
			return (input_state_escape);
		input_handle_c0_control(ch, ictx);
		return (input_state_first);
	}

	if (INPUT_C1CONTROL(ch)) {
		ch -= 0x40;
		if (ch == '[')
			return (input_state_sequence_first);
		if (ch == ']')
			return (input_state_title_first);
		input_handle_c1_control(ch, ictx);
		return (input_state_first);
	}

	input_handle_character(ch, ictx);
	return (input_state_first);
}
	    
void *
input_state_escape(u_char ch, struct input_ctx *ictx)
{
	/* Treat C1 control and G1 displayable as 7-bit equivalent. */
	if (INPUT_C1CONTROL(ch) || INPUT_G1DISPLAYABLE(ch))
		ch &= 0x7f;

	if (INPUT_C0CONTROL(ch)) {
		input_handle_c0_control(ch, ictx);
		return (input_state_escape);
	}

	if (INPUT_INTERMEDIATE(ch))
		return (input_state_intermediate);

	if (INPUT_PARAMETER(ch)) {
		input_handle_private_two(ch, ictx);
		return (input_state_first);
	}

	if (INPUT_UPPERCASE(ch)) {
		if (ch == '[')
			return (input_state_sequence_first);
		if (ch == ']')
			return (input_state_title_first);
		input_handle_c1_control(ch, ictx);
		return (input_state_first);
	}

	if (INPUT_LOWERCASE(ch)) {
		input_handle_standard_two(ch, ictx);
		return (input_state_first);
	}

	return (input_state_first);
}

void *
input_state_title_first(u_char ch, struct input_ctx *ictx)
{
	if (ch >= '0' && ch <= '9') {
		ictx->title_type = ch - '0';
		return (input_state_title_second);
	}

	return (input_state_first);
}

void *
input_state_title_second(u_char ch, struct input_ctx *ictx)
{
	if (ch == ';') {
		ictx->title_len = 0;
		return (input_state_title_next);
	}

	return (input_state_first);
}

void *
input_state_title_next(u_char ch, struct input_ctx *ictx)
{
	struct screen	*s = &ictx->w->screen;

	if (ch == '\007') {
		ictx->title_buf[ictx->title_len++] = '\0';
		switch (ictx->title_type) {
		case 0:
			strlcpy(s->title, ictx->title_buf, sizeof s->title);
			input_store_one(ictx->b, CODE_TITLE, ictx->title_len);
			buffer_write(ictx->b, ictx->title_buf, ictx->title_len);
			break;
		}
		return (input_state_first);
	}

	if (ch >= 0x20) {
		if (ictx->title_len < (sizeof ictx->title_buf) - 1) {
			ictx->title_buf[ictx->title_len++] = ch;
			return (input_state_title_next);
		}
		return (input_state_first);
	}

 	return (input_state_first);
}

void *
input_state_intermediate(u_char ch, struct input_ctx *ictx)
{
	if (INPUT_INTERMEDIATE(ch))
		return (input_state_intermediate);

	if (INPUT_PARAMETER(ch)) {
		input_handle_private_two(ch, ictx);
		return (input_state_first);
	}

	if (INPUT_UPPERCASE(ch) || INPUT_LOWERCASE(ch)) {
		input_handle_standard_two(ch, ictx);
		return (input_state_first);
	}

	return (input_state_first);
}

void *
input_state_sequence_first(u_char ch, struct input_ctx *ictx)
{
	ictx->private = '\0';
	ARRAY_CLEAR(&ictx->args);

	if (INPUT_PARAMETER(ch)) {
		if (ch >= 0x3c && ch <= 0x3f) {
			/* Private control sequence. */
			ictx->private = ch;
			return (input_state_sequence_next);
		}
		input_new_argument(ictx);
	}		

	/* Pass character on directly. */
	return (input_state_sequence_next(ch, ictx));
}

void *
input_state_sequence_next(u_char ch, struct input_ctx *ictx)
{
	if (INPUT_INTERMEDIATE(ch)) {
		if (input_add_argument(ictx, '\0') != 0)
			return (input_state_first);
		return (input_state_sequence_intermediate);
	}

	if (INPUT_PARAMETER(ch)) {
		if (ch == ';') {
			if (input_add_argument(ictx, '\0') != 0)
				return (input_state_first);
			input_new_argument(ictx);
			return (input_state_sequence_next);
		}
		if (input_add_argument(ictx, ch) != 0)
			return (input_state_first);
		return (input_state_sequence_next);
	}

	if (INPUT_UPPERCASE(ch) || INPUT_LOWERCASE(ch)) {
		if (input_add_argument(ictx, '\0') != 0)
			return (input_state_first);
		input_handle_sequence(ch, ictx);
		return (input_state_first);
	}

	return (input_state_first);
}

void *
input_state_sequence_intermediate(u_char ch, struct input_ctx *ictx)
{
	if (INPUT_INTERMEDIATE(ch))
		return (input_state_sequence_intermediate);

	if (INPUT_UPPERCASE(ch) || INPUT_LOWERCASE(ch)) {
		input_handle_sequence(ch, ictx);
		return (input_state_first);
	}

	return (input_state_first);
}

void
input_handle_character(u_char ch, struct input_ctx *ictx)
{
	struct screen	*s = &ictx->w->screen;

	log_debug2("-- ch %zu: %hhu (%c)", ictx->off, ch, ch);
	
	if (s->cx > s->sx || s->cy > s->sy - 1)
		return;

	if (s->cx == s->sx) {
		input_store8(ictx->b, '\r');
		input_store8(ictx->b, '\n');

		s->cx = 0;
		screen_cursor_down_scroll(s);
	}

	screen_write_character(s, ch);
	input_store8(ictx->b, ch);

	s->cx++;
}

void
input_handle_c0_control(u_char ch, struct input_ctx *ictx)
{
	struct screen	*s = &ictx->w->screen;

	log_debug2("-- c0 %zu: %hhu", ictx->off, ch);

	switch (ch) {
	case '\0':	/* NUL */
		break;
	case '\n':	/* LF */
 		screen_cursor_down_scroll(s);
		break;
	case '\r':	/* CR */
		s->cx = 0;
		break;
	case '\007':	/* BELL */
		ictx->w->flags |= WINDOW_BELL;
		return;
	case '\010': 	/* BS */
		if (s->cx > 0)
			s->cx--;
		break;
	case '\011': 	/* TAB */
		s->cx = ((s->cx / 8) * 8) + 8;
		if (s->cx > s->sx) {
			s->cx = 0;
			screen_cursor_down_scroll(s);
		}
		input_store_two(
		    ictx->b, CODE_CURSORMOVE, s->cy + 1, s->cx + 1);	
		return;
	default:
		log_debug("unknown c0: %hhu", ch);
		return;
	}
	input_store8(ictx->b, ch);
}

void
input_handle_c1_control(u_char ch, struct input_ctx *ictx)
{
	struct screen	*s = &ictx->w->screen;

	log_debug2("-- c1 %zu: %hhu (%c)", ictx->off, ch, ch);

	switch (ch) {
	case 'M':	/* RI */
		screen_cursor_up_scroll(s);
		input_store_zero(ictx->b, CODE_REVERSEINDEX);
		break;
	default:
		log_debug("unknown c1: %hhu", ch);
		break;
	}
}

void
input_handle_private_two(u_char ch, struct input_ctx *ictx)
{
	struct screen	*s = &ictx->w->screen;

	log_debug2("-- p2 %zu: %hhu (%c)", ictx->off, ch, ch);

	switch (ch) {
	case '=':	/* DECKPAM */
		input_store_zero(ictx->b, CODE_KKEYPADON);
		break;
	case '>':	/* DECKPNM*/
		input_store_zero(ictx->b, CODE_KKEYPADOFF);
		break;
	case '7':	/* DECSC */
		s->saved_cx = s->cx;
		s->saved_cy = s->cy;
		s->saved_attr = s->attr;
		s->saved_colr = s->colr;
		s->mode |= MODE_SAVED;
		break;
	case '8':	/* DECRC */
		if (!(s->mode & MODE_SAVED))
			break;
		s->cx = s->saved_cx;
		s->cy = s->saved_cy;
		s->attr = s->saved_attr;
		s->colr = s->saved_colr;
		input_store_two(
		    ictx->b, CODE_ATTRIBUTES, s->attr, s->colr);
		input_store_two(ictx->b, CODE_SCROLLREGION,
		    s->ry_upper + 1, s->ry_lower + 1);
		input_store_two(
		    ictx->b, CODE_CURSORMOVE, s->cy + 1, s->cx + 1);
		break;
	default:
		log_debug("unknown p2: %hhu", ch);
		break;
	}
}

void
input_handle_standard_two(u_char ch, struct input_ctx *ictx)
{
	log_debug2("-- s2 %zu: %hhu (%c)", ictx->off, ch, ch);

	log_debug("unknown s2: %hhu", ch);
}

void
input_handle_sequence(u_char ch, struct input_ctx *ictx)
{
	static const struct {
		u_char	ch;
		void	(*fn)(struct input_ctx *);
	} table[] = {
		{ '@', input_handle_sequence_ich },
		{ 'A', input_handle_sequence_cuu },
		{ 'B', input_handle_sequence_cud },
		{ 'C', input_handle_sequence_cuf },
		{ 'D', input_handle_sequence_cub },
		{ 'G', input_handle_sequence_hpa },
		{ 'H', input_handle_sequence_cup },
		{ 'J', input_handle_sequence_ed },
		{ 'K', input_handle_sequence_el },
		{ 'L', input_handle_sequence_il },
		{ 'M', input_handle_sequence_dl },
		{ 'P', input_handle_sequence_dch },
		{ 'd', input_handle_sequence_vpa },
		{ 'f', input_handle_sequence_cup },
		{ 'h', input_handle_sequence_sm },
		{ 'l', input_handle_sequence_rm },
		{ 'm', input_handle_sequence_sgr },
		{ 'n', input_handle_sequence_dsr },
		{ 'r', input_handle_sequence_decstbm },
	};
	struct screen	 *s = &ictx->w->screen;
	u_int		  i;
	struct input_arg *iarg;
	
	log_debug2("-- sq %zu: %hhu (%c): %u [sx=%u, sy=%u, cx=%u, cy=%u]",
	    ictx->off, ch, ch, ARRAY_LENGTH(&ictx->args),
	    s->sx, s->sy, s->cx, s->cy);
	for (i = 0; i < ARRAY_LENGTH(&ictx->args); i++) {
		iarg = &ARRAY_ITEM(&ictx->args, i);
		if (*iarg->data != '\0')
			log_debug2("      ++ %u: %s", i, iarg->data);
	}

	/* XXX bsearch? */
	for (i = 0; i < (sizeof table / sizeof table[0]); i++) {
		if (table[i].ch == ch) {
			table[i].fn(ictx);
			return;
		}
	}

	log_debug("unknown sq: %c (%hhu %hhu)", ch, ch, ictx->private);
}

void
input_handle_sequence_cuu(struct input_ctx *ictx)
{
	struct screen	*s = &ictx->w->screen;
	uint16_t	 n;

	if (ictx->private != '\0')
		return;

	if (ARRAY_LENGTH(&ictx->args) > 1)
		return;
	if (input_get_argument(ictx, 0, &n, 1) != 0)
		return;

	if (n == 0 || n > s->cy) {
		log_debug3("cuu: out of range: %hu", n);
		return;
	}

	s->cy -= n;
	input_store_one(ictx->b, CODE_CURSORUP, n);
}

void
input_handle_sequence_cud(struct input_ctx *ictx)
{
	struct screen	*s = &ictx->w->screen;
	uint16_t	 n;

	if (ictx->private != '\0')
		return;

	if (ARRAY_LENGTH(&ictx->args) > 1)
		return;
	if (input_get_argument(ictx, 0, &n, 1) != 0)
		return;

	if (n == 0 || n > s->sy - s->cy - 1) {
		log_debug3("cud: out of range: %hu", n);
		return;
	}

	s->cy += n;
	input_store_one(ictx->b, CODE_CURSORDOWN, n);
}

void
input_handle_sequence_cuf(struct input_ctx *ictx)
{
	struct screen	*s = &ictx->w->screen;
	uint16_t	 n;

	if (ictx->private != '\0')
		return;

	if (ARRAY_LENGTH(&ictx->args) > 1)
		return;
	if (input_get_argument(ictx, 0, &n, 1) != 0)
		return;

	if (n == 0 || n > s->sx - s->cx - 1) {
		log_debug3("cuf: out of range: %hu", n);
		return;
	}

	s->cx += n;
	input_store_one(ictx->b, CODE_CURSORRIGHT, n);
}

void
input_handle_sequence_cub(struct input_ctx *ictx)
{
	struct screen	*s = &ictx->w->screen;
	uint16_t	 n;

	if (ictx->private != '\0')
		return;

	if (ARRAY_LENGTH(&ictx->args) > 1)
		return;
	if (input_get_argument(ictx, 0, &n, 1) != 0)
		return;

	if (n == 0 || n > s->cx) {
		log_debug3("cub: out of range: %hu", n);
		return;
	}

	s->cx -= n;
	input_store_one(ictx->b, CODE_CURSORLEFT, n);
}

void
input_handle_sequence_dch(struct input_ctx *ictx)
{
	struct screen	*s = &ictx->w->screen;
	uint16_t	 n;

	if (ictx->private != '\0')
		return;

	if (ARRAY_LENGTH(&ictx->args) > 1)
		return;
	if (input_get_argument(ictx, 0, &n, 1) != 0)
		return;

	if (n == 0 || n > s->sx - s->cx - 1) {
		log_debug3("dch: out of range: %hu", n);
		return;
	}

	screen_delete_characters(s, s->cx, s->cy, n);
	input_store_one(ictx->b, CODE_DELETECHARACTER, n);
}

void
input_handle_sequence_dl(struct input_ctx *ictx)
{
	struct screen	*s = &ictx->w->screen;
	uint16_t	 n;

	if (ictx->private != '\0')
		return;

	if (ARRAY_LENGTH(&ictx->args) > 1)
		return;
	if (input_get_argument(ictx, 0, &n, 1) != 0)
		return;

	if (n == 0 || n > s->sy - s->cy - 1) {
		log_debug3("dl: out of range: %hu", n);
		return;
	}

	if (n < s->ry_upper || n > s->ry_lower)
		screen_delete_lines(s, s->cy, n);
	else
		screen_delete_lines_region(s, s->cy, n);
	input_store_one(ictx->b, CODE_DELETELINE, n);
}

void
input_handle_sequence_ich(struct input_ctx *ictx)
{
	struct screen	*s = &ictx->w->screen;
	uint16_t	 n;

	if (ictx->private != '\0')
		return;

	if (ARRAY_LENGTH(&ictx->args) > 1)
		return;
	if (input_get_argument(ictx, 0, &n, 1) != 0)
		return;

	if (n == 0 || n > s->sx - s->cx - 1) {
		log_debug3("ich: out of range: %hu", n);
		return;
	}

	screen_insert_characters(s, s->cx, s->cy, n);
	input_store_one(ictx->b, CODE_INSERTCHARACTER, n);
}

void
input_handle_sequence_il(struct input_ctx *ictx)
{
	struct screen	*s = &ictx->w->screen;
	uint16_t	 n;

	if (ictx->private != '\0')
		return;

	if (ARRAY_LENGTH(&ictx->args) > 1)
		return;
	if (input_get_argument(ictx, 0, &n, 1) != 0)
		return;

	if (n == 0 || n > s->sy - s->cy - 1) {
		log_debug3("il: out of range: %hu", n);
		return;
	}
	if (n < s->ry_upper || n > s->ry_lower)
		screen_insert_lines(s, s->cy, n);
	else
		screen_insert_lines_region(s, s->cy, n);
	input_store_one(ictx->b, CODE_INSERTLINE, n);
}

void
input_handle_sequence_vpa(struct input_ctx *ictx)
{
	struct screen	*s = &ictx->w->screen;
	uint16_t	 n;

	if (ictx->private != '\0')
		return;

	if (ARRAY_LENGTH(&ictx->args) > 1)
		return;
	if (input_get_argument(ictx, 0, &n, 1) != 0)
		return;

	if (n == 0 || n > s->sy) {
		log_debug3("vpa: out of range: %hu", n);
		return;
	}

	s->cy = n - 1;
	input_store_two(ictx->b, CODE_CURSORMOVE, n, s->cx + 1);
}

void
input_handle_sequence_hpa(struct input_ctx *ictx)
{
	struct screen	*s = &ictx->w->screen;
	uint16_t	 n;

	if (ictx->private != '\0')
		return;

	if (ARRAY_LENGTH(&ictx->args) > 1)
		return;
	if (input_get_argument(ictx, 0, &n, 1) != 0)
		return;

	if (n == 0 || n > s->sx) {
		log_debug3("hpa: out of range: %hu", n);
		return;
	}

	s->cx = n - 1;
	input_store_two(ictx->b, CODE_CURSORMOVE, s->cy + 1, n);
}

void
input_handle_sequence_cup(struct input_ctx *ictx)
{
	struct screen	*s = &ictx->w->screen;
	uint16_t	 n, m;

	if (ictx->private != '\0')
		return;

	if (ARRAY_LENGTH(&ictx->args) > 2)
		return;
	if (input_get_argument(ictx, 0, &n, 1) != 0)
		return;
	if (input_get_argument(ictx, 1, &m, 1) != 0)
		return;

	if (n == 0)
		n = 1;
	if (n > s->sy)
		n = s->sy;
	if (m == 0)
		m = 1;
	if (m > s->sx)
		m = s->sx;

	s->cx = m - 1;
	s->cy = n - 1;
	input_store_two(ictx->b, CODE_CURSORMOVE, n, m);
}

void
input_handle_sequence_ed(struct input_ctx *ictx)
{
	struct screen	*s = &ictx->w->screen;
	uint16_t	 n;
	u_int		 i;

	if (ictx->private != '\0')
		return;

	if (ARRAY_LENGTH(&ictx->args) > 1)
		return;
	if (input_get_argument(ictx, 0, &n, 0) != 0)
		return;

	if (n > 2)
		return;

	switch (n) {
	case 0:
		screen_fill_end_of_screen(
		    s, 0, s->cy, SCREEN_DEFDATA, s->attr, s->colr);
		input_store_zero(ictx->b, CODE_CLEARLINE);
		for (i = s->cy + 1; i < s->sy; i++) {
			input_store_two(ictx->b, CODE_CURSORMOVE, i + 1, 1);
			input_store_zero(ictx->b, CODE_CLEARLINE);
		}
		input_store_two(
		    ictx->b, CODE_CURSORMOVE, s->cy + 1, s->cx + 1);
		break;
	case 2:
		screen_fill_screen(s, SCREEN_DEFDATA, s->attr, s->colr);
		for (i = 0; i < s->sy; i++) {
			input_store_two(ictx->b, CODE_CURSORMOVE, i + 1, 1);
			input_store_zero(ictx->b, CODE_CLEARLINE);
		}
		input_store_two(
		    ictx->b, CODE_CURSORMOVE, s->cy + 1, s->cx + 1);
		break;
	}
}

void
input_handle_sequence_el(struct input_ctx *ictx)
{
	struct screen	*s = &ictx->w->screen;
	uint16_t	 n;

	if (ictx->private != '\0')
		return;

	if (ARRAY_LENGTH(&ictx->args) > 1)
		return;
	if (input_get_argument(ictx, 0, &n, 0) != 0)
		return;

	if (n > 2)
		return;

	switch (n) {
	case 0:
		screen_fill_end_of_line(
		    s, s->cx, s->cy, SCREEN_DEFDATA, s->attr, s->colr);
		input_store_zero(ictx->b, CODE_CLEARENDOFLINE);
		break;
	case 1:
		screen_fill_start_of_line(
		    s, s->cx, s->cy, SCREEN_DEFDATA, s->attr, s->colr);
		input_store_zero(ictx->b, CODE_CLEARSTARTOFLINE);
		break;
	case 2:
		screen_fill_line(s, s->cy, SCREEN_DEFDATA, s->attr, s->colr);
		input_store_zero(ictx->b, CODE_CLEARLINE);
		break;
	}
}

void
input_handle_sequence_sm(struct input_ctx *ictx)
{
	struct screen	*s = &ictx->w->screen;
	uint16_t	 n;

	if (ARRAY_LENGTH(&ictx->args) > 1)
		return;
	if (input_get_argument(ictx, 0, &n, 0) != 0)
		return;

	if (ictx->private == '?') {
		switch (n) {
		case 1:		/* GATM */
			s->mode |= MODE_KCURSOR;
			input_store_zero(ictx->b, CODE_KCURSORON);
			break;
		case 25:	/* TCEM */
			s->mode |= MODE_CURSOR;
			input_store_zero(ictx->b, CODE_CURSORON);
			break;
		default:
			log_debug("unknown SM [%hhu]: %u", ictx->private, n);
			break;
		}
	} else {
		switch (n) {
		case 4:		/* IRM */
			s->mode |= MODE_INSERT;
			input_store_zero(ictx->b, CODE_INSERTON);
			break;
		case 34:
			/* Cursor high visibility not supported. */
			break;
		default:
			log_debug("unknown SM [%hhu]: %u", ictx->private, n);
			break;
		}
	}
}

void
input_handle_sequence_rm(struct input_ctx *ictx)
{
	struct screen	*s = &ictx->w->screen;
	uint16_t	 n;

	if (ARRAY_LENGTH(&ictx->args) > 1)
		return;
	if (input_get_argument(ictx, 0, &n, 0) != 0)
		return;

	if (ictx->private == '?') {
		switch (n) {
		case 1:		/* GATM */
			s->mode &= ~MODE_KCURSOR;
			input_store_zero(ictx->b, CODE_KCURSOROFF);
			break;
		case 25:	/* TCEM */
			s->mode &= ~MODE_CURSOR;
			input_store_zero(ictx->b, CODE_CURSOROFF);
			break;
		default:
			log_debug("unknown RM [%hhu]: %u", ictx->private, n);
			break;
		}
	} else if (ictx->private == '\0') {
		switch (n) {
		case 4:		/* IRM */
			s->mode &= ~MODE_INSERT;
			input_store_zero(ictx->b, CODE_INSERTOFF);
			break;
		case 34:
			/* Cursor high visibility not supported. */
			break;
		default:
			log_debug("unknown RM [%hhu]: %u", ictx->private, n);
			break;
		}
	}
}

void
input_handle_sequence_dsr(struct input_ctx *ictx)
{
	struct screen	*s = &ictx->w->screen;
	uint16_t	 n;
	char		 reply[32];

	if (ARRAY_LENGTH(&ictx->args) > 1)
		return;
	if (input_get_argument(ictx, 0, &n, 0) != 0)
		return;

	if (ictx->private == '\0') {
		switch (n) {
		case 6:	/* cursor position */
			xsnprintf(reply, sizeof reply,
			    "\033[%u;%uR", s->cy + 1, s->cx + 1);
			log_debug("cursor request, reply: %s", reply);
			buffer_write(ictx->w->out, reply, strlen(reply));
			break;
		}
	}

}

void
input_handle_sequence_decstbm(struct input_ctx *ictx)
{
	struct screen	*s = &ictx->w->screen;
	uint16_t	 n, m;

	if (ictx->private != '\0')
		return;

	if (ARRAY_LENGTH(&ictx->args) > 2)
		return;
	if (input_get_argument(ictx, 0, &n, 0) != 0)
		return;
	if (input_get_argument(ictx, 1, &m, 0) != 0)
		return;

	/* Special case: both zero restores to entire screen. */
	/* XXX this will catch [0;0r and [;r etc too, is this right? */
	if (n == 0 && m == 0) {
		n = 1;
		m = s->sy;
	}

	if (n == 0)
		n = 1;
	if (n > s->sy)
		n = s->sy;
	if (m == 0)
		m = 1;
	if (m > s->sy)
		m = s->sy;

	if (n > m) {
		log_debug3("decstbm: out of range: %hu,%hu", n, m);
		return;
	}

	/* Cursor moves to top-left. */
	s->cx = 0;
	s->cy = n - 1;

	s->ry_upper = n - 1;
	s->ry_lower = m - 1;
	input_store_two(ictx->b, CODE_SCROLLREGION, n, m);
}

void
input_handle_sequence_sgr(struct input_ctx *ictx)
{
	struct screen	*s = &ictx->w->screen;
	u_int		 i, n;
	uint16_t	 m;

	n = ARRAY_LENGTH(&ictx->args);
	if (n == 0) {
		s->attr = 0;
		s->colr = SCREEN_DEFCOLR;
	} else {
		for (i = 0; i < n; i++) {
			if (input_get_argument(ictx, i, &m, 0) != 0)
				return;
			switch (m) {
			case 0:
			case 10:
				s->attr = 0;
				s->colr = SCREEN_DEFCOLR;
				break;
			case 1:
				s->attr |= ATTR_BRIGHT;
				break;
			case 2:
				s->attr |= ATTR_DIM;
				break;
			case 3:
				s->attr |= ATTR_ITALICS;
				break;
			case 4:
				s->attr |= ATTR_UNDERSCORE;
				break;
			case 5:
				s->attr |= ATTR_BLINK;
				break;
			case 7:
				s->attr |= ATTR_REVERSE;
				break;
			case 8:
				s->attr |= ATTR_HIDDEN;
				break;
			case 23:
				s->attr &= ~ATTR_ITALICS;
				break;
			case 24:
				s->attr &= ~ATTR_UNDERSCORE;
				break;
			case 30:
			case 31:
			case 32:
			case 33:
			case 34:
			case 35:
			case 36:
			case 37:
				s->colr &= 0x0f;
				s->colr |= (m - 30) << 4;
				break;
			case 39:
				s->colr &= 0x0f;
				s->colr |= 0x80;
				break;
			case 40:
			case 41:
			case 42:
			case 43:
			case 44:
			case 45:
			case 46:
			case 47:
				s->colr &= 0xf0;
				s->colr |= m - 40;
				break;
			case 49:
				s->colr &= 0xf0;
				s->colr |= 0x08;
				break;
			}
		}
	}
	input_store_two(ictx->b, CODE_ATTRIBUTES, s->attr, s->colr);
}

void
input_store_zero(struct buffer *b, u_char code)
{
	input_store8(b, '\e');
	input_store8(b, code);
}

void
input_store_one(struct buffer *b, u_char code, uint16_t ua)
{
	input_store8(b, '\e');
	input_store8(b, code);
	input_store16(b, ua);
}

void
input_store_two(struct buffer *b, u_char code, uint16_t ua, uint16_t ub)
{
	input_store8(b, '\e');
	input_store8(b, code);
	input_store16(b, ua);
	input_store16(b, ub);
}

void
input_store8(struct buffer *b, uint8_t n)
{
	buffer_ensure(b, 1);
	BUFFER_IN(b)[0] = n;
	buffer_add(b, 1);
}

void
input_store16(struct buffer *b, uint16_t n)
{
	buffer_ensure(b, 2);
	BUFFER_IN(b)[0] = n & 0xff;
	BUFFER_IN(b)[1] = n >> 8;
	buffer_add(b, 2);
}

uint8_t
input_extract8(struct buffer *b)
{
	uint8_t	n;

	n = BUFFER_OUT(b)[0];
	buffer_remove(b, 1);
	return (n);
}

uint16_t
input_extract16(struct buffer *b)
{
	uint16_t	n;

	n = BUFFER_OUT(b)[0] | (BUFFER_OUT(b)[1] << 8);
	buffer_remove(b, 2);
	return (n);
}