st

Luke's fork of the suckless terminal: vim-bindings, Xresrouces colors, transparency
Log | Files | Refs | README | LICENSE

st.c (58814B)


      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 NUMMAXLEN(x)		((int)(sizeof(x) * 2.56 + 0.5) + 1)
     43 #define ISCONTROLC0(c)		(BETWEEN(c, 0, 0x1f) || (c) == '\177')
     44 #define ISCONTROLC1(c)		(BETWEEN(c, 0x80, 0x9f))
     45 #define ISCONTROL(c)		(ISCONTROLC0(c) || ISCONTROLC1(c))
     46 #define ISDELIM(u)		(u && wcschr(worddelimiters, u))
     47 #define TLINE(y)		((y) < term.scr ? term.hist[((y) + term.histi - \
     48 				term.scr + HISTSIZE + 1) % HISTSIZE] : \
     49 				term.line[(y) - term.scr])
     50 
     51 #define TLINE_HIST(y)           ((y) <= HISTSIZE-term.row+2 ? term.hist[(y)] : term.line[(y-HISTSIZE+term.row-3)])
     52 
     53 /* constants */
     54 #define ISO14755CMD		"dmenu -w \"$WINDOWID\" -p codepoint: </dev/null"
     55 
     56 enum term_mode {
     57 	MODE_WRAP        = 1 << 0,
     58 	MODE_INSERT      = 1 << 1,
     59 	MODE_ALTSCREEN   = 1 << 2,
     60 	MODE_CRLF        = 1 << 3,
     61 	MODE_ECHO        = 1 << 4,
     62 	MODE_PRINT       = 1 << 5,
     63 	MODE_UTF8        = 1 << 6,
     64 	MODE_SIXEL       = 1 << 7,
     65 };
     66 
     67 enum cursor_movement {
     68 	CURSOR_SAVE,
     69 	CURSOR_LOAD
     70 };
     71 
     72 enum cursor_state {
     73 	CURSOR_DEFAULT  = 0,
     74 	CURSOR_WRAPNEXT = 1,
     75 	CURSOR_ORIGIN   = 2
     76 };
     77 
     78 enum charset {
     79 	CS_GRAPHIC0,
     80 	CS_GRAPHIC1,
     81 	CS_UK,
     82 	CS_USA,
     83 	CS_MULTI,
     84 	CS_GER,
     85 	CS_FIN
     86 };
     87 
     88 enum escape_state {
     89 	ESC_START      = 1,
     90 	ESC_CSI        = 2,
     91 	ESC_STR        = 4,  /* OSC, PM, APC */
     92 	ESC_ALTCHARSET = 8,
     93 	ESC_STR_END    = 16, /* a final string was encountered */
     94 	ESC_TEST       = 32, /* Enter in test mode */
     95 	ESC_UTF8       = 64,
     96 	ESC_DCS        =128,
     97 };
     98 
     99 typedef struct {
    100 	Glyph attr; /* current char attributes */
    101 	int x;
    102 	int y;
    103 	char state;
    104 } TCursor;
    105 
    106 typedef struct {
    107 	int mode;
    108 	int type;
    109 	int snap;
    110 	/*
    111 	 * Selection variables:
    112 	 * nb – normalized coordinates of the beginning of the selection
    113 	 * ne – normalized coordinates of the end of the selection
    114 	 * ob – original coordinates of the beginning of the selection
    115 	 * oe – original coordinates of the end of the selection
    116 	 */
    117 	struct {
    118 		int x, y;
    119 	} nb, ne, ob, oe;
    120 
    121 	int alt;
    122 } Selection;
    123 
    124 /* Internal representation of the screen */
    125 typedef struct {
    126 	int row;      /* nb row */
    127 	int col;      /* nb col */
    128 	Line *line;   /* screen */
    129 	Line *alt;    /* alternate screen */
    130 	Line hist[HISTSIZE]; /* history buffer */
    131 	int histi;    /* history index */
    132 	int scr;      /* scroll back */
    133 	int *dirty;   /* dirtyness of lines */
    134 	TCursor c;    /* cursor */
    135 	int ocx;      /* old cursor col */
    136 	int ocy;      /* old cursor row */
    137 	int top;      /* top    scroll limit */
    138 	int bot;      /* bottom scroll limit */
    139 	int mode;     /* terminal mode flags */
    140 	int esc;      /* escape state flags */
    141 	char trantbl[4]; /* charset table translation */
    142 	int charset;  /* current charset */
    143 	int icharset; /* selected charset for sequence */
    144 	int *tabs;
    145 } Term;
    146 
    147 /* CSI Escape sequence structs */
    148 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
    149 typedef struct {
    150 	char buf[ESC_BUF_SIZ]; /* raw string */
    151 	int len;               /* raw string length */
    152 	char priv;
    153 	int arg[ESC_ARG_SIZ];
    154 	int narg;              /* nb of args */
    155 	char mode[2];
    156 } CSIEscape;
    157 
    158 /* STR Escape sequence structs */
    159 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
    160 typedef struct {
    161 	char type;             /* ESC type ... */
    162 	char buf[STR_BUF_SIZ]; /* raw string */
    163 	int len;               /* raw string length */
    164 	char *args[STR_ARG_SIZ];
    165 	int narg;              /* nb of args */
    166 } STREscape;
    167 
    168 static void execsh(char *, char **);
    169 static void stty(char **);
    170 static void sigchld(int);
    171 static void ttywriteraw(const char *, size_t);
    172 
    173 static void csidump(void);
    174 static void csihandle(void);
    175 static void csiparse(void);
    176 static void csireset(void);
    177 static int eschandle(uchar);
    178 static void strdump(void);
    179 static void strhandle(void);
    180 static void strparse(void);
    181 static void strreset(void);
    182 
    183 static void tprinter(char *, size_t);
    184 static void tdumpsel(void);
    185 static void tdumpline(int);
    186 static void tdump(void);
    187 static void tclearregion(int, int, int, int);
    188 static void tcursor(int);
    189 static void tdeletechar(int);
    190 static void tdeleteline(int);
    191 static void tinsertblank(int);
    192 static void tinsertblankline(int);
    193 static int tlinelen(int);
    194 static void tmoveto(int, int);
    195 static void tmoveato(int, int);
    196 static void tnewline(int);
    197 static void tputtab(int);
    198 static void tputc(Rune);
    199 static void treset(void);
    200 static void tscrollup(int, int, int);
    201 static void tscrolldown(int, int, int);
    202 static void tsetattr(int *, int);
    203 static void tsetchar(Rune, Glyph *, int, int);
    204 static void tsetdirt(int, int);
    205 static void tsetscroll(int, int);
    206 static void tswapscreen(void);
    207 static void tsetmode(int, int, int *, int);
    208 static int twrite(const char *, int, int);
    209 static void tfulldirt(void);
    210 static void tcontrolcode(uchar );
    211 static void tdectest(char );
    212 static void tdefutf8(char);
    213 static int32_t tdefcolor(int *, int *, int);
    214 static void tdeftran(char);
    215 static void tstrsequence(uchar);
    216 
    217 static void drawregion(int, int, int, int);
    218 
    219 static void selnormalize(void);
    220 static void selscroll(int, int);
    221 static void selsnap(int *, int *, int);
    222 
    223 static size_t utf8decode(const char *, Rune *, size_t);
    224 static Rune utf8decodebyte(char, size_t *);
    225 static char utf8encodebyte(Rune, size_t);
    226 static size_t utf8validate(Rune *, size_t);
    227 
    228 static char *base64dec(const char *);
    229 static char base64dec_getc(const char **);
    230 
    231 static ssize_t xwrite(int, const char *, size_t);
    232 
    233 /* Globals */
    234 static Term term;
    235 static Selection sel;
    236 static CSIEscape csiescseq;
    237 static STREscape strescseq;
    238 static int iofd = 1;
    239 static int cmdfd;
    240 static pid_t pid;
    241 
    242 static uchar utfbyte[UTF_SIZ + 1] = {0x80,    0, 0xC0, 0xE0, 0xF0};
    243 static uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
    244 static Rune utfmin[UTF_SIZ + 1] = {       0,    0,  0x80,  0x800,  0x10000};
    245 static Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
    246 
    247 ssize_t
    248 xwrite(int fd, const char *s, size_t len)
    249 {
    250 	size_t aux = len;
    251 	ssize_t r;
    252 
    253 	while (len > 0) {
    254 		r = write(fd, s, len);
    255 		if (r < 0)
    256 			return r;
    257 		len -= r;
    258 		s += r;
    259 	}
    260 
    261 	return aux;
    262 }
    263 
    264 void *
    265 xmalloc(size_t len)
    266 {
    267 	void *p;
    268 
    269 	if (!(p = malloc(len)))
    270 		die("malloc: %s\n", strerror(errno));
    271 
    272 	return p;
    273 }
    274 
    275 void *
    276 xrealloc(void *p, size_t len)
    277 {
    278 	if ((p = realloc(p, len)) == NULL)
    279 		die("realloc: %s\n", strerror(errno));
    280 
    281 	return p;
    282 }
    283 
    284 char *
    285 xstrdup(char *s)
    286 {
    287 	if ((s = strdup(s)) == NULL)
    288 		die("strdup: %s\n", strerror(errno));
    289 
    290 	return s;
    291 }
    292 
    293 size_t
    294 utf8decode(const char *c, Rune *u, size_t clen)
    295 {
    296 	size_t i, j, len, type;
    297 	Rune udecoded;
    298 
    299 	*u = UTF_INVALID;
    300 	if (!clen)
    301 		return 0;
    302 	udecoded = utf8decodebyte(c[0], &len);
    303 	if (!BETWEEN(len, 1, UTF_SIZ))
    304 		return 1;
    305 	for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
    306 		udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
    307 		if (type != 0)
    308 			return j;
    309 	}
    310 	if (j < len)
    311 		return 0;
    312 	*u = udecoded;
    313 	utf8validate(u, len);
    314 
    315 	return len;
    316 }
    317 
    318 Rune
    319 utf8decodebyte(char c, size_t *i)
    320 {
    321 	for (*i = 0; *i < LEN(utfmask); ++(*i))
    322 		if (((uchar)c & utfmask[*i]) == utfbyte[*i])
    323 			return (uchar)c & ~utfmask[*i];
    324 
    325 	return 0;
    326 }
    327 
    328 size_t
    329 utf8encode(Rune u, char *c)
    330 {
    331 	size_t len, i;
    332 
    333 	len = utf8validate(&u, 0);
    334 	if (len > UTF_SIZ)
    335 		return 0;
    336 
    337 	for (i = len - 1; i != 0; --i) {
    338 		c[i] = utf8encodebyte(u, 0);
    339 		u >>= 6;
    340 	}
    341 	c[0] = utf8encodebyte(u, len);
    342 
    343 	return len;
    344 }
    345 
    346 char
    347 utf8encodebyte(Rune u, size_t i)
    348 {
    349 	return utfbyte[i] | (u & ~utfmask[i]);
    350 }
    351 
    352 size_t
    353 utf8validate(Rune *u, size_t i)
    354 {
    355 	if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF))
    356 		*u = UTF_INVALID;
    357 	for (i = 1; *u > utfmax[i]; ++i)
    358 		;
    359 
    360 	return i;
    361 }
    362 
    363 static const char base64_digits[] = {
    364 	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    365 	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0,
    366 	63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
    367 	2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
    368 	22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
    369 	35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    373 	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    374 	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    375 	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
    376 };
    377 
    378 char
    379 base64dec_getc(const char **src)
    380 {
    381 	while (**src && !isprint(**src)) (*src)++;
    382 	return *((*src)++);
    383 }
    384 
    385 char *
    386 base64dec(const char *src)
    387 {
    388 	size_t in_len = strlen(src);
    389 	char *result, *dst;
    390 
    391 	if (in_len % 4)
    392 		in_len += 4 - (in_len % 4);
    393 	result = dst = xmalloc(in_len / 4 * 3 + 1);
    394 	while (*src) {
    395 		int a = base64_digits[(unsigned char) base64dec_getc(&src)];
    396 		int b = base64_digits[(unsigned char) base64dec_getc(&src)];
    397 		int c = base64_digits[(unsigned char) base64dec_getc(&src)];
    398 		int d = base64_digits[(unsigned char) base64dec_getc(&src)];
    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 int
    435 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 	int 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 	int 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 	memset(&strescseq, 0, sizeof(strescseq));
   2028 }
   2029 
   2030 void
   2031 sendbreak(const Arg *arg)
   2032 {
   2033 	if (tcsendbreak(cmdfd, 0))
   2034 		perror("Error sending break");
   2035 }
   2036 
   2037 void
   2038 tprinter(char *s, size_t len)
   2039 {
   2040 	if (iofd != -1 && xwrite(iofd, s, len) < 0) {
   2041 		perror("Error writing to output file");
   2042 		close(iofd);
   2043 		iofd = -1;
   2044 	}
   2045 }
   2046 
   2047 void
   2048 externalpipe(const Arg *arg)
   2049 {
   2050 	int to[2];
   2051 	char buf[UTF_SIZ];
   2052 	void (*oldsigpipe)(int);
   2053 	Glyph *bp, *end;
   2054 	int lastpos, n, newline;
   2055 
   2056 	if (pipe(to) == -1)
   2057 		return;
   2058 
   2059 	switch (fork()) {
   2060 	case -1:
   2061 		close(to[0]);
   2062 		close(to[1]);
   2063 		return;
   2064 	case 0:
   2065 		dup2(to[0], STDIN_FILENO);
   2066 		close(to[0]);
   2067 		close(to[1]);
   2068 		execvp(((char **)arg->v)[0], (char **)arg->v);
   2069 		fprintf(stderr, "st: execvp %s\n", ((char **)arg->v)[0]);
   2070 		perror("failed");
   2071 		exit(0);
   2072 	}
   2073 
   2074 	close(to[0]);
   2075 	/* ignore sigpipe for now, in case child exists early */
   2076 	oldsigpipe = signal(SIGPIPE, SIG_IGN);
   2077 	newline = 0;
   2078 	/* modify externalpipe patch to pipe history too      */
   2079 	for (n = 0; n <= HISTSIZE + 2; n++) {
   2080 		bp = TLINE_HIST(n);
   2081 		lastpos = MIN(tlinehistlen(n) +1, term.col) - 1;
   2082 		if (lastpos < 0)
   2083 			break;
   2084 		if (lastpos == 0)
   2085 			continue;
   2086 		end = &bp[lastpos + 1];
   2087 		for (; bp < end; ++bp)
   2088 			if (xwrite(to[1], buf, utf8encode(bp->u, buf)) < 0)
   2089 				break;
   2090 		if ((newline = TLINE_HIST(n)[lastpos].mode & ATTR_WRAP))
   2091 			continue;
   2092 		if (xwrite(to[1], "\n", 1) < 0)
   2093 			break;
   2094 		newline = 0;
   2095 	}
   2096 	if (newline)
   2097 		(void)xwrite(to[1], "\n", 1);
   2098 	close(to[1]);
   2099 	/* restore */
   2100 	signal(SIGPIPE, oldsigpipe);
   2101 }
   2102 
   2103 void
   2104 iso14755(const Arg *arg)
   2105 {
   2106 	FILE *p;
   2107 	char *us, *e, codepoint[9], uc[UTF_SIZ];
   2108 	unsigned long utf32;
   2109 
   2110 	if (!(p = popen(ISO14755CMD, "r")))
   2111 		return;
   2112 
   2113 	us = fgets(codepoint, sizeof(codepoint), p);
   2114 	pclose(p);
   2115 
   2116 	if (!us || *us == '\0' || *us == '-' || strlen(us) > 7)
   2117 		return;
   2118 	if ((utf32 = strtoul(us, &e, 16)) == ULONG_MAX ||
   2119 	    (*e != '\n' && *e != '\0'))
   2120 		return;
   2121 
   2122 	ttywrite(uc, utf8encode(utf32, uc), 1);
   2123 }
   2124 
   2125 void
   2126 toggleprinter(const Arg *arg)
   2127 {
   2128 	term.mode ^= MODE_PRINT;
   2129 }
   2130 
   2131 void
   2132 printscreen(const Arg *arg)
   2133 {
   2134 	tdump();
   2135 }
   2136 
   2137 void
   2138 printsel(const Arg *arg)
   2139 {
   2140 	tdumpsel();
   2141 }
   2142 
   2143 void
   2144 tdumpsel(void)
   2145 {
   2146 	char *ptr;
   2147 
   2148 	if ((ptr = getsel())) {
   2149 		tprinter(ptr, strlen(ptr));
   2150 		free(ptr);
   2151 	}
   2152 }
   2153 
   2154 void
   2155 tdumpline(int n)
   2156 {
   2157 	char buf[UTF_SIZ];
   2158 	Glyph *bp, *end;
   2159 
   2160 	bp = &term.line[n][0];
   2161 	end = &bp[MIN(tlinelen(n), term.col) - 1];
   2162 	if (bp != end || bp->u != ' ') {
   2163 		for ( ;bp <= end; ++bp)
   2164 			tprinter(buf, utf8encode(bp->u, buf));
   2165 	}
   2166 	tprinter("\n", 1);
   2167 }
   2168 
   2169 void
   2170 tdump(void)
   2171 {
   2172 	int i;
   2173 
   2174 	for (i = 0; i < term.row; ++i)
   2175 		tdumpline(i);
   2176 }
   2177 
   2178 void
   2179 tputtab(int n)
   2180 {
   2181 	uint x = term.c.x;
   2182 
   2183 	if (n > 0) {
   2184 		while (x < term.col && n--)
   2185 			for (++x; x < term.col && !term.tabs[x]; ++x)
   2186 				/* nothing */ ;
   2187 	} else if (n < 0) {
   2188 		while (x > 0 && n++)
   2189 			for (--x; x > 0 && !term.tabs[x]; --x)
   2190 				/* nothing */ ;
   2191 	}
   2192 	term.c.x = LIMIT(x, 0, term.col-1);
   2193 }
   2194 
   2195 void
   2196 tdefutf8(char ascii)
   2197 {
   2198 	if (ascii == 'G')
   2199 		term.mode |= MODE_UTF8;
   2200 	else if (ascii == '@')
   2201 		term.mode &= ~MODE_UTF8;
   2202 }
   2203 
   2204 void
   2205 tdeftran(char ascii)
   2206 {
   2207 	static char cs[] = "0B";
   2208 	static int vcs[] = {CS_GRAPHIC0, CS_USA};
   2209 	char *p;
   2210 
   2211 	if ((p = strchr(cs, ascii)) == NULL) {
   2212 		fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
   2213 	} else {
   2214 		term.trantbl[term.icharset] = vcs[p - cs];
   2215 	}
   2216 }
   2217 
   2218 void
   2219 tdectest(char c)
   2220 {
   2221 	int x, y;
   2222 
   2223 	if (c == '8') { /* DEC screen alignment test. */
   2224 		for (x = 0; x < term.col; ++x) {
   2225 			for (y = 0; y < term.row; ++y)
   2226 				tsetchar('E', &term.c.attr, x, y);
   2227 		}
   2228 	}
   2229 }
   2230 
   2231 void
   2232 tstrsequence(uchar c)
   2233 {
   2234 	strreset();
   2235 
   2236 	switch (c) {
   2237 	case 0x90:   /* DCS -- Device Control String */
   2238 		c = 'P';
   2239 		term.esc |= ESC_DCS;
   2240 		break;
   2241 	case 0x9f:   /* APC -- Application Program Command */
   2242 		c = '_';
   2243 		break;
   2244 	case 0x9e:   /* PM -- Privacy Message */
   2245 		c = '^';
   2246 		break;
   2247 	case 0x9d:   /* OSC -- Operating System Command */
   2248 		c = ']';
   2249 		break;
   2250 	}
   2251 	strescseq.type = c;
   2252 	term.esc |= ESC_STR;
   2253 }
   2254 
   2255 void
   2256 tcontrolcode(uchar ascii)
   2257 {
   2258 	switch (ascii) {
   2259 	case '\t':   /* HT */
   2260 		tputtab(1);
   2261 		return;
   2262 	case '\b':   /* BS */
   2263 		tmoveto(term.c.x-1, term.c.y);
   2264 		return;
   2265 	case '\r':   /* CR */
   2266 		tmoveto(0, term.c.y);
   2267 		return;
   2268 	case '\f':   /* LF */
   2269 	case '\v':   /* VT */
   2270 	case '\n':   /* LF */
   2271 		/* go to first col if the mode is set */
   2272 		tnewline(IS_SET(MODE_CRLF));
   2273 		return;
   2274 	case '\a':   /* BEL */
   2275 		if (term.esc & ESC_STR_END) {
   2276 			/* backwards compatibility to xterm */
   2277 			strhandle();
   2278 		} else {
   2279 			xbell();
   2280 		}
   2281 		break;
   2282 	case '\033': /* ESC */
   2283 		csireset();
   2284 		term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
   2285 		term.esc |= ESC_START;
   2286 		return;
   2287 	case '\016': /* SO (LS1 -- Locking shift 1) */
   2288 	case '\017': /* SI (LS0 -- Locking shift 0) */
   2289 		term.charset = 1 - (ascii - '\016');
   2290 		return;
   2291 	case '\032': /* SUB */
   2292 		tsetchar('?', &term.c.attr, term.c.x, term.c.y);
   2293 	case '\030': /* CAN */
   2294 		csireset();
   2295 		break;
   2296 	case '\005': /* ENQ (IGNORED) */
   2297 	case '\000': /* NUL (IGNORED) */
   2298 	case '\021': /* XON (IGNORED) */
   2299 	case '\023': /* XOFF (IGNORED) */
   2300 	case 0177:   /* DEL (IGNORED) */
   2301 		return;
   2302 	case 0x80:   /* TODO: PAD */
   2303 	case 0x81:   /* TODO: HOP */
   2304 	case 0x82:   /* TODO: BPH */
   2305 	case 0x83:   /* TODO: NBH */
   2306 	case 0x84:   /* TODO: IND */
   2307 		break;
   2308 	case 0x85:   /* NEL -- Next line */
   2309 		tnewline(1); /* always go to first col */
   2310 		break;
   2311 	case 0x86:   /* TODO: SSA */
   2312 	case 0x87:   /* TODO: ESA */
   2313 		break;
   2314 	case 0x88:   /* HTS -- Horizontal tab stop */
   2315 		term.tabs[term.c.x] = 1;
   2316 		break;
   2317 	case 0x89:   /* TODO: HTJ */
   2318 	case 0x8a:   /* TODO: VTS */
   2319 	case 0x8b:   /* TODO: PLD */
   2320 	case 0x8c:   /* TODO: PLU */
   2321 	case 0x8d:   /* TODO: RI */
   2322 	case 0x8e:   /* TODO: SS2 */
   2323 	case 0x8f:   /* TODO: SS3 */
   2324 	case 0x91:   /* TODO: PU1 */
   2325 	case 0x92:   /* TODO: PU2 */
   2326 	case 0x93:   /* TODO: STS */
   2327 	case 0x94:   /* TODO: CCH */
   2328 	case 0x95:   /* TODO: MW */
   2329 	case 0x96:   /* TODO: SPA */
   2330 	case 0x97:   /* TODO: EPA */
   2331 	case 0x98:   /* TODO: SOS */
   2332 	case 0x99:   /* TODO: SGCI */
   2333 		break;
   2334 	case 0x9a:   /* DECID -- Identify Terminal */
   2335 		ttywrite(vtiden, strlen(vtiden), 0);
   2336 		break;
   2337 	case 0x9b:   /* TODO: CSI */
   2338 	case 0x9c:   /* TODO: ST */
   2339 		break;
   2340 	case 0x90:   /* DCS -- Device Control String */
   2341 	case 0x9d:   /* OSC -- Operating System Command */
   2342 	case 0x9e:   /* PM -- Privacy Message */
   2343 	case 0x9f:   /* APC -- Application Program Command */
   2344 		tstrsequence(ascii);
   2345 		return;
   2346 	}
   2347 	/* only CAN, SUB, \a and C1 chars interrupt a sequence */
   2348 	term.esc &= ~(ESC_STR_END|ESC_STR);
   2349 }
   2350 
   2351 /*
   2352  * returns 1 when the sequence is finished and it hasn't to read
   2353  * more characters for this sequence, otherwise 0
   2354  */
   2355 int
   2356 eschandle(uchar ascii)
   2357 {
   2358 	switch (ascii) {
   2359 	case '[':
   2360 		term.esc |= ESC_CSI;
   2361 		return 0;
   2362 	case '#':
   2363 		term.esc |= ESC_TEST;
   2364 		return 0;
   2365 	case '%':
   2366 		term.esc |= ESC_UTF8;
   2367 		return 0;
   2368 	case 'P': /* DCS -- Device Control String */
   2369 	case '_': /* APC -- Application Program Command */
   2370 	case '^': /* PM -- Privacy Message */
   2371 	case ']': /* OSC -- Operating System Command */
   2372 	case 'k': /* old title set compatibility */
   2373 		tstrsequence(ascii);
   2374 		return 0;
   2375 	case 'n': /* LS2 -- Locking shift 2 */
   2376 	case 'o': /* LS3 -- Locking shift 3 */
   2377 		term.charset = 2 + (ascii - 'n');
   2378 		break;
   2379 	case '(': /* GZD4 -- set primary charset G0 */
   2380 	case ')': /* G1D4 -- set secondary charset G1 */
   2381 	case '*': /* G2D4 -- set tertiary charset G2 */
   2382 	case '+': /* G3D4 -- set quaternary charset G3 */
   2383 		term.icharset = ascii - '(';
   2384 		term.esc |= ESC_ALTCHARSET;
   2385 		return 0;
   2386 	case 'D': /* IND -- Linefeed */
   2387 		if (term.c.y == term.bot) {
   2388 			tscrollup(term.top, 1, 1);
   2389 		} else {
   2390 			tmoveto(term.c.x, term.c.y+1);
   2391 		}
   2392 		break;
   2393 	case 'E': /* NEL -- Next line */
   2394 		tnewline(1); /* always go to first col */
   2395 		break;
   2396 	case 'H': /* HTS -- Horizontal tab stop */
   2397 		term.tabs[term.c.x] = 1;
   2398 		break;
   2399 	case 'M': /* RI -- Reverse index */
   2400 		if (term.c.y == term.top) {
   2401 			tscrolldown(term.top, 1, 1);
   2402 		} else {
   2403 			tmoveto(term.c.x, term.c.y-1);
   2404 		}
   2405 		break;
   2406 	case 'Z': /* DECID -- Identify Terminal */
   2407 		ttywrite(vtiden, strlen(vtiden), 0);
   2408 		break;
   2409 	case 'c': /* RIS -- Reset to initial state */
   2410 		treset();
   2411 		resettitle();
   2412 		xloadcols();
   2413 		break;
   2414 	case '=': /* DECPAM -- Application keypad */
   2415 		xsetmode(1, MODE_APPKEYPAD);
   2416 		break;
   2417 	case '>': /* DECPNM -- Normal keypad */
   2418 		xsetmode(0, MODE_APPKEYPAD);
   2419 		break;
   2420 	case '7': /* DECSC -- Save Cursor */
   2421 		tcursor(CURSOR_SAVE);
   2422 		break;
   2423 	case '8': /* DECRC -- Restore Cursor */
   2424 		tcursor(CURSOR_LOAD);
   2425 		break;
   2426 	case '\\': /* ST -- String Terminator */
   2427 		if (term.esc & ESC_STR_END)
   2428 			strhandle();
   2429 		break;
   2430 	default:
   2431 		fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
   2432 			(uchar) ascii, isprint(ascii)? ascii:'.');
   2433 		break;
   2434 	}
   2435 	return 1;
   2436 }
   2437 
   2438 void
   2439 tputc(Rune u)
   2440 {
   2441 	char c[UTF_SIZ];
   2442 	int control;
   2443 	int width, len;
   2444 	Glyph *gp;
   2445 
   2446 	control = ISCONTROL(u);
   2447 	if (!IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
   2448 		c[0] = u;
   2449 		width = len = 1;
   2450 	} else {
   2451 		len = utf8encode(u, c);
   2452 		if (!control && (width = wcwidth(u)) == -1) {
   2453 			memcpy(c, "\357\277\275", 4); /* UTF_INVALID */
   2454 			width = 1;
   2455 		}
   2456 	}
   2457 
   2458 	if (IS_SET(MODE_PRINT))
   2459 		tprinter(c, len);
   2460 
   2461 	/*
   2462 	 * STR sequence must be checked before anything else
   2463 	 * because it uses all following characters until it
   2464 	 * receives a ESC, a SUB, a ST or any other C1 control
   2465 	 * character.
   2466 	 */
   2467 	if (term.esc & ESC_STR) {
   2468 		if (u == '\a' || u == 030 || u == 032 || u == 033 ||
   2469 		   ISCONTROLC1(u)) {
   2470 			term.esc &= ~(ESC_START|ESC_STR|ESC_DCS);
   2471 			if (IS_SET(MODE_SIXEL)) {
   2472 				/* TODO: render sixel */;
   2473 				term.mode &= ~MODE_SIXEL;
   2474 				return;
   2475 			}
   2476 			term.esc |= ESC_STR_END;
   2477 			goto check_control_code;
   2478 		}
   2479 
   2480 		if (IS_SET(MODE_SIXEL)) {
   2481 			/* TODO: implement sixel mode */
   2482 			return;
   2483 		}
   2484 		if (term.esc&ESC_DCS && strescseq.len == 0 && u == 'q')
   2485 			term.mode |= MODE_SIXEL;
   2486 
   2487 		if (strescseq.len+len >= sizeof(strescseq.buf)-1) {
   2488 			/*
   2489 			 * Here is a bug in terminals. If the user never sends
   2490 			 * some code to stop the str or esc command, then st
   2491 			 * will stop responding. But this is better than
   2492 			 * silently failing with unknown characters. At least
   2493 			 * then users will report back.
   2494 			 *
   2495 			 * In the case users ever get fixed, here is the code:
   2496 			 */
   2497 			/*
   2498 			 * term.esc = 0;
   2499 			 * strhandle();
   2500 			 */
   2501 			return;
   2502 		}
   2503 
   2504 		memmove(&strescseq.buf[strescseq.len], c, len);
   2505 		strescseq.len += len;
   2506 		return;
   2507 	}
   2508 
   2509 check_control_code:
   2510 	/*
   2511 	 * Actions of control codes must be performed as soon they arrive
   2512 	 * because they can be embedded inside a control sequence, and
   2513 	 * they must not cause conflicts with sequences.
   2514 	 */
   2515 	if (control) {
   2516 		tcontrolcode(u);
   2517 		/*
   2518 		 * control codes are not shown ever
   2519 		 */
   2520 		return;
   2521 	} else if (term.esc & ESC_START) {
   2522 		if (term.esc & ESC_CSI) {
   2523 			csiescseq.buf[csiescseq.len++] = u;
   2524 			if (BETWEEN(u, 0x40, 0x7E)
   2525 					|| csiescseq.len >= \
   2526 					sizeof(csiescseq.buf)-1) {
   2527 				term.esc = 0;
   2528 				csiparse();
   2529 				csihandle();
   2530 			}
   2531 			return;
   2532 		} else if (term.esc & ESC_UTF8) {
   2533 			tdefutf8(u);
   2534 		} else if (term.esc & ESC_ALTCHARSET) {
   2535 			tdeftran(u);
   2536 		} else if (term.esc & ESC_TEST) {
   2537 			tdectest(u);
   2538 		} else {
   2539 			if (!eschandle(u))
   2540 				return;
   2541 			/* sequence already finished */
   2542 		}
   2543 		term.esc = 0;
   2544 		/*
   2545 		 * All characters which form part of a sequence are not
   2546 		 * printed
   2547 		 */
   2548 		return;
   2549 	}
   2550 	if (sel.ob.x != -1 && BETWEEN(term.c.y, sel.ob.y, sel.oe.y))
   2551 		selclear();
   2552 
   2553 	gp = &term.line[term.c.y][term.c.x];
   2554 	if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
   2555 		gp->mode |= ATTR_WRAP;
   2556 		tnewline(1);
   2557 		gp = &term.line[term.c.y][term.c.x];
   2558 	}
   2559 
   2560 	if (IS_SET(MODE_INSERT) && term.c.x+width < term.col)
   2561 		memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
   2562 
   2563 	if (term.c.x+width > term.col) {
   2564 		tnewline(1);
   2565 		gp = &term.line[term.c.y][term.c.x];
   2566 	}
   2567 
   2568 	tsetchar(u, &term.c.attr, term.c.x, term.c.y);
   2569 
   2570 	if (width == 2) {
   2571 		gp->mode |= ATTR_WIDE;
   2572 		if (term.c.x+1 < term.col) {
   2573 			gp[1].u = '\0';
   2574 			gp[1].mode = ATTR_WDUMMY;
   2575 		}
   2576 	}
   2577 	if (term.c.x+width < term.col) {
   2578 		tmoveto(term.c.x+width, term.c.y);
   2579 	} else {
   2580 		term.c.state |= CURSOR_WRAPNEXT;
   2581 	}
   2582 }
   2583 
   2584 int
   2585 twrite(const char *buf, int buflen, int show_ctrl)
   2586 {
   2587 	int charsize;
   2588 	Rune u;
   2589 	int n;
   2590 
   2591 	for (n = 0; n < buflen; n += charsize) {
   2592 		if (IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
   2593 			/* process a complete utf8 char */
   2594 			charsize = utf8decode(buf + n, &u, buflen - n);
   2595 			if (charsize == 0)
   2596 				break;
   2597 		} else {
   2598 			u = buf[n] & 0xFF;
   2599 			charsize = 1;
   2600 		}
   2601 		if (show_ctrl && ISCONTROL(u)) {
   2602 			if (u & 0x80) {
   2603 				u &= 0x7f;
   2604 				tputc('^');
   2605 				tputc('[');
   2606 			} else if (u != '\n' && u != '\r' && u != '\t') {
   2607 				u ^= 0x40;
   2608 				tputc('^');
   2609 			}
   2610 		}
   2611 		tputc(u);
   2612 	}
   2613 	return n;
   2614 }
   2615 
   2616 void
   2617 tresize(int col, int row)
   2618 {
   2619 	int i, j;
   2620 	int minrow = MIN(row, term.row);
   2621 	int mincol = MIN(col, term.col);
   2622 	int *bp;
   2623 	TCursor c;
   2624 
   2625 	if (col < 1 || row < 1) {
   2626 		fprintf(stderr,
   2627 		        "tresize: error resizing to %dx%d\n", col, row);
   2628 		return;
   2629 	}
   2630 
   2631 	/*
   2632 	 * slide screen to keep cursor where we expect it -
   2633 	 * tscrollup would work here, but we can optimize to
   2634 	 * memmove because we're freeing the earlier lines
   2635 	 */
   2636 	for (i = 0; i <= term.c.y - row; i++) {
   2637 		free(term.line[i]);
   2638 		free(term.alt[i]);
   2639 	}
   2640 	/* ensure that both src and dst are not NULL */
   2641 	if (i > 0) {
   2642 		memmove(term.line, term.line + i, row * sizeof(Line));
   2643 		memmove(term.alt, term.alt + i, row * sizeof(Line));
   2644 	}
   2645 	for (i += row; i < term.row; i++) {
   2646 		free(term.line[i]);
   2647 		free(term.alt[i]);
   2648 	}
   2649 
   2650 	/* resize to new height */
   2651 	term.line = xrealloc(term.line, row * sizeof(Line));
   2652 	term.alt  = xrealloc(term.alt,  row * sizeof(Line));
   2653 	term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
   2654 	term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
   2655 
   2656 	for (i = 0; i < HISTSIZE; i++) {
   2657 		term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph));
   2658 		for (j = mincol; j < col; j++) {
   2659 			term.hist[i][j] = term.c.attr;
   2660 			term.hist[i][j].u = ' ';
   2661 		}
   2662 	}
   2663 
   2664 	/* resize each row to new width, zero-pad if needed */
   2665 	for (i = 0; i < minrow; i++) {
   2666 		term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
   2667 		term.alt[i]  = xrealloc(term.alt[i],  col * sizeof(Glyph));
   2668 	}
   2669 
   2670 	/* allocate any new rows */
   2671 	for (/* i = minrow */; i < row; i++) {
   2672 		term.line[i] = xmalloc(col * sizeof(Glyph));
   2673 		term.alt[i] = xmalloc(col * sizeof(Glyph));
   2674 	}
   2675 	if (col > term.col) {
   2676 		bp = term.tabs + term.col;
   2677 
   2678 		memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
   2679 		while (--bp > term.tabs && !*bp)
   2680 			/* nothing */ ;
   2681 		for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
   2682 			*bp = 1;
   2683 	}
   2684 	/* update terminal size */
   2685 	term.col = col;
   2686 	term.row = row;
   2687 	/* reset scrolling region */
   2688 	tsetscroll(0, row-1);
   2689 	/* make use of the LIMIT in tmoveto */
   2690 	tmoveto(term.c.x, term.c.y);
   2691 	/* Clearing both screens (it makes dirty all lines) */
   2692 	c = term.c;
   2693 	for (i = 0; i < 2; i++) {
   2694 		if (mincol < col && 0 < minrow) {
   2695 			tclearregion(mincol, 0, col - 1, minrow - 1);
   2696 		}
   2697 		if (0 < col && minrow < row) {
   2698 			tclearregion(0, minrow, col - 1, row - 1);
   2699 		}
   2700 		tswapscreen();
   2701 		tcursor(CURSOR_LOAD);
   2702 	}
   2703 	term.c = c;
   2704 }
   2705 
   2706 void
   2707 resettitle(void)
   2708 {
   2709 	xsettitle(NULL);
   2710 }
   2711 
   2712 void
   2713 drawregion(int x1, int y1, int x2, int y2)
   2714 {
   2715 	int y;
   2716 	for (y = y1; y < y2; y++) {
   2717 		if (!term.dirty[y])
   2718 			continue;
   2719 
   2720 		term.dirty[y] = 0;
   2721 		xdrawline(TLINE(y), x1, y, x2);
   2722 	}
   2723 }
   2724 
   2725 void
   2726 draw(void)
   2727 {
   2728 	int cx = term.c.x;
   2729 
   2730 	if (!xstartdraw())
   2731 		return;
   2732 
   2733 	/* adjust cursor position */
   2734 	LIMIT(term.ocx, 0, term.col-1);
   2735 	LIMIT(term.ocy, 0, term.row-1);
   2736 	if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY)
   2737 		term.ocx--;
   2738 	if (term.line[term.c.y][cx].mode & ATTR_WDUMMY)
   2739 		cx--;
   2740 
   2741 	drawregion(0, 0, term.col, term.row);
   2742 	if (term.scr == 0)
   2743 		xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
   2744 				term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
   2745 	term.ocx = cx, term.ocy = term.c.y;
   2746 	xfinishdraw();
   2747 	xximspot(term.ocx, term.ocy);
   2748 }
   2749 
   2750 void
   2751 redraw(void)
   2752 {
   2753 	tfulldirt();
   2754 	draw();
   2755 }