st

My fork of st.
git clone git://git.alex.balgavy.eu/st.git
Log | Files | Refs | README | LICENSE

st.c (57536B)


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