1 /* See LICENSE for license details. */
12 #include <sys/ioctl.h>
13 #include <sys/select.h>
14 #include <sys/types.h>
25 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
27 #elif defined(__FreeBSD__) || defined(__DragonFly__)
32 #define UTF_INVALID 0xFFFD
34 #define ESC_BUF_SIZ (128*UTF_SIZ)
35 #define ESC_ARG_SIZ 16
36 #define STR_BUF_SIZ ESC_BUF_SIZ
37 #define STR_ARG_SIZ ESC_ARG_SIZ
40 #define IS_SET(flag) ((term.mode & (flag)) != 0)
41 #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f)
42 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
43 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
44 #define ISDELIM(u) (u && wcschr(worddelimiters, u))
45 #define IS_SNAP_LINE_DELIM(u) (u && wcschr(snap_line_delimiters, u))
50 MODE_ALTSCREEN = 1 << 2,
57 enum cursor_movement {
81 ESC_STR = 4, /* DCS, OSC, PM, APC */
83 ESC_STR_END = 16, /* a final string was encountered */
84 ESC_TEST = 32, /* Enter in test mode */
89 Glyph attr; /* current char attributes */
100 * Selection variables:
101 * nb – normalized coordinates of the beginning of the selection
102 * ne – normalized coordinates of the end of the selection
103 * ob – original coordinates of the beginning of the selection
104 * oe – original coordinates of the end of the selection
113 /* Internal representation of the screen */
115 int row; /* nb row */
116 int col; /* nb col */
117 Line *line; /* screen */
118 Line *alt; /* alternate screen */
119 int *dirty; /* dirtyness of lines */
120 TCursor c; /* cursor */
121 int ocx; /* old cursor col */
122 int ocy; /* old cursor row */
123 int top; /* top scroll limit */
124 int bot; /* bottom scroll limit */
125 int mode; /* terminal mode flags */
126 int esc; /* escape state flags */
127 char trantbl[4]; /* charset table translation */
128 int charset; /* current charset */
129 int icharset; /* selected charset for sequence */
131 Rune lastc; /* last printed char outside of sequence, 0 if control */
134 /* CSI Escape sequence structs */
135 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
137 char buf[ESC_BUF_SIZ]; /* raw string */
138 size_t len; /* raw string length */
140 int arg[ESC_ARG_SIZ];
141 int narg; /* nb of args */
145 /* STR Escape sequence structs */
146 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
148 char type; /* ESC type ... */
149 char *buf; /* allocated raw string */
150 size_t siz; /* allocation size */
151 size_t len; /* raw string length */
152 char *args[STR_ARG_SIZ];
153 int narg; /* nb of args */
156 static void execsh(char *, char **);
157 static void stty(char **);
158 static void sigchld(int);
159 static void ttywriteraw(const char *, size_t);
161 static void csidump(void);
162 static void csihandle(void);
163 static void csiparse(void);
164 static void csireset(void);
165 static void osc_color_response(int, int, int);
166 static int eschandle(uchar);
167 static void strdump(void);
168 static void strhandle(void);
169 static void strparse(void);
170 static void strreset(void);
172 static void tprinter(char *, size_t);
173 static void tdumpsel(void);
174 static void tdumpline(int);
175 static void tdump(void);
176 static void tclearregion(int, int, int, int);
177 static void tcursor(int);
178 static void tdeletechar(int);
179 static void tdeleteline(int);
180 static void tinsertblank(int);
181 static void tinsertblankline(int);
182 static int tlinelen(int);
183 static void tmoveto(int, int);
184 static void tmoveato(int, int);
185 static void tnewline(int);
186 static void tputtab(int);
187 static void tputc(Rune);
188 static void treset(void);
189 static void tscrollup(int, int);
190 static void tscrolldown(int, int);
191 static void tsetattr(const int *, int);
192 static void tsetchar(Rune, const Glyph *, int, int);
193 static void tsetdirt(int, int);
194 static void tsetscroll(int, int);
195 static void tswapscreen(void);
196 static void tsetmode(int, int, const int *, int);
197 static int twrite(const char *, int, int);
198 static void tfulldirt(void);
199 static void tcontrolcode(uchar );
200 static void tdectest(char );
201 static void tdefutf8(char);
202 static int32_t tdefcolor(const int *, int *, int);
203 static void tdeftran(char);
204 static void tstrsequence(uchar);
206 static void drawregion(int, int, int, int);
208 static void selnormalize(void);
209 static void selscroll(int, int);
210 static void selsnap(int *, int *, int);
212 static size_t utf8decode(const char *, Rune *, size_t);
213 static Rune utf8decodebyte(char, size_t *);
214 static char utf8encodebyte(Rune, size_t);
215 static size_t utf8validate(Rune *, size_t);
217 static char *base64dec(const char *);
218 static char base64dec_getc(const char **);
220 static ssize_t xwrite(int, const char *, size_t);
224 static Selection sel;
225 static CSIEscape csiescseq;
226 static STREscape strescseq;
231 static const uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
232 static const uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
233 static const Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000};
234 static const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
237 xwrite(int fd, const char *s, size_t len)
243 r = write(fd, s, len);
258 if (!(p = malloc(len)))
259 die("malloc: %s\n", strerror(errno));
265 xrealloc(void *p, size_t len)
267 if ((p = realloc(p, len)) == NULL)
268 die("realloc: %s\n", strerror(errno));
274 xstrdup(const char *s)
278 if ((p = strdup(s)) == NULL)
279 die("strdup: %s\n", strerror(errno));
285 utf8decode(const char *c, Rune *u, size_t clen)
287 size_t i, j, len, type;
293 udecoded = utf8decodebyte(c[0], &len);
294 if (!BETWEEN(len, 1, UTF_SIZ))
296 for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
297 udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
304 utf8validate(u, len);
310 utf8decodebyte(char c, size_t *i)
312 for (*i = 0; *i < LEN(utfmask); ++(*i))
313 if (((uchar)c & utfmask[*i]) == utfbyte[*i])
314 return (uchar)c & ~utfmask[*i];
320 utf8encode(Rune u, char *c)
324 len = utf8validate(&u, 0);
328 for (i = len - 1; i != 0; --i) {
329 c[i] = utf8encodebyte(u, 0);
332 c[0] = utf8encodebyte(u, len);
338 utf8encodebyte(Rune u, size_t i)
340 return utfbyte[i] | (u & ~utfmask[i]);
344 utf8validate(Rune *u, size_t i)
346 if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF))
348 for (i = 1; *u > utfmax[i]; ++i)
355 base64dec_getc(const char **src)
357 while (**src && !isprint((unsigned char)**src))
359 return **src ? *((*src)++) : '='; /* emulate padding if string ends */
363 base64dec(const char *src)
365 size_t in_len = strlen(src);
367 static const char base64_digits[256] = {
368 [43] = 62, 0, 0, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
369 0, 0, 0, -1, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
370 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0,
371 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
372 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
376 in_len += 4 - (in_len % 4);
377 result = dst = xmalloc(in_len / 4 * 3 + 1);
379 int a = base64_digits[(unsigned char) base64dec_getc(&src)];
380 int b = base64_digits[(unsigned char) base64dec_getc(&src)];
381 int c = base64_digits[(unsigned char) base64dec_getc(&src)];
382 int d = base64_digits[(unsigned char) base64dec_getc(&src)];
384 /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */
385 if (a == -1 || b == -1)
388 *dst++ = (a << 2) | ((b & 0x30) >> 4);
391 *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2);
394 *dst++ = ((c & 0x03) << 6) | d;
413 if (term.line[y][i - 1].mode & ATTR_WRAP)
416 while (i > 0 && term.line[y][i - 1].u == ' ')
423 selstart(int col, int row, int snap)
426 sel.mode = SEL_EMPTY;
427 sel.type = SEL_REGULAR;
428 sel.alt = IS_SET(MODE_ALTSCREEN);
430 sel.oe.x = sel.ob.x = col;
431 sel.oe.y = sel.ob.y = row;
435 sel.mode = SEL_READY;
436 tsetdirt(sel.nb.y, sel.ne.y);
440 selextend(int col, int row, int type, int done)
442 int oldey, oldex, oldsby, oldsey, oldtype;
444 if (sel.mode == SEL_IDLE)
446 if (done && sel.mode == SEL_EMPTY) {
462 if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY)
463 tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey));
465 sel.mode = done ? SEL_IDLE : SEL_READY;
473 if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) {
474 sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x;
475 sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x;
477 sel.nb.x = MIN(sel.ob.x, sel.oe.x);
478 sel.ne.x = MAX(sel.ob.x, sel.oe.x);
480 sel.nb.y = MIN(sel.ob.y, sel.oe.y);
481 sel.ne.y = MAX(sel.ob.y, sel.oe.y);
483 selsnap(&sel.nb.x, &sel.nb.y, -1);
484 selsnap(&sel.ne.x, &sel.ne.y, +1);
486 /* expand selection over line breaks */
487 if (sel.type == SEL_RECTANGULAR)
489 i = tlinelen(sel.nb.y);
492 if (tlinelen(sel.ne.y) <= sel.ne.x)
493 sel.ne.x = term.col - 1;
497 selected(int x, int y)
499 if (sel.mode == SEL_EMPTY || sel.ob.x == -1 ||
500 sel.alt != IS_SET(MODE_ALTSCREEN))
503 if (sel.type == SEL_RECTANGULAR)
504 return BETWEEN(y, sel.nb.y, sel.ne.y)
505 && BETWEEN(x, sel.nb.x, sel.ne.x);
507 return BETWEEN(y, sel.nb.y, sel.ne.y)
508 && (y != sel.nb.y || x >= sel.nb.x)
509 && (y != sel.ne.y || x <= sel.ne.x);
513 selsnap(int *x, int *y, int direction)
515 int newx, newy, xt, yt;
516 int delim, prevdelim;
517 const Glyph *gp, *prevgp;
522 * Snap around if the word wraps around at the end or
523 * beginning of a line.
525 prevgp = &term.line[*y][*x];
526 prevdelim = ISDELIM(prevgp->u);
528 newx = *x + direction;
530 if (!BETWEEN(newx, 0, term.col - 1)) {
532 newx = (newx + term.col) % term.col;
533 if (!BETWEEN(newy, 0, term.row - 1))
539 yt = newy, xt = newx;
540 if (!(term.line[yt][xt].mode & ATTR_WRAP))
544 if (newx >= tlinelen(newy))
547 gp = &term.line[newy][newx];
548 delim = ISDELIM(gp->u);
549 if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
550 || (delim && gp->u != prevgp->u)))
561 * Don't snap past a line snap delimiter
562 * (see 'snap_line_delimiters' in config.h)
565 newx = *x + direction;
567 if (!BETWEEN(newx, 0, term.col - 1)) {
571 gp = &term.line[*y][newx];
573 if(IS_SNAP_LINE_DELIM(gp->u)) {
581 * Snap around if the the previous line or the current one
582 * has set ATTR_WRAP at its end. Then the whole next or
583 * previous line will be selected.
586 for (; *y > 0; *y += direction) {
587 if (!(term.line[*y-1][term.col-1].mode
592 } else if (direction > 0) {
593 for (; *y < term.row-1; *y += direction) {
594 if (!(term.line[*y][term.col-1].mode
608 int y, bufsize, lastx, linelen;
609 const Glyph *gp, *last;
614 bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ;
615 ptr = str = xmalloc(bufsize);
617 /* append every set & selected glyph to the selection */
618 for (y = sel.nb.y; y <= sel.ne.y; y++) {
619 if ((linelen = tlinelen(y)) == 0) {
624 if (sel.type == SEL_RECTANGULAR) {
625 gp = &term.line[y][sel.nb.x];
628 gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0];
629 lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
631 last = &term.line[y][MIN(lastx, linelen-1)];
632 while (last >= gp && last->u == ' ')
635 for ( ; gp <= last; ++gp) {
636 if (gp->mode & ATTR_WDUMMY)
639 ptr += utf8encode(gp->u, ptr);
643 * Copy and pasting of line endings is inconsistent
644 * in the inconsistent terminal and GUI world.
645 * The best solution seems like to produce '\n' when
646 * something is copied from st and convert '\n' to
647 * '\r', when something to be pasted is received by
649 * FIXME: Fix the computer world.
651 if ((y < sel.ne.y || lastx >= linelen) &&
652 (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR))
666 tsetdirt(sel.nb.y, sel.ne.y);
670 die(const char *errstr, ...)
674 va_start(ap, errstr);
675 vfprintf(stderr, errstr, ap);
681 execsh(char *cmd, char **args)
683 char *sh, *prog, *arg;
684 const struct passwd *pw;
687 if ((pw = getpwuid(getuid())) == NULL) {
689 die("getpwuid: %s\n", strerror(errno));
691 die("who are you?\n");
694 if ((sh = getenv("SHELL")) == NULL)
695 sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd;
702 arg = utmp ? utmp : sh;
710 DEFAULT(args, ((char *[]) {prog, arg, NULL}));
715 setenv("LOGNAME", pw->pw_name, 1);
716 setenv("USER", pw->pw_name, 1);
717 setenv("SHELL", sh, 1);
718 setenv("HOME", pw->pw_dir, 1);
719 setenv("TERM", termname, 1);
721 signal(SIGCHLD, SIG_DFL);
722 signal(SIGHUP, SIG_DFL);
723 signal(SIGINT, SIG_DFL);
724 signal(SIGQUIT, SIG_DFL);
725 signal(SIGTERM, SIG_DFL);
726 signal(SIGALRM, SIG_DFL);
738 if ((p = waitpid(pid, &stat, WNOHANG)) < 0)
739 die("waiting for pid %hd failed: %s\n", pid, strerror(errno));
744 if (WIFEXITED(stat) && WEXITSTATUS(stat))
745 die("child exited with status %d\n", WEXITSTATUS(stat));
746 else if (WIFSIGNALED(stat))
747 die("child terminated due to signal %d\n", WTERMSIG(stat));
754 char cmd[_POSIX_ARG_MAX], **p, *q, *s;
757 if ((n = strlen(stty_args)) > sizeof(cmd)-1)
758 die("incorrect stty parameters\n");
759 memcpy(cmd, stty_args, n);
761 siz = sizeof(cmd) - n;
762 for (p = args; p && (s = *p); ++p) {
763 if ((n = strlen(s)) > siz-1)
764 die("stty parameter length too long\n");
771 if (system(cmd) != 0)
772 perror("Couldn't call stty");
776 ttynew(const char *line, char *cmd, const char *out, char **args)
781 term.mode |= MODE_PRINT;
782 iofd = (!strcmp(out, "-")) ?
783 1 : open(out, O_WRONLY | O_CREAT, 0666);
785 fprintf(stderr, "Error opening %s:%s\n",
786 out, strerror(errno));
791 if ((cmdfd = open(line, O_RDWR)) < 0)
792 die("open line '%s' failed: %s\n",
793 line, strerror(errno));
799 /* seems to work fine on linux, openbsd and freebsd */
800 if (openpty(&m, &s, NULL, NULL, NULL) < 0)
801 die("openpty failed: %s\n", strerror(errno));
803 switch (pid = fork()) {
805 die("fork failed: %s\n", strerror(errno));
810 setsid(); /* create a new process group */
814 if (ioctl(s, TIOCSCTTY, NULL) < 0)
815 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno));
819 if (pledge("stdio getpw proc exec", NULL) == -1)
826 if (pledge("stdio rpath tty proc", NULL) == -1)
831 signal(SIGCHLD, sigchld);
840 static char buf[BUFSIZ];
841 static int buflen = 0;
844 /* append read bytes to unprocessed bytes */
845 ret = read(cmdfd, buf+buflen, LEN(buf)-buflen);
851 die("couldn't read from shell: %s\n", strerror(errno));
854 written = twrite(buf, buflen, 0);
856 /* keep any incomplete UTF-8 byte sequence for the next call */
858 memmove(buf, buf + written, buflen);
864 ttywrite(const char *s, size_t n, int may_echo)
868 if (may_echo && IS_SET(MODE_ECHO))
871 if (!IS_SET(MODE_CRLF)) {
876 /* This is similar to how the kernel handles ONLCR for ttys */
880 ttywriteraw("\r\n", 2);
882 next = memchr(s, '\r', n);
883 DEFAULT(next, s + n);
884 ttywriteraw(s, next - s);
892 ttywriteraw(const char *s, size_t n)
899 * Remember that we are using a pty, which might be a modem line.
900 * Writing too much will clog the line. That's why we are doing this
902 * FIXME: Migrate the world to Plan 9.
910 /* Check if we can write. */
911 if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) {
914 die("select failed: %s\n", strerror(errno));
916 if (FD_ISSET(cmdfd, &wfd)) {
918 * Only write the bytes written by ttywrite() or the
919 * default of 256. This seems to be a reasonable value
920 * for a serial line. Bigger values might clog the I/O.
922 if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0)
926 * We weren't able to write out everything.
927 * This means the buffer is getting full
935 /* All bytes have been written. */
939 if (FD_ISSET(cmdfd, &rfd))
945 die("write error on tty: %s\n", strerror(errno));
949 ttyresize(int tw, int th)
957 if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
958 fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
964 /* Send SIGHUP to shell */
973 for (i = 0; i < term.row-1; i++) {
974 for (j = 0; j < term.col-1; j++) {
975 if (term.line[i][j].mode & attr)
984 tsetdirt(int top, int bot)
988 LIMIT(top, 0, term.row-1);
989 LIMIT(bot, 0, term.row-1);
991 for (i = top; i <= bot; i++)
996 tsetdirtattr(int attr)
1000 for (i = 0; i < term.row-1; i++) {
1001 for (j = 0; j < term.col-1; j++) {
1002 if (term.line[i][j].mode & attr) {
1013 tsetdirt(0, term.row-1);
1019 static TCursor c[2];
1020 int alt = IS_SET(MODE_ALTSCREEN);
1022 if (mode == CURSOR_SAVE) {
1024 } else if (mode == CURSOR_LOAD) {
1026 tmoveto(c[alt].x, c[alt].y);
1035 term.c = (TCursor){{
1039 }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
1041 memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1042 for (i = tabspaces; i < term.col; i += tabspaces)
1045 term.bot = term.row - 1;
1046 term.mode = MODE_WRAP|MODE_UTF8;
1047 memset(term.trantbl, CS_USA, sizeof(term.trantbl));
1050 for (i = 0; i < 2; i++) {
1052 tcursor(CURSOR_SAVE);
1053 tclearregion(0, 0, term.col-1, term.row-1);
1059 tnew(int col, int row)
1061 term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
1069 Line *tmp = term.line;
1071 term.line = term.alt;
1073 term.mode ^= MODE_ALTSCREEN;
1078 tscrolldown(int orig, int n)
1083 LIMIT(n, 0, term.bot-orig+1);
1085 tsetdirt(orig, term.bot-n);
1086 tclearregion(0, term.bot-n+1, term.col-1, term.bot);
1088 for (i = term.bot; i >= orig+n; i--) {
1089 temp = term.line[i];
1090 term.line[i] = term.line[i-n];
1091 term.line[i-n] = temp;
1098 tscrollup(int orig, int n)
1103 LIMIT(n, 0, term.bot-orig+1);
1105 tclearregion(0, orig, term.col-1, orig+n-1);
1106 tsetdirt(orig+n, term.bot);
1108 for (i = orig; i <= term.bot-n; i++) {
1109 temp = term.line[i];
1110 term.line[i] = term.line[i+n];
1111 term.line[i+n] = temp;
1114 selscroll(orig, -n);
1118 selscroll(int orig, int n)
1123 if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) {
1125 } else if (BETWEEN(sel.nb.y, orig, term.bot)) {
1128 if (sel.ob.y < term.top || sel.ob.y > term.bot ||
1129 sel.oe.y < term.top || sel.oe.y > term.bot) {
1138 tnewline(int first_col)
1142 if (y == term.bot) {
1143 tscrollup(term.top, 1);
1147 tmoveto(first_col ? 0 : term.c.x, y);
1153 char *p = csiescseq.buf, *np;
1162 csiescseq.buf[csiescseq.len] = '\0';
1163 while (p < csiescseq.buf+csiescseq.len) {
1165 v = strtol(p, &np, 10);
1168 if (v == LONG_MAX || v == LONG_MIN)
1170 csiescseq.arg[csiescseq.narg++] = v;
1172 if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ)
1176 csiescseq.mode[0] = *p++;
1177 csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0';
1180 /* for absolute user moves, when decom is set */
1182 tmoveato(int x, int y)
1184 tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0));
1188 tmoveto(int x, int y)
1192 if (term.c.state & CURSOR_ORIGIN) {
1197 maxy = term.row - 1;
1199 term.c.state &= ~CURSOR_WRAPNEXT;
1200 term.c.x = LIMIT(x, 0, term.col-1);
1201 term.c.y = LIMIT(y, miny, maxy);
1205 tsetchar(Rune u, const Glyph *attr, int x, int y)
1207 static const char *vt100_0[62] = { /* 0x41 - 0x7e */
1208 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1209 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1210 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1211 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1212 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1213 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1214 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1215 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1219 * The table is proudly stolen from rxvt.
1221 if (term.trantbl[term.charset] == CS_GRAPHIC0 &&
1222 BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
1223 utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ);
1225 if (term.line[y][x].mode & ATTR_WIDE) {
1226 if (x+1 < term.col) {
1227 term.line[y][x+1].u = ' ';
1228 term.line[y][x+1].mode &= ~ATTR_WDUMMY;
1230 } else if (term.line[y][x].mode & ATTR_WDUMMY) {
1231 term.line[y][x-1].u = ' ';
1232 term.line[y][x-1].mode &= ~ATTR_WIDE;
1236 term.line[y][x] = *attr;
1237 term.line[y][x].u = u;
1241 tclearregion(int x1, int y1, int x2, int y2)
1247 temp = x1, x1 = x2, x2 = temp;
1249 temp = y1, y1 = y2, y2 = temp;
1251 LIMIT(x1, 0, term.col-1);
1252 LIMIT(x2, 0, term.col-1);
1253 LIMIT(y1, 0, term.row-1);
1254 LIMIT(y2, 0, term.row-1);
1256 for (y = y1; y <= y2; y++) {
1258 for (x = x1; x <= x2; x++) {
1259 gp = &term.line[y][x];
1262 gp->fg = term.c.attr.fg;
1263 gp->bg = term.c.attr.bg;
1276 LIMIT(n, 0, term.col - term.c.x);
1280 size = term.col - src;
1281 line = term.line[term.c.y];
1283 memmove(&line[dst], &line[src], size * sizeof(Glyph));
1284 tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
1293 LIMIT(n, 0, term.col - term.c.x);
1297 size = term.col - dst;
1298 line = term.line[term.c.y];
1300 memmove(&line[dst], &line[src], size * sizeof(Glyph));
1301 tclearregion(src, term.c.y, dst - 1, term.c.y);
1305 tinsertblankline(int n)
1307 if (BETWEEN(term.c.y, term.top, term.bot))
1308 tscrolldown(term.c.y, n);
1314 if (BETWEEN(term.c.y, term.top, term.bot))
1315 tscrollup(term.c.y, n);
1319 tdefcolor(const int *attr, int *npar, int l)
1324 switch (attr[*npar + 1]) {
1325 case 2: /* direct color in RGB space */
1326 if (*npar + 4 >= l) {
1328 "erresc(38): Incorrect number of parameters (%d)\n",
1332 r = attr[*npar + 2];
1333 g = attr[*npar + 3];
1334 b = attr[*npar + 4];
1336 if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255))
1337 fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n",
1340 idx = TRUECOLOR(r, g, b);
1342 case 5: /* indexed color */
1343 if (*npar + 2 >= l) {
1345 "erresc(38): Incorrect number of parameters (%d)\n",
1350 if (!BETWEEN(attr[*npar], 0, 255))
1351 fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]);
1355 case 0: /* implemented defined (only foreground) */
1356 case 1: /* transparent */
1357 case 3: /* direct color in CMY space */
1358 case 4: /* direct color in CMYK space */
1361 "erresc(38): gfx attr %d unknown\n", attr[*npar]);
1369 tsetattr(const int *attr, int l)
1374 for (i = 0; i < l; i++) {
1377 term.c.attr.mode &= ~(
1386 term.c.attr.fg = defaultfg;
1387 term.c.attr.bg = defaultbg;
1390 term.c.attr.mode |= ATTR_BOLD;
1393 term.c.attr.mode |= ATTR_FAINT;
1396 term.c.attr.mode |= ATTR_ITALIC;
1399 term.c.attr.mode |= ATTR_UNDERLINE;
1401 case 5: /* slow blink */
1403 case 6: /* rapid blink */
1404 term.c.attr.mode |= ATTR_BLINK;
1407 term.c.attr.mode |= ATTR_REVERSE;
1410 term.c.attr.mode |= ATTR_INVISIBLE;
1413 term.c.attr.mode |= ATTR_STRUCK;
1416 term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT);
1419 term.c.attr.mode &= ~ATTR_ITALIC;
1422 term.c.attr.mode &= ~ATTR_UNDERLINE;
1425 term.c.attr.mode &= ~ATTR_BLINK;
1428 term.c.attr.mode &= ~ATTR_REVERSE;
1431 term.c.attr.mode &= ~ATTR_INVISIBLE;
1434 term.c.attr.mode &= ~ATTR_STRUCK;
1437 if ((idx = tdefcolor(attr, &i, l)) >= 0)
1438 term.c.attr.fg = idx;
1441 term.c.attr.fg = defaultfg;
1444 if ((idx = tdefcolor(attr, &i, l)) >= 0)
1445 term.c.attr.bg = idx;
1448 term.c.attr.bg = defaultbg;
1451 if (BETWEEN(attr[i], 30, 37)) {
1452 term.c.attr.fg = attr[i] - 30;
1453 } else if (BETWEEN(attr[i], 40, 47)) {
1454 term.c.attr.bg = attr[i] - 40;
1455 } else if (BETWEEN(attr[i], 90, 97)) {
1456 term.c.attr.fg = attr[i] - 90 + 8;
1457 } else if (BETWEEN(attr[i], 100, 107)) {
1458 term.c.attr.bg = attr[i] - 100 + 8;
1461 "erresc(default): gfx attr %d unknown\n",
1471 tsetscroll(int t, int b)
1475 LIMIT(t, 0, term.row-1);
1476 LIMIT(b, 0, term.row-1);
1487 tsetmode(int priv, int set, const int *args, int narg)
1489 int alt; const int *lim;
1491 for (lim = args + narg; args < lim; ++args) {
1494 case 1: /* DECCKM -- Cursor key */
1495 xsetmode(set, MODE_APPCURSOR);
1497 case 5: /* DECSCNM -- Reverse video */
1498 xsetmode(set, MODE_REVERSE);
1500 case 6: /* DECOM -- Origin */
1501 MODBIT(term.c.state, set, CURSOR_ORIGIN);
1504 case 7: /* DECAWM -- Auto wrap */
1505 MODBIT(term.mode, set, MODE_WRAP);
1507 case 0: /* Error (IGNORED) */
1508 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1509 case 3: /* DECCOLM -- Column (IGNORED) */
1510 case 4: /* DECSCLM -- Scroll (IGNORED) */
1511 case 8: /* DECARM -- Auto repeat (IGNORED) */
1512 case 18: /* DECPFF -- Printer feed (IGNORED) */
1513 case 19: /* DECPEX -- Printer extent (IGNORED) */
1514 case 42: /* DECNRCM -- National characters (IGNORED) */
1515 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1517 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1518 xsetmode(!set, MODE_HIDE);
1520 case 9: /* X10 mouse compatibility mode */
1521 xsetpointermotion(0);
1522 xsetmode(0, MODE_MOUSE);
1523 xsetmode(set, MODE_MOUSEX10);
1525 case 1000: /* 1000: report button press */
1526 xsetpointermotion(0);
1527 xsetmode(0, MODE_MOUSE);
1528 xsetmode(set, MODE_MOUSEBTN);
1530 case 1002: /* 1002: report motion on button press */
1531 xsetpointermotion(0);
1532 xsetmode(0, MODE_MOUSE);
1533 xsetmode(set, MODE_MOUSEMOTION);
1535 case 1003: /* 1003: enable all mouse motions */
1536 xsetpointermotion(set);
1537 xsetmode(0, MODE_MOUSE);
1538 xsetmode(set, MODE_MOUSEMANY);
1540 case 1004: /* 1004: send focus events to tty */
1541 xsetmode(set, MODE_FOCUS);
1543 case 1006: /* 1006: extended reporting mode */
1544 xsetmode(set, MODE_MOUSESGR);
1547 xsetmode(set, MODE_8BIT);
1549 case 1049: /* swap screen & set/restore cursor as xterm */
1550 if (!allowaltscreen)
1552 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1554 case 47: /* swap screen */
1556 if (!allowaltscreen)
1558 alt = IS_SET(MODE_ALTSCREEN);
1560 tclearregion(0, 0, term.col-1,
1563 if (set ^ alt) /* set is always 1 or 0 */
1569 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1571 case 2004: /* 2004: bracketed paste mode */
1572 xsetmode(set, MODE_BRCKTPASTE);
1574 /* Not implemented mouse modes. See comments there. */
1575 case 1001: /* mouse highlight mode; can hang the
1576 terminal by design when implemented. */
1577 case 1005: /* UTF-8 mouse mode; will confuse
1578 applications not supporting UTF-8
1580 case 1015: /* urxvt mangled mouse mode; incompatible
1581 and can be mistaken for other control
1586 "erresc: unknown private set/reset mode %d\n",
1592 case 0: /* Error (IGNORED) */
1595 xsetmode(set, MODE_KBDLOCK);
1597 case 4: /* IRM -- Insertion-replacement */
1598 MODBIT(term.mode, set, MODE_INSERT);
1600 case 12: /* SRM -- Send/Receive */
1601 MODBIT(term.mode, !set, MODE_ECHO);
1603 case 20: /* LNM -- Linefeed/new line */
1604 MODBIT(term.mode, set, MODE_CRLF);
1608 "erresc: unknown set/reset mode %d\n",
1622 switch (csiescseq.mode[0]) {
1625 fprintf(stderr, "erresc: unknown csi ");
1629 case '@': /* ICH -- Insert <n> blank char */
1630 DEFAULT(csiescseq.arg[0], 1);
1631 tinsertblank(csiescseq.arg[0]);
1633 case 'A': /* CUU -- Cursor <n> Up */
1634 DEFAULT(csiescseq.arg[0], 1);
1635 tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
1637 case 'B': /* CUD -- Cursor <n> Down */
1638 case 'e': /* VPR --Cursor <n> Down */
1639 DEFAULT(csiescseq.arg[0], 1);
1640 tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
1642 case 'i': /* MC -- Media Copy */
1643 switch (csiescseq.arg[0]) {
1648 tdumpline(term.c.y);
1654 term.mode &= ~MODE_PRINT;
1657 term.mode |= MODE_PRINT;
1661 case 'c': /* DA -- Device Attributes */
1662 if (csiescseq.arg[0] == 0)
1663 ttywrite(vtiden, strlen(vtiden), 0);
1665 case 'b': /* REP -- if last char is printable print it <n> more times */
1666 DEFAULT(csiescseq.arg[0], 1);
1668 while (csiescseq.arg[0]-- > 0)
1671 case 'C': /* CUF -- Cursor <n> Forward */
1672 case 'a': /* HPR -- Cursor <n> Forward */
1673 DEFAULT(csiescseq.arg[0], 1);
1674 tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
1676 case 'D': /* CUB -- Cursor <n> Backward */
1677 DEFAULT(csiescseq.arg[0], 1);
1678 tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
1680 case 'E': /* CNL -- Cursor <n> Down and first col */
1681 DEFAULT(csiescseq.arg[0], 1);
1682 tmoveto(0, term.c.y+csiescseq.arg[0]);
1684 case 'F': /* CPL -- Cursor <n> Up and first col */
1685 DEFAULT(csiescseq.arg[0], 1);
1686 tmoveto(0, term.c.y-csiescseq.arg[0]);
1688 case 'g': /* TBC -- Tabulation clear */
1689 switch (csiescseq.arg[0]) {
1690 case 0: /* clear current tab stop */
1691 term.tabs[term.c.x] = 0;
1693 case 3: /* clear all the tabs */
1694 memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1700 case 'G': /* CHA -- Move to <col> */
1702 DEFAULT(csiescseq.arg[0], 1);
1703 tmoveto(csiescseq.arg[0]-1, term.c.y);
1705 case 'H': /* CUP -- Move to <row> <col> */
1707 DEFAULT(csiescseq.arg[0], 1);
1708 DEFAULT(csiescseq.arg[1], 1);
1709 tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
1711 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1712 DEFAULT(csiescseq.arg[0], 1);
1713 tputtab(csiescseq.arg[0]);
1715 case 'J': /* ED -- Clear screen */
1716 switch (csiescseq.arg[0]) {
1718 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
1719 if (term.c.y < term.row-1) {
1720 tclearregion(0, term.c.y+1, term.col-1,
1726 tclearregion(0, 0, term.col-1, term.c.y-1);
1727 tclearregion(0, term.c.y, term.c.x, term.c.y);
1730 tclearregion(0, 0, term.col-1, term.row-1);
1736 case 'K': /* EL -- Clear line */
1737 switch (csiescseq.arg[0]) {
1739 tclearregion(term.c.x, term.c.y, term.col-1,
1743 tclearregion(0, term.c.y, term.c.x, term.c.y);
1746 tclearregion(0, term.c.y, term.col-1, term.c.y);
1750 case 'S': /* SU -- Scroll <n> line up */
1751 DEFAULT(csiescseq.arg[0], 1);
1752 tscrollup(term.top, csiescseq.arg[0]);
1754 case 'T': /* SD -- Scroll <n> line down */
1755 DEFAULT(csiescseq.arg[0], 1);
1756 tscrolldown(term.top, csiescseq.arg[0]);
1758 case 'L': /* IL -- Insert <n> blank lines */
1759 DEFAULT(csiescseq.arg[0], 1);
1760 tinsertblankline(csiescseq.arg[0]);
1762 case 'l': /* RM -- Reset Mode */
1763 tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
1765 case 'M': /* DL -- Delete <n> lines */
1766 DEFAULT(csiescseq.arg[0], 1);
1767 tdeleteline(csiescseq.arg[0]);
1769 case 'X': /* ECH -- Erase <n> char */
1770 DEFAULT(csiescseq.arg[0], 1);
1771 tclearregion(term.c.x, term.c.y,
1772 term.c.x + csiescseq.arg[0] - 1, term.c.y);
1774 case 'P': /* DCH -- Delete <n> char */
1775 DEFAULT(csiescseq.arg[0], 1);
1776 tdeletechar(csiescseq.arg[0]);
1778 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1779 DEFAULT(csiescseq.arg[0], 1);
1780 tputtab(-csiescseq.arg[0]);
1782 case 'd': /* VPA -- Move to <row> */
1783 DEFAULT(csiescseq.arg[0], 1);
1784 tmoveato(term.c.x, csiescseq.arg[0]-1);
1786 case 'h': /* SM -- Set terminal mode */
1787 tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
1789 case 'm': /* SGR -- Terminal attribute (color) */
1790 tsetattr(csiescseq.arg, csiescseq.narg);
1792 case 'n': /* DSR – Device Status Report (cursor position) */
1793 if (csiescseq.arg[0] == 6) {
1794 len = snprintf(buf, sizeof(buf), "\033[%i;%iR",
1795 term.c.y+1, term.c.x+1);
1796 ttywrite(buf, len, 0);
1799 case 'r': /* DECSTBM -- Set Scrolling Region */
1800 if (csiescseq.priv) {
1803 DEFAULT(csiescseq.arg[0], 1);
1804 DEFAULT(csiescseq.arg[1], term.row);
1805 tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
1809 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1810 tcursor(CURSOR_SAVE);
1812 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1813 tcursor(CURSOR_LOAD);
1816 switch (csiescseq.mode[1]) {
1817 case 'q': /* DECSCUSR -- Set Cursor Style */
1818 if (xsetcursor(csiescseq.arg[0]))
1834 fprintf(stderr, "ESC[");
1835 for (i = 0; i < csiescseq.len; i++) {
1836 c = csiescseq.buf[i] & 0xff;
1839 } else if (c == '\n') {
1840 fprintf(stderr, "(\\n)");
1841 } else if (c == '\r') {
1842 fprintf(stderr, "(\\r)");
1843 } else if (c == 0x1b) {
1844 fprintf(stderr, "(\\e)");
1846 fprintf(stderr, "(%02x)", c);
1855 memset(&csiescseq, 0, sizeof(csiescseq));
1859 osc_color_response(int num, int index, int is_osc4)
1863 unsigned char r, g, b;
1865 if (xgetcolor(is_osc4 ? num : index, &r, &g, &b)) {
1866 fprintf(stderr, "erresc: failed to fetch %s color %d\n",
1867 is_osc4 ? "osc4" : "osc",
1868 is_osc4 ? num : index);
1872 n = snprintf(buf, sizeof buf, "\033]%s%d;rgb:%02x%02x/%02x%02x/%02x%02x\007",
1873 is_osc4 ? "4;" : "", num, r, r, g, g, b, b);
1874 if (n < 0 || n >= sizeof(buf)) {
1875 fprintf(stderr, "error: %s while printing %s response\n",
1876 n < 0 ? "snprintf failed" : "truncation occurred",
1877 is_osc4 ? "osc4" : "osc");
1879 ttywrite(buf, n, 1);
1886 char *p = NULL, *dec;
1888 const struct { int idx; char *str; } osc_table[] = {
1889 { defaultfg, "foreground" },
1890 { defaultbg, "background" },
1891 { defaultcs, "cursor" }
1894 term.esc &= ~(ESC_STR_END|ESC_STR);
1896 par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
1898 switch (strescseq.type) {
1899 case ']': /* OSC -- Operating System Command */
1903 xsettitle(strescseq.args[1]);
1904 xseticontitle(strescseq.args[1]);
1909 xseticontitle(strescseq.args[1]);
1913 xsettitle(strescseq.args[1]);
1916 if (narg > 2 && allowwindowops) {
1917 dec = base64dec(strescseq.args[2]);
1922 fprintf(stderr, "erresc: invalid base64\n");
1931 p = strescseq.args[1];
1932 if ((j = par - 10) < 0 || j >= LEN(osc_table))
1933 break; /* shouldn't be possible */
1935 if (!strcmp(p, "?")) {
1936 osc_color_response(par, osc_table[j].idx, 0);
1937 } else if (xsetcolorname(osc_table[j].idx, p)) {
1938 fprintf(stderr, "erresc: invalid %s color: %s\n",
1939 osc_table[j].str, p);
1944 case 4: /* color set */
1947 p = strescseq.args[2];
1949 case 104: /* color reset */
1950 j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
1952 if (p && !strcmp(p, "?")) {
1953 osc_color_response(j, 0, 1);
1954 } else if (xsetcolorname(j, p)) {
1955 if (par == 104 && narg <= 1)
1956 return; /* color reset without parameter */
1957 fprintf(stderr, "erresc: invalid color j=%d, p=%s\n",
1958 j, p ? p : "(null)");
1961 * TODO if defaultbg color is changed, borders
1969 case 'k': /* old title set compatibility */
1970 xsettitle(strescseq.args[0]);
1972 case 'P': /* DCS -- Device Control String */
1973 case '_': /* APC -- Application Program Command */
1974 case '^': /* PM -- Privacy Message */
1978 fprintf(stderr, "erresc: unknown str ");
1986 char *p = strescseq.buf;
1989 strescseq.buf[strescseq.len] = '\0';
1994 while (strescseq.narg < STR_ARG_SIZ) {
1995 strescseq.args[strescseq.narg++] = p;
1996 while ((c = *p) != ';' && c != '\0')
2010 fprintf(stderr, "ESC%c", strescseq.type);
2011 for (i = 0; i < strescseq.len; i++) {
2012 c = strescseq.buf[i] & 0xff;
2016 } else if (isprint(c)) {
2018 } else if (c == '\n') {
2019 fprintf(stderr, "(\\n)");
2020 } else if (c == '\r') {
2021 fprintf(stderr, "(\\r)");
2022 } else if (c == 0x1b) {
2023 fprintf(stderr, "(\\e)");
2025 fprintf(stderr, "(%02x)", c);
2028 fprintf(stderr, "ESC\\\n");
2034 strescseq = (STREscape){
2035 .buf = xrealloc(strescseq.buf, STR_BUF_SIZ),
2041 sendbreak(const Arg *arg)
2043 if (tcsendbreak(cmdfd, 0))
2044 perror("Error sending break");
2048 tprinter(char *s, size_t len)
2050 if (iofd != -1 && xwrite(iofd, s, len) < 0) {
2051 perror("Error writing to output file");
2058 toggleprinter(const Arg *arg)
2060 term.mode ^= MODE_PRINT;
2064 printscreen(const Arg *arg)
2070 printsel(const Arg *arg)
2080 if ((ptr = getsel())) {
2081 tprinter(ptr, strlen(ptr));
2090 const Glyph *bp, *end;
2092 bp = &term.line[n][0];
2093 end = &bp[MIN(tlinelen(n), term.col) - 1];
2094 if (bp != end || bp->u != ' ') {
2095 for ( ; bp <= end; ++bp)
2096 tprinter(buf, utf8encode(bp->u, buf));
2106 for (i = 0; i < term.row; ++i)
2116 while (x < term.col && n--)
2117 for (++x; x < term.col && !term.tabs[x]; ++x)
2120 while (x > 0 && n++)
2121 for (--x; x > 0 && !term.tabs[x]; --x)
2124 term.c.x = LIMIT(x, 0, term.col-1);
2128 tdefutf8(char ascii)
2131 term.mode |= MODE_UTF8;
2132 else if (ascii == '@')
2133 term.mode &= ~MODE_UTF8;
2137 tdeftran(char ascii)
2139 static char cs[] = "0B";
2140 static int vcs[] = {CS_GRAPHIC0, CS_USA};
2143 if ((p = strchr(cs, ascii)) == NULL) {
2144 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
2146 term.trantbl[term.icharset] = vcs[p - cs];
2155 if (c == '8') { /* DEC screen alignment test. */
2156 for (x = 0; x < term.col; ++x) {
2157 for (y = 0; y < term.row; ++y)
2158 tsetchar('E', &term.c.attr, x, y);
2164 tstrsequence(uchar c)
2167 case 0x90: /* DCS -- Device Control String */
2170 case 0x9f: /* APC -- Application Program Command */
2173 case 0x9e: /* PM -- Privacy Message */
2176 case 0x9d: /* OSC -- Operating System Command */
2182 term.esc |= ESC_STR;
2186 tcontrolcode(uchar ascii)
2193 tmoveto(term.c.x-1, term.c.y);
2196 tmoveto(0, term.c.y);
2201 /* go to first col if the mode is set */
2202 tnewline(IS_SET(MODE_CRLF));
2204 case '\a': /* BEL */
2205 if (term.esc & ESC_STR_END) {
2206 /* backwards compatibility to xterm */
2212 case '\033': /* ESC */
2214 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
2215 term.esc |= ESC_START;
2217 case '\016': /* SO (LS1 -- Locking shift 1) */
2218 case '\017': /* SI (LS0 -- Locking shift 0) */
2219 term.charset = 1 - (ascii - '\016');
2221 case '\032': /* SUB */
2222 tsetchar('?', &term.c.attr, term.c.x, term.c.y);
2224 case '\030': /* CAN */
2227 case '\005': /* ENQ (IGNORED) */
2228 case '\000': /* NUL (IGNORED) */
2229 case '\021': /* XON (IGNORED) */
2230 case '\023': /* XOFF (IGNORED) */
2231 case 0177: /* DEL (IGNORED) */
2233 case 0x80: /* TODO: PAD */
2234 case 0x81: /* TODO: HOP */
2235 case 0x82: /* TODO: BPH */
2236 case 0x83: /* TODO: NBH */
2237 case 0x84: /* TODO: IND */
2239 case 0x85: /* NEL -- Next line */
2240 tnewline(1); /* always go to first col */
2242 case 0x86: /* TODO: SSA */
2243 case 0x87: /* TODO: ESA */
2245 case 0x88: /* HTS -- Horizontal tab stop */
2246 term.tabs[term.c.x] = 1;
2248 case 0x89: /* TODO: HTJ */
2249 case 0x8a: /* TODO: VTS */
2250 case 0x8b: /* TODO: PLD */
2251 case 0x8c: /* TODO: PLU */
2252 case 0x8d: /* TODO: RI */
2253 case 0x8e: /* TODO: SS2 */
2254 case 0x8f: /* TODO: SS3 */
2255 case 0x91: /* TODO: PU1 */
2256 case 0x92: /* TODO: PU2 */
2257 case 0x93: /* TODO: STS */
2258 case 0x94: /* TODO: CCH */
2259 case 0x95: /* TODO: MW */
2260 case 0x96: /* TODO: SPA */
2261 case 0x97: /* TODO: EPA */
2262 case 0x98: /* TODO: SOS */
2263 case 0x99: /* TODO: SGCI */
2265 case 0x9a: /* DECID -- Identify Terminal */
2266 ttywrite(vtiden, strlen(vtiden), 0);
2268 case 0x9b: /* TODO: CSI */
2269 case 0x9c: /* TODO: ST */
2271 case 0x90: /* DCS -- Device Control String */
2272 case 0x9d: /* OSC -- Operating System Command */
2273 case 0x9e: /* PM -- Privacy Message */
2274 case 0x9f: /* APC -- Application Program Command */
2275 tstrsequence(ascii);
2278 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2279 term.esc &= ~(ESC_STR_END|ESC_STR);
2283 * returns 1 when the sequence is finished and it hasn't to read
2284 * more characters for this sequence, otherwise 0
2287 eschandle(uchar ascii)
2291 term.esc |= ESC_CSI;
2294 term.esc |= ESC_TEST;
2297 term.esc |= ESC_UTF8;
2299 case 'P': /* DCS -- Device Control String */
2300 case '_': /* APC -- Application Program Command */
2301 case '^': /* PM -- Privacy Message */
2302 case ']': /* OSC -- Operating System Command */
2303 case 'k': /* old title set compatibility */
2304 tstrsequence(ascii);
2306 case 'n': /* LS2 -- Locking shift 2 */
2307 case 'o': /* LS3 -- Locking shift 3 */
2308 term.charset = 2 + (ascii - 'n');
2310 case '(': /* GZD4 -- set primary charset G0 */
2311 case ')': /* G1D4 -- set secondary charset G1 */
2312 case '*': /* G2D4 -- set tertiary charset G2 */
2313 case '+': /* G3D4 -- set quaternary charset G3 */
2314 term.icharset = ascii - '(';
2315 term.esc |= ESC_ALTCHARSET;
2317 case 'D': /* IND -- Linefeed */
2318 if (term.c.y == term.bot) {
2319 tscrollup(term.top, 1);
2321 tmoveto(term.c.x, term.c.y+1);
2324 case 'E': /* NEL -- Next line */
2325 tnewline(1); /* always go to first col */
2327 case 'H': /* HTS -- Horizontal tab stop */
2328 term.tabs[term.c.x] = 1;
2330 case 'M': /* RI -- Reverse index */
2331 if (term.c.y == term.top) {
2332 tscrolldown(term.top, 1);
2334 tmoveto(term.c.x, term.c.y-1);
2337 case 'Z': /* DECID -- Identify Terminal */
2338 ttywrite(vtiden, strlen(vtiden), 0);
2340 case 'c': /* RIS -- Reset to initial state */
2345 case '=': /* DECPAM -- Application keypad */
2346 xsetmode(1, MODE_APPKEYPAD);
2348 case '>': /* DECPNM -- Normal keypad */
2349 xsetmode(0, MODE_APPKEYPAD);
2351 case '7': /* DECSC -- Save Cursor */
2352 tcursor(CURSOR_SAVE);
2354 case '8': /* DECRC -- Restore Cursor */
2355 tcursor(CURSOR_LOAD);
2357 case '\\': /* ST -- String Terminator */
2358 if (term.esc & ESC_STR_END)
2362 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2363 (uchar) ascii, isprint(ascii)? ascii:'.');
2377 control = ISCONTROL(u);
2378 if (u < 127 || !IS_SET(MODE_UTF8)) {
2382 len = utf8encode(u, c);
2383 if (!control && (width = wcwidth(u)) == -1)
2387 if (IS_SET(MODE_PRINT))
2391 * STR sequence must be checked before anything else
2392 * because it uses all following characters until it
2393 * receives a ESC, a SUB, a ST or any other C1 control
2396 if (term.esc & ESC_STR) {
2397 if (u == '\a' || u == 030 || u == 032 || u == 033 ||
2399 term.esc &= ~(ESC_START|ESC_STR);
2400 term.esc |= ESC_STR_END;
2401 goto check_control_code;
2404 if (strescseq.len+len >= strescseq.siz) {
2406 * Here is a bug in terminals. If the user never sends
2407 * some code to stop the str or esc command, then st
2408 * will stop responding. But this is better than
2409 * silently failing with unknown characters. At least
2410 * then users will report back.
2412 * In the case users ever get fixed, here is the code:
2418 if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2)
2421 strescseq.buf = xrealloc(strescseq.buf, strescseq.siz);
2424 memmove(&strescseq.buf[strescseq.len], c, len);
2425 strescseq.len += len;
2431 * Actions of control codes must be performed as soon they arrive
2432 * because they can be embedded inside a control sequence, and
2433 * they must not cause conflicts with sequences.
2438 * control codes are not shown ever
2443 } else if (term.esc & ESC_START) {
2444 if (term.esc & ESC_CSI) {
2445 csiescseq.buf[csiescseq.len++] = u;
2446 if (BETWEEN(u, 0x40, 0x7E)
2447 || csiescseq.len >= \
2448 sizeof(csiescseq.buf)-1) {
2454 } else if (term.esc & ESC_UTF8) {
2456 } else if (term.esc & ESC_ALTCHARSET) {
2458 } else if (term.esc & ESC_TEST) {
2463 /* sequence already finished */
2467 * All characters which form part of a sequence are not
2472 if (selected(term.c.x, term.c.y))
2475 gp = &term.line[term.c.y][term.c.x];
2476 if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
2477 gp->mode |= ATTR_WRAP;
2479 gp = &term.line[term.c.y][term.c.x];
2482 if (IS_SET(MODE_INSERT) && term.c.x+width < term.col)
2483 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
2485 if (term.c.x+width > term.col) {
2487 gp = &term.line[term.c.y][term.c.x];
2490 tsetchar(u, &term.c.attr, term.c.x, term.c.y);
2494 gp->mode |= ATTR_WIDE;
2495 if (term.c.x+1 < term.col) {
2496 if (gp[1].mode == ATTR_WIDE && term.c.x+2 < term.col) {
2498 gp[2].mode &= ~ATTR_WDUMMY;
2501 gp[1].mode = ATTR_WDUMMY;
2504 if (term.c.x+width < term.col) {
2505 tmoveto(term.c.x+width, term.c.y);
2507 term.c.state |= CURSOR_WRAPNEXT;
2512 twrite(const char *buf, int buflen, int show_ctrl)
2518 for (n = 0; n < buflen; n += charsize) {
2519 if (IS_SET(MODE_UTF8)) {
2520 /* process a complete utf8 char */
2521 charsize = utf8decode(buf + n, &u, buflen - n);
2528 if (show_ctrl && ISCONTROL(u)) {
2533 } else if (u != '\n' && u != '\r' && u != '\t') {
2544 tresize(int col, int row)
2547 int minrow = MIN(row, term.row);
2548 int mincol = MIN(col, term.col);
2552 if (col < 1 || row < 1) {
2554 "tresize: error resizing to %dx%d\n", col, row);
2559 * slide screen to keep cursor where we expect it -
2560 * tscrollup would work here, but we can optimize to
2561 * memmove because we're freeing the earlier lines
2563 for (i = 0; i <= term.c.y - row; i++) {
2567 /* ensure that both src and dst are not NULL */
2569 memmove(term.line, term.line + i, row * sizeof(Line));
2570 memmove(term.alt, term.alt + i, row * sizeof(Line));
2572 for (i += row; i < term.row; i++) {
2577 /* resize to new height */
2578 term.line = xrealloc(term.line, row * sizeof(Line));
2579 term.alt = xrealloc(term.alt, row * sizeof(Line));
2580 term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
2581 term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
2583 /* resize each row to new width, zero-pad if needed */
2584 for (i = 0; i < minrow; i++) {
2585 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
2586 term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph));
2589 /* allocate any new rows */
2590 for (/* i = minrow */; i < row; i++) {
2591 term.line[i] = xmalloc(col * sizeof(Glyph));
2592 term.alt[i] = xmalloc(col * sizeof(Glyph));
2594 if (col > term.col) {
2595 bp = term.tabs + term.col;
2597 memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
2598 while (--bp > term.tabs && !*bp)
2600 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
2603 /* update terminal size */
2606 /* reset scrolling region */
2607 tsetscroll(0, row-1);
2608 /* make use of the LIMIT in tmoveto */
2609 tmoveto(term.c.x, term.c.y);
2610 /* Clearing both screens (it makes dirty all lines) */
2612 for (i = 0; i < 2; i++) {
2613 if (mincol < col && 0 < minrow) {
2614 tclearregion(mincol, 0, col - 1, minrow - 1);
2616 if (0 < col && minrow < row) {
2617 tclearregion(0, minrow, col - 1, row - 1);
2620 tcursor(CURSOR_LOAD);
2632 drawregion(int x1, int y1, int x2, int y2)
2636 for (y = y1; y < y2; y++) {
2641 xdrawline(term.line[y], x1, y, x2);
2648 int cx = term.c.x, ocx = term.ocx, ocy = term.ocy;
2653 /* adjust cursor position */
2654 LIMIT(term.ocx, 0, term.col-1);
2655 LIMIT(term.ocy, 0, term.row-1);
2656 if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY)
2658 if (term.line[term.c.y][cx].mode & ATTR_WDUMMY)
2661 drawregion(0, 0, term.col, term.row);
2662 xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
2663 term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
2665 term.ocy = term.c.y;
2667 if (ocx != term.ocx || ocy != term.ocy)
2668 xximspot(term.ocx, term.ocy);