st

My st build
git clone https://git.regexghost.com/st.git
Log | Files | Refs | README | LICENSE

st.c (65651B)


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