aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/term/term.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/term/term.c')
-rw-r--r--src/cmd/term/term.c2417
1 files changed, 2417 insertions, 0 deletions
diff --git a/src/cmd/term/term.c b/src/cmd/term/term.c
new file mode 100644
index 0000000..50ab29c
--- /dev/null
+++ b/src/cmd/term/term.c
@@ -0,0 +1,2417 @@
+/* See LICENSE for license details. */
+#include "term.h"
+
+#include <pwd.h>
+#include <termios.h>
+#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
+
+/* 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))
+
+/* forward declare functions */
+static void execsh(char *, char **);
+static void stty(char **);
+static void sigchld(int);
+static void ttywriteraw(char *, size_t);
+
+static void csidump(void);
+static void csihandle(void);
+static void csiparse(void);
+static void csireset(void);
+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);
+static void tscrolldown(int, int);
+static void tsetattr(int *, int);
+static void tsetchar(rune, Letter *, int, int);
+static void tsetdirt(int, int);
+static void tsetscroll(int, int);
+static void tswapscreen(void);
+static void tsetmode(int, int, int *, int);
+static int twrite(char *, int, int);
+static void tfulldirt(void);
+static void tcontrolcode(uchar );
+static void tdectest(char );
+static void tdefutf8(char);
+static int32 tdefcolor(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 char *base64dec(char *);
+static char base64dec_getc(char **);
+
+static uintptr xwrite(int, char *, size_t);
+extern int wcwidth(wchar_t wc);
+
+/* globals */
+static Terminal term;
+static Selection sel;
+static CSIEscape csiescseq;
+static STREscape strescseq;
+static int iofd = 1;
+static int cmdfd;
+static pid_t pid;
+
+/* functions */
+uintptr
+xwrite(int fd, 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)))
+ fatal("malloc: %s\n", strerror(errno));
+
+ return p;
+}
+
+void *
+xrealloc(void *p, size_t len)
+{
+ if ((p = realloc(p, len)) == nil)
+ fatal("realloc: %s\n", strerror(errno));
+
+ return p;
+}
+
+char *
+xstrdup(char *s)
+{
+ if ((s = strdup(s)) == nil)
+ fatal("strdup: %s\n", strerror(errno));
+
+ return s;
+}
+
+static char base64_digits[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+char
+base64dec_getc(char **src)
+{
+ while (**src && !isprint(**src))
+ (*src)++;
+ return **src ? *((*src)++) : '='; /* emulate padding if string ends */
+}
+
+char *
+base64dec(char *src)
+{
+ size_t in_len = strlen(src);
+ char *result, *dst;
+
+ 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 = SelIdle;
+ sel.snap = 0;
+ sel.ob.x = -1;
+}
+
+int
+tlinelen(int y)
+{
+ int i = term.col;
+
+ if (term.line[y][i - 1].mode & Gwrap)
+ return i;
+
+ while (i > 0 && term.line[y][i - 1].u == ' ')
+ --i;
+
+ return i;
+}
+
+void
+selstart(int col, int row, int snap)
+{
+ selclear();
+ sel.mode = SelEmpty;
+ sel.type = SelRegular;
+ sel.alt = IS_SET(Taltscreen);
+ sel.snap = snap;
+ sel.oe.x = sel.ob.x = col;
+ sel.oe.y = sel.ob.y = row;
+ selnormalize();
+
+ if (sel.snap != 0)
+ sel.mode = SelReady;
+ 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 == SelIdle)
+ return;
+ if (done && sel.mode == SelEmpty) {
+ 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 == SelEmpty)
+ tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey));
+
+ sel.mode = done ? SelIdle : SelReady;
+}
+
+void
+selnormalize(void)
+{
+ int i;
+
+ if (sel.type == SelRegular && 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 == SelRectangular)
+ 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 == SelEmpty || sel.ob.x == -1 ||
+ sel.alt != IS_SET(Taltscreen))
+ return 0;
+
+ if(sel.type == SelRectangular)
+ 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;
+ Letter *gp, *prevgp;
+
+ switch (sel.snap) {
+ case SnapWord:
+ /*
+ * Snap around if the word wraps around at the end or
+ * beginning of a line.
+ */
+ prevgp = &term.line[*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 (!(term.line[yt][xt].mode & Gwrap))
+ break;
+ }
+
+ if (newx >= tlinelen(newy))
+ break;
+
+ gp = &term.line[newy][newx];
+ delim = ISDELIM(gp->u);
+ if (!(gp->mode & Gwdummy) && (delim != prevdelim
+ || (delim && gp->u != prevgp->u)))
+ break;
+
+ *x = newx;
+ *y = newy;
+ prevgp = gp;
+ prevdelim = delim;
+ }
+ break;
+ case SnapLine:
+ /*
+ * 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 (!(term.line[*y-1][term.col-1].mode
+ & Gwrap)) {
+ break;
+ }
+ }
+ } else if (direction > 0) {
+ for (; *y < term.row-1; *y += direction) {
+ if (!(term.line[*y][term.col-1].mode
+ & Gwrap)) {
+ break;
+ }
+ }
+ }
+ break;
+ }
+}
+
+char *
+getsel(void)
+{
+ char *str, *ptr;
+ int y, bufsize, lastx, linelen;
+ Letter *gp, *last;
+
+ if (sel.ob.x == -1)
+ return nil;
+
+ bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTFmax;
+ 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 == SelRectangular) {
+ gp = &term.line[y][sel.nb.x];
+ lastx = sel.ne.x;
+ }else{
+ gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0];
+ lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
+ }
+ last = &term.line[y][MIN(lastx, linelen-1)];
+ while (last >= gp && last->u == ' ')
+ --last;
+
+ for ( ; gp <= last; ++gp) {
+ if (gp->mode & Gwdummy)
+ continue;
+
+ ptr += utf8·encode(&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 & Gwrap) || sel.type == SelRectangular))
+ *ptr++ = '\n';
+ }
+ *ptr = 0;
+ return str;
+}
+
+void
+selclear(void)
+{
+ if (sel.ob.x == -1)
+ return;
+ sel.mode = SelIdle;
+ sel.ob.x = -1;
+ tsetdirt(sel.nb.y, sel.ne.y);
+}
+
+void
+fatal(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;
+ struct passwd *pw;
+
+ errno = 0;
+ if ((pw = getpwuid(getuid())) == nil) {
+ if (errno)
+ fatal("getpwuid: %s\n", strerror(errno));
+ else
+ fatal("who are you?\n");
+ }
+
+ if ((sh = getenv("SHELL")) == nil)
+ sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd;
+
+ if (args) {
+ prog = args[0];
+ arg = nil;
+ } else if (scroll) {
+ prog = scroll;
+ arg = utmp ? utmp : sh;
+ } else if (utmp) {
+ prog = utmp;
+ arg = nil;
+ } else {
+ prog = sh;
+ arg = nil;
+ }
+ DEFAULT(args, ((char *[]) {prog, arg, nil}));
+
+ 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)
+ fatal("waiting for pid %hd failed: %s\n", pid, strerror(errno));
+
+ if (pid != p)
+ return;
+
+ if (WIFEXITED(stat) && WEXITSTATUS(stat))
+ fatal("child exited with status %d\n", WEXITSTATUS(stat));
+ else if (WIFSIGNALED(stat))
+ fatal("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)
+ fatal("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)
+ fatal("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(char *line, char *cmd, char *out, char **args)
+{
+ int m, s;
+
+ if (out) {
+ term.mode |= Tprint;
+ 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)
+ fatal("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, nil, nil, nil) < 0)
+ fatal("openpty failed: %s\n", strerror(errno));
+
+ switch (pid = fork()) {
+ case -1:
+ fatal("fork failed: %s\n", strerror(errno));
+ break;
+ case 0:
+ close(iofd);
+ setsid(); /* create a new process group */
+ dup2(s, 0);
+ dup2(s, 1);
+ dup2(s, 2);
+ if (ioctl(s, TIOCSCTTY, nil) < 0)
+ fatal("ioctl TIOCSCTTY failed: %s\n", strerror(errno));
+ close(s);
+ close(m);
+#ifdef __OpenBSD__
+ if (pledge("stdio getpw proc exec", nil) == -1)
+ fatal("pledge\n");
+#endif
+ execsh(cmd, args);
+ break;
+ default:
+#ifdef __OpenBSD__
+ if (pledge("stdio rpath tty proc", nil) == -1)
+ fatal("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, arrlen(buf)-buflen);
+
+ switch (ret) {
+ case 0:
+ exit(0);
+ case -1:
+ fatal("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(char *s, size_t n, int may_echo)
+{
+ char *next;
+
+ if (may_echo && IS_SET(Techo))
+ twrite(s, n, 1);
+
+ if (!IS_SET(Tcrlf)) {
+ 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(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, nil, nil, nil) < 0) {
+ if (errno == EINTR)
+ continue;
+ fatal("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:
+ fatal("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()
+{
+ /* 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 Dot c[2];
+ int alt = IS_SET(Taltscreen);
+
+ if (mode == CursorSave) {
+ c[alt] = term.c;
+ } else if (mode == CursorLoad) {
+ term.c = c[alt];
+ tmoveto(c[alt].x, c[alt].y);
+ }
+}
+
+void
+treset(void)
+{
+ uint i;
+
+ term.c = (Dot){{
+ .mode = Gnil,
+ .fg = defaultfg,
+ .bg = defaultbg
+ }, .x = 0, .y = 0, .state = CursorDefault};
+
+ 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 = Twrap|Tutf8;
+ memset(term.trantbl, CSusa, sizeof(term.trantbl));
+ term.charset = 0;
+
+ for (i = 0; i < 2; i++) {
+ tmoveto(0, 0);
+ tcursor(CursorSave);
+ tclearregion(0, 0, term.col-1, term.row-1);
+ tswapscreen();
+ }
+}
+
+void
+tnew(int col, int row)
+{
+ term = (Terminal){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
+ tresize(col, row);
+ treset();
+}
+
+void
+tswapscreen(void)
+{
+ Letter **tmp = term.line;
+
+ term.line = term.alt;
+ term.alt = tmp;
+ term.mode ^= Taltscreen;
+ tfulldirt();
+}
+
+void
+tscrolldown(int orig, int n)
+{
+ int i;
+ Letter *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;
+ Letter *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.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);
+ } 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 = nil;
+ 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[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 & CursorOrigin) ? term.top: 0));
+}
+
+void
+tmoveto(int x, int y)
+{
+ int miny, maxy;
+
+ if (term.c.state & CursorOrigin) {
+ miny = term.top;
+ maxy = term.bot;
+ } else {
+ miny = 0;
+ maxy = term.row - 1;
+ }
+ term.c.state &= ~CursorWrap;
+ term.c.x = LIMIT(x, 0, term.col-1);
+ term.c.y = LIMIT(y, miny, maxy);
+}
+
+void
+tsetchar(rune u, Letter *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 - ~ */
+ };
+
+ /*
+ * table is proudly stolen from rxvt.
+ */
+ if (term.trantbl[term.charset] == CSgfx0 &&
+ BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
+ utf8·decode(vt100_0[u - 0x41], &u);
+
+ if (term.line[y][x].mode & Gwide) {
+ if (x+1 < term.col) {
+ term.line[y][x+1].u = ' ';
+ term.line[y][x+1].mode &= ~Gwdummy;
+ }
+ } else if (term.line[y][x].mode & Gwdummy) {
+ term.line[y][x-1].u = ' ';
+ term.line[y][x-1].mode &= ~Gwide;
+ }
+
+ 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;
+ Letter *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;
+ Letter *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(Letter));
+ tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
+}
+
+void
+tinsertblank(int n)
+{
+ int dst, src, size;
+ Letter *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(Letter));
+ 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(38): bad rgb color (%u,%u,%u)\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 color %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;
+
+ for (i = 0; i < l; i++) {
+ switch (attr[i]) {
+ case 0:
+ term.c.attr.mode &= ~(
+ Gbold |
+ Gfaint |
+ Gitalic |
+ Gunline |
+ Gblink |
+ Greverse |
+ Ginvisible |
+ Gstruck );
+ term.c.attr.fg = defaultfg;
+ term.c.attr.bg = defaultbg;
+ break;
+ case 1:
+ term.c.attr.mode |= Gbold;
+ break;
+ case 2:
+ term.c.attr.mode |= Gfaint;
+ break;
+ case 3:
+ term.c.attr.mode |= Gitalic;
+ break;
+ case 4:
+ term.c.attr.mode |= Gunline;
+ break;
+ case 5: /* slow blink */
+ /* FALLTHROUGH */
+ case 6: /* rapid blink */
+ term.c.attr.mode |= Gblink;
+ break;
+ case 7:
+ term.c.attr.mode |= Greverse;
+ break;
+ case 8:
+ term.c.attr.mode |= Ginvisible;
+ break;
+ case 9:
+ term.c.attr.mode |= Gstruck;
+ break;
+ case 22:
+ term.c.attr.mode &= ~(Gbold | Gfaint);
+ break;
+ case 23:
+ term.c.attr.mode &= ~Gitalic;
+ break;
+ case 24:
+ term.c.attr.mode &= ~Gunline;
+ break;
+ case 25:
+ term.c.attr.mode &= ~Gblink;
+ break;
+ case 27:
+ term.c.attr.mode &= ~Greverse;
+ break;
+ case 28:
+ term.c.attr.mode &= ~Ginvisible;
+ break;
+ case 29:
+ term.c.attr.mode &= ~Gstruck;
+ break;
+ case 38:
+ if ((idx = tdefcolor(attr, &i, l)) >= 0)
+ term.c.attr.fg = idx;
+ break;
+ case 39:
+ term.c.attr.fg = defaultfg;
+ break;
+ case 48:
+ if ((idx = tdefcolor(attr, &i, l)) >= 0)
+ term.c.attr.bg = idx;
+ break;
+ case 49:
+ term.c.attr.bg = defaultbg;
+ break;
+ default:
+ if (BETWEEN(attr[i], 30, 37)) {
+ term.c.attr.fg = attr[i] - 30;
+ } else if (BETWEEN(attr[i], 40, 47)) {
+ term.c.attr.bg = attr[i] - 40;
+ } else if (BETWEEN(attr[i], 90, 97)) {
+ term.c.attr.fg = attr[i] - 90 + 8;
+ } else if (BETWEEN(attr[i], 100, 107)) {
+ term.c.attr.bg = attr[i] - 100 + 8;
+ } else {
+ fprintf(stderr,
+ "erresc(default): gfx attr %d unknown\n",
+ attr[i]);
+ csidump();
+ }
+ break;
+ }
+ }
+}
+
+void
+tsetscroll(int t, int b)
+{
+ int temp;
+
+ LIMIT(t, 0, term.row-1);
+ LIMIT(b, 0, term.row-1);
+ if (t > b) {
+ temp = t;
+ t = b;
+ b = temp;
+ }
+ term.top = t;
+ term.bot = b;
+}
+
+void
+tsetmode(int priv, int set, int *args, int narg)
+{
+ int alt, *lim;
+
+ for (lim = args + narg; args < lim; ++args) {
+ if (priv) {
+ switch (*args) {
+ case 1: /* DECCKM -- Cursor key */
+ xsetmode(set, Wappcursor);
+ break;
+ case 5: /* DECSCNM -- Reverse video */
+ xsetmode(set, Wreverse);
+ break;
+ case 6: /* DECOM -- Origin */
+ MODBIT(term.c.state, set, CursorOrigin);
+ tmoveato(0, 0);
+ break;
+ case 7: /* DECAWM -- Auto wrap */
+ MODBIT(term.mode, set, Twrap);
+ break;
+ case 0: /* Error (IGNORED) */
+ case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
+ case 3: /* DECCOLM -- Column (IGNORED) */
+ case 4: /* DECSCLM -- Scroll (IGNORED) */
+ case 8: /* DECARM -- Auto repeat (IGNORED) */
+ case 18: /* DECPFF -- Printer feed (IGNORED) */
+ case 19: /* DECPEX -- Printer extent (IGNORED) */
+ case 42: /* DECNRCM -- National characters (IGNORED) */
+ case 12: /* att610 -- Start blinking cursor (IGNORED) */
+ break;
+ case 25: /* DECTCEM -- Text Cursor Enable Mode */
+ xsetmode(!set, Whide);
+ break;
+ case 9: /* X10 mouse compatibility mode */
+ xsetpointermotion(0);
+ xsetmode(0, Wmouse);
+ xsetmode(set, Wmousex10);
+ break;
+ case 1000: /* 1000: report button press */
+ xsetpointermotion(0);
+ xsetmode(0, Wmouse);
+ xsetmode(set, Wmousebtn);
+ break;
+ case 1002: /* 1002: report motion on button press */
+ xsetpointermotion(0);
+ xsetmode(0, Wmouse);
+ xsetmode(set, Wmousemotion);
+ break;
+ case 1003: /* 1003: enable all mouse motions */
+ xsetpointermotion(set);
+ xsetmode(0, Wmouse);
+ xsetmode(set, Wmousemany);
+ break;
+ case 1004: /* 1004: send focus events to tty */
+ xsetmode(set, Wfocus);
+ break;
+ case 1006: /* 1006: extended reporting mode */
+ xsetmode(set, Wmousesgr);
+ break;
+ case 1034:
+ xsetmode(set, W8bit);
+ break;
+ case 1049: /* swap screen & set/restore cursor as xterm */
+ if (!allowaltscreen)
+ break;
+ tcursor((set) ? CursorSave : CursorLoad);
+ /* FALLTHROUGH */
+ case 47: /* swap screen */
+ case 1047:
+ if (!allowaltscreen)
+ break;
+ alt = IS_SET(Taltscreen);
+ if (alt) {
+ tclearregion(0, 0, term.col-1,
+ term.row-1);
+ }
+ if (set ^ alt) /* set is always 1 or 0 */
+ tswapscreen();
+ if (*args != 1049)
+ break;
+ /* FALLTHROUGH */
+ case 1048:
+ tcursor((set) ? CursorSave : CursorLoad);
+ break;
+ case 2004: /* 2004: bracketed paste mode */
+ xsetmode(set, Wbrcktpaste);
+ break;
+ /* Not implemented mouse modes. See comments there. */
+ case 1001: /* mouse highlight mode; can hang the
+ terminal by design when implemented. */
+ case 1005: /* UTF-8 mouse mode; will confuse
+ applications not supporting UTF-8
+ and luit. */
+ case 1015: /* urxvt mangled mouse mode; incompatible
+ and can be mistaken for other control
+ codes. */
+ break;
+ default:
+ fprintf(stderr,
+ "erresc: unknown private set/reset mode %d\n",
+ *args);
+ break;
+ }
+ } else {
+ switch (*args) {
+ case 0: /* Error (IGNORED) */
+ break;
+ case 2:
+ xsetmode(set, Wkbdblock);
+ break;
+ case 4: /* IRM -- Insertion-replacement */
+ MODBIT(term.mode, set, Tinsert);
+ break;
+ case 12: /* SRM -- Send/Receive */
+ MODBIT(term.mode, !set, Techo);
+ break;
+ case 20: /* LNM -- Linefeed/new line */
+ MODBIT(term.mode, set, Tcrlf);
+ break;
+ default:
+ fprintf(stderr,
+ "erresc: unknown set/reset mode %d\n",
+ *args);
+ break;
+ }
+ }
+ }
+}
+
+void
+csihandle(void)
+{
+ char buf[40];
+ int len;
+
+ switch (csiescseq.mode[0]) {
+ default:
+ unknown:
+ fprintf(stderr, "erresc: unknown csi ");
+ csidump();
+ /* fatal(""); */
+ break;
+ case '@': /* ICH -- Insert <n> blank char */
+ DEFAULT(csiescseq.arg[0], 1);
+ tinsertblank(csiescseq.arg[0]);
+ break;
+ case 'A': /* CUU -- Cursor <n> Up */
+ DEFAULT(csiescseq.arg[0], 1);
+ tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
+ break;
+ case 'B': /* CUD -- Cursor <n> Down */
+ case 'e': /* VPR --Cursor <n> Down */
+ DEFAULT(csiescseq.arg[0], 1);
+ tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
+ break;
+ case 'i': /* MC -- Media Copy */
+ switch (csiescseq.arg[0]) {
+ case 0:
+ tdump();
+ break;
+ case 1:
+ tdumpline(term.c.y);
+ break;
+ case 2:
+ tdumpsel();
+ break;
+ case 4:
+ term.mode &= ~Tprint;
+ break;
+ case 5:
+ term.mode |= Tprint;
+ break;
+ }
+ break;
+ case 'c': /* DA -- Device Attributes */
+ if (csiescseq.arg[0] == 0)
+ ttywrite(vtiden, strlen(vtiden), 0);
+ break;
+ case 'b': /* REP -- if last char is printable print it <n> more times */
+ DEFAULT(csiescseq.arg[0], 1);
+ if (term.lastc)
+ while (csiescseq.arg[0]-- > 0)
+ tputc(term.lastc);
+ break;
+ case 'C': /* CUF -- Cursor <n> Forward */
+ case 'a': /* HPR -- Cursor <n> Forward */
+ DEFAULT(csiescseq.arg[0], 1);
+ tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
+ break;
+ case 'D': /* CUB -- Cursor <n> Backward */
+ DEFAULT(csiescseq.arg[0], 1);
+ tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
+ break;
+ case 'E': /* CNL -- Cursor <n> Down and first col */
+ DEFAULT(csiescseq.arg[0], 1);
+ tmoveto(0, term.c.y+csiescseq.arg[0]);
+ break;
+ case 'F': /* CPL -- Cursor <n> Up and first col */
+ DEFAULT(csiescseq.arg[0], 1);
+ tmoveto(0, term.c.y-csiescseq.arg[0]);
+ break;
+ case 'g': /* TBC -- Tabulation clear */
+ switch (csiescseq.arg[0]) {
+ case 0: /* clear current tab stop */
+ term.tabs[term.c.x] = 0;
+ break;
+ case 3: /* clear all the tabs */
+ memset(term.tabs, 0, term.col * sizeof(*term.tabs));
+ break;
+ default:
+ goto unknown;
+ }
+ break;
+ case 'G': /* CHA -- Move to <col> */
+ case '`': /* HPA */
+ DEFAULT(csiescseq.arg[0], 1);
+ tmoveto(csiescseq.arg[0]-1, term.c.y);
+ break;
+ case 'H': /* CUP -- Move to <row> <col> */
+ case 'f': /* HVP */
+ DEFAULT(csiescseq.arg[0], 1);
+ DEFAULT(csiescseq.arg[1], 1);
+ tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
+ break;
+ case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
+ DEFAULT(csiescseq.arg[0], 1);
+ tputtab(csiescseq.arg[0]);
+ break;
+ case 'J': /* ED -- Clear screen */
+ switch (csiescseq.arg[0]) {
+ case 0: /* below */
+ tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
+ if (term.c.y < term.row-1) {
+ tclearregion(0, term.c.y+1, term.col-1,
+ term.row-1);
+ }
+ break;
+ case 1: /* above */
+ if (term.c.y > 1)
+ tclearregion(0, 0, term.col-1, term.c.y-1);
+ tclearregion(0, term.c.y, term.c.x, term.c.y);
+ break;
+ case 2: /* all */
+ tclearregion(0, 0, term.col-1, term.row-1);
+ break;
+ default:
+ goto unknown;
+ }
+ break;
+ case 'K': /* EL -- Clear line */
+ switch (csiescseq.arg[0]) {
+ case 0: /* right */
+ tclearregion(term.c.x, term.c.y, term.col-1,
+ term.c.y);
+ break;
+ case 1: /* left */
+ tclearregion(0, term.c.y, term.c.x, term.c.y);
+ break;
+ case 2: /* all */
+ tclearregion(0, term.c.y, term.col-1, term.c.y);
+ break;
+ }
+ break;
+ case 'S': /* SU -- Scroll <n> line up */
+ DEFAULT(csiescseq.arg[0], 1);
+ tscrollup(term.top, csiescseq.arg[0]);
+ break;
+ case 'T': /* SD -- Scroll <n> line down */
+ DEFAULT(csiescseq.arg[0], 1);
+ tscrolldown(term.top, csiescseq.arg[0]);
+ break;
+ case 'L': /* IL -- Insert <n> blank lines */
+ DEFAULT(csiescseq.arg[0], 1);
+ tinsertblankline(csiescseq.arg[0]);
+ break;
+ case 'l': /* RM -- Reset Mode */
+ tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
+ break;
+ case 'M': /* DL -- Delete <n> lines */
+ DEFAULT(csiescseq.arg[0], 1);
+ tdeleteline(csiescseq.arg[0]);
+ break;
+ case 'X': /* ECH -- Erase <n> char */
+ DEFAULT(csiescseq.arg[0], 1);
+ tclearregion(term.c.x, term.c.y,
+ term.c.x + csiescseq.arg[0] - 1, term.c.y);
+ break;
+ case 'P': /* DCH -- Delete <n> char */
+ DEFAULT(csiescseq.arg[0], 1);
+ tdeletechar(csiescseq.arg[0]);
+ break;
+ case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
+ DEFAULT(csiescseq.arg[0], 1);
+ tputtab(-csiescseq.arg[0]);
+ break;
+ case 'd': /* VPA -- Move to <row> */
+ DEFAULT(csiescseq.arg[0], 1);
+ tmoveato(term.c.x, csiescseq.arg[0]-1);
+ break;
+ case 'h': /* SM -- Set terminal mode */
+ tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
+ break;
+ case 'm': /* SGR -- Terminal attribute (color) */
+ tsetattr(csiescseq.arg, csiescseq.narg);
+ break;
+ case 'n': /* DSR – Device Status Report (cursor position) */
+ if (csiescseq.arg[0] == 6) {
+ len = snprintf(buf, sizeof(buf), "\033[%i;%iR", term.c.y+1, term.c.x+1);
+ ttywrite(buf, len, 0);
+ }
+ break;
+ case 'r': /* DECSTBM -- Set Scrolling Region */
+ if (csiescseq.priv) {
+ goto unknown;
+ } else {
+ DEFAULT(csiescseq.arg[0], 1);
+ DEFAULT(csiescseq.arg[1], term.row);
+ tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
+ tmoveato(0, 0);
+ }
+ break;
+ case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
+ tcursor(CursorSave);
+ break;
+ case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
+ tcursor(CursorLoad);
+ break;
+ case ' ':
+ switch (csiescseq.mode[1]) {
+ case 'q': /* DECSCUSR -- Set Cursor Style */
+ if (xsetcursor(csiescseq.arg[0]))
+ goto unknown;
+ break;
+ default:
+ goto unknown;
+ }
+ break;
+ }
+}
+
+void
+csidump(void)
+{
+ size_t i;
+ uint c;
+
+ fprintf(stderr, "ESC[");
+ for (i = 0; i < csiescseq.len; i++) {
+ c = csiescseq.buf[i] & 0xff;
+ if (isprint(c)) {
+ putc(c, stderr);
+ } else if (c == '\n') {
+ fprintf(stderr, "(\\n)");
+ } else if (c == '\r') {
+ fprintf(stderr, "(\\r)");
+ } else if (c == 0x1b) {
+ fprintf(stderr, "(\\e)");
+ } else {
+ fprintf(stderr, "(%02x)", c);
+ }
+ }
+ putc('\n', stderr);
+}
+
+void
+csireset(void)
+{
+ memset(&csiescseq, 0, sizeof(csiescseq));
+}
+
+void
+strhandle(void)
+{
+ char *p = nil, *dec;
+ int j, narg, par;
+
+ term.esc &= ~(Xstrend|Xstr);
+ strparse();
+ par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
+
+ switch (strescseq.type) {
+ case ']': /* OSC -- Operating System Command */
+ switch (par) {
+ case 0:
+ case 1:
+ case 2:
+ if (narg > 1)
+ xsettitle(strescseq.args[1]);
+ return;
+ case 52:
+ if (narg > 2 && allowwindowops) {
+ dec = base64dec(strescseq.args[2]);
+ if (dec) {
+ xsetsel(dec);
+ xclipcopy();
+ } else {
+ fprintf(stderr, "erresc: invalid base64\n");
+ }
+ }
+ return;
+ case 4: /* color set */
+ if (narg < 3)
+ break;
+ p = strescseq.args[2];
+ /* FALLTHROUGH */
+ case 104: /* color reset, here p = nil */
+ j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
+ if (xsetcolorname(j, p)) {
+ if (par == 104 && narg <= 1)
+ return; /* color reset without parameter */
+ fprintf(stderr, "erresc: invalid color j=%d, p=%s\n",
+ j, p ? p : "(null)");
+ } else {
+ /*
+ * TODO if defaultbg color is changed, borders
+ * are dirty
+ */
+ redraw();
+ }
+ return;
+ }
+ break;
+ case 'k': /* old title set compatibility */
+ xsettitle(strescseq.args[0]);
+ return;
+ case 'P': /* DCS -- Device Control String */
+ term.mode |= Xdcs;
+ case '_': /* APC -- Application Program Command */
+ case '^': /* PM -- Privacy Message */
+ return;
+ }
+
+ fprintf(stderr, "erresc: unknown str ");
+ strdump();
+}
+
+void
+strparse(void)
+{
+ int c;
+ char *p = strescseq.buf;
+
+ strescseq.narg = 0;
+ strescseq.buf[strescseq.len] = '\0';
+
+ if (*p == '\0')
+ return;
+
+ while (strescseq.narg < STR_ARG_SIZ) {
+ strescseq.args[strescseq.narg++] = p;
+ while ((c = *p) != ';' && c != '\0')
+ ++p;
+ if (c == '\0')
+ return;
+ *p++ = '\0';
+ }
+}
+
+void
+strdump(void)
+{
+ size_t i;
+ uint c;
+
+ fprintf(stderr, "ESC%c", strescseq.type);
+ for (i = 0; i < strescseq.len; i++) {
+ c = strescseq.buf[i] & 0xff;
+ if (c == '\0') {
+ putc('\n', stderr);
+ return;
+ } else if (isprint(c)) {
+ putc(c, stderr);
+ } else if (c == '\n') {
+ fprintf(stderr, "(\\n)");
+ } else if (c == '\r') {
+ fprintf(stderr, "(\\r)");
+ } else if (c == 0x1b) {
+ fprintf(stderr, "(\\e)");
+ } else {
+ fprintf(stderr, "(%02x)", c);
+ }
+ }
+ fprintf(stderr, "ESC\\\n");
+}
+
+void
+strreset(void)
+{
+ strescseq = (STREscape){
+ .buf = xrealloc(strescseq.buf, STR_BUF_SIZ),
+ .siz = STR_BUF_SIZ,
+ };
+}
+
+void
+sendbreak(Arg *arg)
+{
+ if (tcsendbreak(cmdfd, 0))
+ perror("Error sending break");
+}
+
+void
+tprinter(char *s, size_t len)
+{
+ if (iofd != -1 && xwrite(iofd, s, len) < 0) {
+ perror("Error writing to output file");
+ close(iofd);
+ iofd = -1;
+ }
+}
+
+void
+toggleprinter(Arg *arg)
+{
+ term.mode ^= Tprint;
+}
+
+void
+printscreen(Arg *arg)
+{
+ tdump();
+}
+
+void
+printsel(Arg *arg)
+{
+ tdumpsel();
+}
+
+void
+tdumpsel(void)
+{
+ char *ptr;
+
+ if ((ptr = getsel())) {
+ tprinter(ptr, strlen(ptr));
+ free(ptr);
+ }
+}
+
+void
+tdumpline(int n)
+{
+ char buf[UTFmax];
+ Letter *bp, *end;
+
+ bp = &term.line[n][0];
+ end = &bp[MIN(tlinelen(n), term.col) - 1];
+ if (bp != end || bp->u != ' ') {
+ for ( ; bp <= end; ++bp)
+ tprinter(buf, utf8·encode(&bp->u, buf));
+ }
+ tprinter("\n", 1);
+}
+
+void
+tdump(void)
+{
+ int i;
+
+ for (i = 0; i < term.row; ++i)
+ tdumpline(i);
+}
+
+void
+tputtab(int n)
+{
+ uint x = term.c.x;
+
+ if (n > 0) {
+ while (x < term.col && n--)
+ for (++x; x < term.col && !term.tabs[x]; ++x)
+ /* nothing */ ;
+ } else if (n < 0) {
+ while (x > 0 && n++)
+ for (--x; x > 0 && !term.tabs[x]; --x)
+ /* nothing */ ;
+ }
+ term.c.x = LIMIT(x, 0, term.col-1);
+}
+
+void
+tdefutf8(char ascii)
+{
+ if (ascii == 'G')
+ term.mode |= Tutf8;
+ else if (ascii == '@')
+ term.mode &= ~Tutf8;
+}
+
+void
+tdeftran(char ascii)
+{
+ static char cs[] = "0B";
+ static int vcs[] = {CSgfx0, CSusa};
+ char *p;
+
+ if ((p = strchr(cs, ascii)) == nil) {
+ fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
+ } else {
+ term.trantbl[term.icharset] = vcs[p - cs];
+ }
+}
+
+void
+tdectest(char c)
+{
+ int x, y;
+
+ if (c == '8') { /* DEC screen alignment test. */
+ for (x = 0; x < term.col; ++x) {
+ for (y = 0; y < term.row; ++y)
+ tsetchar('E', &term.c.attr, x, y);
+ }
+ }
+}
+
+void
+tstrsequence(uchar c)
+{
+ strreset();
+
+ switch (c) {
+ case 0x90: /* DCS -- Device Control String */
+ c = 'P';
+ term.esc |= Xdcs;
+ break;
+ case 0x9f: /* APC -- Application Program Command */
+ c = '_';
+ break;
+ case 0x9e: /* PM -- Privacy Message */
+ c = '^';
+ break;
+ case 0x9d: /* OSC -- Operating System Command */
+ c = ']';
+ break;
+ }
+ strescseq.type = c;
+ term.esc |= Xstr;
+}
+
+void
+tcontrolcode(uchar ascii)
+{
+ switch (ascii) {
+ case '\t': /* HT */
+ tputtab(1);
+ return;
+ case '\b': /* BS */
+ tmoveto(term.c.x-1, term.c.y);
+ return;
+ case '\r': /* CR */
+ tmoveto(0, term.c.y);
+ return;
+ case '\f': /* LF */
+ case '\v': /* VT */
+ case '\n': /* LF */
+ /* go to first col if the mode is set */
+ tnewline(IS_SET(Tcrlf));
+ return;
+ case '\a': /* BEL */
+ if (term.esc & Xstrend) {
+ /* backwards compatibility to xterm */
+ strhandle();
+ } else {
+ xbell();
+ }
+ break;
+ case '\033': /* ESC */
+ csireset();
+ term.esc &= ~(Xcsi|Xaltcs|Xtest);
+ term.esc |= Xstart;
+ return;
+ case '\016': /* SO (LS1 -- Locking shift 1) */
+ case '\017': /* SI (LS0 -- Locking shift 0) */
+ term.charset = 1 - (ascii - '\016');
+ return;
+ case '\032': /* SUB */
+ tsetchar('?', &term.c.attr, term.c.x, term.c.y);
+ /* FALLTHROUGH */
+ case '\030': /* CAN */
+ csireset();
+ break;
+ case '\005': /* ENQ (IGNORED) */
+ case '\000': /* NUL (IGNORED) */
+ case '\021': /* XON (IGNORED) */
+ case '\023': /* XOFF (IGNORED) */
+ case 0177: /* DEL (IGNORED) */
+ return;
+ case 0x80: /* TODO: PAD */
+ case 0x81: /* TODO: HOP */
+ case 0x82: /* TODO: BPH */
+ case 0x83: /* TODO: NBH */
+ case 0x84: /* TODO: IND */
+ break;
+ case 0x85: /* NEL -- Next line */
+ tnewline(1); /* always go to first col */
+ break;
+ case 0x86: /* TODO: SSA */
+ case 0x87: /* TODO: ESA */
+ break;
+ case 0x88: /* HTS -- Horizontal tab stop */
+ term.tabs[term.c.x] = 1;
+ break;
+ case 0x89: /* TODO: HTJ */
+ case 0x8a: /* TODO: VTS */
+ case 0x8b: /* TODO: PLD */
+ case 0x8c: /* TODO: PLU */
+ case 0x8d: /* TODO: RI */
+ case 0x8e: /* TODO: SS2 */
+ case 0x8f: /* TODO: SS3 */
+ case 0x91: /* TODO: PU1 */
+ case 0x92: /* TODO: PU2 */
+ case 0x93: /* TODO: STS */
+ case 0x94: /* TODO: CCH */
+ case 0x95: /* TODO: MW */
+ case 0x96: /* TODO: SPA */
+ case 0x97: /* TODO: EPA */
+ case 0x98: /* TODO: SOS */
+ case 0x99: /* TODO: SGCI */
+ break;
+ case 0x9a: /* DECID -- Identify Terminal */
+ ttywrite(vtiden, strlen(vtiden), 0);
+ break;
+ case 0x9b: /* TODO: CSI */
+ case 0x9c: /* TODO: ST */
+ break;
+ case 0x90: /* DCS -- Device Control String */
+ case 0x9d: /* OSC -- Operating System Command */
+ case 0x9e: /* PM -- Privacy Message */
+ case 0x9f: /* APC -- Application Program Command */
+ tstrsequence(ascii);
+ return;
+ }
+ /* only CAN, SUB, \a and C1 chars interrupt a sequence */
+ term.esc &= ~(Xstrend|Xstr);
+}
+
+/*
+ * returns 1 when the sequence is finished and it hasn't to read
+ * more characters for this sequence, otherwise 0
+ */
+int
+eschandle(uchar ascii)
+{
+ switch (ascii) {
+ case '[':
+ term.esc |= Xcsi;
+ return 0;
+ case '#':
+ term.esc |= Xtest;
+ return 0;
+ case '%':
+ term.esc |= Xutf8;
+ return 0;
+ case 'P': /* DCS -- Device Control String */
+ case '_': /* APC -- Application Program Command */
+ case '^': /* PM -- Privacy Message */
+ case ']': /* OSC -- Operating System Command */
+ case 'k': /* old title set compatibility */
+ tstrsequence(ascii);
+ return 0;
+ case 'n': /* LS2 -- Locking shift 2 */
+ case 'o': /* LS3 -- Locking shift 3 */
+ term.charset = 2 + (ascii - 'n');
+ break;
+ case '(': /* GZD4 -- set primary charset G0 */
+ case ')': /* G1D4 -- set secondary charset G1 */
+ case '*': /* G2D4 -- set tertiary charset G2 */
+ case '+': /* G3D4 -- set quaternary charset G3 */
+ term.icharset = ascii - '(';
+ term.esc |= Xaltcs;
+ return 0;
+ case 'D': /* IND -- Linefeed */
+ if (term.c.y == term.bot) {
+ tscrollup(term.top, 1);
+ } else {
+ tmoveto(term.c.x, term.c.y+1);
+ }
+ break;
+ case 'E': /* NEL -- Next line */
+ tnewline(1); /* always go to first col */
+ break;
+ case 'H': /* HTS -- Horizontal tab stop */
+ term.tabs[term.c.x] = 1;
+ break;
+ case 'M': /* RI -- Reverse index */
+ if (term.c.y == term.top) {
+ tscrolldown(term.top, 1);
+ } else {
+ tmoveto(term.c.x, term.c.y-1);
+ }
+ break;
+ case 'Z': /* DECID -- Identify Terminal */
+ ttywrite(vtiden, strlen(vtiden), 0);
+ break;
+ case 'c': /* RIS -- Reset to initial state */
+ treset();
+ resettitle();
+ xloadcols();
+ break;
+ case '=': /* DECPAM -- Application keypad */
+ xsetmode(1, Wappkeypad);
+ break;
+ case '>': /* DECPNM -- Normal keypad */
+ xsetmode(0, Wappkeypad);
+ break;
+ case '7': /* DECSC -- Save Cursor */
+ tcursor(CursorSave);
+ break;
+ case '8': /* DECRC -- Restore Cursor */
+ tcursor(CursorLoad);
+ break;
+ case '\\': /* ST -- String Terminator */
+ if (term.esc & Xstrend)
+ strhandle();
+ break;
+ default:
+ fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
+ (uchar) ascii, isprint(ascii)? ascii:'.');
+ break;
+ }
+ return 1;
+}
+
+void
+tputc(rune u)
+{
+ char c[UTFmax];
+ int control;
+ int width, len;
+ rune nu;
+ Letter *gp;
+
+ control = ISCONTROL(u);
+ if (u < 127 || !IS_SET(Tutf8 | Tsixel)) {
+ c[0] = u;
+ width = len = 1;
+ } else {
+ len = utf8·encode(&u, c);
+ if(!control && (width = wcwidth(u)) == -1)
+ width = 1;
+ }
+
+ /* combining characters */
+ if(!width){
+ if(term.c.x > 0)
+ gp = &term.line[term.c.y][term.c.x-1];
+ else if(term.c.y > 0)
+ gp = &term.line[term.c.y-1][term.col-1];
+ else
+ return;
+
+#if 0
+ if(!hb_unicode_compose(hb_unicode_funcs_get_default(),gp->u, u, &nu)) {
+ return;
+ }
+#endif
+
+ gp->u = nu;
+ return;
+ }
+
+ if (IS_SET(Tprint))
+ tprinter(c, len);
+
+ /*
+ * STR sequence must be checked before anything else
+ * because it uses all following characters until it
+ * receives a ESC, a SUB, a ST or any other C1 control
+ * character.
+ */
+ if(term.esc & Xstr) {
+ if (u == '\a' || u == 030 || u == 032 || u == 033 ||
+ ISCONTROLC1(u)) {
+ term.esc &= ~(Xstart|Xstr|Xdcs);
+ if (IS_SET(Tsixel)) {
+ /* TODO: render sixel */;
+ term.mode &= ~Tsixel;
+ return;
+ }
+ term.esc |= Xstrend;
+ goto check_control_code;
+ }
+
+ if(IS_SET(Tsixel)) {
+ /* TODO: implement sixel mode */
+ return;
+ }
+ if (term.esc&Xdcs && strescseq.len == 0 && u == 'q')
+ term.mode |= Tsixel;
+
+ if (strescseq.len+len >= strescseq.siz) {
+ /*
+ * Here is a bug in terminals. If the user never sends
+ * some code to stop the str or esc command, then st
+ * will stop responding. But this is better than
+ * silently failing with unknown characters. At least
+ * then users will report back.
+ *
+ * In the case users ever get fixed, here is the code:
+ */
+ /*
+ * term.esc = 0;
+ * strhandle();
+ */
+ if(strescseq.siz > (SIZE_MAX - UTFmax) / 2)
+ return;
+ strescseq.siz *= 2;
+ strescseq.buf = xrealloc(strescseq.buf, strescseq.siz);
+ }
+
+ memmove(&strescseq.buf[strescseq.len], c, len);
+ strescseq.len += len;
+ return;
+ }
+
+check_control_code:
+ /*
+ * Actions of control codes must be performed as soon they arrive
+ * because they can be embedded inside a control sequence, and
+ * they must not cause conflicts with sequences.
+ */
+ if(control) {
+ tcontrolcode(u);
+ /*
+ * control codes are not shown ever
+ */
+ if (!term.esc)
+ term.lastc = 0;
+ return;
+ } else if(term.esc & Xstart) {
+ if (term.esc & Xcsi) {
+ csiescseq.buf[csiescseq.len++] = u;
+ if (BETWEEN(u, 0x40, 0x7E)
+ || csiescseq.len >= \
+ sizeof(csiescseq.buf)-1) {
+ term.esc = 0;
+ csiparse();
+ csihandle();
+ }
+ return;
+ } else if (term.esc & Xutf8) {
+ tdefutf8(u);
+ } else if (term.esc & Xaltcs) {
+ tdeftran(u);
+ } else if (term.esc & Xtest) {
+ tdectest(u);
+ } else {
+ if (!eschandle(u))
+ return;
+ /* sequence already finished */
+ }
+ term.esc = 0;
+ /*
+ * All characters which form part of a sequence are not
+ * printed
+ */
+ return;
+ }
+
+ if(selected(term.c.x, term.c.y))
+ selclear();
+
+ gp = &term.line[term.c.y][term.c.x];
+ if(IS_SET(Twrap) && (term.c.state & CursorWrap)) {
+ gp->mode |= Gwrap;
+ tnewline(1);
+ gp = &term.line[term.c.y][term.c.x];
+ }
+
+ if(IS_SET(Tinsert) && term.c.x+width < term.col)
+ memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Letter));
+
+ if(term.c.x+width > term.col) {
+ tnewline(1);
+ gp = &term.line[term.c.y][term.c.x];
+ }
+
+ tsetchar(u, &term.c.attr, term.c.x, term.c.y);
+ term.lastc = u;
+
+ if(width == 2) {
+ gp->mode |= Gwrap;
+ if (term.c.x+1 < term.col) {
+ gp[1].u = '\0';
+ gp[1].mode = Gwdummy;
+ }
+ }
+ if(term.c.x+width < term.col) {
+ tmoveto(term.c.x+width, term.c.y);
+ }else{
+ term.c.state |= CursorWrap;
+ }
+}
+
+int
+twrite(char *buf, int buflen, int show_ctrl)
+{
+ int charsize;
+ rune u;
+ int n;
+
+ for (n = 0; n < buflen; n += charsize) {
+ if(IS_SET(Tutf8) && !IS_SET(Tsixel)) {
+ /* process a complete utf8 char */
+ charsize = utf8·decode(buf + n, &u);
+ if(charsize == 0)
+ break;
+ } else {
+ u = buf[n] & 0xFF;
+ charsize = 1;
+ }
+ if(show_ctrl && ISCONTROL(u)) {
+ if (u & 0x80) {
+ u &= 0x7f;
+ tputc('^');
+ tputc('[');
+ } else if (u != '\n' && u != '\r' && u != '\t') {
+ u ^= 0x40;
+ tputc('^');
+ }
+ }
+ tputc(u);
+ }
+ return n;
+}
+
+void
+tresize(int col, int row)
+{
+ int i;
+ int minrow = MIN(row, term.row);
+ int mincol = MIN(col, term.col);
+ int *bp;
+ Dot c;
+
+ if (col < 1 || row < 1) {
+ fprintf(stderr,
+ "tresize: error resizing to %dx%d\n", col, row);
+ return;
+ }
+
+ /*
+ * slide screen to keep cursor where we expect it -
+ * tscrollup would work here, but we can optimize to
+ * memmove because we're freeing the earlier lines
+ */
+ for (i = 0; i <= term.c.y - row; i++) {
+ free(term.line[i]);
+ free(term.alt[i]);
+ }
+ /* ensure that both src and dst are not nil */
+ if (i > 0) {
+ memmove(term.line, term.line + i, row * sizeof(Letter*));
+ memmove(term.alt, term.alt + i, row * sizeof(Letter*));
+ }
+ for (i += row; i < term.row; i++) {
+ free(term.line[i]);
+ free(term.alt[i]);
+ }
+
+ /* resize to new height */
+ term.line = xrealloc(term.line, row * sizeof(Letter*));
+ term.alt = xrealloc(term.alt, row * sizeof(Letter*));
+ term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
+ term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
+
+ /* resize each row to new width, zero-pad if needed */
+ for (i = 0; i < minrow; i++) {
+ term.line[i] = xrealloc(term.line[i], col * sizeof(Letter));
+ term.alt[i] = xrealloc(term.alt[i], col * sizeof(Letter));
+ }
+
+ /* allocate any new rows */
+ for (/* i = minrow */; i < row; i++) {
+ term.line[i] = xmalloc(col * sizeof(Letter));
+ term.alt[i] = xmalloc(col * sizeof(Letter));
+ }
+ if (col > term.col) {
+ bp = term.tabs + term.col;
+
+ memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
+ while (--bp > term.tabs && !*bp)
+ /* nothing */ ;
+ for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
+ *bp = 1;
+ }
+ /* update terminal size */
+ term.col = col;
+ term.row = row;
+ /* reset scrolling region */
+ tsetscroll(0, row-1);
+ /* make use of the LIMIT in tmoveto */
+ tmoveto(term.c.x, term.c.y);
+ /* Clearing both screens (it makes dirty all lines) */
+ c = term.c;
+ for (i = 0; i < 2; i++) {
+ if (mincol < col && 0 < minrow) {
+ tclearregion(mincol, 0, col - 1, minrow - 1);
+ }
+ if (0 < col && minrow < row) {
+ tclearregion(0, minrow, col - 1, row - 1);
+ }
+ tswapscreen();
+ tcursor(CursorLoad);
+ }
+ term.c = c;
+}
+
+void
+resettitle(void)
+{
+ xsettitle(nil);
+}
+
+void
+drawregion(int x1, int y1, int x2, int y2)
+{
+ int y;
+
+ for (y = y1; y < y2; y++) {
+ if (!term.dirty[y])
+ continue;
+
+ term.dirty[y] = 0;
+ xdrawline(term.line[y], x1, y, x2);
+ }
+}
+
+void
+draw(void)
+{
+ int cx = term.c.x, ocx = term.ocx, ocy = term.ocy;
+
+ if (!xstartdraw())
+ return;
+
+ /* adjust cursor position */
+ LIMIT(term.ocx, 0, term.col-1);
+ LIMIT(term.ocy, 0, term.row-1);
+ if (term.line[term.ocy][term.ocx].mode & Gwdummy)
+ term.ocx--;
+ if (term.line[term.c.y][cx].mode & Gwdummy)
+ cx--;
+
+ drawregion(0, 0, term.col, term.row);
+ xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
+ term.ocx, term.ocy, term.line[term.ocy][term.ocx],
+ term.line[term.ocy], term.col
+ );
+ term.ocx = cx;
+ term.ocy = term.c.y;
+ xfinishdraw();
+ if (ocx != term.ocx || ocy != term.ocy)
+ xximspot(term.ocx, term.ocy);
+}
+
+void
+redraw(void)
+{
+ tfulldirt();
+ draw();
+}