st

my build of st
git clone git://git.ckyln.com/st
Log | Files | Refs | README | LICENSE

st.c (58633B)


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