sucklessConfigs

configurations of the suckless.org software that I use
Log | Files | Refs

st.c (66469B)


      1 /* See LICENSE for license details. */
      2 #include <ctype.h>
      3 #include <errno.h>
      4 #include <fcntl.h>
      5 #include <limits.h>
      6 #include <pwd.h>
      7 #include <stdarg.h>
      8 #include <stdio.h>
      9 #include <stdlib.h>
     10 #include <string.h>
     11 #include <signal.h>
     12 #include <sys/ioctl.h>
     13 #include <sys/select.h>
     14 #include <sys/types.h>
     15 #include <sys/wait.h>
     16 #include <termios.h>
     17 #include <unistd.h>
     18 #include <wchar.h>
     19 
     20 #include "st.h"
     21 #include "win.h"
     22 #include "graphics.h"
     23 
     24 #if   defined(__linux)
     25  #include <pty.h>
     26 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
     27  #include <util.h>
     28 #elif defined(__FreeBSD__) || defined(__DragonFly__)
     29  #include <libutil.h>
     30 #endif
     31 
     32 /* Arbitrary sizes */
     33 #define UTF_INVALID   0xFFFD
     34 #define UTF_SIZ       4
     35 #define ESC_BUF_SIZ   (128*UTF_SIZ)
     36 #define ESC_ARG_SIZ   16
     37 #define STR_BUF_SIZ   ESC_BUF_SIZ
     38 #define STR_ARG_SIZ   ESC_ARG_SIZ
     39 #define HISTSIZE      2000
     40 
     41 /* PUA character used as an image placeholder */
     42 #define IMAGE_PLACEHOLDER_CHAR 0x10EEEE
     43 #define IMAGE_PLACEHOLDER_CHAR_OLD 0xEEEE
     44 
     45 /* macros */
     46 #define IS_SET(flag)		((term.mode & (flag)) != 0)
     47 #define ISCONTROLC0(c)		(BETWEEN(c, 0, 0x1f) || (c) == 0x7f)
     48 #define ISCONTROLC1(c)		(BETWEEN(c, 0x80, 0x9f))
     49 #define ISCONTROL(c)		(ISCONTROLC0(c) || ISCONTROLC1(c))
     50 #define ISDELIM(u)		(u && wcschr(worddelimiters, u))
     51 #define TLINE(y)		((y) < term.scr ? term.hist[((y) + term.histi - \
     52 				term.scr + HISTSIZE + 1) % HISTSIZE] : \
     53 				term.line[(y) - term.scr])
     54 
     55 enum term_mode {
     56 	MODE_WRAP        = 1 << 0,
     57 	MODE_INSERT      = 1 << 1,
     58 	MODE_ALTSCREEN   = 1 << 2,
     59 	MODE_CRLF        = 1 << 3,
     60 	MODE_ECHO        = 1 << 4,
     61 	MODE_PRINT       = 1 << 5,
     62 	MODE_UTF8        = 1 << 6,
     63 };
     64 
     65 enum cursor_movement {
     66 	CURSOR_SAVE,
     67 	CURSOR_LOAD
     68 };
     69 
     70 enum cursor_state {
     71 	CURSOR_DEFAULT  = 0,
     72 	CURSOR_WRAPNEXT = 1,
     73 	CURSOR_ORIGIN   = 2
     74 };
     75 
     76 enum charset {
     77 	CS_GRAPHIC0,
     78 	CS_GRAPHIC1,
     79 	CS_UK,
     80 	CS_USA,
     81 	CS_MULTI,
     82 	CS_GER,
     83 	CS_FIN
     84 };
     85 
     86 enum escape_state {
     87 	ESC_START      = 1,
     88 	ESC_CSI        = 2,
     89 	ESC_STR        = 4,  /* DCS, OSC, PM, APC */
     90 	ESC_ALTCHARSET = 8,
     91 	ESC_STR_END    = 16, /* a final string was encountered */
     92 	ESC_TEST       = 32, /* Enter in test mode */
     93 	ESC_UTF8       = 64,
     94 };
     95 
     96 typedef struct {
     97 	Glyph attr; /* current char attributes */
     98 	int x;
     99 	int y;
    100 	char state;
    101 } TCursor;
    102 
    103 typedef struct {
    104 	int mode;
    105 	int type;
    106 	int snap;
    107 	/*
    108 	 * Selection variables:
    109 	 * nb – normalized coordinates of the beginning of the selection
    110 	 * ne – normalized coordinates of the end of the selection
    111 	 * ob – original coordinates of the beginning of the selection
    112 	 * oe – original coordinates of the end of the selection
    113 	 */
    114 	struct {
    115 		int x, y;
    116 	} nb, ne, ob, oe;
    117 
    118 	int alt;
    119 } Selection;
    120 
    121 /* Internal representation of the screen */
    122 typedef struct {
    123 	int row;      /* nb row */
    124 	int col;      /* nb col */
    125 	int pixw;     /* width of the text area in pixels */
    126 	int pixh;     /* height of the text area in pixels */
    127 	Line *line;   /* screen */
    128 	Line *alt;    /* alternate screen */
    129 	Line hist[HISTSIZE]; /* history buffer */
    130 	int histi;    /* history index */
    131 	int scr;      /* scroll back */
    132 	int *dirty;   /* dirtyness of lines */
    133 	TCursor c;    /* cursor */
    134 	int ocx;      /* old cursor col */
    135 	int ocy;      /* old cursor row */
    136 	int top;      /* top    scroll limit */
    137 	int bot;      /* bottom scroll limit */
    138 	int mode;     /* terminal mode flags */
    139 	int esc;      /* escape state flags */
    140 	char trantbl[4]; /* charset table translation */
    141 	int charset;  /* current charset */
    142 	int icharset; /* selected charset for sequence */
    143 	int *tabs;
    144 	Rune lastc;   /* last printed char outside of sequence, 0 if control */
    145 } Term;
    146 
    147 /* CSI Escape sequence structs */
    148 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
    149 typedef struct {
    150 	char buf[ESC_BUF_SIZ]; /* raw string */
    151 	size_t len;            /* raw string length */
    152 	char priv;
    153 	int arg[ESC_ARG_SIZ];
    154 	int narg;              /* nb of args */
    155 	char mode[2];
    156 } CSIEscape;
    157 
    158 /* STR Escape sequence structs */
    159 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
    160 typedef struct {
    161 	char type;             /* ESC type ... */
    162 	char *buf;             /* allocated raw string */
    163 	size_t siz;            /* allocation size */
    164 	size_t len;            /* raw string length */
    165 	char *args[STR_ARG_SIZ];
    166 	int narg;              /* nb of args */
    167 } STREscape;
    168 
    169 static void execsh(char *, char **);
    170 static void stty(char **);
    171 static void sigchld(int);
    172 static void ttywriteraw(const char *, size_t);
    173 
    174 static void csidump(void);
    175 static void csihandle(void);
    176 static void csiparse(void);
    177 static void csireset(void);
    178 static void osc_color_response(int, int, int);
    179 static int eschandle(uchar);
    180 static void strdump(void);
    181 static void strhandle(void);
    182 static void strparse(void);
    183 static void strreset(void);
    184 
    185 static void tprinter(char *, size_t);
    186 static void tdumpsel(void);
    187 static void tdumpline(int);
    188 static void tdump(void);
    189 static void tclearregion(int, int, int, int);
    190 static void tcursor(int);
    191 static void tdeletechar(int);
    192 static void tdeleteline(int);
    193 static void tinsertblank(int);
    194 static void tinsertblankline(int);
    195 static int tlinelen(int);
    196 static void tmoveto(int, int);
    197 static void tmoveato(int, int);
    198 static void tnewline(int);
    199 static void tputtab(int);
    200 static void tputc(Rune);
    201 static void treset(void);
    202 static void tscrollup(int, int, int);
    203 static void tscrolldown(int, int, int);
    204 static void tsetattr(const int *, int);
    205 static void tsetchar(Rune, const Glyph *, int, int);
    206 static void tsetdirt(int, int);
    207 static void tsetscroll(int, int);
    208 static void tswapscreen(void);
    209 static void tsetmode(int, int, const int *, int);
    210 static int twrite(const char *, int, int);
    211 static void tfulldirt(void);
    212 static void tcontrolcode(uchar );
    213 static void tdectest(char );
    214 static void tdefutf8(char);
    215 static int32_t tdefcolor(const int *, int *, int);
    216 static void tdeftran(char);
    217 static void tstrsequence(uchar);
    218 
    219 static void drawregion(int, int, int, int);
    220 
    221 static void selnormalize(void);
    222 static void selscroll(int, int);
    223 static void selsnap(int *, int *, int);
    224 
    225 static size_t utf8decode(const char *, Rune *, size_t);
    226 static Rune utf8decodebyte(char, size_t *);
    227 static char utf8encodebyte(Rune, size_t);
    228 static size_t utf8validate(Rune *, size_t);
    229 
    230 static char base64dec_getc(const char **);
    231 
    232 static ssize_t xwrite(int, const char *, size_t);
    233 
    234 /* Globals */
    235 static Term term;
    236 static Selection sel;
    237 static CSIEscape csiescseq;
    238 static STREscape strescseq;
    239 static int iofd = 1;
    240 static int cmdfd;
    241 static pid_t pid;
    242 
    243 static const uchar utfbyte[UTF_SIZ + 1] = {0x80,    0, 0xC0, 0xE0, 0xF0};
    244 static const uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
    245 static const Rune utfmin[UTF_SIZ + 1] = {       0,    0,  0x80,  0x800,  0x10000};
    246 static const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
    247 
    248 /* Converts a diacritic to a row/column/etc number. The result is 1-base, 0
    249  * means "couldn't convert". Defined in rowcolumn_diacritics_helpers.c */
    250 uint16_t diacritic_to_num(uint32_t code);
    251 
    252 ssize_t
    253 xwrite(int fd, const char *s, size_t len)
    254 {
    255 	size_t aux = len;
    256 	ssize_t r;
    257 
    258 	while (len > 0) {
    259 		r = write(fd, s, len);
    260 		if (r < 0)
    261 			return r;
    262 		len -= r;
    263 		s += r;
    264 	}
    265 
    266 	return aux;
    267 }
    268 
    269 void *
    270 xmalloc(size_t len)
    271 {
    272 	void *p;
    273 
    274 	if (!(p = malloc(len)))
    275 		die("malloc: %s\n", strerror(errno));
    276 
    277 	return p;
    278 }
    279 
    280 void *
    281 xrealloc(void *p, size_t len)
    282 {
    283 	if ((p = realloc(p, len)) == NULL)
    284 		die("realloc: %s\n", strerror(errno));
    285 
    286 	return p;
    287 }
    288 
    289 char *
    290 xstrdup(const char *s)
    291 {
    292 	char *p;
    293 
    294 	if ((p = strdup(s)) == NULL)
    295 		die("strdup: %s\n", strerror(errno));
    296 
    297 	return p;
    298 }
    299 
    300 size_t
    301 utf8decode(const char *c, Rune *u, size_t clen)
    302 {
    303 	size_t i, j, len, type;
    304 	Rune udecoded;
    305 
    306 	*u = UTF_INVALID;
    307 	if (!clen)
    308 		return 0;
    309 	udecoded = utf8decodebyte(c[0], &len);
    310 	if (!BETWEEN(len, 1, UTF_SIZ))
    311 		return 1;
    312 	for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
    313 		udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
    314 		if (type != 0)
    315 			return j;
    316 	}
    317 	if (j < len)
    318 		return 0;
    319 	*u = udecoded;
    320 	utf8validate(u, len);
    321 
    322 	return len;
    323 }
    324 
    325 Rune
    326 utf8decodebyte(char c, size_t *i)
    327 {
    328 	for (*i = 0; *i < LEN(utfmask); ++(*i))
    329 		if (((uchar)c & utfmask[*i]) == utfbyte[*i])
    330 			return (uchar)c & ~utfmask[*i];
    331 
    332 	return 0;
    333 }
    334 
    335 size_t
    336 utf8encode(Rune u, char *c)
    337 {
    338 	size_t len, i;
    339 
    340 	len = utf8validate(&u, 0);
    341 	if (len > UTF_SIZ)
    342 		return 0;
    343 
    344 	for (i = len - 1; i != 0; --i) {
    345 		c[i] = utf8encodebyte(u, 0);
    346 		u >>= 6;
    347 	}
    348 	c[0] = utf8encodebyte(u, len);
    349 
    350 	return len;
    351 }
    352 
    353 char
    354 utf8encodebyte(Rune u, size_t i)
    355 {
    356 	return utfbyte[i] | (u & ~utfmask[i]);
    357 }
    358 
    359 size_t
    360 utf8validate(Rune *u, size_t i)
    361 {
    362 	if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF))
    363 		*u = UTF_INVALID;
    364 	for (i = 1; *u > utfmax[i]; ++i)
    365 		;
    366 
    367 	return i;
    368 }
    369 
    370 char
    371 base64dec_getc(const char **src)
    372 {
    373 	while (**src && !isprint((unsigned char)**src))
    374 		(*src)++;
    375 	return **src ? *((*src)++) : '=';  /* emulate padding if string ends */
    376 }
    377 
    378 char *
    379 base64dec(const char *src)
    380 {
    381 	size_t in_len = strlen(src);
    382 	char *result, *dst;
    383 	static const char base64_digits[256] = {
    384 		[43] = 62, 0, 0, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
    385 		0, 0, 0, -1, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
    386 		13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0,
    387 		0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
    388 		40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
    389 	};
    390 
    391 	if (in_len % 4)
    392 		in_len += 4 - (in_len % 4);
    393 	result = dst = xmalloc(in_len / 4 * 3 + 1);
    394 	while (*src) {
    395 		int a = base64_digits[(unsigned char) base64dec_getc(&src)];
    396 		int b = base64_digits[(unsigned char) base64dec_getc(&src)];
    397 		int c = base64_digits[(unsigned char) base64dec_getc(&src)];
    398 		int d = base64_digits[(unsigned char) base64dec_getc(&src)];
    399 
    400 		/* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */
    401 		if (a == -1 || b == -1)
    402 			break;
    403 
    404 		*dst++ = (a << 2) | ((b & 0x30) >> 4);
    405 		if (c == -1)
    406 			break;
    407 		*dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2);
    408 		if (d == -1)
    409 			break;
    410 		*dst++ = ((c & 0x03) << 6) | d;
    411 	}
    412 	*dst = '\0';
    413 	return result;
    414 }
    415 
    416 void
    417 selinit(void)
    418 {
    419 	sel.mode = SEL_IDLE;
    420 	sel.snap = 0;
    421 	sel.ob.x = -1;
    422 }
    423 
    424 int
    425 tlinelen(int y)
    426 {
    427 	int i = term.col;
    428 
    429 	if (TLINE(y)[i - 1].mode & ATTR_WRAP)
    430 		return i;
    431 
    432 	while (i > 0 && TLINE(y)[i - 1].u == ' ')
    433 		--i;
    434 
    435 	return i;
    436 }
    437 
    438 void
    439 selstart(int col, int row, int snap)
    440 {
    441 	selclear();
    442 	sel.mode = SEL_EMPTY;
    443 	sel.type = SEL_REGULAR;
    444 	sel.alt = IS_SET(MODE_ALTSCREEN);
    445 	sel.snap = snap;
    446 	sel.oe.x = sel.ob.x = col;
    447 	sel.oe.y = sel.ob.y = row;
    448 	selnormalize();
    449 
    450 	if (sel.snap != 0)
    451 		sel.mode = SEL_READY;
    452 	tsetdirt(sel.nb.y, sel.ne.y);
    453 }
    454 
    455 void
    456 selextend(int col, int row, int type, int done)
    457 {
    458 	int oldey, oldex, oldsby, oldsey, oldtype;
    459 
    460 	if (sel.mode == SEL_IDLE)
    461 		return;
    462 	if (done && sel.mode == SEL_EMPTY) {
    463 		selclear();
    464 		return;
    465 	}
    466 
    467 	oldey = sel.oe.y;
    468 	oldex = sel.oe.x;
    469 	oldsby = sel.nb.y;
    470 	oldsey = sel.ne.y;
    471 	oldtype = sel.type;
    472 
    473 	sel.oe.x = col;
    474 	sel.oe.y = row;
    475 	selnormalize();
    476 	sel.type = type;
    477 
    478 	if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY)
    479 		tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey));
    480 
    481 	sel.mode = done ? SEL_IDLE : SEL_READY;
    482 }
    483 
    484 void
    485 selnormalize(void)
    486 {
    487 	int i;
    488 
    489 	if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) {
    490 		sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x;
    491 		sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x;
    492 	} else {
    493 		sel.nb.x = MIN(sel.ob.x, sel.oe.x);
    494 		sel.ne.x = MAX(sel.ob.x, sel.oe.x);
    495 	}
    496 	sel.nb.y = MIN(sel.ob.y, sel.oe.y);
    497 	sel.ne.y = MAX(sel.ob.y, sel.oe.y);
    498 
    499 	selsnap(&sel.nb.x, &sel.nb.y, -1);
    500 	selsnap(&sel.ne.x, &sel.ne.y, +1);
    501 
    502 	/* expand selection over line breaks */
    503 	if (sel.type == SEL_RECTANGULAR)
    504 		return;
    505 	i = tlinelen(sel.nb.y);
    506 	if (i < sel.nb.x)
    507 		sel.nb.x = i;
    508 	if (tlinelen(sel.ne.y) <= sel.ne.x)
    509 		sel.ne.x = term.col - 1;
    510 }
    511 
    512 int
    513 selected(int x, int y)
    514 {
    515 	if (sel.mode == SEL_EMPTY || sel.ob.x == -1 ||
    516 			sel.alt != IS_SET(MODE_ALTSCREEN))
    517 		return 0;
    518 
    519 	if (sel.type == SEL_RECTANGULAR)
    520 		return BETWEEN(y, sel.nb.y, sel.ne.y)
    521 		    && BETWEEN(x, sel.nb.x, sel.ne.x);
    522 
    523 	return BETWEEN(y, sel.nb.y, sel.ne.y)
    524 	    && (y != sel.nb.y || x >= sel.nb.x)
    525 	    && (y != sel.ne.y || x <= sel.ne.x);
    526 }
    527 
    528 void
    529 selsnap(int *x, int *y, int direction)
    530 {
    531 	int newx, newy, xt, yt;
    532 	int delim, prevdelim;
    533 	const Glyph *gp, *prevgp;
    534 
    535 	switch (sel.snap) {
    536 	case SNAP_WORD:
    537 		/*
    538 		 * Snap around if the word wraps around at the end or
    539 		 * beginning of a line.
    540 		 */
    541 		prevgp = &TLINE(*y)[*x];
    542 		prevdelim = ISDELIM(prevgp->u);
    543 		for (;;) {
    544 			newx = *x + direction;
    545 			newy = *y;
    546 			if (!BETWEEN(newx, 0, term.col - 1)) {
    547 				newy += direction;
    548 				newx = (newx + term.col) % term.col;
    549 				if (!BETWEEN(newy, 0, term.row - 1))
    550 					break;
    551 
    552 				if (direction > 0)
    553 					yt = *y, xt = *x;
    554 				else
    555 					yt = newy, xt = newx;
    556 				if (!(TLINE(yt)[xt].mode & ATTR_WRAP))
    557 					break;
    558 			}
    559 
    560 			if (newx >= tlinelen(newy))
    561 				break;
    562 
    563 			gp = &TLINE(newy)[newx];
    564 			delim = ISDELIM(gp->u);
    565 			if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
    566 					|| (delim && gp->u != prevgp->u)))
    567 				break;
    568 
    569 			*x = newx;
    570 			*y = newy;
    571 			prevgp = gp;
    572 			prevdelim = delim;
    573 		}
    574 		break;
    575 	case SNAP_LINE:
    576 		/*
    577 		 * Snap around if the the previous line or the current one
    578 		 * has set ATTR_WRAP at its end. Then the whole next or
    579 		 * previous line will be selected.
    580 		 */
    581 		*x = (direction < 0) ? 0 : term.col - 1;
    582 		if (direction < 0) {
    583 			for (; *y > 0; *y += direction) {
    584 				if (!(TLINE(*y-1)[term.col-1].mode
    585 						& ATTR_WRAP)) {
    586 					break;
    587 				}
    588 			}
    589 		} else if (direction > 0) {
    590 			for (; *y < term.row-1; *y += direction) {
    591 				if (!(TLINE(*y)[term.col-1].mode
    592 						& ATTR_WRAP)) {
    593 					break;
    594 				}
    595 			}
    596 		}
    597 		break;
    598 	}
    599 }
    600 
    601 char *
    602 getsel(void)
    603 {
    604 	char *str, *ptr;
    605 	int y, bufsize, lastx, linelen;
    606 	const Glyph *gp, *last;
    607 
    608 	if (sel.ob.x == -1)
    609 		return NULL;
    610 
    611 	bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ;
    612 	ptr = str = xmalloc(bufsize);
    613 
    614 	/* append every set & selected glyph to the selection */
    615 	for (y = sel.nb.y; y <= sel.ne.y; y++) {
    616 		if ((linelen = tlinelen(y)) == 0) {
    617 			*ptr++ = '\n';
    618 			continue;
    619 		}
    620 
    621 		if (sel.type == SEL_RECTANGULAR) {
    622 			gp = &TLINE(y)[sel.nb.x];
    623 			lastx = sel.ne.x;
    624 		} else {
    625 			gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0];
    626 			lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
    627 		}
    628 		last = &TLINE(y)[MIN(lastx, linelen-1)];
    629 		while (last >= gp && last->u == ' ')
    630 			--last;
    631 
    632 		for ( ; gp <= last; ++gp) {
    633 			if (gp->mode & ATTR_WDUMMY)
    634 				continue;
    635 
    636 			if (gp->mode & ATTR_IMAGE) {
    637 				// TODO: Copy diacritics as well
    638 				ptr += utf8encode(IMAGE_PLACEHOLDER_CHAR, ptr);
    639 				continue;
    640 			}
    641 
    642 			ptr += utf8encode(gp->u, ptr);
    643 		}
    644 
    645 		/*
    646 		 * Copy and pasting of line endings is inconsistent
    647 		 * in the inconsistent terminal and GUI world.
    648 		 * The best solution seems like to produce '\n' when
    649 		 * something is copied from st and convert '\n' to
    650 		 * '\r', when something to be pasted is received by
    651 		 * st.
    652 		 * FIXME: Fix the computer world.
    653 		 */
    654 		if ((y < sel.ne.y || lastx >= linelen) &&
    655 		    (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR))
    656 			*ptr++ = '\n';
    657 	}
    658 	*ptr = 0;
    659 	return str;
    660 }
    661 
    662 void
    663 selclear(void)
    664 {
    665 	if (sel.ob.x == -1)
    666 		return;
    667 	sel.mode = SEL_IDLE;
    668 	sel.ob.x = -1;
    669 	tsetdirt(sel.nb.y, sel.ne.y);
    670 }
    671 
    672 void
    673 die(const char *errstr, ...)
    674 {
    675 	va_list ap;
    676 
    677 	va_start(ap, errstr);
    678 	vfprintf(stderr, errstr, ap);
    679 	va_end(ap);
    680 	exit(1);
    681 }
    682 
    683 void
    684 execsh(char *cmd, char **args)
    685 {
    686 	char *sh, *prog, *arg;
    687 	const struct passwd *pw;
    688 
    689 	errno = 0;
    690 	if ((pw = getpwuid(getuid())) == NULL) {
    691 		if (errno)
    692 			die("getpwuid: %s\n", strerror(errno));
    693 		else
    694 			die("who are you?\n");
    695 	}
    696 
    697 	if ((sh = getenv("SHELL")) == NULL)
    698 		sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd;
    699 
    700 	if (args) {
    701 		prog = args[0];
    702 		arg = NULL;
    703 	} else if (scroll) {
    704 		prog = scroll;
    705 		arg = utmp ? utmp : sh;
    706 	} else if (utmp) {
    707 		prog = utmp;
    708 		arg = NULL;
    709 	} else {
    710 		prog = sh;
    711 		arg = NULL;
    712 	}
    713 	DEFAULT(args, ((char *[]) {prog, arg, NULL}));
    714 
    715 	unsetenv("COLUMNS");
    716 	unsetenv("LINES");
    717 	unsetenv("TERMCAP");
    718 	setenv("LOGNAME", pw->pw_name, 1);
    719 	setenv("USER", pw->pw_name, 1);
    720 	setenv("SHELL", sh, 1);
    721 	setenv("HOME", pw->pw_dir, 1);
    722 	setenv("TERM", termname, 1);
    723 
    724 	signal(SIGCHLD, SIG_DFL);
    725 	signal(SIGHUP, SIG_DFL);
    726 	signal(SIGINT, SIG_DFL);
    727 	signal(SIGQUIT, SIG_DFL);
    728 	signal(SIGTERM, SIG_DFL);
    729 	signal(SIGALRM, SIG_DFL);
    730 
    731 	execvp(prog, args);
    732 	_exit(1);
    733 }
    734 
    735 void
    736 sigchld(int a)
    737 {
    738 	int stat;
    739 	pid_t p;
    740 
    741 	if ((p = waitpid(pid, &stat, WNOHANG)) < 0)
    742 		die("waiting for pid %hd failed: %s\n", pid, strerror(errno));
    743 
    744 	if (pid != p)
    745 		return;
    746 
    747 	if (WIFEXITED(stat) && WEXITSTATUS(stat))
    748 		die("child exited with status %d\n", WEXITSTATUS(stat));
    749 	else if (WIFSIGNALED(stat))
    750 		die("child terminated due to signal %d\n", WTERMSIG(stat));
    751 	_exit(0);
    752 }
    753 
    754 void
    755 stty(char **args)
    756 {
    757 	char cmd[_POSIX_ARG_MAX], **p, *q, *s;
    758 	size_t n, siz;
    759 
    760 	if ((n = strlen(stty_args)) > sizeof(cmd)-1)
    761 		die("incorrect stty parameters\n");
    762 	memcpy(cmd, stty_args, n);
    763 	q = cmd + n;
    764 	siz = sizeof(cmd) - n;
    765 	for (p = args; p && (s = *p); ++p) {
    766 		if ((n = strlen(s)) > siz-1)
    767 			die("stty parameter length too long\n");
    768 		*q++ = ' ';
    769 		memcpy(q, s, n);
    770 		q += n;
    771 		siz -= n + 1;
    772 	}
    773 	*q = '\0';
    774 	if (system(cmd) != 0)
    775 		perror("Couldn't call stty");
    776 }
    777 
    778 int
    779 ttynew(const char *line, char *cmd, const char *out, char **args)
    780 {
    781 	int m, s;
    782 
    783 	if (out) {
    784 		term.mode |= MODE_PRINT;
    785 		iofd = (!strcmp(out, "-")) ?
    786 			  1 : open(out, O_WRONLY | O_CREAT, 0666);
    787 		if (iofd < 0) {
    788 			fprintf(stderr, "Error opening %s:%s\n",
    789 				out, strerror(errno));
    790 		}
    791 	}
    792 
    793 	if (line) {
    794 		if ((cmdfd = open(line, O_RDWR)) < 0)
    795 			die("open line '%s' failed: %s\n",
    796 			    line, strerror(errno));
    797 		dup2(cmdfd, 0);
    798 		stty(args);
    799 		return cmdfd;
    800 	}
    801 
    802 	/* seems to work fine on linux, openbsd and freebsd */
    803 	if (openpty(&m, &s, NULL, NULL, NULL) < 0)
    804 		die("openpty failed: %s\n", strerror(errno));
    805 
    806 	switch (pid = fork()) {
    807 	case -1:
    808 		die("fork failed: %s\n", strerror(errno));
    809 		break;
    810 	case 0:
    811 		close(iofd);
    812 		close(m);
    813 		setsid(); /* create a new process group */
    814 		dup2(s, 0);
    815 		dup2(s, 1);
    816 		dup2(s, 2);
    817 		if (ioctl(s, TIOCSCTTY, NULL) < 0)
    818 			die("ioctl TIOCSCTTY failed: %s\n", strerror(errno));
    819 		if (s > 2)
    820 			close(s);
    821 #ifdef __OpenBSD__
    822 		if (pledge("stdio getpw proc exec", NULL) == -1)
    823 			die("pledge\n");
    824 #endif
    825 		execsh(cmd, args);
    826 		break;
    827 	default:
    828 #ifdef __OpenBSD__
    829 		if (pledge("stdio rpath tty proc", NULL) == -1)
    830 			die("pledge\n");
    831 #endif
    832 		close(s);
    833 		cmdfd = m;
    834 		signal(SIGCHLD, sigchld);
    835 		break;
    836 	}
    837 	return cmdfd;
    838 }
    839 
    840 size_t
    841 ttyread(void)
    842 {
    843 	static char buf[BUFSIZ];
    844 	static int buflen = 0;
    845 	static int already_processing = 0;
    846 	int ret, written = 0;
    847 
    848 	if (buflen >= LEN(buf))
    849 		return 0;
    850 
    851 	/* append read bytes to unprocessed bytes */
    852 	ret = read(cmdfd, buf+buflen, LEN(buf)-buflen);
    853 
    854 	switch (ret) {
    855 	case 0:
    856 		exit(0);
    857 	case -1:
    858 		die("couldn't read from shell: %s\n", strerror(errno));
    859 	default:
    860 		buflen += ret;
    861 		if (already_processing) {
    862 			/* Avoid recursive call to twrite() */
    863 			return ret;
    864 		}
    865 		already_processing = 1;
    866 		while (1) {
    867 			int buflen_before_processing = buflen;
    868 			written += twrite(buf + written, buflen - written, 0);
    869 			// If buflen changed during the call to twrite, there is
    870 			// new data, and we need to keep processing, otherwise
    871 			// we can exit. This will not loop forever because the
    872 			// buffer is limited, and we don't clean it in this
    873 			// loop, so at some point ttywrite will have to drop
    874 			// some data.
    875 			if (buflen_before_processing == buflen)
    876 				break;
    877 		}
    878 		already_processing = 0;
    879 		buflen -= written;
    880 		/* keep any incomplete UTF-8 byte sequence for the next call */
    881 		if (buflen > 0)
    882 			memmove(buf, buf + written, buflen);
    883 		return ret;
    884 	}
    885 }
    886 
    887 void
    888 ttywrite(const char *s, size_t n, int may_echo)
    889 {
    890 	const char *next;
    891 	Arg arg = (Arg) { .i = term.scr };
    892 
    893 	kscrolldown(&arg);
    894 
    895 	if (may_echo && IS_SET(MODE_ECHO))
    896 		twrite(s, n, 1);
    897 
    898 	if (!IS_SET(MODE_CRLF)) {
    899 		ttywriteraw(s, n);
    900 		return;
    901 	}
    902 
    903 	/* This is similar to how the kernel handles ONLCR for ttys */
    904 	while (n > 0) {
    905 		if (*s == '\r') {
    906 			next = s + 1;
    907 			ttywriteraw("\r\n", 2);
    908 		} else {
    909 			next = memchr(s, '\r', n);
    910 			DEFAULT(next, s + n);
    911 			ttywriteraw(s, next - s);
    912 		}
    913 		n -= next - s;
    914 		s = next;
    915 	}
    916 }
    917 
    918 void
    919 ttywriteraw(const char *s, size_t n)
    920 {
    921 	fd_set wfd, rfd;
    922 	ssize_t r;
    923 	size_t lim = 256;
    924 	int retries_left = 100;
    925 
    926 	/*
    927 	 * Remember that we are using a pty, which might be a modem line.
    928 	 * Writing too much will clog the line. That's why we are doing this
    929 	 * dance.
    930 	 * FIXME: Migrate the world to Plan 9.
    931 	 */
    932 	while (n > 0) {
    933 		if (retries_left-- <= 0)
    934 			goto too_many_retries;
    935 
    936 		FD_ZERO(&wfd);
    937 		FD_ZERO(&rfd);
    938 		FD_SET(cmdfd, &wfd);
    939 		FD_SET(cmdfd, &rfd);
    940 
    941 		/* Check if we can write. */
    942 		if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) {
    943 			if (errno == EINTR)
    944 				continue;
    945 			die("select failed: %s\n", strerror(errno));
    946 		}
    947 		if (FD_ISSET(cmdfd, &wfd)) {
    948 			/*
    949 			 * Only write the bytes written by ttywrite() or the
    950 			 * default of 256. This seems to be a reasonable value
    951 			 * for a serial line. Bigger values might clog the I/O.
    952 			 */
    953 			if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0)
    954 				goto write_error;
    955 			if (r < n) {
    956 				/*
    957 				 * We weren't able to write out everything.
    958 				 * This means the buffer is getting full
    959 				 * again. Empty it.
    960 				 */
    961 				if (n < lim)
    962 					lim = ttyread();
    963 				n -= r;
    964 				s += r;
    965 			} else {
    966 				/* All bytes have been written. */
    967 				break;
    968 			}
    969 		}
    970 		if (FD_ISSET(cmdfd, &rfd))
    971 			lim = ttyread();
    972 	}
    973 	return;
    974 
    975 write_error:
    976 	die("write error on tty: %s\n", strerror(errno));
    977 too_many_retries:
    978 	fprintf(stderr, "Could not write %zu bytes to tty\n", n);
    979 }
    980 
    981 void
    982 ttyresize(int tw, int th)
    983 {
    984 	term.pixw = tw;
    985 	term.pixh = th;
    986 
    987 	struct winsize w;
    988 
    989 	w.ws_row = term.row;
    990 	w.ws_col = term.col;
    991 	w.ws_xpixel = tw;
    992 	w.ws_ypixel = th;
    993 	if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
    994 		fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
    995 }
    996 
    997 void
    998 ttyhangup(void)
    999 {
   1000 	/* Send SIGHUP to shell */
   1001 	kill(pid, SIGHUP);
   1002 }
   1003 
   1004 int
   1005 tattrset(int attr)
   1006 {
   1007 	int i, j;
   1008 
   1009 	for (i = 0; i < term.row-1; i++) {
   1010 		for (j = 0; j < term.col-1; j++) {
   1011 			if (term.line[i][j].mode & attr)
   1012 				return 1;
   1013 		}
   1014 	}
   1015 
   1016 	return 0;
   1017 }
   1018 
   1019 void
   1020 tsetdirt(int top, int bot)
   1021 {
   1022 	int i;
   1023 
   1024 	LIMIT(top, 0, term.row-1);
   1025 	LIMIT(bot, 0, term.row-1);
   1026 
   1027 	for (i = top; i <= bot; i++)
   1028 		term.dirty[i] = 1;
   1029 }
   1030 
   1031 void
   1032 tsetdirtattr(int attr)
   1033 {
   1034 	int i, j;
   1035 
   1036 	for (i = 0; i < term.row-1; i++) {
   1037 		for (j = 0; j < term.col-1; j++) {
   1038 			if (term.line[i][j].mode & attr) {
   1039 				tsetdirt(i, i);
   1040 				break;
   1041 			}
   1042 		}
   1043 	}
   1044 }
   1045 
   1046 void
   1047 tfulldirt(void)
   1048 {
   1049 	tsetdirt(0, term.row-1);
   1050 }
   1051 
   1052 void
   1053 tcursor(int mode)
   1054 {
   1055 	static TCursor c[2];
   1056 	int alt = IS_SET(MODE_ALTSCREEN);
   1057 
   1058 	if (mode == CURSOR_SAVE) {
   1059 		c[alt] = term.c;
   1060 	} else if (mode == CURSOR_LOAD) {
   1061 		term.c = c[alt];
   1062 		tmoveto(c[alt].x, c[alt].y);
   1063 	}
   1064 }
   1065 
   1066 void
   1067 treset(void)
   1068 {
   1069 	uint i;
   1070 
   1071 	term.c = (TCursor){{
   1072 		.mode = ATTR_NULL,
   1073 		.fg = defaultfg,
   1074 		.bg = defaultbg,
   1075 		.decor = DECOR_DEFAULT_COLOR
   1076 	}, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
   1077 
   1078 	memset(term.tabs, 0, term.col * sizeof(*term.tabs));
   1079 	for (i = tabspaces; i < term.col; i += tabspaces)
   1080 		term.tabs[i] = 1;
   1081 	term.top = 0;
   1082 	term.bot = term.row - 1;
   1083 	term.mode = MODE_WRAP|MODE_UTF8;
   1084 	memset(term.trantbl, CS_USA, sizeof(term.trantbl));
   1085 	term.charset = 0;
   1086 
   1087 	for (i = 0; i < 2; i++) {
   1088 		tmoveto(0, 0);
   1089 		tcursor(CURSOR_SAVE);
   1090 		tclearregion(0, 0, term.col-1, term.row-1);
   1091 		tswapscreen();
   1092 	}
   1093 }
   1094 
   1095 void
   1096 tnew(int col, int row)
   1097 {
   1098 	term = (Term){.c = {.attr = {.fg = defaultfg,
   1099 				     .bg = defaultbg,
   1100 				     .decor = DECOR_DEFAULT_COLOR}}};
   1101 	tresize(col, row);
   1102 	treset();
   1103 }
   1104 
   1105 void
   1106 tswapscreen(void)
   1107 {
   1108 	Line *tmp = term.line;
   1109 
   1110 	term.line = term.alt;
   1111 	term.alt = tmp;
   1112 	term.mode ^= MODE_ALTSCREEN;
   1113 	tfulldirt();
   1114 }
   1115 
   1116 void
   1117 kscrolldown(const Arg* a)
   1118 {
   1119 	int n = a->i;
   1120 
   1121 	if (n < 0)
   1122 		n = term.row + n;
   1123 
   1124 	if (n > term.scr)
   1125 		n = term.scr;
   1126 
   1127 	if (term.scr > 0) {
   1128 		term.scr -= n;
   1129 		selscroll(0, -n);
   1130 		tfulldirt();
   1131 	}
   1132 }
   1133 
   1134 void
   1135 kscrollup(const Arg* a)
   1136 {
   1137 	int n = a->i;
   1138 
   1139 	if (n < 0)
   1140 		n = term.row + n;
   1141 
   1142 	if (term.scr <= HISTSIZE-n) {
   1143 		term.scr += n;
   1144 		selscroll(0, n);
   1145 		tfulldirt();
   1146 	}
   1147 }
   1148 
   1149 void
   1150 tscrolldown(int orig, int n, int copyhist)
   1151 {
   1152 	int i;
   1153 	Line temp;
   1154 
   1155 	LIMIT(n, 0, term.bot-orig+1);
   1156 
   1157 	if (copyhist) {
   1158 		term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE;
   1159 		temp = term.hist[term.histi];
   1160 		term.hist[term.histi] = term.line[term.bot];
   1161 		term.line[term.bot] = temp;
   1162 	}
   1163 
   1164 	tsetdirt(orig, term.bot-n);
   1165 	tclearregion(0, term.bot-n+1, term.col-1, term.bot);
   1166 
   1167 	for (i = term.bot; i >= orig+n; i--) {
   1168 		temp = term.line[i];
   1169 		term.line[i] = term.line[i-n];
   1170 		term.line[i-n] = temp;
   1171 	}
   1172 
   1173 	if (term.scr == 0)
   1174 		selscroll(orig, n);
   1175 }
   1176 
   1177 void
   1178 tscrollup(int orig, int n, int copyhist)
   1179 {
   1180 	int i;
   1181 	Line temp;
   1182 
   1183 	LIMIT(n, 0, term.bot-orig+1);
   1184 
   1185 	if (copyhist) {
   1186 		term.histi = (term.histi + 1) % HISTSIZE;
   1187 		temp = term.hist[term.histi];
   1188 		term.hist[term.histi] = term.line[orig];
   1189 		term.line[orig] = temp;
   1190 	}
   1191 
   1192 	if (term.scr > 0 && term.scr < HISTSIZE)
   1193 		term.scr = MIN(term.scr + n, HISTSIZE-1);
   1194 
   1195 	tclearregion(0, orig, term.col-1, orig+n-1);
   1196 	tsetdirt(orig+n, term.bot);
   1197 
   1198 	for (i = orig; i <= term.bot-n; i++) {
   1199 		temp = term.line[i];
   1200 		term.line[i] = term.line[i+n];
   1201 		term.line[i+n] = temp;
   1202 	}
   1203 
   1204 	if (term.scr == 0)
   1205 		selscroll(orig, -n);
   1206 }
   1207 
   1208 void
   1209 selscroll(int orig, int n)
   1210 {
   1211 	if (sel.ob.x == -1)
   1212 		return;
   1213 
   1214 	if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) {
   1215 		selclear();
   1216 	} else if (BETWEEN(sel.nb.y, orig, term.bot)) {
   1217 		sel.ob.y += n;
   1218 		sel.oe.y += n;
   1219 		if (sel.ob.y < term.top || sel.ob.y > term.bot ||
   1220 		    sel.oe.y < term.top || sel.oe.y > term.bot) {
   1221 			selclear();
   1222 		} else {
   1223 			selnormalize();
   1224 		}
   1225 	}
   1226 }
   1227 
   1228 void
   1229 tnewline(int first_col)
   1230 {
   1231 	int y = term.c.y;
   1232 
   1233 	if (y == term.bot) {
   1234 		tscrollup(term.top, 1, 1);
   1235 	} else {
   1236 		y++;
   1237 	}
   1238 	tmoveto(first_col ? 0 : term.c.x, y);
   1239 }
   1240 
   1241 void
   1242 csiparse(void)
   1243 {
   1244 	char *p = csiescseq.buf, *np;
   1245 	long int v;
   1246 
   1247 	csiescseq.narg = 0;
   1248 	if (*p == '?') {
   1249 		csiescseq.priv = 1;
   1250 		p++;
   1251 	}
   1252 
   1253 	csiescseq.buf[csiescseq.len] = '\0';
   1254 	while (p < csiescseq.buf+csiescseq.len) {
   1255 		np = NULL;
   1256 		v = strtol(p, &np, 10);
   1257 		if (np == p)
   1258 			v = 0;
   1259 		if (v == LONG_MAX || v == LONG_MIN)
   1260 			v = -1;
   1261 		csiescseq.arg[csiescseq.narg++] = v;
   1262 		p = np;
   1263 		if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ)
   1264 			break;
   1265 		p++;
   1266 	}
   1267 	csiescseq.mode[0] = *p++;
   1268 	csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0';
   1269 }
   1270 
   1271 /* for absolute user moves, when decom is set */
   1272 void
   1273 tmoveato(int x, int y)
   1274 {
   1275 	tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0));
   1276 }
   1277 
   1278 void
   1279 tmoveto(int x, int y)
   1280 {
   1281 	int miny, maxy;
   1282 
   1283 	if (term.c.state & CURSOR_ORIGIN) {
   1284 		miny = term.top;
   1285 		maxy = term.bot;
   1286 	} else {
   1287 		miny = 0;
   1288 		maxy = term.row - 1;
   1289 	}
   1290 	term.c.state &= ~CURSOR_WRAPNEXT;
   1291 	term.c.x = LIMIT(x, 0, term.col-1);
   1292 	term.c.y = LIMIT(y, miny, maxy);
   1293 }
   1294 
   1295 void
   1296 tsetchar(Rune u, const Glyph *attr, int x, int y)
   1297 {
   1298 	static const char *vt100_0[62] = { /* 0x41 - 0x7e */
   1299 		"↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
   1300 		0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
   1301 		0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
   1302 		0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
   1303 		"◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
   1304 		"␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
   1305 		"⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
   1306 		"│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
   1307 	};
   1308 
   1309 	/*
   1310 	 * The table is proudly stolen from rxvt.
   1311 	 */
   1312 	if (term.trantbl[term.charset] == CS_GRAPHIC0 &&
   1313 	   BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
   1314 		utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ);
   1315 
   1316 	if (term.line[y][x].mode & ATTR_WIDE) {
   1317 		if (x+1 < term.col) {
   1318 			term.line[y][x+1].u = ' ';
   1319 			term.line[y][x+1].mode &= ~ATTR_WDUMMY;
   1320 		}
   1321 	} else if (term.line[y][x].mode & ATTR_WDUMMY) {
   1322 		term.line[y][x-1].u = ' ';
   1323 		term.line[y][x-1].mode &= ~ATTR_WIDE;
   1324 	}
   1325 
   1326 	if (u == ' ' && term.line[y][x].mode & ATTR_IMAGE &&
   1327 	    tgetisclassicplaceholder(&term.line[y][x])) {
   1328 		// This is a workaround: don't overwrite classic placement
   1329 		// placeholders with space symbols (unlike Unicode placeholders
   1330 		// which must be overwritten by anything).
   1331 		term.line[y][x].bg = attr->bg;
   1332 		term.dirty[y] = 1;
   1333 		return;
   1334 	}
   1335 
   1336 	term.dirty[y] = 1;
   1337 	term.line[y][x] = *attr;
   1338 	term.line[y][x].u = u;
   1339 
   1340 	if (u == IMAGE_PLACEHOLDER_CHAR || u == IMAGE_PLACEHOLDER_CHAR_OLD) {
   1341 		term.line[y][x].u = 0;
   1342 		term.line[y][x].mode |= ATTR_IMAGE;
   1343 	}
   1344 }
   1345 
   1346 void
   1347 tclearregion(int x1, int y1, int x2, int y2)
   1348 {
   1349 	int x, y, temp;
   1350 	Glyph *gp;
   1351 
   1352 	if (x1 > x2)
   1353 		temp = x1, x1 = x2, x2 = temp;
   1354 	if (y1 > y2)
   1355 		temp = y1, y1 = y2, y2 = temp;
   1356 
   1357 	LIMIT(x1, 0, term.col-1);
   1358 	LIMIT(x2, 0, term.col-1);
   1359 	LIMIT(y1, 0, term.row-1);
   1360 	LIMIT(y2, 0, term.row-1);
   1361 
   1362 	for (y = y1; y <= y2; y++) {
   1363 		term.dirty[y] = 1;
   1364 		for (x = x1; x <= x2; x++) {
   1365 			gp = &term.line[y][x];
   1366 			if (selected(x, y))
   1367 				selclear();
   1368 			gp->fg = term.c.attr.fg;
   1369 			gp->bg = term.c.attr.bg;
   1370 			gp->decor = term.c.attr.decor;
   1371 			gp->mode = 0;
   1372 			gp->u = ' ';
   1373 		}
   1374 	}
   1375 }
   1376 
   1377 /// Fills a rectangle area with an image placeholder. The starting point is the
   1378 /// cursor. Adds empty lines if needed. The placeholder will be marked as
   1379 /// classic.
   1380 void
   1381 tcreateimgplaceholder(uint32_t image_id, uint32_t placement_id,
   1382 		      int cols, int rows, char do_not_move_cursor)
   1383 {
   1384 	for (int row = 0; row < rows; ++row) {
   1385 		int y = term.c.y;
   1386 		term.dirty[y] = 1;
   1387 		for (int col = 0; col < cols; ++col) {
   1388 			int x = term.c.x + col;
   1389 			if (x >= term.col)
   1390 				break;
   1391 			Glyph *gp = &term.line[y][x];
   1392 			if (selected(x, y))
   1393 				selclear();
   1394 			gp->mode = ATTR_IMAGE;
   1395 			gp->u = 0;
   1396 			tsetimgrow(gp, row + 1);
   1397 			tsetimgcol(gp, col + 1);
   1398 			tsetimgid(gp, image_id);
   1399 			tsetimgplacementid(gp, placement_id);
   1400 			tsetimgdiacriticcount(gp, 3);
   1401 			tsetisclassicplaceholder(gp, 1);
   1402 		}
   1403 		// If moving the cursor is not allowed and this is the last line
   1404 		// of the terminal, we are done.
   1405 		if (do_not_move_cursor && y == term.row - 1)
   1406 			break;
   1407 		// Move the cursor down, maybe creating a new line. The x is
   1408 		// preserved (we never change term.c.x in the loop above).
   1409 		if (row != rows - 1)
   1410 			tnewline(/*first_col=*/0);
   1411 	}
   1412 	if (do_not_move_cursor) {
   1413 		// Return the cursor to the original position.
   1414 		tmoveto(term.c.x, term.c.y - rows + 1);
   1415 	} else {
   1416 		// Move the cursor beyond the last column, as required by the
   1417 		// protocol. If the cursor goes beyond the screen edge, insert a
   1418 		// newline to match the behavior of kitty.
   1419 		if (term.c.x + cols >= term.col)
   1420 			tnewline(/*first_col=*/1);
   1421 		else
   1422 			tmoveto(term.c.x + cols, term.c.y);
   1423 	}
   1424 }
   1425 
   1426 void gr_for_each_image_cell(int (*callback)(void *data, uint32_t image_id,
   1427 					    uint32_t placement_id, int col,
   1428 					    int row, char is_classic),
   1429 			    void *data) {
   1430 	for (int row = 0; row < term.row; ++row) {
   1431 		for (int col = 0; col < term.col; ++col) {
   1432 			Glyph *gp = &term.line[row][col];
   1433 			if (gp->mode & ATTR_IMAGE) {
   1434 				uint32_t image_id = tgetimgid(gp);
   1435 				uint32_t placement_id = tgetimgplacementid(gp);
   1436 				int ret =
   1437 					callback(data, tgetimgid(gp),
   1438 						 tgetimgplacementid(gp),
   1439 						 tgetimgcol(gp), tgetimgrow(gp),
   1440 						 tgetisclassicplaceholder(gp));
   1441 				if (ret == 1) {
   1442 					term.dirty[row] = 1;
   1443 					gp->mode = 0;
   1444 					gp->u = ' ';
   1445 				}
   1446 			}
   1447 		}
   1448 	}
   1449 }
   1450 
   1451 void gr_schedule_image_redraw_by_id(uint32_t image_id) {
   1452 	for (int row = 0; row < term.row; ++row) {
   1453 		if (term.dirty[row])
   1454 			continue;
   1455 		for (int col = 0; col < term.col; ++col) {
   1456 			Glyph *gp = &term.line[row][col];
   1457 			if (gp->mode & ATTR_IMAGE) {
   1458 				uint32_t cell_image_id = tgetimgid(gp);
   1459 				if (cell_image_id == image_id) {
   1460 					term.dirty[row] = 1;
   1461 					break;
   1462 				}
   1463 			}
   1464 		}
   1465 	}
   1466 }
   1467 
   1468 void
   1469 tdeletechar(int n)
   1470 {
   1471 	int dst, src, size;
   1472 	Glyph *line;
   1473 
   1474 	LIMIT(n, 0, term.col - term.c.x);
   1475 
   1476 	dst = term.c.x;
   1477 	src = term.c.x + n;
   1478 	size = term.col - src;
   1479 	line = term.line[term.c.y];
   1480 
   1481 	memmove(&line[dst], &line[src], size * sizeof(Glyph));
   1482 	tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
   1483 }
   1484 
   1485 void
   1486 tinsertblank(int n)
   1487 {
   1488 	int dst, src, size;
   1489 	Glyph *line;
   1490 
   1491 	LIMIT(n, 0, term.col - term.c.x);
   1492 
   1493 	dst = term.c.x + n;
   1494 	src = term.c.x;
   1495 	size = term.col - dst;
   1496 	line = term.line[term.c.y];
   1497 
   1498 	memmove(&line[dst], &line[src], size * sizeof(Glyph));
   1499 	tclearregion(src, term.c.y, dst - 1, term.c.y);
   1500 }
   1501 
   1502 void
   1503 tinsertblankline(int n)
   1504 {
   1505 	if (BETWEEN(term.c.y, term.top, term.bot))
   1506 		tscrolldown(term.c.y, n, 0);
   1507 }
   1508 
   1509 void
   1510 tdeleteline(int n)
   1511 {
   1512 	if (BETWEEN(term.c.y, term.top, term.bot))
   1513 		tscrollup(term.c.y, n, 0);
   1514 }
   1515 
   1516 int32_t
   1517 tdefcolor(const int *attr, int *npar, int l)
   1518 {
   1519 	int32_t idx = -1;
   1520 	uint r, g, b;
   1521 
   1522 	switch (attr[*npar + 1]) {
   1523 	case 2: /* direct color in RGB space */
   1524 		if (*npar + 4 >= l) {
   1525 			fprintf(stderr,
   1526 				"erresc(38): Incorrect number of parameters (%d)\n",
   1527 				*npar);
   1528 			break;
   1529 		}
   1530 		r = attr[*npar + 2];
   1531 		g = attr[*npar + 3];
   1532 		b = attr[*npar + 4];
   1533 		*npar += 4;
   1534 		if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255))
   1535 			fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n",
   1536 				r, g, b);
   1537 		else
   1538 			idx = TRUECOLOR(r, g, b);
   1539 		break;
   1540 	case 5: /* indexed color */
   1541 		if (*npar + 2 >= l) {
   1542 			fprintf(stderr,
   1543 				"erresc(38): Incorrect number of parameters (%d)\n",
   1544 				*npar);
   1545 			break;
   1546 		}
   1547 		*npar += 2;
   1548 		if (!BETWEEN(attr[*npar], 0, 255))
   1549 			fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]);
   1550 		else
   1551 			idx = attr[*npar];
   1552 		break;
   1553 	case 0: /* implemented defined (only foreground) */
   1554 	case 1: /* transparent */
   1555 	case 3: /* direct color in CMY space */
   1556 	case 4: /* direct color in CMYK space */
   1557 	default:
   1558 		fprintf(stderr,
   1559 		        "erresc(38): gfx attr %d unknown\n", attr[*npar]);
   1560 		break;
   1561 	}
   1562 
   1563 	return idx;
   1564 }
   1565 
   1566 void
   1567 tsetattr(const int *attr, int l)
   1568 {
   1569 	int i;
   1570 	int32_t idx;
   1571 
   1572 	for (i = 0; i < l; i++) {
   1573 		switch (attr[i]) {
   1574 		case 0:
   1575 			term.c.attr.mode &= ~(
   1576 				ATTR_BOLD       |
   1577 				ATTR_FAINT      |
   1578 				ATTR_ITALIC     |
   1579 				ATTR_UNDERLINE  |
   1580 				ATTR_BLINK      |
   1581 				ATTR_REVERSE    |
   1582 				ATTR_INVISIBLE  |
   1583 				ATTR_STRUCK     );
   1584 			term.c.attr.fg = defaultfg;
   1585 			term.c.attr.bg = defaultbg;
   1586 			term.c.attr.decor = DECOR_DEFAULT_COLOR;
   1587 			break;
   1588 		case 1:
   1589 			term.c.attr.mode |= ATTR_BOLD;
   1590 			break;
   1591 		case 2:
   1592 			term.c.attr.mode |= ATTR_FAINT;
   1593 			break;
   1594 		case 3:
   1595 			term.c.attr.mode |= ATTR_ITALIC;
   1596 			break;
   1597 		case 4:
   1598 			term.c.attr.mode |= ATTR_UNDERLINE;
   1599 			if (i + 1 < l) {
   1600 				idx = attr[++i];
   1601 				if (BETWEEN(idx, 1, 5)) {
   1602 					tsetdecorstyle(&term.c.attr, idx);
   1603 				} else if (idx == 0) {
   1604 					term.c.attr.mode &= ~ATTR_UNDERLINE;
   1605 					tsetdecorstyle(&term.c.attr, 0);
   1606 				} else {
   1607 					fprintf(stderr,
   1608 						"erresc: unknown underline "
   1609 						"style %d\n",
   1610 						idx);
   1611 				}
   1612 			}
   1613 			break;
   1614 		case 5: /* slow blink */
   1615 			/* FALLTHROUGH */
   1616 		case 6: /* rapid blink */
   1617 			term.c.attr.mode |= ATTR_BLINK;
   1618 			break;
   1619 		case 7:
   1620 			term.c.attr.mode |= ATTR_REVERSE;
   1621 			break;
   1622 		case 8:
   1623 			term.c.attr.mode |= ATTR_INVISIBLE;
   1624 			break;
   1625 		case 9:
   1626 			term.c.attr.mode |= ATTR_STRUCK;
   1627 			break;
   1628 		case 22:
   1629 			term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT);
   1630 			break;
   1631 		case 23:
   1632 			term.c.attr.mode &= ~ATTR_ITALIC;
   1633 			break;
   1634 		case 24:
   1635 			term.c.attr.mode &= ~ATTR_UNDERLINE;
   1636 			tsetdecorstyle(&term.c.attr, 0);
   1637 			break;
   1638 		case 25:
   1639 			term.c.attr.mode &= ~ATTR_BLINK;
   1640 			break;
   1641 		case 27:
   1642 			term.c.attr.mode &= ~ATTR_REVERSE;
   1643 			break;
   1644 		case 28:
   1645 			term.c.attr.mode &= ~ATTR_INVISIBLE;
   1646 			break;
   1647 		case 29:
   1648 			term.c.attr.mode &= ~ATTR_STRUCK;
   1649 			break;
   1650 		case 38:
   1651 			if ((idx = tdefcolor(attr, &i, l)) >= 0)
   1652 				term.c.attr.fg = idx;
   1653 			break;
   1654 		case 39:
   1655 			term.c.attr.fg = defaultfg;
   1656 			break;
   1657 		case 48:
   1658 			if ((idx = tdefcolor(attr, &i, l)) >= 0)
   1659 				term.c.attr.bg = idx;
   1660 			break;
   1661 		case 49:
   1662 			term.c.attr.bg = defaultbg;
   1663 			break;
   1664 		case 58:
   1665 			if ((idx = tdefcolor(attr, &i, l)) >= 0)
   1666 				tsetdecorcolor(&term.c.attr, idx);
   1667 			break;
   1668 		case 59:
   1669 			tsetdecorcolor(&term.c.attr, DECOR_DEFAULT_COLOR);
   1670 			break;
   1671 		default:
   1672 			if (BETWEEN(attr[i], 30, 37)) {
   1673 				term.c.attr.fg = attr[i] - 30;
   1674 			} else if (BETWEEN(attr[i], 40, 47)) {
   1675 				term.c.attr.bg = attr[i] - 40;
   1676 			} else if (BETWEEN(attr[i], 90, 97)) {
   1677 				term.c.attr.fg = attr[i] - 90 + 8;
   1678 			} else if (BETWEEN(attr[i], 100, 107)) {
   1679 				term.c.attr.bg = attr[i] - 100 + 8;
   1680 			} else {
   1681 				fprintf(stderr,
   1682 					"erresc(default): gfx attr %d unknown\n",
   1683 					attr[i]);
   1684 				csidump();
   1685 			}
   1686 			break;
   1687 		}
   1688 	}
   1689 }
   1690 
   1691 void
   1692 tsetscroll(int t, int b)
   1693 {
   1694 	int temp;
   1695 
   1696 	LIMIT(t, 0, term.row-1);
   1697 	LIMIT(b, 0, term.row-1);
   1698 	if (t > b) {
   1699 		temp = t;
   1700 		t = b;
   1701 		b = temp;
   1702 	}
   1703 	term.top = t;
   1704 	term.bot = b;
   1705 }
   1706 
   1707 void
   1708 tsetmode(int priv, int set, const int *args, int narg)
   1709 {
   1710 	int alt; const int *lim;
   1711 
   1712 	for (lim = args + narg; args < lim; ++args) {
   1713 		if (priv) {
   1714 			switch (*args) {
   1715 			case 1: /* DECCKM -- Cursor key */
   1716 				xsetmode(set, MODE_APPCURSOR);
   1717 				break;
   1718 			case 5: /* DECSCNM -- Reverse video */
   1719 				xsetmode(set, MODE_REVERSE);
   1720 				break;
   1721 			case 6: /* DECOM -- Origin */
   1722 				MODBIT(term.c.state, set, CURSOR_ORIGIN);
   1723 				tmoveato(0, 0);
   1724 				break;
   1725 			case 7: /* DECAWM -- Auto wrap */
   1726 				MODBIT(term.mode, set, MODE_WRAP);
   1727 				break;
   1728 			case 0:  /* Error (IGNORED) */
   1729 			case 2:  /* DECANM -- ANSI/VT52 (IGNORED) */
   1730 			case 3:  /* DECCOLM -- Column  (IGNORED) */
   1731 			case 4:  /* DECSCLM -- Scroll (IGNORED) */
   1732 			case 8:  /* DECARM -- Auto repeat (IGNORED) */
   1733 			case 18: /* DECPFF -- Printer feed (IGNORED) */
   1734 			case 19: /* DECPEX -- Printer extent (IGNORED) */
   1735 			case 42: /* DECNRCM -- National characters (IGNORED) */
   1736 			case 12: /* att610 -- Start blinking cursor (IGNORED) */
   1737 				break;
   1738 			case 25: /* DECTCEM -- Text Cursor Enable Mode */
   1739 				xsetmode(!set, MODE_HIDE);
   1740 				break;
   1741 			case 9:    /* X10 mouse compatibility mode */
   1742 				xsetpointermotion(0);
   1743 				xsetmode(0, MODE_MOUSE);
   1744 				xsetmode(set, MODE_MOUSEX10);
   1745 				break;
   1746 			case 1000: /* 1000: report button press */
   1747 				xsetpointermotion(0);
   1748 				xsetmode(0, MODE_MOUSE);
   1749 				xsetmode(set, MODE_MOUSEBTN);
   1750 				break;
   1751 			case 1002: /* 1002: report motion on button press */
   1752 				xsetpointermotion(0);
   1753 				xsetmode(0, MODE_MOUSE);
   1754 				xsetmode(set, MODE_MOUSEMOTION);
   1755 				break;
   1756 			case 1003: /* 1003: enable all mouse motions */
   1757 				xsetpointermotion(set);
   1758 				xsetmode(0, MODE_MOUSE);
   1759 				xsetmode(set, MODE_MOUSEMANY);
   1760 				break;
   1761 			case 1004: /* 1004: send focus events to tty */
   1762 				xsetmode(set, MODE_FOCUS);
   1763 				break;
   1764 			case 1006: /* 1006: extended reporting mode */
   1765 				xsetmode(set, MODE_MOUSESGR);
   1766 				break;
   1767 			case 1034:
   1768 				xsetmode(set, MODE_8BIT);
   1769 				break;
   1770 			case 1049: /* swap screen & set/restore cursor as xterm */
   1771 				if (!allowaltscreen)
   1772 					break;
   1773 				tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
   1774 				/* FALLTHROUGH */
   1775 			case 47: /* swap screen */
   1776 			case 1047:
   1777 				if (!allowaltscreen)
   1778 					break;
   1779 				alt = IS_SET(MODE_ALTSCREEN);
   1780 				if (alt) {
   1781 					tclearregion(0, 0, term.col-1,
   1782 							term.row-1);
   1783 				}
   1784 				if (set ^ alt) /* set is always 1 or 0 */
   1785 					tswapscreen();
   1786 				if (*args != 1049)
   1787 					break;
   1788 				/* FALLTHROUGH */
   1789 			case 1048:
   1790 				tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
   1791 				break;
   1792 			case 2004: /* 2004: bracketed paste mode */
   1793 				xsetmode(set, MODE_BRCKTPASTE);
   1794 				break;
   1795 			/* Not implemented mouse modes. See comments there. */
   1796 			case 1001: /* mouse highlight mode; can hang the
   1797 				      terminal by design when implemented. */
   1798 			case 1005: /* UTF-8 mouse mode; will confuse
   1799 				      applications not supporting UTF-8
   1800 				      and luit. */
   1801 			case 1015: /* urxvt mangled mouse mode; incompatible
   1802 				      and can be mistaken for other control
   1803 				      codes. */
   1804 				break;
   1805 			default:
   1806 				fprintf(stderr,
   1807 					"erresc: unknown private set/reset mode %d\n",
   1808 					*args);
   1809 				break;
   1810 			}
   1811 		} else {
   1812 			switch (*args) {
   1813 			case 0:  /* Error (IGNORED) */
   1814 				break;
   1815 			case 2:
   1816 				xsetmode(set, MODE_KBDLOCK);
   1817 				break;
   1818 			case 4:  /* IRM -- Insertion-replacement */
   1819 				MODBIT(term.mode, set, MODE_INSERT);
   1820 				break;
   1821 			case 12: /* SRM -- Send/Receive */
   1822 				MODBIT(term.mode, !set, MODE_ECHO);
   1823 				break;
   1824 			case 20: /* LNM -- Linefeed/new line */
   1825 				MODBIT(term.mode, set, MODE_CRLF);
   1826 				break;
   1827 			default:
   1828 				fprintf(stderr,
   1829 					"erresc: unknown set/reset mode %d\n",
   1830 					*args);
   1831 				break;
   1832 			}
   1833 		}
   1834 	}
   1835 }
   1836 
   1837 void
   1838 csihandle(void)
   1839 {
   1840 	char buf[40];
   1841 	int len;
   1842 
   1843 	switch (csiescseq.mode[0]) {
   1844 	default:
   1845 	unknown:
   1846 		fprintf(stderr, "erresc: unknown csi ");
   1847 		csidump();
   1848 		/* die(""); */
   1849 		break;
   1850 	case '@': /* ICH -- Insert <n> blank char */
   1851 		DEFAULT(csiescseq.arg[0], 1);
   1852 		tinsertblank(csiescseq.arg[0]);
   1853 		break;
   1854 	case 'A': /* CUU -- Cursor <n> Up */
   1855 		DEFAULT(csiescseq.arg[0], 1);
   1856 		tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
   1857 		break;
   1858 	case 'B': /* CUD -- Cursor <n> Down */
   1859 	case 'e': /* VPR --Cursor <n> Down */
   1860 		DEFAULT(csiescseq.arg[0], 1);
   1861 		tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
   1862 		break;
   1863 	case 'i': /* MC -- Media Copy */
   1864 		switch (csiescseq.arg[0]) {
   1865 		case 0:
   1866 			tdump();
   1867 			break;
   1868 		case 1:
   1869 			tdumpline(term.c.y);
   1870 			break;
   1871 		case 2:
   1872 			tdumpsel();
   1873 			break;
   1874 		case 4:
   1875 			term.mode &= ~MODE_PRINT;
   1876 			break;
   1877 		case 5:
   1878 			term.mode |= MODE_PRINT;
   1879 			break;
   1880 		}
   1881 		break;
   1882 	case 'c': /* DA -- Device Attributes */
   1883 		if (csiescseq.arg[0] == 0)
   1884 			ttywrite(vtiden, strlen(vtiden), 0);
   1885 		break;
   1886 	case 'b': /* REP -- if last char is printable print it <n> more times */
   1887 		DEFAULT(csiescseq.arg[0], 1);
   1888 		if (term.lastc)
   1889 			while (csiescseq.arg[0]-- > 0)
   1890 				tputc(term.lastc);
   1891 		break;
   1892 	case 'C': /* CUF -- Cursor <n> Forward */
   1893 	case 'a': /* HPR -- Cursor <n> Forward */
   1894 		DEFAULT(csiescseq.arg[0], 1);
   1895 		tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
   1896 		break;
   1897 	case 'D': /* CUB -- Cursor <n> Backward */
   1898 		DEFAULT(csiescseq.arg[0], 1);
   1899 		tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
   1900 		break;
   1901 	case 'E': /* CNL -- Cursor <n> Down and first col */
   1902 		DEFAULT(csiescseq.arg[0], 1);
   1903 		tmoveto(0, term.c.y+csiescseq.arg[0]);
   1904 		break;
   1905 	case 'F': /* CPL -- Cursor <n> Up and first col */
   1906 		DEFAULT(csiescseq.arg[0], 1);
   1907 		tmoveto(0, term.c.y-csiescseq.arg[0]);
   1908 		break;
   1909 	case 'g': /* TBC -- Tabulation clear */
   1910 		switch (csiescseq.arg[0]) {
   1911 		case 0: /* clear current tab stop */
   1912 			term.tabs[term.c.x] = 0;
   1913 			break;
   1914 		case 3: /* clear all the tabs */
   1915 			memset(term.tabs, 0, term.col * sizeof(*term.tabs));
   1916 			break;
   1917 		default:
   1918 			goto unknown;
   1919 		}
   1920 		break;
   1921 	case 'G': /* CHA -- Move to <col> */
   1922 	case '`': /* HPA */
   1923 		DEFAULT(csiescseq.arg[0], 1);
   1924 		tmoveto(csiescseq.arg[0]-1, term.c.y);
   1925 		break;
   1926 	case 'H': /* CUP -- Move to <row> <col> */
   1927 	case 'f': /* HVP */
   1928 		DEFAULT(csiescseq.arg[0], 1);
   1929 		DEFAULT(csiescseq.arg[1], 1);
   1930 		tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
   1931 		break;
   1932 	case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
   1933 		DEFAULT(csiescseq.arg[0], 1);
   1934 		tputtab(csiescseq.arg[0]);
   1935 		break;
   1936 	case 'J': /* ED -- Clear screen */
   1937 		switch (csiescseq.arg[0]) {
   1938 		case 0: /* below */
   1939 			tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
   1940 			if (term.c.y < term.row-1) {
   1941 				tclearregion(0, term.c.y+1, term.col-1,
   1942 						term.row-1);
   1943 			}
   1944 			break;
   1945 		case 1: /* above */
   1946 			if (term.c.y > 1)
   1947 				tclearregion(0, 0, term.col-1, term.c.y-1);
   1948 			tclearregion(0, term.c.y, term.c.x, term.c.y);
   1949 			break;
   1950 		case 2: /* all */
   1951 			tclearregion(0, 0, term.col-1, term.row-1);
   1952 			break;
   1953 		default:
   1954 			goto unknown;
   1955 		}
   1956 		break;
   1957 	case 'K': /* EL -- Clear line */
   1958 		switch (csiescseq.arg[0]) {
   1959 		case 0: /* right */
   1960 			tclearregion(term.c.x, term.c.y, term.col-1,
   1961 					term.c.y);
   1962 			break;
   1963 		case 1: /* left */
   1964 			tclearregion(0, term.c.y, term.c.x, term.c.y);
   1965 			break;
   1966 		case 2: /* all */
   1967 			tclearregion(0, term.c.y, term.col-1, term.c.y);
   1968 			break;
   1969 		}
   1970 		break;
   1971 	case 'S': /* SU -- Scroll <n> line up */
   1972 		DEFAULT(csiescseq.arg[0], 1);
   1973 		tscrollup(term.top, csiescseq.arg[0], 0);
   1974 		break;
   1975 	case 'T': /* SD -- Scroll <n> line down */
   1976 		DEFAULT(csiescseq.arg[0], 1);
   1977 		tscrolldown(term.top, csiescseq.arg[0], 0);
   1978 		break;
   1979 	case 'L': /* IL -- Insert <n> blank lines */
   1980 		DEFAULT(csiescseq.arg[0], 1);
   1981 		tinsertblankline(csiescseq.arg[0]);
   1982 		break;
   1983 	case 'l': /* RM -- Reset Mode */
   1984 		tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
   1985 		break;
   1986 	case 'M': /* DL -- Delete <n> lines */
   1987 		DEFAULT(csiescseq.arg[0], 1);
   1988 		tdeleteline(csiescseq.arg[0]);
   1989 		break;
   1990 	case 'X': /* ECH -- Erase <n> char */
   1991 		DEFAULT(csiescseq.arg[0], 1);
   1992 		tclearregion(term.c.x, term.c.y,
   1993 				term.c.x + csiescseq.arg[0] - 1, term.c.y);
   1994 		break;
   1995 	case 'P': /* DCH -- Delete <n> char */
   1996 		DEFAULT(csiescseq.arg[0], 1);
   1997 		tdeletechar(csiescseq.arg[0]);
   1998 		break;
   1999 	case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
   2000 		DEFAULT(csiescseq.arg[0], 1);
   2001 		tputtab(-csiescseq.arg[0]);
   2002 		break;
   2003 	case 'd': /* VPA -- Move to <row> */
   2004 		DEFAULT(csiescseq.arg[0], 1);
   2005 		tmoveato(term.c.x, csiescseq.arg[0]-1);
   2006 		break;
   2007 	case 'h': /* SM -- Set terminal mode */
   2008 		tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
   2009 		break;
   2010 	case 'm': /* SGR -- Terminal attribute (color) */
   2011 		tsetattr(csiescseq.arg, csiescseq.narg);
   2012 		break;
   2013 	case 'n': /* DSR -- Device Status Report */
   2014 		switch (csiescseq.arg[0]) {
   2015 		case 5: /* Status Report "OK" `0n` */
   2016 			ttywrite("\033[0n", sizeof("\033[0n") - 1, 0);
   2017 			break;
   2018 		case 6: /* Report Cursor Position (CPR) "<row>;<column>R" */
   2019 			len = snprintf(buf, sizeof(buf), "\033[%i;%iR",
   2020 			               term.c.y+1, term.c.x+1);
   2021 			ttywrite(buf, len, 0);
   2022 			break;
   2023 		default:
   2024 			goto unknown;
   2025 		}
   2026 		break;
   2027 	case 'r': /* DECSTBM -- Set Scrolling Region */
   2028 		if (csiescseq.priv) {
   2029 			goto unknown;
   2030 		} else {
   2031 			DEFAULT(csiescseq.arg[0], 1);
   2032 			DEFAULT(csiescseq.arg[1], term.row);
   2033 			tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
   2034 			tmoveato(0, 0);
   2035 		}
   2036 		break;
   2037 	case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
   2038 		tcursor(CURSOR_SAVE);
   2039 		break;
   2040 	case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
   2041 		tcursor(CURSOR_LOAD);
   2042 		break;
   2043 	case ' ':
   2044 		switch (csiescseq.mode[1]) {
   2045 		case 'q': /* DECSCUSR -- Set Cursor Style */
   2046 			if (xsetcursor(csiescseq.arg[0]))
   2047 				goto unknown;
   2048 			break;
   2049 		default:
   2050 			goto unknown;
   2051 		}
   2052 		break;
   2053 	case '>':
   2054 		switch (csiescseq.mode[1]) {
   2055 		case 'q': /* XTVERSION -- Print terminal name and version */
   2056 			len = snprintf(buf, sizeof(buf),
   2057 				       "\033P>|st-graphics(%s)\033\\", VERSION);
   2058 			ttywrite(buf, len, 0);
   2059 			break;
   2060 		default:
   2061 			goto unknown;
   2062 		}
   2063 		break;
   2064 	case 't': /* XTWINOPS -- Window manipulation */
   2065 		switch (csiescseq.arg[0]) {
   2066 		case 14: /* Report text area size in pixels. */
   2067 			len = snprintf(buf, sizeof(buf), "\033[4;%i;%it",
   2068 					term.pixh, term.pixw);
   2069 			ttywrite(buf, len, 0);
   2070 			break;
   2071 		case 16: /* Report character cell size in pixels. */
   2072 			len = snprintf(buf, sizeof(buf), "\033[6;%i;%it",
   2073 					term.pixh / term.row,
   2074 					term.pixw / term.col);
   2075 			ttywrite(buf, len, 0);
   2076 			break;
   2077 		case 18: /* Report the size of the text area in characters. */
   2078 			len = snprintf(buf, sizeof(buf), "\033[8;%i;%it",
   2079 					term.row, term.col);
   2080 			ttywrite(buf, len, 0);
   2081 			break;
   2082 		default:
   2083 			goto unknown;
   2084 		}
   2085 		break;
   2086 	}
   2087 }
   2088 
   2089 void
   2090 csidump(void)
   2091 {
   2092 	size_t i;
   2093 	uint c;
   2094 
   2095 	fprintf(stderr, "ESC[");
   2096 	for (i = 0; i < csiescseq.len; i++) {
   2097 		c = csiescseq.buf[i] & 0xff;
   2098 		if (isprint(c)) {
   2099 			putc(c, stderr);
   2100 		} else if (c == '\n') {
   2101 			fprintf(stderr, "(\\n)");
   2102 		} else if (c == '\r') {
   2103 			fprintf(stderr, "(\\r)");
   2104 		} else if (c == 0x1b) {
   2105 			fprintf(stderr, "(\\e)");
   2106 		} else {
   2107 			fprintf(stderr, "(%02x)", c);
   2108 		}
   2109 	}
   2110 	putc('\n', stderr);
   2111 }
   2112 
   2113 void
   2114 csireset(void)
   2115 {
   2116 	memset(&csiescseq, 0, sizeof(csiescseq));
   2117 }
   2118 
   2119 void
   2120 osc_color_response(int num, int index, int is_osc4)
   2121 {
   2122 	int n;
   2123 	char buf[32];
   2124 	unsigned char r, g, b;
   2125 
   2126 	if (xgetcolor(is_osc4 ? num : index, &r, &g, &b)) {
   2127 		fprintf(stderr, "erresc: failed to fetch %s color %d\n",
   2128 		        is_osc4 ? "osc4" : "osc",
   2129 		        is_osc4 ? num : index);
   2130 		return;
   2131 	}
   2132 
   2133 	n = snprintf(buf, sizeof buf, "\033]%s%d;rgb:%02x%02x/%02x%02x/%02x%02x\007",
   2134 	             is_osc4 ? "4;" : "", num, r, r, g, g, b, b);
   2135 	if (n < 0 || n >= sizeof(buf)) {
   2136 		fprintf(stderr, "error: %s while printing %s response\n",
   2137 		        n < 0 ? "snprintf failed" : "truncation occurred",
   2138 		        is_osc4 ? "osc4" : "osc");
   2139 	} else {
   2140 		ttywrite(buf, n, 1);
   2141 	}
   2142 }
   2143 
   2144 void
   2145 strhandle(void)
   2146 {
   2147 	char *p = NULL, *dec;
   2148 	int j, narg, par;
   2149 	const struct { int idx; char *str; } osc_table[] = {
   2150 		{ defaultfg, "foreground" },
   2151 		{ defaultbg, "background" },
   2152 		{ defaultcs, "cursor" }
   2153 	};
   2154 
   2155 	term.esc &= ~(ESC_STR_END|ESC_STR);
   2156 	strparse();
   2157 	par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
   2158 
   2159 	switch (strescseq.type) {
   2160 	case ']': /* OSC -- Operating System Command */
   2161 		switch (par) {
   2162 		case 0:
   2163 			if (narg > 1) {
   2164 				xsettitle(strescseq.args[1]);
   2165 				xseticontitle(strescseq.args[1]);
   2166 			}
   2167 			return;
   2168 		case 1:
   2169 			if (narg > 1)
   2170 				xseticontitle(strescseq.args[1]);
   2171 			return;
   2172 		case 2:
   2173 			if (narg > 1)
   2174 				xsettitle(strescseq.args[1]);
   2175 			return;
   2176 		case 52:
   2177 			if (narg > 2 && allowwindowops) {
   2178 				dec = base64dec(strescseq.args[2]);
   2179 				if (dec) {
   2180 					xsetsel(dec);
   2181 					xclipcopy();
   2182 				} else {
   2183 					fprintf(stderr, "erresc: invalid base64\n");
   2184 				}
   2185 			}
   2186 			return;
   2187 		case 10:
   2188 		case 11:
   2189 		case 12:
   2190 			if (narg < 2)
   2191 				break;
   2192 			p = strescseq.args[1];
   2193 			if ((j = par - 10) < 0 || j >= LEN(osc_table))
   2194 				break; /* shouldn't be possible */
   2195 
   2196 			if (!strcmp(p, "?")) {
   2197 				osc_color_response(par, osc_table[j].idx, 0);
   2198 			} else if (xsetcolorname(osc_table[j].idx, p)) {
   2199 				fprintf(stderr, "erresc: invalid %s color: %s\n",
   2200 				        osc_table[j].str, p);
   2201 			} else {
   2202 				tfulldirt();
   2203 			}
   2204 			return;
   2205 		case 4: /* color set */
   2206 			if (narg < 3)
   2207 				break;
   2208 			p = strescseq.args[2];
   2209 			/* FALLTHROUGH */
   2210 		case 104: /* color reset */
   2211 			j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
   2212 
   2213 			if (p && !strcmp(p, "?")) {
   2214 				osc_color_response(j, 0, 1);
   2215 			} else if (xsetcolorname(j, p)) {
   2216 				if (par == 104 && narg <= 1) {
   2217 					xloadcols();
   2218 					return; /* color reset without parameter */
   2219 				}
   2220 				fprintf(stderr, "erresc: invalid color j=%d, p=%s\n",
   2221 				        j, p ? p : "(null)");
   2222 			} else {
   2223 				/*
   2224 				 * TODO if defaultbg color is changed, borders
   2225 				 * are dirty
   2226 				 */
   2227 				tfulldirt();
   2228 			}
   2229 			return;
   2230 		}
   2231 		break;
   2232 	case 'k': /* old title set compatibility */
   2233 		xsettitle(strescseq.args[0]);
   2234 		return;
   2235 	case '_': /* APC -- Application Program Command */
   2236 		if (gr_parse_command(strescseq.buf, strescseq.len)) {
   2237 			GraphicsCommandResult *res = &graphics_command_result;
   2238 			if (res->create_placeholder) {
   2239 				tcreateimgplaceholder(
   2240 					res->placeholder.image_id,
   2241 					res->placeholder.placement_id,
   2242 					res->placeholder.columns,
   2243 					res->placeholder.rows,
   2244 					res->placeholder.do_not_move_cursor);
   2245 			}
   2246 			if (res->response[0])
   2247 				ttywrite(res->response, strlen(res->response),
   2248 					 0);
   2249 			if (res->redraw)
   2250 				tfulldirt();
   2251 			return;
   2252 		}
   2253 		return;
   2254 	case 'P': /* DCS -- Device Control String */
   2255 	case '^': /* PM -- Privacy Message */
   2256 		return;
   2257 	}
   2258 
   2259 	fprintf(stderr, "erresc: unknown str ");
   2260 	strdump();
   2261 }
   2262 
   2263 void
   2264 strparse(void)
   2265 {
   2266 	int c;
   2267 	char *p = strescseq.buf;
   2268 
   2269 	strescseq.narg = 0;
   2270 	strescseq.buf[strescseq.len] = '\0';
   2271 
   2272 	if (*p == '\0')
   2273 		return;
   2274 
   2275 	while (strescseq.narg < STR_ARG_SIZ) {
   2276 		strescseq.args[strescseq.narg++] = p;
   2277 		while ((c = *p) != ';' && c != '\0')
   2278 			++p;
   2279 		if (c == '\0')
   2280 			return;
   2281 		*p++ = '\0';
   2282 	}
   2283 }
   2284 
   2285 void
   2286 strdump(void)
   2287 {
   2288 	size_t i;
   2289 	uint c;
   2290 
   2291 	fprintf(stderr, "ESC%c", strescseq.type);
   2292 	for (i = 0; i < strescseq.len; i++) {
   2293 		c = strescseq.buf[i] & 0xff;
   2294 		if (c == '\0') {
   2295 			putc('\n', stderr);
   2296 			return;
   2297 		} else if (isprint(c)) {
   2298 			putc(c, stderr);
   2299 		} else if (c == '\n') {
   2300 			fprintf(stderr, "(\\n)");
   2301 		} else if (c == '\r') {
   2302 			fprintf(stderr, "(\\r)");
   2303 		} else if (c == 0x1b) {
   2304 			fprintf(stderr, "(\\e)");
   2305 		} else {
   2306 			fprintf(stderr, "(%02x)", c);
   2307 		}
   2308 	}
   2309 	fprintf(stderr, "ESC\\\n");
   2310 }
   2311 
   2312 void
   2313 strreset(void)
   2314 {
   2315 	strescseq = (STREscape){
   2316 		.buf = xrealloc(strescseq.buf, STR_BUF_SIZ),
   2317 		.siz = STR_BUF_SIZ,
   2318 	};
   2319 }
   2320 
   2321 void
   2322 sendbreak(const Arg *arg)
   2323 {
   2324 	if (tcsendbreak(cmdfd, 0))
   2325 		perror("Error sending break");
   2326 }
   2327 
   2328 void
   2329 tprinter(char *s, size_t len)
   2330 {
   2331 	if (iofd != -1 && xwrite(iofd, s, len) < 0) {
   2332 		perror("Error writing to output file");
   2333 		close(iofd);
   2334 		iofd = -1;
   2335 	}
   2336 }
   2337 
   2338 void
   2339 toggleprinter(const Arg *arg)
   2340 {
   2341 	term.mode ^= MODE_PRINT;
   2342 }
   2343 
   2344 void
   2345 printscreen(const Arg *arg)
   2346 {
   2347 	tdump();
   2348 }
   2349 
   2350 void
   2351 printsel(const Arg *arg)
   2352 {
   2353 	tdumpsel();
   2354 }
   2355 
   2356 void
   2357 tdumpsel(void)
   2358 {
   2359 	char *ptr;
   2360 
   2361 	if ((ptr = getsel())) {
   2362 		tprinter(ptr, strlen(ptr));
   2363 		free(ptr);
   2364 	}
   2365 }
   2366 
   2367 void
   2368 tdumpline(int n)
   2369 {
   2370 	char buf[UTF_SIZ];
   2371 	const Glyph *bp, *end;
   2372 
   2373 	bp = &term.line[n][0];
   2374 	end = &bp[MIN(tlinelen(n), term.col) - 1];
   2375 	if (bp != end || bp->u != ' ') {
   2376 		for ( ; bp <= end; ++bp)
   2377 			tprinter(buf, utf8encode(bp->u, buf));
   2378 	}
   2379 	tprinter("\n", 1);
   2380 }
   2381 
   2382 void
   2383 tdump(void)
   2384 {
   2385 	int i;
   2386 
   2387 	for (i = 0; i < term.row; ++i)
   2388 		tdumpline(i);
   2389 }
   2390 
   2391 void
   2392 tputtab(int n)
   2393 {
   2394 	uint x = term.c.x;
   2395 
   2396 	if (n > 0) {
   2397 		while (x < term.col && n--)
   2398 			for (++x; x < term.col && !term.tabs[x]; ++x)
   2399 				/* nothing */ ;
   2400 	} else if (n < 0) {
   2401 		while (x > 0 && n++)
   2402 			for (--x; x > 0 && !term.tabs[x]; --x)
   2403 				/* nothing */ ;
   2404 	}
   2405 	term.c.x = LIMIT(x, 0, term.col-1);
   2406 }
   2407 
   2408 void
   2409 tdefutf8(char ascii)
   2410 {
   2411 	if (ascii == 'G')
   2412 		term.mode |= MODE_UTF8;
   2413 	else if (ascii == '@')
   2414 		term.mode &= ~MODE_UTF8;
   2415 }
   2416 
   2417 void
   2418 tdeftran(char ascii)
   2419 {
   2420 	static char cs[] = "0B";
   2421 	static int vcs[] = {CS_GRAPHIC0, CS_USA};
   2422 	char *p;
   2423 
   2424 	if ((p = strchr(cs, ascii)) == NULL) {
   2425 		fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
   2426 	} else {
   2427 		term.trantbl[term.icharset] = vcs[p - cs];
   2428 	}
   2429 }
   2430 
   2431 void
   2432 tdectest(char c)
   2433 {
   2434 	int x, y;
   2435 
   2436 	if (c == '8') { /* DEC screen alignment test. */
   2437 		for (x = 0; x < term.col; ++x) {
   2438 			for (y = 0; y < term.row; ++y)
   2439 				tsetchar('E', &term.c.attr, x, y);
   2440 		}
   2441 	}
   2442 }
   2443 
   2444 void
   2445 tstrsequence(uchar c)
   2446 {
   2447 	switch (c) {
   2448 	case 0x90:   /* DCS -- Device Control String */
   2449 		c = 'P';
   2450 		break;
   2451 	case 0x9f:   /* APC -- Application Program Command */
   2452 		c = '_';
   2453 		break;
   2454 	case 0x9e:   /* PM -- Privacy Message */
   2455 		c = '^';
   2456 		break;
   2457 	case 0x9d:   /* OSC -- Operating System Command */
   2458 		c = ']';
   2459 		break;
   2460 	}
   2461 	strreset();
   2462 	strescseq.type = c;
   2463 	term.esc |= ESC_STR;
   2464 }
   2465 
   2466 void
   2467 tcontrolcode(uchar ascii)
   2468 {
   2469 	switch (ascii) {
   2470 	case '\t':   /* HT */
   2471 		tputtab(1);
   2472 		return;
   2473 	case '\b':   /* BS */
   2474 		tmoveto(term.c.x-1, term.c.y);
   2475 		return;
   2476 	case '\r':   /* CR */
   2477 		tmoveto(0, term.c.y);
   2478 		return;
   2479 	case '\f':   /* LF */
   2480 	case '\v':   /* VT */
   2481 	case '\n':   /* LF */
   2482 		/* go to first col if the mode is set */
   2483 		tnewline(IS_SET(MODE_CRLF));
   2484 		return;
   2485 	case '\a':   /* BEL */
   2486 		if (term.esc & ESC_STR_END) {
   2487 			/* backwards compatibility to xterm */
   2488 			strhandle();
   2489 		} else {
   2490 			xbell();
   2491 		}
   2492 		break;
   2493 	case '\033': /* ESC */
   2494 		csireset();
   2495 		term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
   2496 		term.esc |= ESC_START;
   2497 		return;
   2498 	case '\016': /* SO (LS1 -- Locking shift 1) */
   2499 	case '\017': /* SI (LS0 -- Locking shift 0) */
   2500 		term.charset = 1 - (ascii - '\016');
   2501 		return;
   2502 	case '\032': /* SUB */
   2503 		tsetchar('?', &term.c.attr, term.c.x, term.c.y);
   2504 		/* FALLTHROUGH */
   2505 	case '\030': /* CAN */
   2506 		csireset();
   2507 		break;
   2508 	case '\005': /* ENQ (IGNORED) */
   2509 	case '\000': /* NUL (IGNORED) */
   2510 	case '\021': /* XON (IGNORED) */
   2511 	case '\023': /* XOFF (IGNORED) */
   2512 	case 0177:   /* DEL (IGNORED) */
   2513 		return;
   2514 	case 0x80:   /* TODO: PAD */
   2515 	case 0x81:   /* TODO: HOP */
   2516 	case 0x82:   /* TODO: BPH */
   2517 	case 0x83:   /* TODO: NBH */
   2518 	case 0x84:   /* TODO: IND */
   2519 		break;
   2520 	case 0x85:   /* NEL -- Next line */
   2521 		tnewline(1); /* always go to first col */
   2522 		break;
   2523 	case 0x86:   /* TODO: SSA */
   2524 	case 0x87:   /* TODO: ESA */
   2525 		break;
   2526 	case 0x88:   /* HTS -- Horizontal tab stop */
   2527 		term.tabs[term.c.x] = 1;
   2528 		break;
   2529 	case 0x89:   /* TODO: HTJ */
   2530 	case 0x8a:   /* TODO: VTS */
   2531 	case 0x8b:   /* TODO: PLD */
   2532 	case 0x8c:   /* TODO: PLU */
   2533 	case 0x8d:   /* TODO: RI */
   2534 	case 0x8e:   /* TODO: SS2 */
   2535 	case 0x8f:   /* TODO: SS3 */
   2536 	case 0x91:   /* TODO: PU1 */
   2537 	case 0x92:   /* TODO: PU2 */
   2538 	case 0x93:   /* TODO: STS */
   2539 	case 0x94:   /* TODO: CCH */
   2540 	case 0x95:   /* TODO: MW */
   2541 	case 0x96:   /* TODO: SPA */
   2542 	case 0x97:   /* TODO: EPA */
   2543 	case 0x98:   /* TODO: SOS */
   2544 	case 0x99:   /* TODO: SGCI */
   2545 		break;
   2546 	case 0x9a:   /* DECID -- Identify Terminal */
   2547 		ttywrite(vtiden, strlen(vtiden), 0);
   2548 		break;
   2549 	case 0x9b:   /* TODO: CSI */
   2550 	case 0x9c:   /* TODO: ST */
   2551 		break;
   2552 	case 0x90:   /* DCS -- Device Control String */
   2553 	case 0x9d:   /* OSC -- Operating System Command */
   2554 	case 0x9e:   /* PM -- Privacy Message */
   2555 	case 0x9f:   /* APC -- Application Program Command */
   2556 		tstrsequence(ascii);
   2557 		return;
   2558 	}
   2559 	/* only CAN, SUB, \a and C1 chars interrupt a sequence */
   2560 	term.esc &= ~(ESC_STR_END|ESC_STR);
   2561 }
   2562 
   2563 /*
   2564  * returns 1 when the sequence is finished and it hasn't to read
   2565  * more characters for this sequence, otherwise 0
   2566  */
   2567 int
   2568 eschandle(uchar ascii)
   2569 {
   2570 	switch (ascii) {
   2571 	case '[':
   2572 		term.esc |= ESC_CSI;
   2573 		return 0;
   2574 	case '#':
   2575 		term.esc |= ESC_TEST;
   2576 		return 0;
   2577 	case '%':
   2578 		term.esc |= ESC_UTF8;
   2579 		return 0;
   2580 	case 'P': /* DCS -- Device Control String */
   2581 	case '_': /* APC -- Application Program Command */
   2582 	case '^': /* PM -- Privacy Message */
   2583 	case ']': /* OSC -- Operating System Command */
   2584 	case 'k': /* old title set compatibility */
   2585 		tstrsequence(ascii);
   2586 		return 0;
   2587 	case 'n': /* LS2 -- Locking shift 2 */
   2588 	case 'o': /* LS3 -- Locking shift 3 */
   2589 		term.charset = 2 + (ascii - 'n');
   2590 		break;
   2591 	case '(': /* GZD4 -- set primary charset G0 */
   2592 	case ')': /* G1D4 -- set secondary charset G1 */
   2593 	case '*': /* G2D4 -- set tertiary charset G2 */
   2594 	case '+': /* G3D4 -- set quaternary charset G3 */
   2595 		term.icharset = ascii - '(';
   2596 		term.esc |= ESC_ALTCHARSET;
   2597 		return 0;
   2598 	case 'D': /* IND -- Linefeed */
   2599 		if (term.c.y == term.bot) {
   2600 			tscrollup(term.top, 1, 1);
   2601 		} else {
   2602 			tmoveto(term.c.x, term.c.y+1);
   2603 		}
   2604 		break;
   2605 	case 'E': /* NEL -- Next line */
   2606 		tnewline(1); /* always go to first col */
   2607 		break;
   2608 	case 'H': /* HTS -- Horizontal tab stop */
   2609 		term.tabs[term.c.x] = 1;
   2610 		break;
   2611 	case 'M': /* RI -- Reverse index */
   2612 		if (term.c.y == term.top) {
   2613 			tscrolldown(term.top, 1, 1);
   2614 		} else {
   2615 			tmoveto(term.c.x, term.c.y-1);
   2616 		}
   2617 		break;
   2618 	case 'Z': /* DECID -- Identify Terminal */
   2619 		ttywrite(vtiden, strlen(vtiden), 0);
   2620 		break;
   2621 	case 'c': /* RIS -- Reset to initial state */
   2622 		treset();
   2623 		resettitle();
   2624 		xloadcols();
   2625 		break;
   2626 	case '=': /* DECPAM -- Application keypad */
   2627 		xsetmode(1, MODE_APPKEYPAD);
   2628 		break;
   2629 	case '>': /* DECPNM -- Normal keypad */
   2630 		xsetmode(0, MODE_APPKEYPAD);
   2631 		break;
   2632 	case '7': /* DECSC -- Save Cursor */
   2633 		tcursor(CURSOR_SAVE);
   2634 		break;
   2635 	case '8': /* DECRC -- Restore Cursor */
   2636 		tcursor(CURSOR_LOAD);
   2637 		break;
   2638 	case '\\': /* ST -- String Terminator */
   2639 		if (term.esc & ESC_STR_END)
   2640 			strhandle();
   2641 		break;
   2642 	default:
   2643 		fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
   2644 			(uchar) ascii, isprint(ascii)? ascii:'.');
   2645 		break;
   2646 	}
   2647 	return 1;
   2648 }
   2649 
   2650 void
   2651 tputc(Rune u)
   2652 {
   2653 	char c[UTF_SIZ];
   2654 	int control;
   2655 	int width, len;
   2656 	Glyph *gp;
   2657 
   2658 	control = ISCONTROL(u);
   2659 	if (u < 127 || !IS_SET(MODE_UTF8)) {
   2660 		c[0] = u;
   2661 		width = len = 1;
   2662 	} else {
   2663 		len = utf8encode(u, c);
   2664 		if (!control && (width = wcwidth(u)) == -1)
   2665 			width = 1;
   2666 	}
   2667 
   2668 	if (IS_SET(MODE_PRINT))
   2669 		tprinter(c, len);
   2670 
   2671 	/*
   2672 	 * STR sequence must be checked before anything else
   2673 	 * because it uses all following characters until it
   2674 	 * receives a ESC, a SUB, a ST or any other C1 control
   2675 	 * character.
   2676 	 */
   2677 	if (term.esc & ESC_STR) {
   2678 		if (u == '\a' || u == 030 || u == 032 || u == 033 ||
   2679 		   ISCONTROLC1(u)) {
   2680 			term.esc &= ~(ESC_START|ESC_STR);
   2681 			term.esc |= ESC_STR_END;
   2682 			goto check_control_code;
   2683 		}
   2684 
   2685 		if (strescseq.len+len >= strescseq.siz) {
   2686 			/*
   2687 			 * Here is a bug in terminals. If the user never sends
   2688 			 * some code to stop the str or esc command, then st
   2689 			 * will stop responding. But this is better than
   2690 			 * silently failing with unknown characters. At least
   2691 			 * then users will report back.
   2692 			 *
   2693 			 * In the case users ever get fixed, here is the code:
   2694 			 */
   2695 			/*
   2696 			 * term.esc = 0;
   2697 			 * strhandle();
   2698 			 */
   2699 			if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2)
   2700 				return;
   2701 			strescseq.siz *= 2;
   2702 			strescseq.buf = xrealloc(strescseq.buf, strescseq.siz);
   2703 		}
   2704 
   2705 		memmove(&strescseq.buf[strescseq.len], c, len);
   2706 		strescseq.len += len;
   2707 		return;
   2708 	}
   2709 
   2710 check_control_code:
   2711 	/*
   2712 	 * Actions of control codes must be performed as soon they arrive
   2713 	 * because they can be embedded inside a control sequence, and
   2714 	 * they must not cause conflicts with sequences.
   2715 	 */
   2716 	if (control) {
   2717 		/* in UTF-8 mode ignore handling C1 control characters */
   2718 		if (IS_SET(MODE_UTF8) && ISCONTROLC1(u))
   2719 			return;
   2720 		tcontrolcode(u);
   2721 		/*
   2722 		 * control codes are not shown ever
   2723 		 */
   2724 		if (!term.esc)
   2725 			term.lastc = 0;
   2726 		return;
   2727 	} else if (term.esc & ESC_START) {
   2728 		if (term.esc & ESC_CSI) {
   2729 			csiescseq.buf[csiescseq.len++] = u;
   2730 			if (BETWEEN(u, 0x40, 0x7E)
   2731 					|| csiescseq.len >= \
   2732 					sizeof(csiescseq.buf)-1) {
   2733 				term.esc = 0;
   2734 				csiparse();
   2735 				csihandle();
   2736 			}
   2737 			return;
   2738 		} else if (term.esc & ESC_UTF8) {
   2739 			tdefutf8(u);
   2740 		} else if (term.esc & ESC_ALTCHARSET) {
   2741 			tdeftran(u);
   2742 		} else if (term.esc & ESC_TEST) {
   2743 			tdectest(u);
   2744 		} else {
   2745 			if (!eschandle(u))
   2746 				return;
   2747 			/* sequence already finished */
   2748 		}
   2749 		term.esc = 0;
   2750 		/*
   2751 		 * All characters which form part of a sequence are not
   2752 		 * printed
   2753 		 */
   2754 		return;
   2755 	}
   2756 	if (selected(term.c.x, term.c.y))
   2757 		selclear();
   2758 
   2759 	if (width == 0) {
   2760 		// It's probably a combining char. Combining characters are not
   2761 		// supported, so we just ignore them, unless it denotes the row and
   2762 		// column of an image character.
   2763 		if (term.c.y <= 0 && term.c.x <= 0)
   2764 			return;
   2765 		else if (term.c.x == 0)
   2766 			gp = &term.line[term.c.y-1][term.col-1];
   2767 		else if (term.c.state & CURSOR_WRAPNEXT)
   2768 			gp = &term.line[term.c.y][term.c.x];
   2769 		else
   2770 			gp = &term.line[term.c.y][term.c.x-1];
   2771 		uint16_t num = diacritic_to_num(u);
   2772 		if (num && (gp->mode & ATTR_IMAGE)) {
   2773 			unsigned diaccount = tgetimgdiacriticcount(gp);
   2774 			if (diaccount == 0)
   2775 				tsetimgrow(gp, num);
   2776 			else if (diaccount == 1)
   2777 				tsetimgcol(gp, num);
   2778 			else if (diaccount == 2)
   2779 				tsetimg4thbyteplus1(gp, num);
   2780 			tsetimgdiacriticcount(gp, diaccount + 1);
   2781 		}
   2782 		term.lastc = u;
   2783 		return;
   2784 	}
   2785 
   2786 	gp = &term.line[term.c.y][term.c.x];
   2787 	if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
   2788 		gp->mode |= ATTR_WRAP;
   2789 		tnewline(1);
   2790 		gp = &term.line[term.c.y][term.c.x];
   2791 	}
   2792 
   2793 	if (IS_SET(MODE_INSERT) && term.c.x+width < term.col) {
   2794 		memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
   2795 		gp->mode &= ~ATTR_WIDE;
   2796 	}
   2797 
   2798 	if (term.c.x+width > term.col) {
   2799 		tnewline(1);
   2800 		gp = &term.line[term.c.y][term.c.x];
   2801 	}
   2802 
   2803 	tsetchar(u, &term.c.attr, term.c.x, term.c.y);
   2804 	term.lastc = u;
   2805 
   2806 	if (width == 2) {
   2807 		gp->mode |= ATTR_WIDE;
   2808 		if (term.c.x+1 < term.col) {
   2809 			if (gp[1].mode == ATTR_WIDE && term.c.x+2 < term.col) {
   2810 				gp[2].u = ' ';
   2811 				gp[2].mode &= ~ATTR_WDUMMY;
   2812 			}
   2813 			gp[1].u = '\0';
   2814 			gp[1].mode = ATTR_WDUMMY;
   2815 		}
   2816 	}
   2817 	if (term.c.x+width < term.col) {
   2818 		tmoveto(term.c.x+width, term.c.y);
   2819 	} else {
   2820 		term.c.state |= CURSOR_WRAPNEXT;
   2821 	}
   2822 }
   2823 
   2824 int
   2825 twrite(const char *buf, int buflen, int show_ctrl)
   2826 {
   2827 	int charsize;
   2828 	Rune u;
   2829 	int n;
   2830 
   2831 	for (n = 0; n < buflen; n += charsize) {
   2832 		if (IS_SET(MODE_UTF8)) {
   2833 			/* process a complete utf8 char */
   2834 			charsize = utf8decode(buf + n, &u, buflen - n);
   2835 			if (charsize == 0)
   2836 				break;
   2837 		} else {
   2838 			u = buf[n] & 0xFF;
   2839 			charsize = 1;
   2840 		}
   2841 		if (show_ctrl && ISCONTROL(u)) {
   2842 			if (u & 0x80) {
   2843 				u &= 0x7f;
   2844 				tputc('^');
   2845 				tputc('[');
   2846 			} else if (u != '\n' && u != '\r' && u != '\t') {
   2847 				u ^= 0x40;
   2848 				tputc('^');
   2849 			}
   2850 		}
   2851 		tputc(u);
   2852 	}
   2853 	return n;
   2854 }
   2855 
   2856 void
   2857 tresize(int col, int row)
   2858 {
   2859 	int i, j;
   2860 	int minrow = MIN(row, term.row);
   2861 	int mincol = MIN(col, term.col);
   2862 	int *bp;
   2863 	TCursor c;
   2864 
   2865 	if (col < 1 || row < 1) {
   2866 		fprintf(stderr,
   2867 		        "tresize: error resizing to %dx%d\n", col, row);
   2868 		return;
   2869 	}
   2870 
   2871 	/*
   2872 	 * slide screen to keep cursor where we expect it -
   2873 	 * tscrollup would work here, but we can optimize to
   2874 	 * memmove because we're freeing the earlier lines
   2875 	 */
   2876 	for (i = 0; i <= term.c.y - row; i++) {
   2877 		free(term.line[i]);
   2878 		free(term.alt[i]);
   2879 	}
   2880 	/* ensure that both src and dst are not NULL */
   2881 	if (i > 0) {
   2882 		memmove(term.line, term.line + i, row * sizeof(Line));
   2883 		memmove(term.alt, term.alt + i, row * sizeof(Line));
   2884 	}
   2885 	for (i += row; i < term.row; i++) {
   2886 		free(term.line[i]);
   2887 		free(term.alt[i]);
   2888 	}
   2889 
   2890 	/* resize to new height */
   2891 	term.line = xrealloc(term.line, row * sizeof(Line));
   2892 	term.alt  = xrealloc(term.alt,  row * sizeof(Line));
   2893 	term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
   2894 	term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
   2895 
   2896 	for (i = 0; i < HISTSIZE; i++) {
   2897 		term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph));
   2898 		for (j = mincol; j < col; j++) {
   2899 			term.hist[i][j] = term.c.attr;
   2900 			term.hist[i][j].u = ' ';
   2901 		}
   2902 	}
   2903 
   2904 	/* resize each row to new width, zero-pad if needed */
   2905 	for (i = 0; i < minrow; i++) {
   2906 		term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
   2907 		term.alt[i]  = xrealloc(term.alt[i],  col * sizeof(Glyph));
   2908 	}
   2909 
   2910 	/* allocate any new rows */
   2911 	for (/* i = minrow */; i < row; i++) {
   2912 		term.line[i] = xmalloc(col * sizeof(Glyph));
   2913 		term.alt[i] = xmalloc(col * sizeof(Glyph));
   2914 	}
   2915 	if (col > term.col) {
   2916 		bp = term.tabs + term.col;
   2917 
   2918 		memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
   2919 		while (--bp > term.tabs && !*bp)
   2920 			/* nothing */ ;
   2921 		for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
   2922 			*bp = 1;
   2923 	}
   2924 	/* update terminal size */
   2925 	term.col = col;
   2926 	term.row = row;
   2927 	/* reset scrolling region */
   2928 	tsetscroll(0, row-1);
   2929 	/* make use of the LIMIT in tmoveto */
   2930 	tmoveto(term.c.x, term.c.y);
   2931 	/* Clearing both screens (it makes dirty all lines) */
   2932 	c = term.c;
   2933 	for (i = 0; i < 2; i++) {
   2934 		if (mincol < col && 0 < minrow) {
   2935 			tclearregion(mincol, 0, col - 1, minrow - 1);
   2936 		}
   2937 		if (0 < col && minrow < row) {
   2938 			tclearregion(0, minrow, col - 1, row - 1);
   2939 		}
   2940 		tswapscreen();
   2941 		tcursor(CURSOR_LOAD);
   2942 	}
   2943 	term.c = c;
   2944 }
   2945 
   2946 void
   2947 resettitle(void)
   2948 {
   2949 	xsettitle(NULL);
   2950 }
   2951 
   2952 void
   2953 drawregion(int x1, int y1, int x2, int y2)
   2954 {
   2955 	int y;
   2956 
   2957 	xstartimagedraw(term.dirty, term.row);
   2958 
   2959 	for (y = y1; y < y2; y++) {
   2960 		if (!term.dirty[y])
   2961 			continue;
   2962 
   2963 		term.dirty[y] = 0;
   2964 		xdrawline(TLINE(y), x1, y, x2);
   2965 	}
   2966 
   2967 	xfinishimagedraw();
   2968 }
   2969 
   2970 void
   2971 draw(void)
   2972 {
   2973 	int cx = term.c.x, ocx = term.ocx, ocy = term.ocy;
   2974 
   2975 	if (!xstartdraw())
   2976 		return;
   2977 
   2978 	/* adjust cursor position */
   2979 	LIMIT(term.ocx, 0, term.col-1);
   2980 	LIMIT(term.ocy, 0, term.row-1);
   2981 	if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY)
   2982 		term.ocx--;
   2983 	if (term.line[term.c.y][cx].mode & ATTR_WDUMMY)
   2984 		cx--;
   2985 
   2986 	drawregion(0, 0, term.col, term.row);
   2987 	if (term.scr == 0)
   2988 		xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
   2989 				term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
   2990 	term.ocx = cx;
   2991 	term.ocy = term.c.y;
   2992 	xfinishdraw();
   2993 	if (ocx != term.ocx || ocy != term.ocy)
   2994 		xximspot(term.ocx, term.ocy);
   2995 }
   2996 
   2997 void
   2998 redraw(void)
   2999 {
   3000 	tfulldirt();
   3001 	draw();
   3002 }
   3003 
   3004 Glyph
   3005 getglyphat(int col, int row)
   3006 {
   3007 	return term.line[row][col];
   3008 }