st

simple terminal
git clone git://mfeller.io/st.git
Log | Files | Refs | README | LICENSE

st.c (58645B)


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