Remove the ISO 14755 feature
[st.git] / st.c
diff --git a/st.c b/st.c
index 21cba9e..574dbee 100644 (file)
--- a/st.c
+++ b/st.c
@@ -3,24 +3,18 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <limits.h>
-#include <locale.h>
 #include <pwd.h>
 #include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <signal.h>
-#include <stdint.h>
 #include <sys/ioctl.h>
 #include <sys/select.h>
-#include <sys/stat.h>
-#include <sys/time.h>
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <termios.h>
-#include <time.h>
 #include <unistd.h>
-#include <libgen.h>
 #include <wchar.h>
 
 #include "st.h"
 
 /* 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
 
 /* macros */
-#define NUMMAXLEN(x)           ((int)(sizeof(x) * 2.56 + 0.5) + 1)
+#define IS_SET(flag)           ((term.mode & (flag)) != 0)
 #define ISCONTROLC0(c)         (BETWEEN(c, 0, 0x1f) || (c) == '\177')
 #define ISCONTROLC1(c)         (BETWEEN(c, 0x80, 0x9f))
 #define ISCONTROL(c)           (ISCONTROLC0(c) || ISCONTROLC1(c))
 #define ISDELIM(u)             (utf8strchr(worddelimiters, u) != NULL)
 
-/* constants */
-#define ISO14755CMD            "dmenu -w \"$WINDOWID\" -p codepoint: </dev/null"
+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,
+       MODE_SIXEL       = 1 << 7,
+};
 
 enum cursor_movement {
        CURSOR_SAVE,
@@ -83,6 +86,51 @@ enum escape_state {
        ESC_DCS        =128,
 };
 
+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 */
+       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;
+} Term;
+
 /* CSI Escape sequence structs */
 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
 typedef struct {
@@ -104,10 +152,10 @@ typedef struct {
        int narg;              /* nb of args */
 } STREscape;
 
-
-static void execsh(char **);
+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);
@@ -153,28 +201,31 @@ static int32_t 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 size_t utf8decode(const char *, Rune *, size_t);
 static Rune utf8decodebyte(char, size_t *);
 static char utf8encodebyte(Rune, size_t);
-static char *utf8strchr(char *s, Rune u);
+static char *utf8strchr(char *, Rune);
 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 */
-Term term;
-Selection sel;
-int cmdfd;
-pid_t pid;
-int oldbutton   = 3; /* button event on startup: 3 = release */
-
+static Term term;
+static Selection sel;
 static CSIEscape csiescseq;
 static STREscape strescseq;
 static int iofd = 1;
+static int cmdfd;
+static pid_t pid;
 
 static uchar utfbyte[UTF_SIZ + 1] = {0x80,    0, 0xC0, 0xE0, 0xF0};
 static uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
@@ -201,10 +252,10 @@ xwrite(int fd, const char *s, size_t len)
 void *
 xmalloc(size_t len)
 {
-       void *p = malloc(len);
+       void *p;
 
-       if (!p)
-               die("Out of memory\n");
+       if (!(p = malloc(len)))
+               die("malloc: %s\n", strerror(errno));
 
        return p;
 }
@@ -213,7 +264,7 @@ void *
 xrealloc(void *p, size_t len)
 {
        if ((p = realloc(p, len)) == NULL)
-               die("Out of memory\n");
+               die("realloc: %s\n", strerror(errno));
 
        return p;
 }
@@ -222,7 +273,7 @@ char *
 xstrdup(char *s)
 {
        if ((s = strdup(s)) == NULL)
-               die("Out of memory\n");
+               die("strdup: %s\n", strerror(errno));
 
        return s;
 }
@@ -391,6 +442,7 @@ 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;
@@ -402,16 +454,23 @@ selstart(int col, int row, int snap)
 }
 
 void
-selextend(int col, int row, int type)
+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.alt = IS_SET(MODE_ALTSCREEN);
        sel.oe.x = col;
        sel.oe.y = row;
        selnormalize();
@@ -419,6 +478,8 @@ selextend(int col, int row, int type)
 
        if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type)
                tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey));
+
+       sel.mode = done ? SEL_IDLE : SEL_READY;
 }
 
 void
@@ -614,7 +675,7 @@ die(const char *errstr, ...)
 }
 
 void
-execsh(char **args)
+execsh(char *cmd, char **args)
 {
        char *sh, *prog;
        const struct passwd *pw;
@@ -622,13 +683,13 @@ execsh(char **args)
        errno = 0;
        if ((pw = getpwuid(getuid())) == NULL) {
                if (errno)
-                       die("getpwuid:%s\n", strerror(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 : shell;
+               sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd;
 
        if (args)
                prog = args[0];
@@ -665,7 +726,7 @@ sigchld(int a)
        pid_t p;
 
        if ((p = waitpid(pid, &stat, WNOHANG)) < 0)
-               die("Waiting for pid %hd failed: %s\n", pid, strerror(errno));
+               die("waiting for pid %hd failed: %s\n", pid, strerror(errno));
 
        if (pid != p)
                return;
@@ -675,7 +736,6 @@ sigchld(int a)
        exit(0);
 }
 
-
 void
 stty(char **args)
 {
@@ -697,11 +757,11 @@ stty(char **args)
        }
        *q = '\0';
        if (system(cmd) != 0)
-           perror("Couldn't call stty");
+               perror("Couldn't call stty");
 }
 
-void
-ttynew(char *line, char *out, char **args)
+int
+ttynew(char *line, char *cmd, char *out, char **args)
 {
        int m, s;
 
@@ -717,10 +777,11 @@ ttynew(char *line, char *out, char **args)
 
        if (line) {
                if ((cmdfd = open(line, O_RDWR)) < 0)
-                       die("open line failed: %s\n", strerror(errno));
+                       die("open line '%s' failed: %s\n",
+                           line, strerror(errno));
                dup2(cmdfd, 0);
                stty(args);
-               return;
+               return cmdfd;
        }
 
        /* seems to work fine on linux, openbsd and freebsd */
@@ -729,7 +790,7 @@ ttynew(char *line, char *out, char **args)
 
        switch (pid = fork()) {
        case -1:
-               die("fork failed\n");
+               die("fork failed: %s\n", strerror(errno));
                break;
        case 0:
                close(iofd);
@@ -741,14 +802,23 @@ ttynew(char *line, char *out, char **args)
                        die("ioctl TIOCSCTTY failed: %s\n", strerror(errno));
                close(s);
                close(m);
-               execsh(args);
+#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
@@ -761,7 +831,7 @@ ttyread(void)
 
        /* 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));
+               die("couldn't read from shell: %s\n", strerror(errno));
        buflen += ret;
 
        written = twrite(buf, buflen, 0);
@@ -774,7 +844,35 @@ ttyread(void)
 }
 
 void
-ttywrite(const char *s, size_t n)
+ttywrite(const char *s, size_t n, int may_echo)
+{
+       const char *next;
+
+       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;
@@ -830,14 +928,6 @@ write_error:
        die("write error on tty: %s\n", strerror(errno));
 }
 
-void
-ttysend(char *s, size_t n)
-{
-       ttywrite(s, n);
-       if (IS_SET(MODE_ECHO))
-               twrite(s, n, 1);
-}
-
 void
 ttyresize(int tw, int th)
 {
@@ -851,6 +941,13 @@ ttyresize(int tw, int th)
                fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
 }
 
+void
+ttyhangup()
+{
+       /* Send SIGHUP to shell */
+       kill(pid, SIGHUP);
+}
+
 int
 tattrset(int attr)
 {
@@ -946,8 +1043,6 @@ tnew(int col, int row)
 {
        term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
        tresize(col, row);
-       term.numlock = 1;
-
        treset();
 }
 
@@ -1383,20 +1478,16 @@ tsetscroll(int t, int b)
 void
 tsetmode(int priv, int set, int *args, int narg)
 {
-       int *lim, mode;
-       int alt;
+       int alt, *lim;
 
        for (lim = args + narg; args < lim; ++args) {
                if (priv) {
                        switch (*args) {
                        case 1: /* DECCKM -- Cursor key */
-                               MODBIT(term.mode, set, MODE_APPCURSOR);
+                               xsetmode(set, MODE_APPCURSOR);
                                break;
                        case 5: /* DECSCNM -- Reverse video */
-                               mode = term.mode;
-                               MODBIT(term.mode, set, MODE_REVERSE);
-                               if (mode != term.mode)
-                                       redraw();
+                               xsetmode(set, MODE_REVERSE);
                                break;
                        case 6: /* DECOM -- Origin */
                                MODBIT(term.c.state, set, CURSOR_ORIGIN);
@@ -1416,36 +1507,36 @@ tsetmode(int priv, int set, int *args, int narg)
                        case 12: /* att610 -- Start blinking cursor (IGNORED) */
                                break;
                        case 25: /* DECTCEM -- Text Cursor Enable Mode */
-                               MODBIT(term.mode, !set, MODE_HIDE);
+                               xsetmode(!set, MODE_HIDE);
                                break;
                        case 9:    /* X10 mouse compatibility mode */
                                xsetpointermotion(0);
-                               MODBIT(term.mode, 0, MODE_MOUSE);
-                               MODBIT(term.mode, set, MODE_MOUSEX10);
+                               xsetmode(0, MODE_MOUSE);
+                               xsetmode(set, MODE_MOUSEX10);
                                break;
                        case 1000: /* 1000: report button press */
                                xsetpointermotion(0);
-                               MODBIT(term.mode, 0, MODE_MOUSE);
-                               MODBIT(term.mode, set, MODE_MOUSEBTN);
+                               xsetmode(0, MODE_MOUSE);
+                               xsetmode(set, MODE_MOUSEBTN);
                                break;
                        case 1002: /* 1002: report motion on button press */
                                xsetpointermotion(0);
-                               MODBIT(term.mode, 0, MODE_MOUSE);
-                               MODBIT(term.mode, set, MODE_MOUSEMOTION);
+                               xsetmode(0, MODE_MOUSE);
+                               xsetmode(set, MODE_MOUSEMOTION);
                                break;
                        case 1003: /* 1003: enable all mouse motions */
                                xsetpointermotion(set);
-                               MODBIT(term.mode, 0, MODE_MOUSE);
-                               MODBIT(term.mode, set, MODE_MOUSEMANY);
+                               xsetmode(0, MODE_MOUSE);
+                               xsetmode(set, MODE_MOUSEMANY);
                                break;
                        case 1004: /* 1004: send focus events to tty */
-                               MODBIT(term.mode, set, MODE_FOCUS);
+                               xsetmode(set, MODE_FOCUS);
                                break;
                        case 1006: /* 1006: extended reporting mode */
-                               MODBIT(term.mode, set, MODE_MOUSESGR);
+                               xsetmode(set, MODE_MOUSESGR);
                                break;
                        case 1034:
-                               MODBIT(term.mode, set, MODE_8BIT);
+                               xsetmode(set, MODE_8BIT);
                                break;
                        case 1049: /* swap screen & set/restore cursor as xterm */
                                if (!allowaltscreen)
@@ -1470,7 +1561,7 @@ tsetmode(int priv, int set, int *args, int narg)
                                tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
                                break;
                        case 2004: /* 2004: bracketed paste mode */
-                               MODBIT(term.mode, set, MODE_BRCKTPASTE);
+                               xsetmode(set, MODE_BRCKTPASTE);
                                break;
                        /* Not implemented mouse modes. See comments there. */
                        case 1001: /* mouse highlight mode; can hang the
@@ -1491,8 +1582,8 @@ tsetmode(int priv, int set, int *args, int narg)
                        switch (*args) {
                        case 0:  /* Error (IGNORED) */
                                break;
-                       case 2:  /* KAM -- keyboard action */
-                               MODBIT(term.mode, set, MODE_KBDLOCK);
+                       case 2:
+                               xsetmode(set, MODE_KBDLOCK);
                                break;
                        case 4:  /* IRM -- Insertion-replacement */
                                MODBIT(term.mode, set, MODE_INSERT);
@@ -1560,7 +1651,7 @@ csihandle(void)
                break;
        case 'c': /* DA -- Device Attributes */
                if (csiescseq.arg[0] == 0)
-                       ttywrite(vtiden, strlen(vtiden));
+                       ttywrite(vtiden, strlen(vtiden), 0);
                break;
        case 'C': /* CUF -- Cursor <n> Forward */
        case 'a': /* HPR -- Cursor <n> Forward */
@@ -1607,7 +1698,6 @@ csihandle(void)
                tputtab(csiescseq.arg[0]);
                break;
        case 'J': /* ED -- Clear screen */
-               selclear();
                switch (csiescseq.arg[0]) {
                case 0: /* below */
                        tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
@@ -1688,7 +1778,7 @@ csihandle(void)
                if (csiescseq.arg[0] == 6) {
                        len = snprintf(buf, sizeof(buf),"\033[%i;%iR",
                                        term.c.y+1, term.c.x+1);
-                       ttywrite(buf, len);
+                       ttywrite(buf, len, 0);
                }
                break;
        case 'r': /* DECSTBM -- Set Scrolling Region */
@@ -1887,28 +1977,6 @@ tprinter(char *s, size_t len)
        }
 }
 
-void
-iso14755(const Arg *arg)
-{
-       FILE *p;
-       char *us, *e, codepoint[9], uc[UTF_SIZ];
-       unsigned long utf32;
-
-       if (!(p = popen(ISO14755CMD, "r")))
-               return;
-
-       us = fgets(codepoint, sizeof(codepoint), p);
-       pclose(p);
-
-       if (!us || *us == '\0' || *us == '-' || strlen(us) > 7)
-               return;
-       if ((utf32 = strtoul(us, &e, 16)) == ULONG_MAX ||
-           (*e != '\n' && *e != '\0'))
-               return;
-
-       ttysend(uc, utf8encode(utf32, uc));
-}
-
 void
 toggleprinter(const Arg *arg)
 {
@@ -2119,7 +2187,7 @@ tcontrolcode(uchar ascii)
        case 0x99:   /* TODO: SGCI */
                break;
        case 0x9a:   /* DECID -- Identify Terminal */
-               ttywrite(vtiden, strlen(vtiden));
+               ttywrite(vtiden, strlen(vtiden), 0);
                break;
        case 0x9b:   /* TODO: CSI */
        case 0x9c:   /* TODO: ST */
@@ -2191,7 +2259,7 @@ eschandle(uchar ascii)
                }
                break;
        case 'Z': /* DECID -- Identify Terminal */
-               ttywrite(vtiden, strlen(vtiden));
+               ttywrite(vtiden, strlen(vtiden), 0);
                break;
        case 'c': /* RIS -- Reset to inital state */
                treset();
@@ -2199,10 +2267,10 @@ eschandle(uchar ascii)
                xloadcols();
                break;
        case '=': /* DECPAM -- Application keypad */
-               term.mode |= MODE_APPKEYPAD;
+               xsetmode(1, MODE_APPKEYPAD);
                break;
        case '>': /* DECPNM -- Normal keypad */
-               term.mode &= ~MODE_APPKEYPAD;
+               xsetmode(0, MODE_APPKEYPAD);
                break;
        case '7': /* DECSC -- Save Cursor */
                tcursor(CURSOR_SAVE);
@@ -2490,14 +2558,44 @@ resettitle(void)
 }
 
 void
-redraw(void)
+drawregion(int x1, int y1, int x2, int y2)
 {
-       tfulldirt();
-       draw();
+       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;
+
+       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 & ATTR_WDUMMY)
+               term.ocx--;
+       if (term.line[term.c.y][cx].mode & ATTR_WDUMMY)
+               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.ocx = cx, term.ocy = term.c.y;
+       xfinishdraw();
 }
 
 void
-numlock(const Arg *dummy)
+redraw(void)
 {
-       term.numlock ^= 1;
+       tfulldirt();
+       draw();
 }