st

Mahdi's build of st
git clone git://mahdi.pw/st.git
Log | Files | Refs | README | LICENSE

st.c (69369B)


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