st

my build of st
git clone git://git.hanetzok.net/st
Log | Files | Refs | README | LICENSE

st.c (60876B)


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