dotfiles

[void/arch] linux dotfiles
git clone git://git.mdnr.space/dotfiles
Log | Files | Refs

st.c (60397B)


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