st

Suckless terminal
git clone https://git.jamzattack.xyz/st
Log | Files | Refs | README | LICENSE

st.c (57347B)


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