aboutsummaryrefslogtreecommitdiff
path: root/st.c
diff options
context:
space:
mode:
authorNikita Langer <nikitalanger@icloud.com>2026-04-06 12:04:31 +0200
committerNikita Langer <nikitalanger@icloud.com>2026-04-06 12:04:31 +0200
commit3a0bbcf5f60aab95bb37671bdb7ba0d781dc0f1d (patch)
treee5bbdfb3a333a3c1f1dda4fbe199f6ca37f996eb /st.c
downloadst-3a0bbcf5f60aab95bb37671bdb7ba0d781dc0f1d.tar.gz
st-3a0bbcf5f60aab95bb37671bdb7ba0d781dc0f1d.tar.bz2
st-3a0bbcf5f60aab95bb37671bdb7ba0d781dc0f1d.zip
hallo
Diffstat (limited to 'st.c')
-rwxr-xr-xst.c2801
1 files changed, 2801 insertions, 0 deletions
diff --git a/st.c b/st.c
new file mode 100755
index 0000000..97534b4
--- /dev/null
+++ b/st.c
@@ -0,0 +1,2801 @@
+/* See LICENSE for license details. */
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <pwd.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <sys/ioctl.h>
+#include <sys/select.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <termios.h>
+#include <unistd.h>
+#include <wchar.h>
+
+#include "st.h"
+#include "win.h"
+#define USE_ARGB (alpha != OPAQUE)
+
+#if defined(__linux)
+ #include <pty.h>
+#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
+ #include <util.h>
+#elif defined(__FreeBSD__) || defined(__DragonFly__)
+ #include <libutil.h>
+#endif
+
+/* Arbitrary sizes */
+#define UTF_INVALID 0xFFFD
+#define UTF_SIZ 4
+#define ESC_BUF_SIZ (128*UTF_SIZ)
+#define ESC_ARG_SIZ 16
+#define STR_BUF_SIZ ESC_BUF_SIZ
+#define STR_ARG_SIZ ESC_ARG_SIZ
+#define HISTSIZE 2000
+
+/* macros */
+#define IS_SET(flag) ((term.mode & (flag)) != 0)
+#define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f)
+#define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
+#define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
+#define ISDELIM(u) (u && wcschr(worddelimiters, u))
+#define TLINE(y) ((y) < term.scr ? term.hist[((y) + term.histi - \
+ term.scr + HISTSIZE + 1) % HISTSIZE] : \
+ term.line[(y) - term.scr])
+
+enum term_mode {
+ MODE_WRAP = 1 << 0,
+ MODE_INSERT = 1 << 1,
+ MODE_ALTSCREEN = 1 << 2,
+ MODE_CRLF = 1 << 3,
+ MODE_ECHO = 1 << 4,
+ MODE_PRINT = 1 << 5,
+ MODE_UTF8 = 1 << 6,
+};
+
+enum cursor_movement {
+ CURSOR_SAVE,
+ CURSOR_LOAD
+};
+
+enum cursor_state {
+ CURSOR_DEFAULT = 0,
+ CURSOR_WRAPNEXT = 1,
+ CURSOR_ORIGIN = 2
+};
+
+enum charset {
+ CS_GRAPHIC0,
+ CS_GRAPHIC1,
+ CS_UK,
+ CS_USA,
+ CS_MULTI,
+ CS_GER,
+ CS_FIN
+};
+
+enum escape_state {
+ ESC_START = 1,
+ ESC_CSI = 2,
+ ESC_STR = 4, /* DCS, OSC, PM, APC */
+ ESC_ALTCHARSET = 8,
+ ESC_STR_END = 16, /* a final string was encountered */
+ ESC_TEST = 32, /* Enter in test mode */
+ ESC_UTF8 = 64,
+};
+
+typedef struct {
+ Glyph attr; /* current char attributes */
+ int x;
+ int y;
+ char state;
+} TCursor;
+
+typedef struct {
+ int mode;
+ int type;
+ int snap;
+ /*
+ * Selection variables:
+ * nb – normalized coordinates of the beginning of the selection
+ * ne – normalized coordinates of the end of the selection
+ * ob – original coordinates of the beginning of the selection
+ * oe – original coordinates of the end of the selection
+ */
+ struct {
+ int x, y;
+ } nb, ne, ob, oe;
+
+ int alt;
+} Selection;
+
+/* Internal representation of the screen */
+typedef struct {
+ int row; /* nb row */
+ int col; /* nb col */
+ Line *line; /* screen */
+ Line *alt; /* alternate screen */
+ Line hist[HISTSIZE]; /* history buffer */
+ int histi; /* history index */
+ int scr; /* scroll back */
+ int *dirty; /* dirtyness of lines */
+ TCursor c; /* cursor */
+ int ocx; /* old cursor col */
+ int ocy; /* old cursor row */
+ int top; /* top scroll limit */
+ int bot; /* bottom scroll limit */
+ int mode; /* terminal mode flags */
+ int esc; /* escape state flags */
+ char trantbl[4]; /* charset table translation */
+ int charset; /* current charset */
+ int icharset; /* selected charset for sequence */
+ int *tabs;
+ Rune lastc; /* last printed char outside of sequence, 0 if control */
+} Term;
+
+/* CSI Escape sequence structs */
+/* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
+typedef struct {
+ char buf[ESC_BUF_SIZ]; /* raw string */
+ size_t len; /* raw string length */
+ char priv;
+ int arg[ESC_ARG_SIZ];
+ int narg; /* nb of args */
+ char mode[2];
+} CSIEscape;
+
+/* STR Escape sequence structs */
+/* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
+typedef struct {
+ char type; /* ESC type ... */
+ char *buf; /* allocated raw string */
+ size_t siz; /* allocation size */
+ size_t len; /* raw string length */
+ char *args[STR_ARG_SIZ];
+ int narg; /* nb of args */
+} STREscape;
+
+static void execsh(char *, char **);
+static void stty(char **);
+static void sigchld(int);
+static void ttywriteraw(const char *, size_t);
+
+static void csidump(void);
+static void csihandle(void);
+static void csiparse(void);
+static void csireset(void);
+static void osc_color_response(int, int, int);
+static int eschandle(uchar);
+static void strdump(void);
+static void strhandle(void);
+static void strparse(void);
+static void strreset(void);
+
+static void tprinter(char *, size_t);
+static void tdumpsel(void);
+static void tdumpline(int);
+static void tdump(void);
+static void tclearregion(int, int, int, int);
+static void tcursor(int);
+static void tdeletechar(int);
+static void tdeleteline(int);
+static void tinsertblank(int);
+static void tinsertblankline(int);
+static int tlinelen(int);
+static void tmoveto(int, int);
+static void tmoveato(int, int);
+static void tnewline(int);
+static void tputtab(int);
+static void tputc(Rune);
+static void treset(void);
+static void tscrollup(int, int, int);
+static void tscrolldown(int, int, int);
+static void tsetattr(const int *, int);
+static void tsetchar(Rune, const Glyph *, int, int);
+static void tsetdirt(int, int);
+static void tsetscroll(int, int);
+static void tswapscreen(void);
+static void tsetmode(int, int, const int *, int);
+static int twrite(const char *, int, int);
+static void tfulldirt(void);
+static void tcontrolcode(uchar );
+static void tdectest(char );
+static void tdefutf8(char);
+static int32_t tdefcolor(const int *, int *, int);
+static void tdeftran(char);
+static void tstrsequence(uchar);
+
+static void drawregion(int, int, int, int);
+
+static void selnormalize(void);
+static void selscroll(int, int);
+static void selsnap(int *, int *, int);
+
+static size_t utf8decode(const char *, Rune *, size_t);
+static Rune utf8decodebyte(char, size_t *);
+static char utf8encodebyte(Rune, size_t);
+static size_t utf8validate(Rune *, size_t);
+
+static char *base64dec(const char *);
+static char base64dec_getc(const char **);
+
+static ssize_t xwrite(int, const char *, size_t);
+
+/* Globals */
+static Term term;
+static Selection sel;
+static CSIEscape csiescseq;
+static STREscape strescseq;
+static int iofd = 1;
+static int cmdfd;
+static pid_t pid;
+
+static const uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
+static const uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
+static const Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000};
+static const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
+
+ssize_t
+xwrite(int fd, const char *s, size_t len)
+{
+ size_t aux = len;
+ ssize_t r;
+
+ while (len > 0) {
+ r = write(fd, s, len);
+ if (r < 0)
+ return r;
+ len -= r;
+ s += r;
+ }
+
+ return aux;
+}
+
+void *
+xmalloc(size_t len)
+{
+ void *p;
+
+ if (!(p = malloc(len)))
+ die("malloc: %s\n", strerror(errno));
+
+ return p;
+}
+
+void *
+xrealloc(void *p, size_t len)
+{
+ if ((p = realloc(p, len)) == NULL)
+ die("realloc: %s\n", strerror(errno));
+
+ return p;
+}
+
+char *
+xstrdup(const char *s)
+{
+ char *p;
+
+ if ((p = strdup(s)) == NULL)
+ die("strdup: %s\n", strerror(errno));
+
+ return p;
+}
+
+size_t
+utf8decode(const char *c, Rune *u, size_t clen)
+{
+ size_t i, j, len, type;
+ Rune udecoded;
+
+ *u = UTF_INVALID;
+ if (!clen)
+ return 0;
+ udecoded = utf8decodebyte(c[0], &len);
+ if (!BETWEEN(len, 1, UTF_SIZ))
+ return 1;
+ for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
+ udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
+ if (type != 0)
+ return j;
+ }
+ if (j < len)
+ return 0;
+ *u = udecoded;
+ utf8validate(u, len);
+
+ return len;
+}
+
+Rune
+utf8decodebyte(char c, size_t *i)
+{
+ for (*i = 0; *i < LEN(utfmask); ++(*i))
+ if (((uchar)c & utfmask[*i]) == utfbyte[*i])
+ return (uchar)c & ~utfmask[*i];
+
+ return 0;
+}
+
+size_t
+utf8encode(Rune u, char *c)
+{
+ size_t len, i;
+
+ len = utf8validate(&u, 0);
+ if (len > UTF_SIZ)
+ return 0;
+
+ for (i = len - 1; i != 0; --i) {
+ c[i] = utf8encodebyte(u, 0);
+ u >>= 6;
+ }
+ c[0] = utf8encodebyte(u, len);
+
+ return len;
+}
+
+char
+utf8encodebyte(Rune u, size_t i)
+{
+ return utfbyte[i] | (u & ~utfmask[i]);
+}
+
+size_t
+utf8validate(Rune *u, size_t i)
+{
+ if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF))
+ *u = UTF_INVALID;
+ for (i = 1; *u > utfmax[i]; ++i)
+ ;
+
+ return i;
+}
+
+char
+base64dec_getc(const char **src)
+{
+ while (**src && !isprint((unsigned char)**src))
+ (*src)++;
+ return **src ? *((*src)++) : '='; /* emulate padding if string ends */
+}
+
+char *
+base64dec(const char *src)
+{
+ size_t in_len = strlen(src);
+ char *result, *dst;
+ static const char base64_digits[256] = {
+ [43] = 62, 0, 0, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
+ 0, 0, 0, -1, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
+ 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0,
+ 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
+ 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
+ };
+
+ if (in_len % 4)
+ in_len += 4 - (in_len % 4);
+ result = dst = xmalloc(in_len / 4 * 3 + 1);
+ while (*src) {
+ int a = base64_digits[(unsigned char) base64dec_getc(&src)];
+ int b = base64_digits[(unsigned char) base64dec_getc(&src)];
+ int c = base64_digits[(unsigned char) base64dec_getc(&src)];
+ int d = base64_digits[(unsigned char) base64dec_getc(&src)];
+
+ /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */
+ if (a == -1 || b == -1)
+ break;
+
+ *dst++ = (a << 2) | ((b & 0x30) >> 4);
+ if (c == -1)
+ break;
+ *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2);
+ if (d == -1)
+ break;
+ *dst++ = ((c & 0x03) << 6) | d;
+ }
+ *dst = '\0';
+ return result;
+}
+
+void
+selinit(void)
+{
+ sel.mode = SEL_IDLE;
+ sel.snap = 0;
+ sel.ob.x = -1;
+}
+
+int
+tlinelen(int y)
+{
+ int i = term.col;
+
+ if (TLINE(y)[i - 1].mode & ATTR_WRAP)
+ return i;
+
+ while (i > 0 && TLINE(y)[i - 1].u == ' ')
+ --i;
+
+ return i;
+}
+
+void
+selstart(int col, int row, int snap)
+{
+ selclear();
+ sel.mode = SEL_EMPTY;
+ sel.type = SEL_REGULAR;
+ sel.alt = IS_SET(MODE_ALTSCREEN);
+ sel.snap = snap;
+ sel.oe.x = sel.ob.x = col;
+ sel.oe.y = sel.ob.y = row;
+ selnormalize();
+
+ if (sel.snap != 0)
+ sel.mode = SEL_READY;
+ tsetdirt(sel.nb.y, sel.ne.y);
+}
+
+void
+selextend(int col, int row, int type, int done)
+{
+ int oldey, oldex, oldsby, oldsey, oldtype;
+
+ if (sel.mode == SEL_IDLE)
+ return;
+ if (done && sel.mode == SEL_EMPTY) {
+ selclear();
+ return;
+ }
+
+ oldey = sel.oe.y;
+ oldex = sel.oe.x;
+ oldsby = sel.nb.y;
+ oldsey = sel.ne.y;
+ oldtype = sel.type;
+
+ sel.oe.x = col;
+ sel.oe.y = row;
+ selnormalize();
+ sel.type = type;
+
+ if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY)
+ tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey));
+
+ sel.mode = done ? SEL_IDLE : SEL_READY;
+}
+
+void
+selnormalize(void)
+{
+ int i;
+
+ if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) {
+ sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x;
+ sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x;
+ } else {
+ sel.nb.x = MIN(sel.ob.x, sel.oe.x);
+ sel.ne.x = MAX(sel.ob.x, sel.oe.x);
+ }
+ sel.nb.y = MIN(sel.ob.y, sel.oe.y);
+ sel.ne.y = MAX(sel.ob.y, sel.oe.y);
+
+ selsnap(&sel.nb.x, &sel.nb.y, -1);
+ selsnap(&sel.ne.x, &sel.ne.y, +1);
+
+ /* expand selection over line breaks */
+ if (sel.type == SEL_RECTANGULAR)
+ return;
+ i = tlinelen(sel.nb.y);
+ if (i < sel.nb.x)
+ sel.nb.x = i;
+ if (tlinelen(sel.ne.y) <= sel.ne.x)
+ sel.ne.x = term.col - 1;
+}
+
+int
+selected(int x, int y)
+{
+ if (sel.mode == SEL_EMPTY || sel.ob.x == -1 ||
+ sel.alt != IS_SET(MODE_ALTSCREEN))
+ return 0;
+
+ if (sel.type == SEL_RECTANGULAR)
+ return BETWEEN(y, sel.nb.y, sel.ne.y)
+ && BETWEEN(x, sel.nb.x, sel.ne.x);
+
+ return BETWEEN(y, sel.nb.y, sel.ne.y)
+ && (y != sel.nb.y || x >= sel.nb.x)
+ && (y != sel.ne.y || x <= sel.ne.x);
+}
+
+void
+selsnap(int *x, int *y, int direction)
+{
+ int newx, newy, xt, yt;
+ int delim, prevdelim;
+ const Glyph *gp, *prevgp;
+
+ switch (sel.snap) {
+ case SNAP_WORD:
+ /*
+ * Snap around if the word wraps around at the end or
+ * beginning of a line.
+ */
+ prevgp = &TLINE(*y)[*x];
+ prevdelim = ISDELIM(prevgp->u);
+ for (;;) {
+ newx = *x + direction;
+ newy = *y;
+ if (!BETWEEN(newx, 0, term.col - 1)) {
+ newy += direction;
+ newx = (newx + term.col) % term.col;
+ if (!BETWEEN(newy, 0, term.row - 1))
+ break;
+
+ if (direction > 0)
+ yt = *y, xt = *x;
+ else
+ yt = newy, xt = newx;
+ if (!(TLINE(yt)[xt].mode & ATTR_WRAP))
+ break;
+ }
+
+ if (newx >= tlinelen(newy))
+ break;
+
+ gp = &TLINE(newy)[newx];
+ delim = ISDELIM(gp->u);
+ if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
+ || (delim && gp->u != prevgp->u)))
+ break;
+
+ *x = newx;
+ *y = newy;
+ prevgp = gp;
+ prevdelim = delim;
+ }
+ break;
+ case SNAP_LINE:
+ /*
+ * Snap around if the the previous line or the current one
+ * has set ATTR_WRAP at its end. Then the whole next or
+ * previous line will be selected.
+ */
+ *x = (direction < 0) ? 0 : term.col - 1;
+ if (direction < 0) {
+ for (; *y > 0; *y += direction) {
+ if (!(TLINE(*y-1)[term.col-1].mode
+ & ATTR_WRAP)) {
+ break;
+ }
+ }
+ } else if (direction > 0) {
+ for (; *y < term.row-1; *y += direction) {
+ if (!(TLINE(*y)[term.col-1].mode
+ & ATTR_WRAP)) {
+ break;
+ }
+ }
+ }
+ break;
+ }
+}
+
+char *
+getsel(void)
+{
+ char *str, *ptr;
+ int y, bufsize, lastx, linelen;
+ const Glyph *gp, *last;
+
+ if (sel.ob.x == -1)
+ return NULL;
+
+ bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ;
+ ptr = str = xmalloc(bufsize);
+
+ /* append every set & selected glyph to the selection */
+ for (y = sel.nb.y; y <= sel.ne.y; y++) {
+ if ((linelen = tlinelen(y)) == 0) {
+ *ptr++ = '\n';
+ continue;
+ }
+
+ if (sel.type == SEL_RECTANGULAR) {
+ gp = &TLINE(y)[sel.nb.x];
+ lastx = sel.ne.x;
+ } else {
+ gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0];
+ lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
+ }
+ last = &TLINE(y)[MIN(lastx, linelen-1)];
+ while (last >= gp && last->u == ' ')
+ --last;
+
+ for ( ; gp <= last; ++gp) {
+ if (gp->mode & ATTR_WDUMMY)
+ continue;
+
+ ptr += utf8encode(gp->u, ptr);
+ }
+
+ /*
+ * Copy and pasting of line endings is inconsistent
+ * in the inconsistent terminal and GUI world.
+ * The best solution seems like to produce '\n' when
+ * something is copied from st and convert '\n' to
+ * '\r', when something to be pasted is received by
+ * st.
+ * FIXME: Fix the computer world.
+ */
+ if ((y < sel.ne.y || lastx >= linelen) &&
+ (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR))
+ *ptr++ = '\n';
+ }
+ *ptr = 0;
+ return str;
+}
+
+void
+selclear(void)
+{
+ if (sel.ob.x == -1)
+ return;
+ sel.mode = SEL_IDLE;
+ sel.ob.x = -1;
+ tsetdirt(sel.nb.y, sel.ne.y);
+}
+
+void
+die(const char *errstr, ...)
+{
+ va_list ap;
+
+ va_start(ap, errstr);
+ vfprintf(stderr, errstr, ap);
+ va_end(ap);
+ exit(1);
+}
+
+void
+execsh(char *cmd, char **args)
+{
+ char *sh, *prog, *arg;
+ const struct passwd *pw;
+
+ errno = 0;
+ if ((pw = getpwuid(getuid())) == NULL) {
+ if (errno)
+ die("getpwuid: %s\n", strerror(errno));
+ else
+ die("who are you?\n");
+ }
+
+ if ((sh = getenv("SHELL")) == NULL)
+ sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd;
+
+ if (args) {
+ prog = args[0];
+ arg = NULL;
+ } else if (scroll) {
+ prog = scroll;
+ arg = utmp ? utmp : sh;
+ } else if (utmp) {
+ prog = utmp;
+ arg = NULL;
+ } else {
+ prog = sh;
+ arg = NULL;
+ }
+ DEFAULT(args, ((char *[]) {prog, arg, NULL}));
+
+ unsetenv("COLUMNS");
+ unsetenv("LINES");
+ unsetenv("TERMCAP");
+ setenv("LOGNAME", pw->pw_name, 1);
+ setenv("USER", pw->pw_name, 1);
+ setenv("SHELL", sh, 1);
+ setenv("HOME", pw->pw_dir, 1);
+ setenv("TERM", termname, 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);
+
+ execvp(prog, args);
+ _exit(1);
+}
+
+void
+sigchld(int a)
+{
+ int stat;
+ pid_t p;
+
+ if ((p = waitpid(pid, &stat, WNOHANG)) < 0)
+ die("waiting for pid %hd failed: %s\n", pid, strerror(errno));
+
+ if (pid != p)
+ return;
+
+ if (WIFEXITED(stat) && WEXITSTATUS(stat))
+ die("child exited with status %d\n", WEXITSTATUS(stat));
+ else if (WIFSIGNALED(stat))
+ die("child terminated due to signal %d\n", WTERMSIG(stat));
+ _exit(0);
+}
+
+void
+stty(char **args)
+{
+ char cmd[_POSIX_ARG_MAX], **p, *q, *s;
+ size_t n, siz;
+
+ if ((n = strlen(stty_args)) > sizeof(cmd)-1)
+ die("incorrect stty parameters\n");
+ memcpy(cmd, stty_args, n);
+ q = cmd + n;
+ siz = sizeof(cmd) - n;
+ for (p = args; p && (s = *p); ++p) {
+ if ((n = strlen(s)) > siz-1)
+ die("stty parameter length too long\n");
+ *q++ = ' ';
+ memcpy(q, s, n);
+ q += n;
+ siz -= n + 1;
+ }
+ *q = '\0';
+ if (system(cmd) != 0)
+ perror("Couldn't call stty");
+}
+
+int
+ttynew(const char *line, char *cmd, const char *out, char **args)
+{
+ int m, s;
+
+ if (out) {
+ term.mode |= MODE_PRINT;
+ iofd = (!strcmp(out, "-")) ?
+ 1 : open(out, O_WRONLY | O_CREAT, 0666);
+ if (iofd < 0) {
+ fprintf(stderr, "Error opening %s:%s\n",
+ out, strerror(errno));
+ }
+ }
+
+ if (line) {
+ if ((cmdfd = open(line, O_RDWR)) < 0)
+ die("open line '%s' failed: %s\n",
+ line, strerror(errno));
+ dup2(cmdfd, 0);
+ stty(args);
+ return cmdfd;
+ }
+
+ /* seems to work fine on linux, openbsd and freebsd */
+ if (openpty(&m, &s, NULL, NULL, NULL) < 0)
+ die("openpty failed: %s\n", strerror(errno));
+
+ switch (pid = fork()) {
+ case -1:
+ die("fork failed: %s\n", strerror(errno));
+ break;
+ case 0:
+ close(iofd);
+ close(m);
+ setsid(); /* create a new process group */
+ dup2(s, 0);
+ dup2(s, 1);
+ dup2(s, 2);
+ if (ioctl(s, TIOCSCTTY, NULL) < 0)
+ die("ioctl TIOCSCTTY failed: %s\n", strerror(errno));
+ if (s > 2)
+ close(s);
+#ifdef __OpenBSD__
+ if (pledge("stdio getpw proc exec", NULL) == -1)
+ die("pledge\n");
+#endif
+ execsh(cmd, args);
+ break;
+ default:
+#ifdef __OpenBSD__
+ if (pledge("stdio rpath tty proc", NULL) == -1)
+ die("pledge\n");
+#endif
+ close(s);
+ cmdfd = m;
+ signal(SIGCHLD, sigchld);
+ break;
+ }
+ return cmdfd;
+}
+
+size_t
+ttyread(void)
+{
+ static char buf[BUFSIZ];
+ static int buflen = 0;
+ int ret, written;
+
+ /* append read bytes to unprocessed bytes */
+ ret = read(cmdfd, buf+buflen, LEN(buf)-buflen);
+
+ switch (ret) {
+ case 0:
+ exit(0);
+ case -1:
+ die("couldn't read from shell: %s\n", strerror(errno));
+ default:
+ buflen += ret;
+ written = twrite(buf, buflen, 0);
+ buflen -= written;
+ /* keep any incomplete UTF-8 byte sequence for the next call */
+ if (buflen > 0)
+ memmove(buf, buf + written, buflen);
+ return ret;
+ }
+}
+
+void
+ttywrite(const char *s, size_t n, int may_echo)
+{
+ const char *next;
+ Arg arg = (Arg) { .i = term.scr };
+
+ kscrolldown(&arg);
+
+ if (may_echo && IS_SET(MODE_ECHO))
+ twrite(s, n, 1);
+
+ if (!IS_SET(MODE_CRLF)) {
+ ttywriteraw(s, n);
+ return;
+ }
+
+ /* This is similar to how the kernel handles ONLCR for ttys */
+ while (n > 0) {
+ if (*s == '\r') {
+ next = s + 1;
+ ttywriteraw("\r\n", 2);
+ } else {
+ next = memchr(s, '\r', n);
+ DEFAULT(next, s + n);
+ ttywriteraw(s, next - s);
+ }
+ n -= next - s;
+ s = next;
+ }
+}
+
+void
+ttywriteraw(const char *s, size_t n)
+{
+ fd_set wfd, rfd;
+ ssize_t r;
+ size_t lim = 256;
+
+ /*
+ * Remember that we are using a pty, which might be a modem line.
+ * Writing too much will clog the line. That's why we are doing this
+ * dance.
+ * FIXME: Migrate the world to Plan 9.
+ */
+ while (n > 0) {
+ FD_ZERO(&wfd);
+ FD_ZERO(&rfd);
+ FD_SET(cmdfd, &wfd);
+ FD_SET(cmdfd, &rfd);
+
+ /* Check if we can write. */
+ if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) {
+ if (errno == EINTR)
+ continue;
+ die("select failed: %s\n", strerror(errno));
+ }
+ if (FD_ISSET(cmdfd, &wfd)) {
+ /*
+ * Only write the bytes written by ttywrite() or the
+ * default of 256. This seems to be a reasonable value
+ * for a serial line. Bigger values might clog the I/O.
+ */
+ if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0)
+ goto write_error;
+ if (r < n) {
+ /*
+ * We weren't able to write out everything.
+ * This means the buffer is getting full
+ * again. Empty it.
+ */
+ if (n < lim)
+ lim = ttyread();
+ n -= r;
+ s += r;
+ } else {
+ /* All bytes have been written. */
+ break;
+ }
+ }
+ if (FD_ISSET(cmdfd, &rfd))
+ lim = ttyread();
+ }
+ return;
+
+write_error:
+ die("write error on tty: %s\n", strerror(errno));
+}
+
+void
+ttyresize(int tw, int th)
+{
+ struct winsize w;
+
+ w.ws_row = term.row;
+ w.ws_col = term.col;
+ w.ws_xpixel = tw;
+ w.ws_ypixel = th;
+ if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
+ fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
+}
+
+void
+ttyhangup(void)
+{
+ /* Send SIGHUP to shell */
+ kill(pid, SIGHUP);
+}
+
+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];
+ int 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|MODE_UTF8;
+ memset(term.trantbl, CS_USA, sizeof(term.trantbl));
+ term.charset = 0;
+
+ for (i = 0; i < 2; i++) {
+ tmoveto(0, 0);
+ tcursor(CURSOR_SAVE);
+ tclearregion(0, 0, term.col-1, term.row-1);
+ tswapscreen();
+ }
+}
+
+void
+tnew(int col, int row)
+{
+ term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
+ tresize(col, row);
+ treset();
+}
+
+void
+tswapscreen(void)
+{
+ Line *tmp = term.line;
+
+ term.line = term.alt;
+ term.alt = tmp;
+ term.mode ^= MODE_ALTSCREEN;
+ tfulldirt();
+}
+
+void
+kscrolldown(const Arg* a)
+{
+ int n = a->i;
+
+ if (n < 0)
+ n = term.row + n;
+
+ if (n > term.scr)
+ n = term.scr;
+
+ if (term.scr > 0) {
+ term.scr -= n;
+ selscroll(0, -n);
+ tfulldirt();
+ }
+}
+
+void
+kscrollup(const Arg* a)
+{
+ int n = a->i;
+
+ if (n < 0)
+ n = term.row + n;
+
+ if (term.scr <= HISTSIZE-n) {
+ term.scr += n;
+ selscroll(0, n);
+ tfulldirt();
+ }
+}
+
+void
+tscrolldown(int orig, int n, int copyhist)
+{
+ int i;
+ Line temp;
+
+ LIMIT(n, 0, term.bot-orig+1);
+
+ if (copyhist) {
+ term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE;
+ temp = term.hist[term.histi];
+ term.hist[term.histi] = term.line[term.bot];
+ term.line[term.bot] = temp;
+ }
+
+ 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;
+ }
+
+ if (term.scr == 0)
+ selscroll(orig, n);
+}
+
+void
+tscrollup(int orig, int n, int copyhist)
+{
+ int i;
+ Line temp;
+
+ LIMIT(n, 0, term.bot-orig+1);
+
+ if (copyhist) {
+ term.histi = (term.histi + 1) % HISTSIZE;
+ temp = term.hist[term.histi];
+ term.hist[term.histi] = term.line[orig];
+ term.line[orig] = temp;
+ }
+
+ if (term.scr > 0 && term.scr < HISTSIZE)
+ term.scr = MIN(term.scr + n, HISTSIZE-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;
+ }
+
+ if (term.scr == 0)
+ selscroll(orig, -n);
+}
+
+void
+selscroll(int orig, int n)
+{
+ if (sel.ob.x == -1 || sel.alt != IS_SET(MODE_ALTSCREEN))
+ return;
+
+ if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) {
+ selclear();
+ } else if (BETWEEN(sel.nb.y, orig, term.bot)) {
+ sel.ob.y += n;
+ sel.oe.y += n;
+ if (sel.ob.y < term.top || sel.ob.y > term.bot ||
+ sel.oe.y < term.top || sel.oe.y > term.bot) {
+ selclear();
+ } else {
+ selnormalize();
+ }
+ }
+}
+
+void
+tnewline(int first_col)
+{
+ int y = term.c.y;
+
+ if (y == term.bot) {
+ tscrollup(term.top, 1, 1);
+ } else {
+ y++;
+ }
+ tmoveto(first_col ? 0 : term.c.x, y);
+}
+
+void
+csiparse(void)
+{
+ char *p = csiescseq.buf, *np;
+ long int v;
+ int sep = ';'; /* colon or semi-colon, but not both */
+
+ 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 (sep == ';' && *p == ':')
+ sep = ':'; /* allow override to colon once */
+ if (*p != sep || csiescseq.narg == ESC_ARG_SIZ)
+ break;
+ p++;
+ }
+ csiescseq.mode[0] = *p++;
+ csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0';
+}
+
+/* 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;
+ }
+ term.c.state &= ~CURSOR_WRAPNEXT;
+ term.c.x = LIMIT(x, 0, term.col-1);
+ term.c.y = LIMIT(y, miny, maxy);
+}
+
+void
+tsetchar(Rune u, const Glyph *attr, int x, int y)
+{
+ static const 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 &&
+ BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
+ utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ);
+
+ if (term.line[y][x].mode & ATTR_WIDE) {
+ if (x+1 < term.col) {
+ term.line[y][x+1].u = ' ';
+ term.line[y][x+1].mode &= ~ATTR_WDUMMY;
+ }
+ } else if (term.line[y][x].mode & ATTR_WDUMMY) {
+ term.line[y][x-1].u = ' ';
+ term.line[y][x-1].mode &= ~ATTR_WIDE;
+ }
+
+ term.dirty[y] = 1;
+ term.line[y][x] = *attr;
+ term.line[y][x].u = u;
+}
+
+void
+tclearregion(int x1, int y1, int x2, int y2)
+{
+ int x, y, temp;
+ Glyph *gp;
+
+ 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++) {
+ gp = &term.line[y][x];
+ if (selected(x, y))
+ selclear();
+ gp->fg = term.c.attr.fg;
+ gp->bg = term.c.attr.bg;
+ gp->mode = 0;
+ gp->u = ' ';
+ }
+ }
+}
+
+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