#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 <fontconfig/fontconfig.h>
#include <wchar.h>
-/* X11 */
-#include <X11/cursorfont.h>
-#include <X11/Xft/Xft.h>
-
#include "st.h"
#include "win.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 DEFAULT(a, b) (a) = (a) ? (a) : (b)
-#define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == '\177')
+#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) (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,
+ 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,
enum escape_state {
ESC_START = 1,
ESC_CSI = 2,
- ESC_STR = 4, /* OSC, PM, APC */
+ 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,
- 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;
+ 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 */
- 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;
-typedef struct {
- KeySym k;
- uint mask;
- char *s;
- /* three valued logic variables: 0 indifferent, 1 on, -1 off */
- signed char appkey; /* application keypad */
- signed char appcursor; /* application cursor */
- signed char crlf; /* crlf mode */
-} Key;
-
-/* function definitions used in config.h */
-static void clipcopy(const Arg *);
-static void clippaste(const Arg *);
-static void numlock(const Arg *);
-static void selpaste(const Arg *);
-static void printsel(const Arg *);
-static void printscreen(const Arg *) ;
-static void iso14755(const Arg *);
-static void toggleprinter(const Arg *);
-static void sendbreak(const Arg *);
-
-/* config.h for applying patches and the configuration. */
-#include "config.h"
-
-static void execsh(void);
-static void stty(void);
+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 tputtab(int);
static void tputc(Rune);
static void treset(void);
-static void tresize(int, int);
static void tscrollup(int, int);
static void tscrolldown(int, int);
-static void tsetattr(int *, int);
-static void tsetchar(Rune, Glyph *, 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, int *, int);
+static void tsetmode(int, int, const int *, int);
+static int twrite(const char *, int, int);
static void tfulldirt(void);
-static void techo(Rune);
static void tcontrolcode(uchar );
static void tdectest(char );
static void tdefutf8(char);
-static int32_t tdefcolor(int *, int *, int);
+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 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 */
-TermWindow win;
-Term term;
-Selection sel;
-int cmdfd;
-pid_t pid;
-char **opt_cmd = NULL;
-char *opt_class = NULL;
-char *opt_embed = NULL;
-char *opt_font = NULL;
-char *opt_io = NULL;
-char *opt_line = NULL;
-char *opt_name = NULL;
-char *opt_title = NULL;
-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};
-static Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000};
-static Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
-
-/* config.h array lengths */
-size_t colornamelen = LEN(colorname);
-size_t mshortcutslen = LEN(mshortcuts);
-size_t shortcutslen = LEN(shortcuts);
-size_t selmaskslen = LEN(selmasks);
+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)
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;
}
char *
-xstrdup(char *s)
+xstrdup(const char *s)
{
- if ((s = strdup(s)) == NULL)
- die("Out of memory\n");
+ char *p;
+
+ if ((p = strdup(s)) == NULL)
+ die("strdup: %s\n", strerror(errno));
- return s;
+ return p;
}
size_t
-utf8decode(char *c, Rune *u, size_t clen)
+utf8decode(const char *c, Rune *u, size_t clen)
{
size_t i, j, len, type;
Rune udecoded;
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)
{
return i;
}
-static const 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(const char **src)
{
- while (**src && !isprint(**src)) (*src)++;
- return *((*src)++);
+ while (**src && !isprint((unsigned char)**src))
+ (*src)++;
+ return **src ? *((*src)++) : '='; /* emulate padding if string ends */
}
char *
{
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);
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;
void
selinit(void)
{
- clock_gettime(CLOCK_MONOTONIC, &sel.tclick1);
- clock_gettime(CLOCK_MONOTONIC, &sel.tclick2);
sel.mode = SEL_IDLE;
sel.snap = 0;
sel.ob.x = -1;
- sel.primary = NULL;
- sel.clipboard = NULL;
}
int
-x2col(int x)
+tlinelen(int y)
{
- x -= borderpx;
- x /= win.cw;
+ int i = term.col;
+
+ if (term.line[y][i - 1].mode & ATTR_WRAP)
+ return i;
- return LIMIT(x, 0, term.col-1);
+ while (i > 0 && term.line[y][i - 1].u == ' ')
+ --i;
+
+ return i;
}
-int
-y2row(int y)
+void
+selstart(int col, int row, int snap)
{
- y -= borderpx;
- y /= win.ch;
+ 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();
- return LIMIT(y, 0, term.row-1);
+ if (sel.snap != 0)
+ sel.mode = SEL_READY;
+ tsetdirt(sel.nb.y, sel.ne.y);
}
-int
-tlinelen(int y)
+void
+selextend(int col, int row, int type, int done)
{
- int i = term.col;
+ int oldey, oldex, oldsby, oldsey, oldtype;
- if (term.line[y][i - 1].mode & ATTR_WRAP)
- return i;
+ if (sel.mode == SEL_IDLE)
+ return;
+ if (done && sel.mode == SEL_EMPTY) {
+ selclear();
+ return;
+ }
- while (i > 0 && term.line[y][i - 1].u == ' ')
- --i;
+ oldey = sel.oe.y;
+ oldex = sel.oe.x;
+ oldsby = sel.nb.y;
+ oldsey = sel.ne.y;
+ oldtype = sel.type;
- return i;
+ 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
int
selected(int x, int y)
{
- if (sel.mode == SEL_EMPTY)
+ if (sel.mode == SEL_EMPTY || sel.ob.x == -1 ||
+ sel.alt != IS_SET(MODE_ALTSCREEN))
return 0;
if (sel.type == SEL_RECTANGULAR)
{
int newx, newy, xt, yt;
int delim, prevdelim;
- Glyph *gp, *prevgp;
+ const Glyph *gp, *prevgp;
switch (sel.snap) {
case SNAP_WORD:
{
char *str, *ptr;
int y, bufsize, lastx, linelen;
- Glyph *gp, *last;
+ const Glyph *gp, *last;
if (sel.ob.x == -1)
return NULL;
* 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;
return str;
}
-void
-selpaste(const Arg *dummy)
-{
- xselpaste();
-}
-
-void
-clipcopy(const Arg *dummy)
-{
- xclipcopy();
-}
-
-void
-clippaste(const Arg *dummy)
-{
- xclippaste();
-}
-
void
selclear(void)
{
}
void
-execsh(void)
+execsh(char *cmd, char **args)
{
- char **args, *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;
-
- if (opt_cmd)
- prog = opt_cmd[0];
- else if (utmp)
+ 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;
- else
+ arg = NULL;
+ } else {
prog = sh;
- args = (opt_cmd) ? opt_cmd : (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(void)
+stty(char **args)
{
char cmd[_POSIX_ARG_MAX], **p, *q, *s;
size_t n, siz;
memcpy(cmd, stty_args, n);
q = cmd + n;
siz = sizeof(cmd) - n;
- for (p = opt_cmd; p && (s = *p); ++p) {
+ for (p = args; p && (s = *p); ++p) {
if ((n = strlen(s)) > siz-1)
die("stty parameter length too long\n");
*q++ = ' ';
}
*q = '\0';
if (system(cmd) != 0)
- perror("Couldn't call stty");
+ perror("Couldn't call stty");
}
-void
-ttynew(void)
+int
+ttynew(const char *line, char *cmd, const char *out, char **args)
{
int m, s;
- struct winsize w = {term.row, term.col, 0, 0};
- if (opt_io) {
+ if (out) {
term.mode |= MODE_PRINT;
- iofd = (!strcmp(opt_io, "-")) ?
- 1 : open(opt_io, O_WRONLY | O_CREAT, 0666);
+ iofd = (!strcmp(out, "-")) ?
+ 1 : open(out, O_WRONLY | O_CREAT, 0666);
if (iofd < 0) {
fprintf(stderr, "Error opening %s:%s\n",
- opt_io, strerror(errno));
+ out, strerror(errno));
}
}
- if (opt_line) {
- if ((cmdfd = open(opt_line, O_RDWR)) < 0)
- die("open line failed: %s\n", 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();
- return;
+ stty(args);
+ return cmdfd;
}
/* seems to work fine on linux, openbsd and freebsd */
- if (openpty(&m, &s, NULL, NULL, &w) < 0)
+ if (openpty(&m, &s, NULL, NULL, NULL) < 0)
die("openpty failed: %s\n", strerror(errno));
switch (pid = fork()) {
case -1:
- die("fork failed\n");
+ 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));
- close(s);
- close(m);
- execsh();
+ 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
{
static char buf[BUFSIZ];
static int buflen = 0;
- char *ptr;
- int charsize; /* size of utf8 char in bytes */
- Rune unicodep;
- 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));
+ ret = read(cmdfd, buf+buflen, LEN(buf)-buflen);
- buflen += ret;
- ptr = buf;
+ 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;
+ }
+}
- for (;;) {
- if (IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
- /* process a complete utf8 char */
- charsize = utf8decode(ptr, &unicodep, buflen);
- if (charsize == 0)
- break;
- tputc(unicodep);
- ptr += charsize;
- buflen -= charsize;
+void
+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 {
- if (buflen <= 0)
- break;
- tputc(*ptr++ & 0xFF);
- buflen--;
+ next = memchr(s, '\r', n);
+ DEFAULT(next, s + n);
+ ttywriteraw(s, next - s);
}
+ n -= next - s;
+ s = next;
}
- /* keep any uncomplete utf8 char for the next call */
- if (buflen > 0)
- memmove(buf, ptr, buflen);
-
- return ret;
}
void
-ttywrite(const char *s, size_t n)
+ttywriteraw(const char *s, size_t n)
{
fd_set wfd, rfd;
ssize_t r;
}
void
-ttysend(char *s, size_t n)
-{
- int len;
- char *t, *lim;
- Rune u;
-
- ttywrite(s, n);
- if (!IS_SET(MODE_ECHO))
- return;
-
- lim = &s[n];
- for (t = s; t < lim; t += len) {
- if (IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
- len = utf8decode(t, &u, n);
- } else {
- u = *t & 0xFF;
- len = 1;
- }
- if (len <= 0)
- break;
- techo(u);
- n -= len;
- }
-}
-
-void
-ttyresize(void)
+ttyresize(int tw, int th)
{
struct winsize w;
w.ws_row = term.row;
w.ws_col = term.col;
- w.ws_xpixel = win.tw;
- w.ws_ypixel = win.th;
+ 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)
{
{
term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
tresize(col, row);
- term.numlock = 1;
-
treset();
}
if (sel.ob.x == -1)
return;
- if (BETWEEN(sel.ob.y, orig, term.bot) || BETWEEN(sel.oe.y, orig, term.bot)) {
- if ((sel.ob.y += n) > term.bot || (sel.oe.y += n) < term.top) {
+ 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();
- return;
- }
- if (sel.type == SEL_RECTANGULAR) {
- if (sel.ob.y < term.top)
- sel.ob.y = term.top;
- if (sel.oe.y > term.bot)
- sel.oe.y = term.bot;
} else {
- if (sel.ob.y < term.top) {
- sel.ob.y = term.top;
- sel.ob.x = 0;
- }
- if (sel.oe.y > term.bot) {
- sel.oe.y = term.bot;
- sel.oe.x = term.col;
- }
+ selnormalize();
}
- selnormalize();
}
}
}
void
-tsetchar(Rune u, Glyph *attr, int x, int y)
+tsetchar(Rune u, const Glyph *attr, int x, int y)
{
- static char *vt100_0[62] = { /* 0x41 - 0x7e */
+ 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 */
}
int32_t
-tdefcolor(int *attr, int *npar, int l)
+tdefcolor(const int *attr, int *npar, int l)
{
int32_t idx = -1;
uint r, g, b;
}
void
-tsetattr(int *attr, int l)
+tsetattr(const int *attr, int l)
{
int i;
int32_t idx;
} else {
fprintf(stderr,
"erresc(default): gfx attr %d unknown\n",
- attr[i]), csidump();
+ attr[i]);
+ csidump();
}
break;
}
}
void
-tsetmode(int priv, int set, int *args, int narg)
+tsetmode(int priv, int set, const int *args, int narg)
{
- int *lim, mode;
- int alt;
+ int alt; const int *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);
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)
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
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",
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);
break;
case 'c': /* DA -- Device Attributes */
if (csiescseq.arg[0] == 0)
- ttywrite(vtiden, sizeof(vtiden) - 1);
+ 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 */
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);
break;
case 'n': /* DSR – Device Status Report (cursor position) */
if (csiescseq.arg[0] == 6) {
- len = snprintf(buf, sizeof(buf),"\033[%i;%iR",
+ 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 */
case ' ':
switch (csiescseq.mode[1]) {
case 'q': /* DECSCUSR -- Set Cursor Style */
- DEFAULT(csiescseq.arg[0], 1);
- if (!BETWEEN(csiescseq.arg[0], 0, 6)) {
+ if (xsetcursor(csiescseq.arg[0]))
goto unknown;
- }
- win.cursor = csiescseq.arg[0];
break;
default:
goto unknown;
void
csidump(void)
{
- int i;
+ size_t i;
uint c;
fprintf(stderr, "ESC[");
memset(&csiescseq, 0, sizeof(csiescseq));
}
+void
+osc_color_response(int num, int index, int is_osc4)
+{
+ int n;
+ char buf[32];
+ unsigned char r, g, b;
+
+ if (xgetcolor(is_osc4 ? num : index, &r, &g, &b)) {
+ fprintf(stderr, "erresc: failed to fetch %s color %d\n",
+ is_osc4 ? "osc4" : "osc",
+ is_osc4 ? num : index);
+ return;
+ }
+
+ n = snprintf(buf, sizeof buf, "\033]%s%d;rgb:%02x%02x/%02x%02x/%02x%02x\007",
+ is_osc4 ? "4;" : "", num, r, r, g, g, b, b);
+ if (n < 0 || n >= sizeof(buf)) {
+ fprintf(stderr, "error: %s while printing %s response\n",
+ n < 0 ? "snprintf failed" : "truncation occurred",
+ is_osc4 ? "osc4" : "osc");
+ } else {
+ ttywrite(buf, n, 1);
+ }
+}
+
void
strhandle(void)
{
- char *p = NULL;
+ char *p = NULL, *dec;
int j, narg, par;
+ const struct { int idx; char *str; } osc_table[] = {
+ { defaultfg, "foreground" },
+ { defaultbg, "background" },
+ { defaultcs, "cursor" }
+ };
term.esc &= ~(ESC_STR_END|ESC_STR);
strparse();
case ']': /* OSC -- Operating System Command */
switch (par) {
case 0:
+ if (narg > 1) {
+ xsettitle(strescseq.args[1]);
+ xseticontitle(strescseq.args[1]);
+ }
+ return;
case 1:
+ if (narg > 1)
+ xseticontitle(strescseq.args[1]);
+ return;
case 2:
if (narg > 1)
xsettitle(strescseq.args[1]);
return;
case 52:
- if (narg > 2) {
- char *dec;
-
+ if (narg > 2 && allowwindowops) {
dec = base64dec(strescseq.args[2]);
if (dec) {
- xsetsel(dec, CurrentTime);
- clipcopy(NULL);
+ xsetsel(dec);
+ xclipcopy();
} else {
fprintf(stderr, "erresc: invalid base64\n");
}
}
return;
+ case 10:
+ case 11:
+ case 12:
+ if (narg < 2)
+ break;
+ p = strescseq.args[1];
+ if ((j = par - 10) < 0 || j >= LEN(osc_table))
+ break; /* shouldn't be possible */
+
+ if (!strcmp(p, "?")) {
+ osc_color_response(par, osc_table[j].idx, 0);
+ } else if (xsetcolorname(osc_table[j].idx, p)) {
+ fprintf(stderr, "erresc: invalid %s color: %s\n",
+ osc_table[j].str, p);
+ } else {
+ tfulldirt();
+ }
+ return;
case 4: /* color set */
if (narg < 3)
break;
p = strescseq.args[2];
/* FALLTHROUGH */
- case 104: /* color reset, here p = NULL */
+ case 104: /* color reset */
j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
- if (xsetcolorname(j, p)) {
- fprintf(stderr, "erresc: invalid color %s\n", p);
+
+ if (p && !strcmp(p, "?")) {
+ osc_color_response(j, 0, 1);
+ } else 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();
+ tfulldirt();
}
return;
}
xsettitle(strescseq.args[0]);
return;
case 'P': /* DCS -- Device Control String */
- term.mode |= ESC_DCS;
case '_': /* APC -- Application Program Command */
case '^': /* PM -- Privacy Message */
return;
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
tprinter(char *s, size_t len)
{
if (iofd != -1 && xwrite(iofd, s, len) < 0) {
- fprintf(stderr, "Error writing in %s:%s\n",
- opt_io, strerror(errno));
+ perror("Error writing to output file");
close(iofd);
iofd = -1;
}
}
-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)
{
tdumpline(int n)
{
char buf[UTF_SIZ];
- Glyph *bp, *end;
+ const Glyph *bp, *end;
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);
term.c.x = LIMIT(x, 0, term.col-1);
}
-void
-techo(Rune u)
-{
- if (ISCONTROL(u)) { /* control code */
- if (u & 0x80) {
- u &= 0x7f;
- tputc('^');
- tputc('[');
- } else if (u != '\n' && u != '\r' && u != '\t') {
- u ^= 0x40;
- tputc('^');
- }
- }
- tputc(u);
-}
-
void
tdefutf8(char ascii)
{
void
tstrsequence(uchar c)
{
- strreset();
-
switch (c) {
case 0x90: /* DCS -- Device Control String */
c = 'P';
- term.esc |= ESC_DCS;
break;
case 0x9f: /* APC -- Application Program Command */
c = '_';
c = ']';
break;
}
+ strreset();
strescseq.type = c;
term.esc |= ESC_STR;
}
return;
case '\032': /* SUB */
tsetchar('?', &term.c.attr, term.c.x, term.c.y);
+ /* FALLTHROUGH */
case '\030': /* CAN */
csireset();
break;
case 0x99: /* TODO: SGCI */
break;
case 0x9a: /* DECID -- Identify Terminal */
- ttywrite(vtiden, sizeof(vtiden) - 1);
+ ttywrite(vtiden, strlen(vtiden), 0);
break;
case 0x9b: /* TODO: CSI */
case 0x9c: /* TODO: ST */
}
break;
case 'Z': /* DECID -- Identify Terminal */
- ttywrite(vtiden, sizeof(vtiden) - 1);
+ ttywrite(vtiden, strlen(vtiden), 0);
break;
- case 'c': /* RIS -- Reset to inital state */
+ case 'c': /* RIS -- Reset to initial state */
treset();
resettitle();
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);
Glyph *gp;
control = ISCONTROL(u);
- if (!IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
+ if (u < 127 || !IS_SET(MODE_UTF8)) {
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))
if (term.esc & ESC_STR) {
if (u == '\a' || u == 030 || u == 032 || u == 033 ||
ISCONTROLC1(u)) {
- term.esc &= ~(ESC_START|ESC_STR|ESC_DCS);
- if (IS_SET(MODE_SIXEL)) {
- /* TODO: render sixel */;
- term.mode &= ~MODE_SIXEL;
- return;
- }
+ term.esc &= ~(ESC_START|ESC_STR);
term.esc |= ESC_STR_END;
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);
/*
* control codes are not shown ever
*/
+ if (!term.esc)
+ term.lastc = 0;
return;
} else if (term.esc & ESC_START) {
if (term.esc & ESC_CSI) {
*/
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];
}
tsetchar(u, &term.c.attr, term.c.x, term.c.y);
+ term.lastc = u;
if (width == 2) {
gp->mode |= ATTR_WIDE;
if (term.c.x+1 < term.col) {
+ if (gp[1].mode == ATTR_WIDE && term.c.x+2 < term.col) {
+ gp[2].u = ' ';
+ gp[2].mode &= ~ATTR_WDUMMY;
+ }
gp[1].u = '\0';
gp[1].mode = ATTR_WDUMMY;
}
}
}
+int
+twrite(const char *buf, int buflen, int show_ctrl)
+{
+ int charsize;
+ Rune u;
+ int n;
+
+ for (n = 0; n < buflen; n += charsize) {
+ if (IS_SET(MODE_UTF8)) {
+ /* process a complete utf8 char */
+ charsize = utf8decode(buf + n, &u, buflen - n);
+ 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)
{
void
resettitle(void)
{
- xsettitle(opt_title ? opt_title : "st");
+ xsettitle(NULL);
}
void
-redraw(void)
+drawregion(int x1, int y1, int x2, int y2)
{
- tfulldirt();
- draw();
-}
+ int y;
-int
-match(uint mask, uint state)
-{
- return mask == XK_ANY_MOD || mask == (state & ~ignoremod);
-}
+ for (y = y1; y < y2; y++) {
+ if (!term.dirty[y])
+ continue;
-void
-numlock(const Arg *dummy)
-{
- term.numlock ^= 1;
+ term.dirty[y] = 0;
+ xdrawline(term.line[y], x1, y, x2);
+ }
}
-char*
-kmap(KeySym k, uint state)
+void
+draw(void)
{
- Key *kp;
- int i;
+ int cx = term.c.x, ocx = term.ocx, ocy = term.ocy;
- /* Check for mapped keys out of X11 function keys. */
- for (i = 0; i < LEN(mappedkeys); i++) {
- if (mappedkeys[i] == k)
- break;
- }
- if (i == LEN(mappedkeys)) {
- if ((k & 0xFFFF) < 0xFD00)
- return NULL;
- }
-
- for (kp = key; kp < key + LEN(key); kp++) {
- if (kp->k != k)
- continue;
-
- if (!match(kp->mask, state))
- continue;
-
- if (IS_SET(MODE_APPKEYPAD) ? kp->appkey < 0 : kp->appkey > 0)
- continue;
- if (term.numlock && kp->appkey == 2)
- continue;
-
- if (IS_SET(MODE_APPCURSOR) ? kp->appcursor < 0 : kp->appcursor > 0)
- continue;
-
- if (IS_SET(MODE_CRLF) ? kp->crlf < 0 : kp->crlf > 0)
- continue;
+ if (!xstartdraw())
+ return;
- return kp->s;
- }
+ /* 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--;
- return NULL;
+ 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();
+ if (ocx != term.ocx || ocy != term.ocy)
+ xximspot(term.ocx, term.ocy);
}
void
-cresize(int width, int height)
+redraw(void)
{
- int col, row;
-
- if (width != 0)
- win.w = width;
- if (height != 0)
- win.h = height;
-
- col = (win.w - 2 * borderpx) / win.cw;
- row = (win.h - 2 * borderpx) / win.ch;
-
- tresize(col, row);
- xresize(col, row);
+ tfulldirt();
+ draw();
}