st

my build of st - simple terminal
git clone git://git.kocotian.pl/st.git
Log | Files | Refs | README | LICENSE

st.c (59352B)


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