st.c (58345B) - raw


      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 
   1291 void
   1292 tclearregion(int x1, int y1, int x2, int y2)
   1293 {
   1294 	int x, y, temp;
   1295 	Glyph *gp;
   1296 
   1297 	if (x1 > x2)
   1298 		temp = x1, x1 = x2, x2 = temp;
   1299 	if (y1 > y2)
   1300 		temp = y1, y1 = y2, y2 = temp;
   1301 
   1302 	LIMIT(x1, 0, term.col-1);
   1303 	LIMIT(x2, 0, term.col-1);
   1304 	LIMIT(y1, 0, term.row-1);
   1305 	LIMIT(y2, 0, term.row-1);
   1306 
   1307 	for (y = y1; y <= y2; y++) {
   1308 		term.dirty[y] = 1;
   1309 		for (x = x1; x <= x2; x++) {
   1310 			gp = &term.line[y][x];
   1311 			if (selected(x, y))
   1312 				selclear();
   1313 			gp->fg = term.c.attr.fg;
   1314 			gp->bg = term.c.attr.bg;
   1315 			gp->mode = 0;
   1316 			gp->u = ' ';
   1317 		}
   1318 	}
   1319 }
   1320 
   1321 void
   1322 tdeletechar(int n)
   1323 {
   1324 	int dst, src, size;
   1325 	Glyph *line;
   1326 
   1327 	LIMIT(n, 0, term.col - term.c.x);
   1328 
   1329 	dst = term.c.x;
   1330 	src = term.c.x + n;
   1331 	size = term.col - src;
   1332 	line = term.line[term.c.y];
   1333 
   1334 	memmove(&line[dst], &line[src], size * sizeof(Glyph));
   1335 	tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
   1336 }
   1337 
   1338 void
   1339 tinsertblank(int n)
   1340 {
   1341 	int dst, src, size;
   1342 	Glyph *line;
   1343 
   1344 	LIMIT(n, 0, term.col - term.c.x);
   1345 
   1346 	dst = term.c.x + n;
   1347 	src = term.c.x;
   1348 	size = term.col - dst;
   1349 	line = term.line[term.c.y];
   1350 
   1351 	memmove(&line[dst], &line[src], size * sizeof(Glyph));
   1352 	tclearregion(src, term.c.y, dst - 1, term.c.y);
   1353 }
   1354 
   1355 void
   1356 tinsertblankline(int n)
   1357 {
   1358 	if (BETWEEN(term.c.y, term.top, term.bot))
   1359 		tscrolldown(term.c.y, n, 0);
   1360 }
   1361 
   1362 void
   1363 tdeleteline(int n)
   1364 {
   1365 	if (BETWEEN(term.c.y, term.top, term.bot))
   1366 		tscrollup(term.c.y, n, 0);
   1367 }
   1368 
   1369 int32_t
   1370 tdefcolor(int *attr, int *npar, int l)
   1371 {
   1372 	int32_t idx = -1;
   1373 	uint r, g, b;
   1374 
   1375 	switch (attr[*npar + 1]) {
   1376 	case 2: /* direct color in RGB space */
   1377 		if (*npar + 4 >= l) {
   1378 			fprintf(stderr,
   1379 				"erresc(38): Incorrect number of parameters (%d)\n",
   1380 				*npar);
   1381 			break;
   1382 		}
   1383 		r = attr[*npar + 2];
   1384 		g = attr[*npar + 3];
   1385 		b = attr[*npar + 4];
   1386 		*npar += 4;
   1387 		if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255))
   1388 			fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n",
   1389 				r, g, b);
   1390 		else
   1391 			idx = TRUECOLOR(r, g, b);
   1392 		break;
   1393 	case 5: /* indexed color */
   1394 		if (*npar + 2 >= l) {
   1395 			fprintf(stderr,
   1396 				"erresc(38): Incorrect number of parameters (%d)\n",
   1397 				*npar);
   1398 			break;
   1399 		}
   1400 		*npar += 2;
   1401 		if (!BETWEEN(attr[*npar], 0, 255))
   1402 			fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]);
   1403 		else
   1404 			idx = attr[*npar];
   1405 		break;
   1406 	case 0: /* implemented defined (only foreground) */
   1407 	case 1: /* transparent */
   1408 	case 3: /* direct color in CMY space */
   1409 	case 4: /* direct color in CMYK space */
   1410 	default:
   1411 		fprintf(stderr,
   1412 		        "erresc(38): gfx attr %d unknown\n", attr[*npar]);
   1413 		break;
   1414 	}
   1415 
   1416 	return idx;
   1417 }
   1418 
   1419 void
   1420 tsetattr(int *attr, int l)
   1421 {
   1422 	int i;
   1423 	int32_t idx;
   1424 
   1425 	for (i = 0; i < l; i++) {
   1426 		switch (attr[i]) {
   1427 		case 0:
   1428 			term.c.attr.mode &= ~(
   1429 				ATTR_BOLD       |
   1430 				ATTR_FAINT      |
   1431 				ATTR_ITALIC     |
   1432 				ATTR_UNDERLINE  |
   1433 				ATTR_BLINK      |
   1434 				ATTR_REVERSE    |
   1435 				ATTR_INVISIBLE  |
   1436 				ATTR_STRUCK     );
   1437 			term.c.attr.fg = defaultfg;
   1438 			term.c.attr.bg = defaultbg;
   1439 			break;
   1440 		case 1:
   1441 			term.c.attr.mode |= ATTR_BOLD;
   1442 			break;
   1443 		case 2:
   1444 			term.c.attr.mode |= ATTR_FAINT;
   1445 			break;
   1446 		case 3:
   1447 			term.c.attr.mode |= ATTR_ITALIC;
   1448 			break;
   1449 		case 4:
   1450 			term.c.attr.mode |= ATTR_UNDERLINE;
   1451 			break;
   1452 		case 5: /* slow blink */
   1453 			/* FALLTHROUGH */
   1454 		case 6: /* rapid blink */
   1455 			term.c.attr.mode |= ATTR_BLINK;
   1456 			break;
   1457 		case 7:
   1458 			term.c.attr.mode |= ATTR_REVERSE;
   1459 			break;
   1460 		case 8:
   1461 			term.c.attr.mode |= ATTR_INVISIBLE;
   1462 			break;
   1463 		case 9:
   1464 			term.c.attr.mode |= ATTR_STRUCK;
   1465 			break;
   1466 		case 22:
   1467 			term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT);
   1468 			break;
   1469 		case 23:
   1470 			term.c.attr.mode &= ~ATTR_ITALIC;
   1471 			break;
   1472 		case 24:
   1473 			term.c.attr.mode &= ~ATTR_UNDERLINE;
   1474 			break;
   1475 		case 25:
   1476 			term.c.attr.mode &= ~ATTR_BLINK;
   1477 			break;
   1478 		case 27:
   1479 			term.c.attr.mode &= ~ATTR_REVERSE;
   1480 			break;
   1481 		case 28:
   1482 			term.c.attr.mode &= ~ATTR_INVISIBLE;
   1483 			break;
   1484 		case 29:
   1485 			term.c.attr.mode &= ~ATTR_STRUCK;
   1486 			break;
   1487 		case 38:
   1488 			if ((idx = tdefcolor(attr, &i, l)) >= 0)
   1489 				term.c.attr.fg = idx;
   1490 			break;
   1491 		case 39:
   1492 			term.c.attr.fg = defaultfg;
   1493 			break;
   1494 		case 48:
   1495 			if ((idx = tdefcolor(attr, &i, l)) >= 0)
   1496 				term.c.attr.bg = idx;
   1497 			break;
   1498 		case 49:
   1499 			term.c.attr.bg = defaultbg;
   1500 			break;
   1501 		default:
   1502 			if (BETWEEN(attr[i], 30, 37)) {
   1503 				term.c.attr.fg = attr[i] - 30;
   1504 			} else if (BETWEEN(attr[i], 40, 47)) {
   1505 				term.c.attr.bg = attr[i] - 40;
   1506 			} else if (BETWEEN(attr[i], 90, 97)) {
   1507 				term.c.attr.fg = attr[i] - 90 + 8;
   1508 			} else if (BETWEEN(attr[i], 100, 107)) {
   1509 				term.c.attr.bg = attr[i] - 100 + 8;
   1510 			} else {
   1511 				fprintf(stderr,
   1512 					"erresc(default): gfx attr %d unknown\n",
   1513 					attr[i]);
   1514 				csidump();
   1515 			}
   1516 			break;
   1517 		}
   1518 	}
   1519 }
   1520 
   1521 void
   1522 tsetscroll(int t, int b)
   1523 {
   1524 	int temp;
   1525 
   1526 	LIMIT(t, 0, term.row-1);
   1527 	LIMIT(b, 0, term.row-1);
   1528 	if (t > b) {
   1529 		temp = t;
   1530 		t = b;
   1531 		b = temp;
   1532 	}
   1533 	term.top = t;
   1534 	term.bot = b;
   1535 }
   1536 
   1537 void
   1538 tsetmode(int priv, int set, int *args, int narg)
   1539 {
   1540 	int alt, *lim;
   1541 
   1542 	for (lim = args + narg; args < lim; ++args) {
   1543 		if (priv) {
   1544 			switch (*args) {
   1545 			case 1: /* DECCKM -- Cursor key */
   1546 				xsetmode(set, MODE_APPCURSOR);
   1547 				break;
   1548 			case 5: /* DECSCNM -- Reverse video */
   1549 				xsetmode(set, MODE_REVERSE);
   1550 				break;
   1551 			case 6: /* DECOM -- Origin */
   1552 				MODBIT(term.c.state, set, CURSOR_ORIGIN);
   1553 				tmoveato(0, 0);
   1554 				break;
   1555 			case 7: /* DECAWM -- Auto wrap */
   1556 				MODBIT(term.mode, set, MODE_WRAP);
   1557 				break;
   1558 			case 0:  /* Error (IGNORED) */
   1559 			case 2:  /* DECANM -- ANSI/VT52 (IGNORED) */
   1560 			case 3:  /* DECCOLM -- Column  (IGNORED) */
   1561 			case 4:  /* DECSCLM -- Scroll (IGNORED) */
   1562 			case 8:  /* DECARM -- Auto repeat (IGNORED) */
   1563 			case 18: /* DECPFF -- Printer feed (IGNORED) */
   1564 			case 19: /* DECPEX -- Printer extent (IGNORED) */
   1565 			case 42: /* DECNRCM -- National characters (IGNORED) */
   1566 			case 12: /* att610 -- Start blinking cursor (IGNORED) */
   1567 				break;
   1568 			case 25: /* DECTCEM -- Text Cursor Enable Mode */
   1569 				xsetmode(!set, MODE_HIDE);
   1570 				break;
   1571 			case 9:    /* X10 mouse compatibility mode */
   1572 				xsetpointermotion(0);
   1573 				xsetmode(0, MODE_MOUSE);
   1574 				xsetmode(set, MODE_MOUSEX10);
   1575 				break;
   1576 			case 1000: /* 1000: report button press */
   1577 				xsetpointermotion(0);
   1578 				xsetmode(0, MODE_MOUSE);
   1579 				xsetmode(set, MODE_MOUSEBTN);
   1580 				break;
   1581 			case 1002: /* 1002: report motion on button press */
   1582 				xsetpointermotion(0);
   1583 				xsetmode(0, MODE_MOUSE);
   1584 				xsetmode(set, MODE_MOUSEMOTION);
   1585 				break;
   1586 			case 1003: /* 1003: enable all mouse motions */
   1587 				xsetpointermotion(set);
   1588 				xsetmode(0, MODE_MOUSE);
   1589 				xsetmode(set, MODE_MOUSEMANY);
   1590 				break;
   1591 			case 1004: /* 1004: send focus events to tty */
   1592 				xsetmode(set, MODE_FOCUS);
   1593 				break;
   1594 			case 1006: /* 1006: extended reporting mode */
   1595 				xsetmode(set, MODE_MOUSESGR);
   1596 				break;
   1597 			case 1034:
   1598 				xsetmode(set, MODE_8BIT);
   1599 				break;
   1600 			case 1049: /* swap screen & set/restore cursor as xterm */
   1601 				if (!allowaltscreen)
   1602 					break;
   1603 				tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
   1604 				/* FALLTHROUGH */
   1605 			case 47: /* swap screen */
   1606 			case 1047:
   1607 				if (!allowaltscreen)
   1608 					break;
   1609 				alt = IS_SET(MODE_ALTSCREEN);
   1610 				if (alt) {
   1611 					tclearregion(0, 0, term.col-1,
   1612 							term.row-1);
   1613 				}
   1614 				if (set ^ alt) /* set is always 1 or 0 */
   1615 					tswapscreen();
   1616 				if (*args != 1049)
   1617 					break;
   1618 				/* FALLTHROUGH */
   1619 			case 1048:
   1620 				tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
   1621 				break;
   1622 			case 2004: /* 2004: bracketed paste mode */
   1623 				xsetmode(set, MODE_BRCKTPASTE);
   1624 				break;
   1625 			/* Not implemented mouse modes. See comments there. */
   1626 			case 1001: /* mouse highlight mode; can hang the
   1627 				      terminal by design when implemented. */
   1628 			case 1005: /* UTF-8 mouse mode; will confuse
   1629 				      applications not supporting UTF-8
   1630 				      and luit. */
   1631 			case 1015: /* urxvt mangled mouse mode; incompatible
   1632 				      and can be mistaken for other control
   1633 				      codes. */
   1634 				break;
   1635 			default:
   1636 				fprintf(stderr,
   1637 					"erresc: unknown private set/reset mode %d\n",
   1638 					*args);
   1639 				break;
   1640 			}
   1641 		} else {
   1642 			switch (*args) {
   1643 			case 0:  /* Error (IGNORED) */
   1644 				break;
   1645 			case 2:
   1646 				xsetmode(set, MODE_KBDLOCK);
   1647 				break;
   1648 			case 4:  /* IRM -- Insertion-replacement */
   1649 				MODBIT(term.mode, set, MODE_INSERT);
   1650 				break;
   1651 			case 12: /* SRM -- Send/Receive */
   1652 				MODBIT(term.mode, !set, MODE_ECHO);
   1653 				break;
   1654 			case 20: /* LNM -- Linefeed/new line */
   1655 				MODBIT(term.mode, set, MODE_CRLF);
   1656 				break;
   1657 			default:
   1658 				fprintf(stderr,
   1659 					"erresc: unknown set/reset mode %d\n",
   1660 					*args);
   1661 				break;
   1662 			}
   1663 		}
   1664 	}
   1665 }
   1666 
   1667 void
   1668 csihandle(void)
   1669 {
   1670 	char buf[40];
   1671 	int len;
   1672 
   1673 	switch (csiescseq.mode[0]) {
   1674 	default:
   1675 	unknown:
   1676 		fprintf(stderr, "erresc: unknown csi ");
   1677 		csidump();
   1678 		/* die(""); */
   1679 		break;
   1680 	case '@': /* ICH -- Insert <n> blank char */
   1681 		DEFAULT(csiescseq.arg[0], 1);
   1682 		tinsertblank(csiescseq.arg[0]);
   1683 		break;
   1684 	case 'A': /* CUU -- Cursor <n> Up */
   1685 		DEFAULT(csiescseq.arg[0], 1);
   1686 		tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
   1687 		break;
   1688 	case 'B': /* CUD -- Cursor <n> Down */
   1689 	case 'e': /* VPR --Cursor <n> Down */
   1690 		DEFAULT(csiescseq.arg[0], 1);
   1691 		tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
   1692 		break;
   1693 	case 'i': /* MC -- Media Copy */
   1694 		switch (csiescseq.arg[0]) {
   1695 		case 0:
   1696 			tdump();
   1697 			break;
   1698 		case 1:
   1699 			tdumpline(term.c.y);
   1700 			break;
   1701 		case 2:
   1702 			tdumpsel();
   1703 			break;
   1704 		case 4:
   1705 			term.mode &= ~MODE_PRINT;
   1706 			break;
   1707 		case 5:
   1708 			term.mode |= MODE_PRINT;
   1709 			break;
   1710 		}
   1711 		break;
   1712 	case 'c': /* DA -- Device Attributes */
   1713 		if (csiescseq.arg[0] == 0)
   1714 			ttywrite(vtiden, strlen(vtiden), 0);
   1715 		break;
   1716 	case 'b': /* REP -- if last char is printable print it <n> more times */
   1717 		DEFAULT(csiescseq.arg[0], 1);
   1718 		if (term.lastc)
   1719 			while (csiescseq.arg[0]-- > 0)
   1720 				tputc(term.lastc);
   1721 		break;
   1722 	case 'C': /* CUF -- Cursor <n> Forward */
   1723 	case 'a': /* HPR -- Cursor <n> Forward */
   1724 		DEFAULT(csiescseq.arg[0], 1);
   1725 		tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
   1726 		break;
   1727 	case 'D': /* CUB -- Cursor <n> Backward */
   1728 		DEFAULT(csiescseq.arg[0], 1);
   1729 		tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
   1730 		break;
   1731 	case 'E': /* CNL -- Cursor <n> Down and first col */
   1732 		DEFAULT(csiescseq.arg[0], 1);
   1733 		tmoveto(0, term.c.y+csiescseq.arg[0]);
   1734 		break;
   1735 	case 'F': /* CPL -- Cursor <n> Up and first col */
   1736 		DEFAULT(csiescseq.arg[0], 1);
   1737 		tmoveto(0, term.c.y-csiescseq.arg[0]);
   1738 		break;
   1739 	case 'g': /* TBC -- Tabulation clear */
   1740 		switch (csiescseq.arg[0]) {
   1741 		case 0: /* clear current tab stop */
   1742 			term.tabs[term.c.x] = 0;
   1743 			break;
   1744 		case 3: /* clear all the tabs */
   1745 			memset(term.tabs, 0, term.col * sizeof(*term.tabs));
   1746 			break;
   1747 		default:
   1748 			goto unknown;
   1749 		}
   1750 		break;
   1751 	case 'G': /* CHA -- Move to <col> */
   1752 	case '`': /* HPA */
   1753 		DEFAULT(csiescseq.arg[0], 1);
   1754 		tmoveto(csiescseq.arg[0]-1, term.c.y);
   1755 		break;
   1756 	case 'H': /* CUP -- Move to <row> <col> */
   1757 	case 'f': /* HVP */
   1758 		DEFAULT(csiescseq.arg[0], 1);
   1759 		DEFAULT(csiescseq.arg[1], 1);
   1760 		tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
   1761 		break;
   1762 	case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
   1763 		DEFAULT(csiescseq.arg[0], 1);
   1764 		tputtab(csiescseq.arg[0]);
   1765 		break;
   1766 	case 'J': /* ED -- Clear screen */
   1767 		switch (csiescseq.arg[0]) {
   1768 		case 0: /* below */
   1769 			tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
   1770 			if (term.c.y < term.row-1) {
   1771 				tclearregion(0, term.c.y+1, term.col-1,
   1772 						term.row-1);
   1773 			}
   1774 			break;
   1775 		case 1: /* above */
   1776 			if (term.c.y > 1)
   1777 				tclearregion(0, 0, term.col-1, term.c.y-1);
   1778 			tclearregion(0, term.c.y, term.c.x, term.c.y);
   1779 			break;
   1780 		case 2: /* all */
   1781 			tclearregion(0, 0, term.col-1, term.row-1);
   1782 			break;
   1783 		default:
   1784 			goto unknown;
   1785 		}
   1786 		break;
   1787 	case 'K': /* EL -- Clear line */
   1788 		switch (csiescseq.arg[0]) {
   1789 		case 0: /* right */
   1790 			tclearregion(term.c.x, term.c.y, term.col-1,
   1791 					term.c.y);
   1792 			break;
   1793 		case 1: /* left */
   1794 			tclearregion(0, term.c.y, term.c.x, term.c.y);
   1795 			break;
   1796 		case 2: /* all */
   1797 			tclearregion(0, term.c.y, term.col-1, term.c.y);
   1798 			break;
   1799 		}
   1800 		break;
   1801 	case 'S': /* SU -- Scroll <n> line up */
   1802 		DEFAULT(csiescseq.arg[0], 1);
   1803 		tscrollup(term.top, csiescseq.arg[0], 0);
   1804 		break;
   1805 	case 'T': /* SD -- Scroll <n> line down */
   1806 		DEFAULT(csiescseq.arg[0], 1);
   1807 		tscrolldown(term.top, csiescseq.arg[0], 0);
   1808 		break;
   1809 	case 'L': /* IL -- Insert <n> blank lines */
   1810 		DEFAULT(csiescseq.arg[0], 1);
   1811 		tinsertblankline(csiescseq.arg[0]);
   1812 		break;
   1813 	case 'l': /* RM -- Reset Mode */
   1814 		tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
   1815 		break;
   1816 	case 'M': /* DL -- Delete <n> lines */
   1817 		DEFAULT(csiescseq.arg[0], 1);
   1818 		tdeleteline(csiescseq.arg[0]);
   1819 		break;
   1820 	case 'X': /* ECH -- Erase <n> char */
   1821 		DEFAULT(csiescseq.arg[0], 1);
   1822 		tclearregion(term.c.x, term.c.y,
   1823 				term.c.x + csiescseq.arg[0] - 1, term.c.y);
   1824 		break;
   1825 	case 'P': /* DCH -- Delete <n> char */
   1826 		DEFAULT(csiescseq.arg[0], 1);
   1827 		tdeletechar(csiescseq.arg[0]);
   1828 		break;
   1829 	case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
   1830 		DEFAULT(csiescseq.arg[0], 1);
   1831 		tputtab(-csiescseq.arg[0]);
   1832 		break;
   1833 	case 'd': /* VPA -- Move to <row> */
   1834 		DEFAULT(csiescseq.arg[0], 1);
   1835 		tmoveato(term.c.x, csiescseq.arg[0]-1);
   1836 		break;
   1837 	case 'h': /* SM -- Set terminal mode */
   1838 		tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
   1839 		break;
   1840 	case 'm': /* SGR -- Terminal attribute (color) */
   1841 		tsetattr(csiescseq.arg, csiescseq.narg);
   1842 		break;
   1843 	case 'n': /* DSR – Device Status Report (cursor position) */
   1844 		if (csiescseq.arg[0] == 6) {
   1845 			len = snprintf(buf, sizeof(buf), "\033[%i;%iR",
   1846 					term.c.y+1, term.c.x+1);
   1847 			ttywrite(buf, len, 0);
   1848 		}
   1849 		break;
   1850 	case 'r': /* DECSTBM -- Set Scrolling Region */
   1851 		if (csiescseq.priv) {
   1852 			goto unknown;
   1853 		} else {
   1854 			DEFAULT(csiescseq.arg[0], 1);
   1855 			DEFAULT(csiescseq.arg[1], term.row);
   1856 			tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
   1857 			tmoveato(0, 0);
   1858 		}
   1859 		break;
   1860 	case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
   1861 		tcursor(CURSOR_SAVE);
   1862 		break;
   1863 	case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
   1864 		tcursor(CURSOR_LOAD);
   1865 		break;
   1866 	case ' ':
   1867 		switch (csiescseq.mode[1]) {
   1868 		case 'q': /* DECSCUSR -- Set Cursor Style */
   1869 			if (xsetcursor(csiescseq.arg[0]))
   1870 				goto unknown;
   1871 			break;
   1872 		default:
   1873 			goto unknown;
   1874 		}
   1875 		break;
   1876 	}
   1877 }
   1878 
   1879 void
   1880 csidump(void)
   1881 {
   1882 	size_t i;
   1883 	uint c;
   1884 
   1885 	fprintf(stderr, "ESC[");
   1886 	for (i = 0; i < csiescseq.len; i++) {
   1887 		c = csiescseq.buf[i] & 0xff;
   1888 		if (isprint(c)) {
   1889 			putc(c, stderr);
   1890 		} else if (c == '\n') {
   1891 			fprintf(stderr, "(\\n)");
   1892 		} else if (c == '\r') {
   1893 			fprintf(stderr, "(\\r)");
   1894 		} else if (c == 0x1b) {
   1895 			fprintf(stderr, "(\\e)");
   1896 		} else {
   1897 			fprintf(stderr, "(%02x)", c);
   1898 		}
   1899 	}
   1900 	putc('\n', stderr);
   1901 }
   1902 
   1903 void
   1904 csireset(void)
   1905 {
   1906 	memset(&csiescseq, 0, sizeof(csiescseq));
   1907 }
   1908 
   1909 void
   1910 strhandle(void)
   1911 {
   1912 	char *p = NULL, *dec;
   1913 	int j, narg, par;
   1914 
   1915 	term.esc &= ~(ESC_STR_END|ESC_STR);
   1916 	strparse();
   1917 	par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
   1918 
   1919 	switch (strescseq.type) {
   1920 	case ']': /* OSC -- Operating System Command */
   1921 		switch (par) {
   1922 		case 0:
   1923 		case 1:
   1924 		case 2:
   1925 			if (narg > 1)
   1926 				xsettitle(strescseq.args[1]);
   1927 			return;
   1928 		case 52:
   1929 			if (narg > 2 && allowwindowops) {
   1930 				dec = base64dec(strescseq.args[2]);
   1931 				if (dec) {
   1932 					xsetsel(dec);
   1933 					xclipcopy();
   1934 				} else {
   1935 					fprintf(stderr, "erresc: invalid base64\n");
   1936 				}
   1937 			}
   1938 			return;
   1939 		case 4: /* color set */
   1940 			if (narg < 3)
   1941 				break;
   1942 			p = strescseq.args[2];
   1943 			/* FALLTHROUGH */
   1944 		case 104: /* color reset, here p = NULL */
   1945 			j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
   1946 			if (xsetcolorname(j, p)) {
   1947 				if (par == 104 && narg <= 1)
   1948 					return; /* color reset without parameter */
   1949 				fprintf(stderr, "erresc: invalid color j=%d, p=%s\n",
   1950 				        j, p ? p : "(null)");
   1951 			} else {
   1952 				/*
   1953 				 * TODO if defaultbg color is changed, borders
   1954 				 * are dirty
   1955 				 */
   1956 				redraw();
   1957 			}
   1958 			return;
   1959 		}
   1960 		break;
   1961 	case 'k': /* old title set compatibility */
   1962 		xsettitle(strescseq.args[0]);
   1963 		return;
   1964 	case 'P': /* DCS -- Device Control String */
   1965 	case '_': /* APC -- Application Program Command */
   1966 	case '^': /* PM -- Privacy Message */
   1967 		return;
   1968 	}
   1969 
   1970 	fprintf(stderr, "erresc: unknown str ");
   1971 	strdump();
   1972 }
   1973 
   1974 void
   1975 strparse(void)
   1976 {
   1977 	int c;
   1978 	char *p = strescseq.buf;
   1979 
   1980 	strescseq.narg = 0;
   1981 	strescseq.buf[strescseq.len] = '\0';
   1982 
   1983 	if (*p == '\0')
   1984 		return;
   1985 
   1986 	while (strescseq.narg < STR_ARG_SIZ) {
   1987 		strescseq.args[strescseq.narg++] = p;
   1988 		while ((c = *p) != ';' && c != '\0')
   1989 			++p;
   1990 		if (c == '\0')
   1991 			return;
   1992 		*p++ = '\0';
   1993 	}
   1994 }
   1995 
   1996 void
   1997 externalpipe(const Arg *arg)
   1998 {
   1999 	int to[2];
   2000 	char buf[UTF_SIZ];
   2001 	void (*oldsigpipe)(int);
   2002 	Glyph *bp, *end;
   2003 	int lastpos, n, newline;
   2004 
   2005 	if (pipe(to) == -1)
   2006 		return;
   2007 
   2008 	switch (fork()) {
   2009 	case -1:
   2010 		close(to[0]);
   2011 		close(to[1]);
   2012 		return;
   2013 	case 0:
   2014 		dup2(to[0], STDIN_FILENO);
   2015 		close(to[0]);
   2016 		close(to[1]);
   2017 		execvp(((char **)arg->v)[0], (char **)arg->v);
   2018 		fprintf(stderr, "st: execvp %s\n", ((char **)arg->v)[0]);
   2019 		perror("failed");
   2020 		exit(0);
   2021 	}
   2022 
   2023 	close(to[0]);
   2024 	/* ignore sigpipe for now, in case child exists early */
   2025 	oldsigpipe = signal(SIGPIPE, SIG_IGN);
   2026 	newline = 0;
   2027 	for (n = 0; n < term.row; n++) {
   2028 		bp = term.line[n];
   2029 		lastpos = MIN(tlinelen(n) + 1, term.col) - 1;
   2030 		if (lastpos < 0)
   2031 			break;
   2032 		end = &bp[lastpos + 1];
   2033 		for (; bp < end; ++bp)
   2034 			if (xwrite(to[1], buf, utf8encode(bp->u, buf)) < 0)
   2035 				break;
   2036 		if ((newline = term.line[n][lastpos].mode & ATTR_WRAP))
   2037 			continue;
   2038 		if (xwrite(to[1], "\n", 1) < 0)
   2039 			break;
   2040 		newline = 0;
   2041 	}
   2042 	if (newline)
   2043 		(void)xwrite(to[1], "\n", 1);
   2044 	close(to[1]);
   2045 	/* restore */
   2046 	signal(SIGPIPE, oldsigpipe);
   2047 }
   2048 
   2049 void
   2050 strdump(void)
   2051 {
   2052 	size_t i;
   2053 	uint c;
   2054 
   2055 	fprintf(stderr, "ESC%c", strescseq.type);
   2056 	for (i = 0; i < strescseq.len; i++) {
   2057 		c = strescseq.buf[i] & 0xff;
   2058 		if (c == '\0') {
   2059 			putc('\n', stderr);
   2060 			return;
   2061 		} else if (isprint(c)) {
   2062 			putc(c, stderr);
   2063 		} else if (c == '\n') {
   2064 			fprintf(stderr, "(\\n)");
   2065 		} else if (c == '\r') {
   2066 			fprintf(stderr, "(\\r)");
   2067 		} else if (c == 0x1b) {
   2068 			fprintf(stderr, "(\\e)");
   2069 		} else {
   2070 			fprintf(stderr, "(%02x)", c);
   2071 		}
   2072 	}
   2073 	fprintf(stderr, "ESC\\\n");
   2074 }
   2075 
   2076 void
   2077 strreset(void)
   2078 {
   2079 	strescseq = (STREscape){
   2080 		.buf = xrealloc(strescseq.buf, STR_BUF_SIZ),
   2081 		.siz = STR_BUF_SIZ,
   2082 	};
   2083 }
   2084 
   2085 void
   2086 sendbreak(const Arg *arg)
   2087 {
   2088 	if (tcsendbreak(cmdfd, 0))
   2089 		perror("Error sending break");
   2090 }
   2091 
   2092 void
   2093 tprinter(char *s, size_t len)
   2094 {
   2095 	if (iofd != -1 && xwrite(iofd, s, len) < 0) {
   2096 		perror("Error writing to output file");
   2097 		close(iofd);
   2098 		iofd = -1;
   2099 	}
   2100 }
   2101 
   2102 void
   2103 toggleprinter(const Arg *arg)
   2104 {
   2105 	term.mode ^= MODE_PRINT;
   2106 }
   2107 
   2108 void
   2109 printscreen(const Arg *arg)
   2110 {
   2111 	tdump();
   2112 }
   2113 
   2114 void
   2115 printsel(const Arg *arg)
   2116 {
   2117 	tdumpsel();
   2118 }
   2119 
   2120 void
   2121 tdumpsel(void)
   2122 {
   2123 	char *ptr;
   2124 
   2125 	if ((ptr = getsel())) {
   2126 		tprinter(ptr, strlen(ptr));
   2127 		free(ptr);
   2128 	}
   2129 }
   2130 
   2131 void
   2132 tdumpline(int n)
   2133 {
   2134 	char buf[UTF_SIZ];
   2135 	Glyph *bp, *end;
   2136 
   2137 	bp = &term.line[n][0];
   2138 	end = &bp[MIN(tlinelen(n), term.col) - 1];
   2139 	if (bp != end || bp->u != ' ') {
   2140 		for ( ; bp <= end; ++bp)
   2141 			tprinter(buf, utf8encode(bp->u, buf));
   2142 	}
   2143 	tprinter("\n", 1);
   2144 }
   2145 
   2146 void
   2147 tdump(void)
   2148 {
   2149 	int i;
   2150 
   2151 	for (i = 0; i < term.row; ++i)
   2152 		tdumpline(i);
   2153 }
   2154 
   2155 void
   2156 tputtab(int n)
   2157 {
   2158 	uint x = term.c.x;
   2159 
   2160 	if (n > 0) {
   2161 		while (x < term.col && n--)
   2162 			for (++x; x < term.col && !term.tabs[x]; ++x)
   2163 				/* nothing */ ;
   2164 	} else if (n < 0) {
   2165 		while (x > 0 && n++)
   2166 			for (--x; x > 0 && !term.tabs[x]; --x)
   2167 				/* nothing */ ;
   2168 	}
   2169 	term.c.x = LIMIT(x, 0, term.col-1);
   2170 }
   2171 
   2172 void
   2173 tdefutf8(char ascii)
   2174 {
   2175 	if (ascii == 'G')
   2176 		term.mode |= MODE_UTF8;
   2177 	else if (ascii == '@')
   2178 		term.mode &= ~MODE_UTF8;
   2179 }
   2180 
   2181 void
   2182 tdeftran(char ascii)
   2183 {
   2184 	static char cs[] = "0B";
   2185 	static int vcs[] = {CS_GRAPHIC0, CS_USA};
   2186 	char *p;
   2187 
   2188 	if ((p = strchr(cs, ascii)) == NULL) {
   2189 		fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
   2190 	} else {
   2191 		term.trantbl[term.icharset] = vcs[p - cs];
   2192 	}
   2193 }
   2194 
   2195 void
   2196 tdectest(char c)
   2197 {
   2198 	int x, y;
   2199 
   2200 	if (c == '8') { /* DEC screen alignment test. */
   2201 		for (x = 0; x < term.col; ++x) {
   2202 			for (y = 0; y < term.row; ++y)
   2203 				tsetchar('E', &term.c.attr, x, y);
   2204 		}
   2205 	}
   2206 }
   2207 
   2208 void
   2209 tstrsequence(uchar c)
   2210 {
   2211 	switch (c) {
   2212 	case 0x90:   /* DCS -- Device Control String */
   2213 		c = 'P';
   2214 		break;
   2215 	case 0x9f:   /* APC -- Application Program Command */
   2216 		c = '_';
   2217 		break;
   2218 	case 0x9e:   /* PM -- Privacy Message */
   2219 		c = '^';
   2220 		break;
   2221 	case 0x9d:   /* OSC -- Operating System Command */
   2222 		c = ']';
   2223 		break;
   2224 	}
   2225 	strreset();
   2226 	strescseq.type = c;
   2227 	term.esc |= ESC_STR;
   2228 }
   2229 
   2230 void
   2231 tcontrolcode(uchar ascii)
   2232 {
   2233 	switch (ascii) {
   2234 	case '\t':   /* HT */
   2235 		tputtab(1);
   2236 		return;
   2237 	case '\b':   /* BS */
   2238 		tmoveto(term.c.x-1, term.c.y);
   2239 		return;
   2240 	case '\r':   /* CR */
   2241 		tmoveto(0, term.c.y);
   2242 		return;
   2243 	case '\f':   /* LF */
   2244 	case '\v':   /* VT */
   2245 	case '\n':   /* LF */
   2246 		/* go to first col if the mode is set */
   2247 		tnewline(IS_SET(MODE_CRLF));
   2248 		return;
   2249 	case '\a':   /* BEL */
   2250 		if (term.esc & ESC_STR_END) {
   2251 			/* backwards compatibility to xterm */
   2252 			strhandle();
   2253 		} else {
   2254 			xbell();
   2255 		}
   2256 		break;
   2257 	case '\033': /* ESC */
   2258 		csireset();
   2259 		term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
   2260 		term.esc |= ESC_START;
   2261 		return;
   2262 	case '\016': /* SO (LS1 -- Locking shift 1) */
   2263 	case '\017': /* SI (LS0 -- Locking shift 0) */
   2264 		term.charset = 1 - (ascii - '\016');
   2265 		return;
   2266 	case '\032': /* SUB */
   2267 		tsetchar('?', &term.c.attr, term.c.x, term.c.y);
   2268 		/* FALLTHROUGH */
   2269 	case '\030': /* CAN */
   2270 		csireset();
   2271 		break;
   2272 	case '\005': /* ENQ (IGNORED) */
   2273 	case '\000': /* NUL (IGNORED) */
   2274 	case '\021': /* XON (IGNORED) */
   2275 	case '\023': /* XOFF (IGNORED) */
   2276 	case 0177:   /* DEL (IGNORED) */
   2277 		return;
   2278 	case 0x80:   /* TODO: PAD */
   2279 	case 0x81:   /* TODO: HOP */
   2280 	case 0x82:   /* TODO: BPH */
   2281 	case 0x83:   /* TODO: NBH */
   2282 	case 0x84:   /* TODO: IND */
   2283 		break;
   2284 	case 0x85:   /* NEL -- Next line */
   2285 		tnewline(1); /* always go to first col */
   2286 		break;
   2287 	case 0x86:   /* TODO: SSA */
   2288 	case 0x87:   /* TODO: ESA */
   2289 		break;
   2290 	case 0x88:   /* HTS -- Horizontal tab stop */
   2291 		term.tabs[term.c.x] = 1;
   2292 		break;
   2293 	case 0x89:   /* TODO: HTJ */
   2294 	case 0x8a:   /* TODO: VTS */
   2295 	case 0x8b:   /* TODO: PLD */
   2296 	case 0x8c:   /* TODO: PLU */
   2297 	case 0x8d:   /* TODO: RI */
   2298 	case 0x8e:   /* TODO: SS2 */
   2299 	case 0x8f:   /* TODO: SS3 */
   2300 	case 0x91:   /* TODO: PU1 */
   2301 	case 0x92:   /* TODO: PU2 */
   2302 	case 0x93:   /* TODO: STS */
   2303 	case 0x94:   /* TODO: CCH */
   2304 	case 0x95:   /* TODO: MW */
   2305 	case 0x96:   /* TODO: SPA */
   2306 	case 0x97:   /* TODO: EPA */
   2307 	case 0x98:   /* TODO: SOS */
   2308 	case 0x99:   /* TODO: SGCI */
   2309 		break;
   2310 	case 0x9a:   /* DECID -- Identify Terminal */
   2311 		ttywrite(vtiden, strlen(vtiden), 0);
   2312 		break;
   2313 	case 0x9b:   /* TODO: CSI */
   2314 	case 0x9c:   /* TODO: ST */
   2315 		break;
   2316 	case 0x90:   /* DCS -- Device Control String */
   2317 	case 0x9d:   /* OSC -- Operating System Command */
   2318 	case 0x9e:   /* PM -- Privacy Message */
   2319 	case 0x9f:   /* APC -- Application Program Command */
   2320 		tstrsequence(ascii);
   2321 		return;
   2322 	}
   2323 	/* only CAN, SUB, \a and C1 chars interrupt a sequence */
   2324 	term.esc &= ~(ESC_STR_END|ESC_STR);
   2325 }
   2326 
   2327 /*
   2328  * returns 1 when the sequence is finished and it hasn't to read
   2329  * more characters for this sequence, otherwise 0
   2330  */
   2331 int
   2332 eschandle(uchar ascii)
   2333 {
   2334 	switch (ascii) {
   2335 	case '[':
   2336 		term.esc |= ESC_CSI;
   2337 		return 0;
   2338 	case '#':
   2339 		term.esc |= ESC_TEST;
   2340 		return 0;
   2341 	case '%':
   2342 		term.esc |= ESC_UTF8;
   2343 		return 0;
   2344 	case 'P': /* DCS -- Device Control String */
   2345 	case '_': /* APC -- Application Program Command */
   2346 	case '^': /* PM -- Privacy Message */
   2347 	case ']': /* OSC -- Operating System Command */
   2348 	case 'k': /* old title set compatibility */
   2349 		tstrsequence(ascii);
   2350 		return 0;
   2351 	case 'n': /* LS2 -- Locking shift 2 */
   2352 	case 'o': /* LS3 -- Locking shift 3 */
   2353 		term.charset = 2 + (ascii - 'n');
   2354 		break;
   2355 	case '(': /* GZD4 -- set primary charset G0 */
   2356 	case ')': /* G1D4 -- set secondary charset G1 */
   2357 	case '*': /* G2D4 -- set tertiary charset G2 */
   2358 	case '+': /* G3D4 -- set quaternary charset G3 */
   2359 		term.icharset = ascii - '(';
   2360 		term.esc |= ESC_ALTCHARSET;
   2361 		return 0;
   2362 	case 'D': /* IND -- Linefeed */
   2363 		if (term.c.y == term.bot) {
   2364 			tscrollup(term.top, 1, 1);
   2365 		} else {
   2366 			tmoveto(term.c.x, term.c.y+1);
   2367 		}
   2368 		break;
   2369 	case 'E': /* NEL -- Next line */
   2370 		tnewline(1); /* always go to first col */
   2371 		break;
   2372 	case 'H': /* HTS -- Horizontal tab stop */
   2373 		term.tabs[term.c.x] = 1;
   2374 		break;
   2375 	case 'M': /* RI -- Reverse index */
   2376 		if (term.c.y == term.top) {
   2377 			tscrolldown(term.top, 1, 1);
   2378 		} else {
   2379 			tmoveto(term.c.x, term.c.y-1);
   2380 		}
   2381 		break;
   2382 	case 'Z': /* DECID -- Identify Terminal */
   2383 		ttywrite(vtiden, strlen(vtiden), 0);
   2384 		break;
   2385 	case 'c': /* RIS -- Reset to initial state */
   2386 		treset();
   2387 		resettitle();
   2388 		xloadcols();
   2389 		break;
   2390 	case '=': /* DECPAM -- Application keypad */
   2391 		xsetmode(1, MODE_APPKEYPAD);
   2392 		break;
   2393 	case '>': /* DECPNM -- Normal keypad */
   2394 		xsetmode(0, MODE_APPKEYPAD);
   2395 		break;
   2396 	case '7': /* DECSC -- Save Cursor */
   2397 		tcursor(CURSOR_SAVE);
   2398 		break;
   2399 	case '8': /* DECRC -- Restore Cursor */
   2400 		tcursor(CURSOR_LOAD);
   2401 		break;
   2402 	case '\\': /* ST -- String Terminator */
   2403 		if (term.esc & ESC_STR_END)
   2404 			strhandle();
   2405 		break;
   2406 	default:
   2407 		fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
   2408 			(uchar) ascii, isprint(ascii)? ascii:'.');
   2409 		break;
   2410 	}
   2411 	return 1;
   2412 }
   2413 
   2414 void
   2415 tputc(Rune u)
   2416 {
   2417 	char c[UTF_SIZ];
   2418 	int control;
   2419 	int width, len;
   2420 	Glyph *gp;
   2421 
   2422 	control = ISCONTROL(u);
   2423 	if (u < 127 || !IS_SET(MODE_UTF8)) {
   2424 		c[0] = u;
   2425 		width = len = 1;
   2426 	} else {
   2427 		len = utf8encode(u, c);
   2428 		if (!control && (width = wcwidth(u)) == -1)
   2429 			width = 1;
   2430 	}
   2431 
   2432 	if (IS_SET(MODE_PRINT))
   2433 		tprinter(c, len);
   2434 
   2435 	/*
   2436 	 * STR sequence must be checked before anything else
   2437 	 * because it uses all following characters until it
   2438 	 * receives a ESC, a SUB, a ST or any other C1 control
   2439 	 * character.
   2440 	 */
   2441 	if (term.esc & ESC_STR) {
   2442 		if (u == '\a' || u == 030 || u == 032 || u == 033 ||
   2443 		   ISCONTROLC1(u)) {
   2444 			term.esc &= ~(ESC_START|ESC_STR);
   2445 			term.esc |= ESC_STR_END;
   2446 			goto check_control_code;
   2447 		}
   2448 
   2449 		if (strescseq.len+len >= strescseq.siz) {
   2450 			/*
   2451 			 * Here is a bug in terminals. If the user never sends
   2452 			 * some code to stop the str or esc command, then st
   2453 			 * will stop responding. But this is better than
   2454 			 * silently failing with unknown characters. At least
   2455 			 * then users will report back.
   2456 			 *
   2457 			 * In the case users ever get fixed, here is the code:
   2458 			 */
   2459 			/*
   2460 			 * term.esc = 0;
   2461 			 * strhandle();
   2462 			 */
   2463 			if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2)
   2464 				return;
   2465 			strescseq.siz *= 2;
   2466 			strescseq.buf = xrealloc(strescseq.buf, strescseq.siz);
   2467 		}
   2468 
   2469 		memmove(&strescseq.buf[strescseq.len], c, len);
   2470 		strescseq.len += len;
   2471 		return;
   2472 	}
   2473 
   2474 check_control_code:
   2475 	/*
   2476 	 * Actions of control codes must be performed as soon they arrive
   2477 	 * because they can be embedded inside a control sequence, and
   2478 	 * they must not cause conflicts with sequences.
   2479 	 */
   2480 	if (control) {
   2481 		tcontrolcode(u);
   2482 		/*
   2483 		 * control codes are not shown ever
   2484 		 */
   2485 		if (!term.esc)
   2486 			term.lastc = 0;
   2487 		return;
   2488 	} else if (term.esc & ESC_START) {
   2489 		if (term.esc & ESC_CSI) {
   2490 			csiescseq.buf[csiescseq.len++] = u;
   2491 			if (BETWEEN(u, 0x40, 0x7E)
   2492 					|| csiescseq.len >= \
   2493 					sizeof(csiescseq.buf)-1) {
   2494 				term.esc = 0;
   2495 				csiparse();
   2496 				csihandle();
   2497 			}
   2498 			return;
   2499 		} else if (term.esc & ESC_UTF8) {
   2500 			tdefutf8(u);
   2501 		} else if (term.esc & ESC_ALTCHARSET) {
   2502 			tdeftran(u);
   2503 		} else if (term.esc & ESC_TEST) {
   2504 			tdectest(u);
   2505 		} else {
   2506 			if (!eschandle(u))
   2507 				return;
   2508 			/* sequence already finished */
   2509 		}
   2510 		term.esc = 0;
   2511 		/*
   2512 		 * All characters which form part of a sequence are not
   2513 		 * printed
   2514 		 */
   2515 		return;
   2516 	}
   2517 	if (selected(term.c.x, term.c.y))
   2518 		selclear();
   2519 
   2520 	gp = &term.line[term.c.y][term.c.x];
   2521 	if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
   2522 		gp->mode |= ATTR_WRAP;
   2523 		tnewline(1);
   2524 		gp = &term.line[term.c.y][term.c.x];
   2525 	}
   2526 
   2527 	if (IS_SET(MODE_INSERT) && term.c.x+width < term.col)
   2528 		memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
   2529 
   2530 	if (term.c.x+width > term.col) {
   2531 		tnewline(1);
   2532 		gp = &term.line[term.c.y][term.c.x];
   2533 	}
   2534 
   2535 	tsetchar(u, &term.c.attr, term.c.x, term.c.y);
   2536 	term.lastc = u;
   2537 
   2538 	if (width == 2) {
   2539 		gp->mode |= ATTR_WIDE;
   2540 		if (term.c.x+1 < term.col) {
   2541 			gp[1].u = '\0';
   2542 			gp[1].mode = ATTR_WDUMMY;
   2543 		}
   2544 	}
   2545 	if (term.c.x+width < term.col) {
   2546 		tmoveto(term.c.x+width, term.c.y);
   2547 	} else {
   2548 		term.c.state |= CURSOR_WRAPNEXT;
   2549 	}
   2550 }
   2551 
   2552 int
   2553 twrite(const char *buf, int buflen, int show_ctrl)
   2554 {
   2555 	int charsize;
   2556 	Rune u;
   2557 	int n;
   2558 
   2559 	for (n = 0; n < buflen; n += charsize) {
   2560 		if (IS_SET(MODE_UTF8)) {
   2561 			/* process a complete utf8 char */
   2562 			charsize = utf8decode(buf + n, &u, buflen - n);
   2563 			if (charsize == 0)
   2564 				break;
   2565 		} else {
   2566 			u = buf[n] & 0xFF;
   2567 			charsize = 1;
   2568 		}
   2569 		if (show_ctrl && ISCONTROL(u)) {
   2570 			if (u & 0x80) {
   2571 				u &= 0x7f;
   2572 				tputc('^');
   2573 				tputc('[');
   2574 			} else if (u != '\n' && u != '\r' && u != '\t') {
   2575 				u ^= 0x40;
   2576 				tputc('^');
   2577 			}
   2578 		}
   2579 		tputc(u);
   2580 	}
   2581 	return n;
   2582 }
   2583 
   2584 void
   2585 tresize(int col, int row)
   2586 {
   2587 	int i, j;
   2588 	int minrow = MIN(row, term.row);
   2589 	int mincol = MIN(col, term.col);
   2590 	int *bp;
   2591 	TCursor c;
   2592 
   2593 	if (col < 1 || row < 1) {
   2594 		fprintf(stderr,
   2595 		        "tresize: error resizing to %dx%d\n", col, row);
   2596 		return;
   2597 	}
   2598 
   2599 	/*
   2600 	 * slide screen to keep cursor where we expect it -
   2601 	 * tscrollup would work here, but we can optimize to
   2602 	 * memmove because we're freeing the earlier lines
   2603 	 */
   2604 	for (i = 0; i <= term.c.y - row; i++) {
   2605 		free(term.line[i]);
   2606 		free(term.alt[i]);
   2607 	}
   2608 	/* ensure that both src and dst are not NULL */
   2609 	if (i > 0) {
   2610 		memmove(term.line, term.line + i, row * sizeof(Line));
   2611 		memmove(term.alt, term.alt + i, row * sizeof(Line));
   2612 	}
   2613 	for (i += row; i < term.row; i++) {
   2614 		free(term.line[i]);
   2615 		free(term.alt[i]);
   2616 	}
   2617 
   2618 	/* resize to new height */
   2619 	term.line = xrealloc(term.line, row * sizeof(Line));
   2620 	term.alt  = xrealloc(term.alt,  row * sizeof(Line));
   2621 	term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
   2622 	term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
   2623 
   2624 	for (i = 0; i < HISTSIZE; i++) {
   2625 		term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph));
   2626 		for (j = mincol; j < col; j++) {
   2627 			term.hist[i][j] = term.c.attr;
   2628 			term.hist[i][j].u = ' ';
   2629 		}
   2630 	}
   2631 
   2632 	/* resize each row to new width, zero-pad if needed */
   2633 	for (i = 0; i < minrow; i++) {
   2634 		term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
   2635 		term.alt[i]  = xrealloc(term.alt[i],  col * sizeof(Glyph));
   2636 	}
   2637 
   2638 	/* allocate any new rows */
   2639 	for (/* i = minrow */; i < row; i++) {
   2640 		term.line[i] = xmalloc(col * sizeof(Glyph));
   2641 		term.alt[i] = xmalloc(col * sizeof(Glyph));
   2642 	}
   2643 	if (col > term.col) {
   2644 		bp = term.tabs + term.col;
   2645 
   2646 		memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
   2647 		while (--bp > term.tabs && !*bp)
   2648 			/* nothing */ ;
   2649 		for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
   2650 			*bp = 1;
   2651 	}
   2652 	/* update terminal size */
   2653 	term.col = col;
   2654 	term.row = row;
   2655 	/* reset scrolling region */
   2656 	tsetscroll(0, row-1);
   2657 	/* make use of the LIMIT in tmoveto */
   2658 	tmoveto(term.c.x, term.c.y);
   2659 	/* Clearing both screens (it makes dirty all lines) */
   2660 	c = term.c;
   2661 	for (i = 0; i < 2; i++) {
   2662 		if (mincol < col && 0 < minrow) {
   2663 			tclearregion(mincol, 0, col - 1, minrow - 1);
   2664 		}
   2665 		if (0 < col && minrow < row) {
   2666 			tclearregion(0, minrow, col - 1, row - 1);
   2667 		}
   2668 		tswapscreen();
   2669 		tcursor(CURSOR_LOAD);
   2670 	}
   2671 	term.c = c;
   2672 }
   2673 
   2674 void
   2675 resettitle(void)
   2676 {
   2677 	xsettitle(NULL);
   2678 }
   2679 
   2680 void
   2681 drawregion(int x1, int y1, int x2, int y2)
   2682 {
   2683 	int y;
   2684 
   2685 	for (y = y1; y < y2; y++) {
   2686 		if (!term.dirty[y])
   2687 			continue;
   2688 
   2689 		term.dirty[y] = 0;
   2690 		xdrawline(TLINE(y), x1, y, x2);
   2691 	}
   2692 }
   2693 
   2694 void
   2695 draw(void)
   2696 {
   2697 	int cx = term.c.x, ocx = term.ocx, ocy = term.ocy;
   2698 
   2699 	if (!xstartdraw())
   2700 		return;
   2701 
   2702 	/* adjust cursor position */
   2703 	LIMIT(term.ocx, 0, term.col-1);
   2704 	LIMIT(term.ocy, 0, term.row-1);
   2705 	if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY)
   2706 		term.ocx--;
   2707 	if (term.line[term.c.y][cx].mode & ATTR_WDUMMY)
   2708 		cx--;
   2709 
   2710 	drawregion(0, 0, term.col, term.row);
   2711 	if (term.scr == 0)
   2712 		xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
   2713 				term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
   2714 	term.ocx = cx;
   2715 	term.ocy = term.c.y;
   2716 	xfinishdraw();
   2717 	if (ocx != term.ocx || ocy != term.ocy)
   2718 		xximspot(term.ocx, term.ocy);
   2719 }
   2720 
   2721 void
   2722 redraw(void)
   2723 {
   2724 	tfulldirt();
   2725 	draw();
   2726 }