#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
/* macros */
#define IS_SET(flag) ((term.mode & (flag)) != 0)
-#define NUMMAXLEN(x) ((int)(sizeof(x) * 2.56 + 0.5) + 1)
-#define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == '\177')
+#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) (utf8strchr(worddelimiters, u) != NULL)
-
-/* constants */
-#define ISO14755CMD "dmenu -w \"$WINDOWID\" -p codepoint: </dev/null"
+#define ISDELIM(u) (u && wcschr(worddelimiters, u))
enum term_mode {
MODE_WRAP = 1 << 0,
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 */
/* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
typedef struct {
char buf[ESC_BUF_SIZ]; /* raw string */
- int len; /* raw string length */
+ size_t len; /* raw string length */
char priv;
int arg[ESC_ARG_SIZ];
int narg; /* nb of args */
/* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
typedef struct {
char type; /* ESC type ... */
- char buf[STR_BUF_SIZ]; /* raw string */
- int len; /* raw string length */
+ 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 **);
+static void execsh(char *, char **);
static void stty(char **);
static void sigchld(int);
static void ttywriteraw(const char *, size_t);
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 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 */
-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};
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;
}
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;
}
xstrdup(char *s)
{
if ((s = strdup(s)) == NULL)
- die("Out of memory\n");
+ die("strdup: %s\n", strerror(errno));
return s;
}
return utfbyte[i] | (u & ~utfmask[i]);
}
-char *
-utf8strchr(char *s, Rune u)
-{
- Rune r;
- size_t i, j, len;
-
- len = strlen(s);
- for (i = 0, j = 0; i < len; i += j) {
- if (!(j = utf8decode(&s[i], &r, len - i)))
- break;
- if (r == u)
- return &(s[i]);
- }
-
- return NULL;
-}
-
size_t
utf8validate(Rune *u, size_t i)
{
char
base64dec_getc(const char **src)
{
- while (**src && !isprint(**src)) (*src)++;
- return *((*src)++);
+ while (**src && !isprint(**src))
+ (*src)++;
+ return **src ? *((*src)++) : '='; /* emulate padding if string ends */
}
char *
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;
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;
{
int oldey, oldex, oldsby, oldsey, oldtype;
- if (!sel.mode)
+ if (sel.mode == SEL_IDLE)
return;
if (done && sel.mode == SEL_EMPTY) {
selclear();
oldsey = sel.ne.y;
oldtype = sel.type;
- sel.alt = IS_SET(MODE_ALTSCREEN);
sel.oe.x = col;
sel.oe.y = row;
selnormalize();
sel.type = type;
- if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.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;
* st.
* FIXME: Fix the computer world.
*/
- if ((y < sel.ne.y || lastx >= linelen) && !(last->mode & ATTR_WRAP))
+ if ((y < sel.ne.y || lastx >= linelen) &&
+ (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR))
*ptr++ = '\n';
}
*ptr = 0;
}
void
-execsh(char **args)
+execsh(char *cmd, char **args)
{
- char *sh, *prog;
+ char *sh, *prog, *arg;
const struct passwd *pw;
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)
+ if (args) {
prog = args[0];
- else if (utmp)
+ arg = NULL;
+ } else if (scroll) {
+ prog = scroll;
+ arg = utmp ? utmp : sh;
+ } else if (utmp) {
prog = utmp;
- else
+ arg = NULL;
+ } else {
prog = sh;
- DEFAULT(args, ((char *[]) {prog, NULL}));
+ arg = NULL;
+ }
+ DEFAULT(args, ((char *[]) {prog, arg, NULL}));
unsetenv("COLUMNS");
unsetenv("LINES");
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;
- if (!WIFEXITED(stat) || WEXITSTATUS(stat))
- die("child finished with error '%d'\n", stat);
- exit(0);
+ 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)
{
}
*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;
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 */
switch (pid = fork()) {
case -1:
- die("fork failed\n");
+ die("fork failed: %s\n", strerror(errno));
break;
case 0:
close(iofd);
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
{
static char buf[BUFSIZ];
static int buflen = 0;
- int written;
- int ret;
+ int ret, written;
/* 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));
- buflen += ret;
+ ret = read(cmdfd, buf+buflen, LEN(buf)-buflen);
- written = twrite(buf, buflen, 0);
- buflen -= written;
- /* keep any uncomplete utf8 char for the next call */
- if (buflen > 0)
- memmove(buf, buf + written, 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;
- return ret;
+ }
}
void
fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
}
+void
+ttyhangup()
+{
+ /* Send SIGHUP to shell */
+ kill(pid, SIGHUP);
+}
+
int
tattrset(int attr)
{
} else {
fprintf(stderr,
"erresc(default): gfx attr %d unknown\n",
- attr[i]), csidump();
+ attr[i]);
+ csidump();
}
break;
}
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",
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);
void
csidump(void)
{
- int i;
+ size_t i;
uint c;
fprintf(stderr, "ESC[");
void
strhandle(void)
{
- char *p = NULL;
+ char *p = NULL, *dec;
int j, narg, par;
term.esc &= ~(ESC_STR_END|ESC_STR);
return;
case 52:
if (narg > 2) {
- char *dec;
-
dec = base64dec(strescseq.args[2]);
if (dec) {
xsetsel(dec);
case 104: /* color reset, here p = NULL */
j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
if (xsetcolorname(j, p)) {
- fprintf(stderr, "erresc: invalid color %s\n", 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
void
strdump(void)
{
- int i;
+ size_t i;
uint c;
fprintf(stderr, "ESC%c", strescseq.type);
void
strreset(void)
{
- memset(&strescseq, 0, sizeof(strescseq));
+ strescseq = (STREscape){
+ .buf = xrealloc(strescseq.buf, STR_BUF_SIZ),
+ .siz = STR_BUF_SIZ,
+ };
}
void
}
}
-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;
-
- ttywrite(uc, utf8encode(utf32, uc), 1);
-}
-
void
toggleprinter(const Arg *arg)
{
bp = &term.line[n][0];
end = &bp[MIN(tlinelen(n), term.col) - 1];
if (bp != end || bp->u != ' ') {
- for ( ;bp <= end; ++bp)
+ for ( ; bp <= end; ++bp)
tprinter(buf, utf8encode(bp->u, buf));
}
tprinter("\n", 1);
return;
case '\032': /* SUB */
tsetchar('?', &term.c.attr, term.c.x, term.c.y);
+ /* FALLTHROUGH */
case '\030': /* CAN */
csireset();
break;
case 'Z': /* DECID -- Identify Terminal */
ttywrite(vtiden, strlen(vtiden), 0);
break;
- case 'c': /* RIS -- Reset to inital state */
+ case 'c': /* RIS -- Reset to initial state */
treset();
resettitle();
xloadcols();
Glyph *gp;
control = ISCONTROL(u);
- if (!IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
+ if (u < 127 || !IS_SET(MODE_UTF8 | MODE_SIXEL)) {
c[0] = u;
width = len = 1;
} else {
len = utf8encode(u, c);
- if (!control && (width = wcwidth(u)) == -1) {
- memcpy(c, "\357\277\275", 4); /* UTF_INVALID */
+ if (!control && (width = wcwidth(u)) == -1)
width = 1;
- }
}
if (IS_SET(MODE_PRINT))
goto check_control_code;
}
-
if (IS_SET(MODE_SIXEL)) {
/* TODO: implement sixel mode */
return;
if (term.esc&ESC_DCS && strescseq.len == 0 && u == 'q')
term.mode |= MODE_SIXEL;
- if (strescseq.len+len >= sizeof(strescseq.buf)-1) {
+ 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
* term.esc = 0;
* strhandle();
*/
- return;
+ if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2)
+ return;
+ strescseq.siz *= 2;
+ strescseq.buf = xrealloc(strescseq.buf, strescseq.siz);
}
memmove(&strescseq.buf[strescseq.len], c, len);
*/
return;
}
- if (sel.ob.x != -1 && BETWEEN(term.c.y, sel.ob.y, sel.oe.y))
+ if (selected(term.c.x, term.c.y))
selclear();
gp = &term.line[term.c.y][term.c.x];
drawregion(int x1, int y1, int x2, int y2)
{
int y;
+
for (y = y1; y < y2; y++) {
if (!term.dirty[y])
continue;
void
draw(void)
{
- int cx = term.c.x;
+ int cx = term.c.x, ocx = term.ocx, ocy = term.ocy;
if (!xstartdraw())
return;
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;
+ term.ocx = cx;
+ term.ocy = term.c.y;
xfinishdraw();
+ if (ocx != term.ocx || ocy != term.ocy)
+ xximspot(term.ocx, term.ocy);
}
void