+die(const char *errstr, ...) {
+ va_list ap;
+
+ va_start(ap, errstr);
+ vfprintf(stderr, errstr, ap);
+ va_end(ap);
+ exit(EXIT_FAILURE);
+}
+
+void
+execsh(void) {
+ char **args;
+ char *envshell = getenv("SHELL");
+ const struct passwd *pass = getpwuid(getuid());
+ char buf[sizeof(long) * 8 + 1];
+
+ unsetenv("COLUMNS");
+ unsetenv("LINES");
+ unsetenv("TERMCAP");
+
+ if(pass) {
+ setenv("LOGNAME", pass->pw_name, 1);
+ setenv("USER", pass->pw_name, 1);
+ setenv("SHELL", pass->pw_shell, 0);
+ setenv("HOME", pass->pw_dir, 0);
+ }
+
+ snprintf(buf, sizeof(buf), "%lu", xw.win);
+ setenv("WINDOWID", buf, 1);
+
+ signal(SIGCHLD, SIG_DFL);
+ signal(SIGHUP, SIG_DFL);
+ signal(SIGINT, SIG_DFL);
+ signal(SIGQUIT, SIG_DFL);
+ signal(SIGTERM, SIG_DFL);
+ signal(SIGALRM, SIG_DFL);
+
+ DEFAULT(envshell, shell);
+ setenv("TERM", termname, 1);
+ args = opt_cmd ? opt_cmd : (char *[]){envshell, "-i", NULL};
+ execvp(args[0], args);
+ exit(EXIT_FAILURE);
+}
+
+void
+sigchld(int a) {
+ int stat = 0;
+
+ if(waitpid(pid, &stat, 0) < 0)
+ die("Waiting for pid %hd failed: %s\n", pid, strerror(errno));
+
+ if(WIFEXITED(stat)) {
+ exit(WEXITSTATUS(stat));
+ } else {
+ exit(EXIT_FAILURE);
+ }
+}
+
+void
+ttynew(void) {
+ int m, s;
+ struct winsize w = {term.row, term.col, 0, 0};
+
+ /* seems to work fine on linux, openbsd and freebsd */
+ if(openpty(&m, &s, NULL, NULL, &w) < 0)
+ die("openpty failed: %s\n", strerror(errno));
+
+ switch(pid = fork()) {
+ case -1:
+ die("fork failed\n");
+ break;
+ case 0:
+ setsid(); /* create a new process group */
+ dup2(s, STDIN_FILENO);
+ dup2(s, STDOUT_FILENO);
+ dup2(s, STDERR_FILENO);
+ if(ioctl(s, TIOCSCTTY, NULL) < 0)
+ die("ioctl TIOCSCTTY failed: %s\n", strerror(errno));
+ close(s);
+ close(m);
+ execsh();
+ break;
+ default:
+ close(s);
+ cmdfd = m;
+ signal(SIGCHLD, sigchld);
+ if(opt_io) {
+ term.mode |= MODE_PRINT;
+ iofd = (!strcmp(opt_io, "-")) ?
+ STDOUT_FILENO :
+ open(opt_io, O_WRONLY | O_CREAT, 0666);
+ if(iofd < 0) {
+ fprintf(stderr, "Error opening %s:%s\n",
+ opt_io, strerror(errno));
+ }
+ }
+ break;
+ }
+}
+
+void
+ttyread(void) {
+ static char buf[BUFSIZ];
+ static int buflen = 0;
+ char *ptr;
+ char s[UTF_SIZ];
+ int charsize; /* size of utf8 char in bytes */
+ long unicodep;
+ int ret;
+
+ /* append read bytes to unprocessed bytes */
+ if((ret = read(cmdfd, buf+buflen, LEN(buf)-buflen)) < 0)
+ die("Couldn't read from shell: %s\n", strerror(errno));
+
+ /* process every complete utf8 char */
+ buflen += ret;
+ ptr = buf;
+ while((charsize = utf8decode(ptr, &unicodep, buflen))) {
+ utf8encode(unicodep, s, UTF_SIZ);
+ tputc(s, charsize);
+ ptr += charsize;
+ buflen -= charsize;
+ }
+
+ /* keep any uncomplete utf8 char for the next call */
+ memmove(buf, ptr, buflen);
+}
+
+void
+ttywrite(const char *s, size_t n) {
+ if(xwrite(cmdfd, s, n) == -1)
+ die("write error on tty: %s\n", strerror(errno));
+}
+
+void
+ttysend(char *s, size_t n) {
+ ttywrite(s, n);
+ if(IS_SET(MODE_ECHO))
+ techo(s, n);
+}
+
+void
+ttyresize(void) {
+ struct winsize w;
+
+ w.ws_row = term.row;
+ w.ws_col = term.col;
+ w.ws_xpixel = xw.tw;
+ w.ws_ypixel = xw.th;
+ if(ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
+ fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
+}
+
+int
+tattrset(int attr) {
+ int i, j;
+
+ for(i = 0; i < term.row-1; i++) {
+ for(j = 0; j < term.col-1; j++) {
+ if(term.line[i][j].mode & attr)
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+void
+tsetdirt(int top, int bot) {
+ int i;
+
+ LIMIT(top, 0, term.row-1);
+ LIMIT(bot, 0, term.row-1);
+
+ for(i = top; i <= bot; i++)
+ term.dirty[i] = 1;
+}
+
+void
+tsetdirtattr(int attr) {
+ int i, j;
+
+ for(i = 0; i < term.row-1; i++) {
+ for(j = 0; j < term.col-1; j++) {
+ if(term.line[i][j].mode & attr) {
+ tsetdirt(i, i);
+ break;
+ }
+ }
+ }
+}
+
+void
+tfulldirt(void) {
+ tsetdirt(0, term.row-1);
+}
+
+void
+tcursor(int mode) {
+ static TCursor c[2];
+ bool alt = IS_SET(MODE_ALTSCREEN);
+
+ if(mode == CURSOR_SAVE) {
+ c[alt] = term.c;
+ } else if(mode == CURSOR_LOAD) {
+ term.c = c[alt];
+ tmoveto(c[alt].x, c[alt].y);
+ }
+}
+
+void
+treset(void) {
+ uint i;
+
+ term.c = (TCursor){{
+ .mode = ATTR_NULL,
+ .fg = defaultfg,
+ .bg = defaultbg
+ }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
+
+ memset(term.tabs, 0, term.col * sizeof(*term.tabs));
+ for(i = tabspaces; i < term.col; i += tabspaces)
+ term.tabs[i] = 1;
+ term.top = 0;
+ term.bot = term.row - 1;
+ term.mode = MODE_WRAP;
+ memset(term.trantbl, sizeof(term.trantbl), CS_USA);
+ term.charset = 0;
+
+ tclearregion(0, 0, term.col-1, term.row-1);
+ tmoveto(0, 0);
+ tcursor(CURSOR_SAVE);
+}
+
+void
+tnew(int col, int row) {
+ term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
+ tresize(col, row);
+ term.numlock = 1;
+
+ treset();
+}
+
+void
+tswapscreen(void) {
+ Line *tmp = term.line;
+
+ term.line = term.alt;
+ term.alt = tmp;
+ term.mode ^= MODE_ALTSCREEN;
+ tfulldirt();
+}
+
+void
+tscrolldown(int orig, int n) {
+ int i;
+ Line temp;
+
+ LIMIT(n, 0, term.bot-orig+1);
+
+ tsetdirt(orig, term.bot-n);
+ tclearregion(0, term.bot-n+1, term.col-1, term.bot);
+
+ for(i = term.bot; i >= orig+n; i--) {
+ temp = term.line[i];
+ term.line[i] = term.line[i-n];
+ term.line[i-n] = temp;
+ }
+
+ selscroll(orig, n);
+}
+
+void
+tscrollup(int orig, int n) {
+ int i;
+ Line temp;
+
+ LIMIT(n, 0, term.bot-orig+1);
+
+ tclearregion(0, orig, term.col-1, orig+n-1);
+ tsetdirt(orig+n, term.bot);
+
+ for(i = orig; i <= term.bot-n; i++) {
+ temp = term.line[i];
+ term.line[i] = term.line[i+n];
+ term.line[i+n] = temp;
+ }
+
+ selscroll(orig, -n);
+}
+
+void
+selscroll(int orig, int n) {
+ if(sel.ob.x == -1)
+ return;
+
+ if(BETWEEN(sel.ob.y, orig, term.bot) || BETWEEN(sel.oe.y, orig, term.bot)) {
+ if((sel.ob.y += n) > term.bot || (sel.oe.y += n) < term.top) {
+ selclear(NULL);
+ return;
+ }
+ if(sel.type == SEL_RECTANGULAR) {
+ if(sel.ob.y < term.top)
+ sel.ob.y = term.top;
+ if(sel.oe.y > term.bot)
+ sel.oe.y = term.bot;
+ } else {
+ if(sel.ob.y < term.top) {
+ sel.ob.y = term.top;
+ sel.ob.x = 0;
+ }
+ if(sel.oe.y > term.bot) {
+ sel.oe.y = term.bot;
+ sel.oe.x = term.col;
+ }
+ }
+ selnormalize();
+ }
+}
+
+void
+tnewline(int first_col) {
+ int y = term.c.y;
+
+ if(y == term.bot) {
+ tscrollup(term.top, 1);
+ } else {
+ y++;
+ }
+ tmoveto(first_col ? 0 : term.c.x, y);
+}
+
+void
+csiparse(void) {
+ char *p = csiescseq.buf, *np;
+ long int v;
+
+ csiescseq.narg = 0;
+ if(*p == '?') {
+ csiescseq.priv = 1;
+ p++;
+ }
+
+ csiescseq.buf[csiescseq.len] = '\0';
+ while(p < csiescseq.buf+csiescseq.len) {
+ np = NULL;
+ v = strtol(p, &np, 10);
+ if(np == p)
+ v = 0;
+ if(v == LONG_MAX || v == LONG_MIN)
+ v = -1;
+ csiescseq.arg[csiescseq.narg++] = v;
+ p = np;
+ if(*p != ';' || csiescseq.narg == ESC_ARG_SIZ)
+ break;
+ p++;
+ }
+ csiescseq.mode = *p;
+}
+
+/* for absolute user moves, when decom is set */
+void
+tmoveato(int x, int y) {
+ tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0));
+}
+
+void
+tmoveto(int x, int y) {
+ int miny, maxy;
+
+ if(term.c.state & CURSOR_ORIGIN) {
+ miny = term.top;
+ maxy = term.bot;
+ } else {
+ miny = 0;
+ maxy = term.row - 1;
+ }
+ LIMIT(x, 0, term.col-1);
+ LIMIT(y, miny, maxy);
+ term.c.state &= ~CURSOR_WRAPNEXT;
+ term.c.x = x;
+ term.c.y = y;
+}
+
+void
+tsetchar(char *c, Glyph *attr, int x, int y) {
+ static char *vt100_0[62] = { /* 0x41 - 0x7e */
+ "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
+ 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
+ 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
+ 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
+ "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
+ "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
+ "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
+ "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
+ };
+
+ /*
+ * The table is proudly stolen from rxvt.
+ */
+ if(term.trantbl[term.charset] == CS_GRAPHIC0) {
+ if(BETWEEN(c[0], 0x41, 0x7e) && vt100_0[c[0] - 0x41]) {
+ c = vt100_0[c[0] - 0x41];
+ }
+ }
+
+ if(term.line[y][x].mode & ATTR_WIDE) {
+ if(x+1 < term.col) {
+ term.line[y][x+1].c[0] = ' ';
+ term.line[y][x+1].mode &= ~ATTR_WDUMMY;
+ }
+ } else if(term.line[y][x].mode & ATTR_WDUMMY) {
+ term.line[y][x-1].c[0] = ' ';
+ term.line[y][x-1].mode &= ~ATTR_WIDE;
+ }
+
+ term.dirty[y] = 1;
+ term.line[y][x] = *attr;
+ memcpy(term.line[y][x].c, c, UTF_SIZ);
+}
+
+void
+tclearregion(int x1, int y1, int x2, int y2) {
+ int x, y, temp;
+
+ if(x1 > x2)
+ temp = x1, x1 = x2, x2 = temp;
+ if(y1 > y2)
+ temp = y1, y1 = y2, y2 = temp;
+
+ LIMIT(x1, 0, term.col-1);
+ LIMIT(x2, 0, term.col-1);
+ LIMIT(y1, 0, term.row-1);
+ LIMIT(y2, 0, term.row-1);
+
+ for(y = y1; y <= y2; y++) {
+ term.dirty[y] = 1;
+ for(x = x1; x <= x2; x++) {
+ if(selected(x, y))
+ selclear(NULL);
+ term.line[y][x] = term.c.attr;
+ memcpy(term.line[y][x].c, " ", 2);
+ }
+ }
+}
+
+void
+tdeletechar(int n) {
+ int dst, src, size;
+ Glyph *line;
+
+ LIMIT(n, 0, term.col - term.c.x);
+
+ dst = term.c.x;
+ src = term.c.x + n;
+ size = term.col - src;
+ line = term.line[term.c.y];
+
+ memmove(&line[dst], &line[src], size * sizeof(Glyph));
+ tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
+}
+
+void
+tinsertblank(int n) {
+ int dst, src, size;
+ Glyph *line;
+
+ LIMIT(n, 0, term.col - term.c.x);
+
+ dst = term.c.x + n;
+ src = term.c.x;
+ size = term.col - dst;
+ line = term.line[term.c.y];
+
+ memmove(&line[dst], &line[src], size * sizeof(Glyph));
+ tclearregion(src, term.c.y, dst - 1, term.c.y);
+}
+
+void
+tinsertblankline(int n) {
+ if(BETWEEN(term.c.y, term.top, term.bot))
+ tscrolldown(term.c.y, n);
+}
+
+void
+tdeleteline(int n) {
+ if(BETWEEN(term.c.y, term.top, term.bot))
+ tscrollup(term.c.y, n);
+}
+
+int32_t
+tdefcolor(int *attr, int *npar, int l) {
+ int32_t idx = -1;
+ uint r, g, b;
+
+ switch (attr[*npar + 1]) {
+ case 2: /* direct color in RGB space */
+ if (*npar + 4 >= l) {
+ fprintf(stderr,
+ "erresc(38): Incorrect number of parameters (%d)\n",
+ *npar);
+ break;
+ }
+ r = attr[*npar + 2];
+ g = attr[*npar + 3];
+ b = attr[*npar + 4];
+ *npar += 4;
+ if(!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255))
+ fprintf(stderr, "erresc: bad rgb color (%d,%d,%d)\n",
+ r, g, b);
+ else
+ idx = TRUECOLOR(r, g, b);
+ break;
+ case 5: /* indexed color */
+ if (*npar + 2 >= l) {
+ fprintf(stderr,
+ "erresc(38): Incorrect number of parameters (%d)\n",
+ *npar);
+ break;
+ }
+ *npar += 2;
+ if(!BETWEEN(attr[*npar], 0, 255))
+ fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]);
+ else
+ idx = attr[*npar];
+ break;
+ case 0: /* implemented defined (only foreground) */
+ case 1: /* transparent */
+ case 3: /* direct color in CMY space */
+ case 4: /* direct color in CMYK space */
+ default:
+ fprintf(stderr,
+ "erresc(38): gfx attr %d unknown\n", attr[*npar]);
+ break;
+ }
+
+ return idx;
+}
+
+void
+tsetattr(int *attr, int l) {
+ int i;
+ int32_t idx;