st-nihal

My personal build of st
git clone git://nihaljere.xyz/st-nihal
Log | Files | Refs | README | LICENSE

st.c (57331B)


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