st

my st build
Log | Files | Refs | README | LICENSE

st.c (59115B)


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