st

[fork] terminal
git clone https://hhvn.uk/st
git clone git://hhvn.uk/st
Log | Files | Refs | README | LICENSE

st.c (65582B)


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