suckless/st

st.c in master
Repositories | Summary | Log | Files | README | LICENSE

st.c (60819B) download


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