st

Simple Terminal by suckless.org (fork)
git clone git://git.ckyln.com/st.git
Log | Files | Refs | README | LICENSE

st.c (58268B)


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