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) == '\177')
42 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
43 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
44 #define ISDELIM(u) (u && wcschr(worddelimiters, u))
49 MODE_ALTSCREEN = 1 << 2,
57 enum cursor_movement {
81 ESC_STR = 4, /* OSC, PM, APC */
83 ESC_STR_END = 16, /* a final string was encountered */
84 ESC_TEST = 32, /* Enter in test mode */
90 Glyph attr; /* current char attributes */
101 * Selection variables:
102 * nb – normalized coordinates of the beginning of the selection
103 * ne – normalized coordinates of the end of the selection
104 * ob – original coordinates of the beginning of the selection
105 * oe – original coordinates of the end of the selection
114 /* Internal representation of the screen */
116 int row; /* nb row */
117 int col; /* nb col */
118 Line *line; /* screen */
119 Line *alt; /* alternate screen */
120 int *dirty; /* dirtyness of lines */
121 TCursor c; /* cursor */
122 int ocx; /* old cursor col */
123 int ocy; /* old cursor row */
124 int top; /* top scroll limit */
125 int bot; /* bottom scroll limit */
126 int mode; /* terminal mode flags */
127 int esc; /* escape state flags */
128 char trantbl[4]; /* charset table translation */
129 int charset; /* current charset */
130 int icharset; /* selected charset for sequence */
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 int eschandle(uchar);
166 static void strdump(void);
167 static void strhandle(void);
168 static void strparse(void);
169 static void strreset(void);
171 static void tprinter(char *, size_t);
172 static void tdumpsel(void);
173 static void tdumpline(int);
174 static void tdump(void);
175 static void tclearregion(int, int, int, int);
176 static void tcursor(int);
177 static void tdeletechar(int);
178 static void tdeleteline(int);
179 static void tinsertblank(int);
180 static void tinsertblankline(int);
181 static int tlinelen(int);
182 static void tmoveto(int, int);
183 static void tmoveato(int, int);
184 static void tnewline(int);
185 static void tputtab(int);
186 static void tputc(Rune);
187 static void treset(void);
188 static void tscrollup(int, int);
189 static void tscrolldown(int, int);
190 static void tsetattr(int *, int);
191 static void tsetchar(Rune, Glyph *, int, int);
192 static void tsetdirt(int, int);
193 static void tsetscroll(int, int);
194 static void tswapscreen(void);
195 static void tsetmode(int, int, int *, int);
196 static int twrite(const char *, int, int);
197 static void tfulldirt(void);
198 static void tcontrolcode(uchar );
199 static void tdectest(char );
200 static void tdefutf8(char);
201 static int32_t tdefcolor(int *, int *, int);
202 static void tdeftran(char);
203 static void tstrsequence(uchar);
205 static void drawregion(int, int, int, int);
207 static void selnormalize(void);
208 static void selscroll(int, int);
209 static void selsnap(int *, int *, int);
211 static size_t utf8decode(const char *, Rune *, size_t);
212 static Rune utf8decodebyte(char, size_t *);
213 static char utf8encodebyte(Rune, size_t);
214 static size_t utf8validate(Rune *, size_t);
216 static char *base64dec(const char *);
217 static char base64dec_getc(const char **);
219 static ssize_t xwrite(int, const char *, size_t);
223 static Selection sel;
224 static CSIEscape csiescseq;
225 static STREscape strescseq;
230 static uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
231 static uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
232 static Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000};
233 static Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
236 xwrite(int fd, const char *s, size_t len)
242 r = write(fd, s, len);
257 if (!(p = malloc(len)))
258 die("malloc: %s\n", strerror(errno));
264 xrealloc(void *p, size_t len)
266 if ((p = realloc(p, len)) == NULL)
267 die("realloc: %s\n", strerror(errno));
275 if ((s = strdup(s)) == NULL)
276 die("strdup: %s\n", strerror(errno));
282 utf8decode(const char *c, Rune *u, size_t clen)
284 size_t i, j, len, type;
290 udecoded = utf8decodebyte(c[0], &len);
291 if (!BETWEEN(len, 1, UTF_SIZ))
293 for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
294 udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
301 utf8validate(u, len);
307 utf8decodebyte(char c, size_t *i)
309 for (*i = 0; *i < LEN(utfmask); ++(*i))
310 if (((uchar)c & utfmask[*i]) == utfbyte[*i])
311 return (uchar)c & ~utfmask[*i];
317 utf8encode(Rune u, char *c)
321 len = utf8validate(&u, 0);
325 for (i = len - 1; i != 0; --i) {
326 c[i] = utf8encodebyte(u, 0);
329 c[0] = utf8encodebyte(u, len);
335 utf8encodebyte(Rune u, size_t i)
337 return utfbyte[i] | (u & ~utfmask[i]);
341 utf8validate(Rune *u, size_t i)
343 if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF))
345 for (i = 1; *u > utfmax[i]; ++i)
351 static const char base64_digits[] = {
352 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
353 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0,
354 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
355 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
356 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
357 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0,
358 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
359 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
360 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
361 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
362 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
363 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
367 base64dec_getc(const char **src)
369 while (**src && !isprint(**src))
371 return **src ? *((*src)++) : '='; /* emulate padding if string ends */
375 base64dec(const char *src)
377 size_t in_len = strlen(src);
381 in_len += 4 - (in_len % 4);
382 result = dst = xmalloc(in_len / 4 * 3 + 1);
384 int a = base64_digits[(unsigned char) base64dec_getc(&src)];
385 int b = base64_digits[(unsigned char) base64dec_getc(&src)];
386 int c = base64_digits[(unsigned char) base64dec_getc(&src)];
387 int d = base64_digits[(unsigned char) base64dec_getc(&src)];
389 /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */
390 if (a == -1 || b == -1)
393 *dst++ = (a << 2) | ((b & 0x30) >> 4);
396 *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2);
399 *dst++ = ((c & 0x03) << 6) | d;
418 if (term.line[y][i - 1].mode & ATTR_WRAP)
421 while (i > 0 && term.line[y][i - 1].u == ' ')
428 selstart(int col, int row, int snap)
431 sel.mode = SEL_EMPTY;
432 sel.type = SEL_REGULAR;
433 sel.alt = IS_SET(MODE_ALTSCREEN);
435 sel.oe.x = sel.ob.x = col;
436 sel.oe.y = sel.ob.y = row;
440 sel.mode = SEL_READY;
441 tsetdirt(sel.nb.y, sel.ne.y);
445 selextend(int col, int row, int type, int done)
447 int oldey, oldex, oldsby, oldsey, oldtype;
449 if (sel.mode == SEL_IDLE)
451 if (done && sel.mode == SEL_EMPTY) {
467 if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY)
468 tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey));
470 sel.mode = done ? SEL_IDLE : SEL_READY;
478 if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) {
479 sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x;
480 sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x;
482 sel.nb.x = MIN(sel.ob.x, sel.oe.x);
483 sel.ne.x = MAX(sel.ob.x, sel.oe.x);
485 sel.nb.y = MIN(sel.ob.y, sel.oe.y);
486 sel.ne.y = MAX(sel.ob.y, sel.oe.y);
488 selsnap(&sel.nb.x, &sel.nb.y, -1);
489 selsnap(&sel.ne.x, &sel.ne.y, +1);
491 /* expand selection over line breaks */
492 if (sel.type == SEL_RECTANGULAR)
494 i = tlinelen(sel.nb.y);
497 if (tlinelen(sel.ne.y) <= sel.ne.x)
498 sel.ne.x = term.col - 1;
502 selected(int x, int y)
504 if (sel.mode == SEL_EMPTY || sel.ob.x == -1 ||
505 sel.alt != IS_SET(MODE_ALTSCREEN))
508 if (sel.type == SEL_RECTANGULAR)
509 return BETWEEN(y, sel.nb.y, sel.ne.y)
510 && BETWEEN(x, sel.nb.x, sel.ne.x);
512 return BETWEEN(y, sel.nb.y, sel.ne.y)
513 && (y != sel.nb.y || x >= sel.nb.x)
514 && (y != sel.ne.y || x <= sel.ne.x);
518 selsnap(int *x, int *y, int direction)
520 int newx, newy, xt, yt;
521 int delim, prevdelim;
527 * Snap around if the word wraps around at the end or
528 * beginning of a line.
530 prevgp = &term.line[*y][*x];
531 prevdelim = ISDELIM(prevgp->u);
533 newx = *x + direction;
535 if (!BETWEEN(newx, 0, term.col - 1)) {
537 newx = (newx + term.col) % term.col;
538 if (!BETWEEN(newy, 0, term.row - 1))
544 yt = newy, xt = newx;
545 if (!(term.line[yt][xt].mode & ATTR_WRAP))
549 if (newx >= tlinelen(newy))
552 gp = &term.line[newy][newx];
553 delim = ISDELIM(gp->u);
554 if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
555 || (delim && gp->u != prevgp->u)))
566 * Snap around if the the previous line or the current one
567 * has set ATTR_WRAP at its end. Then the whole next or
568 * previous line will be selected.
570 *x = (direction < 0) ? 0 : term.col - 1;
572 for (; *y > 0; *y += direction) {
573 if (!(term.line[*y-1][term.col-1].mode
578 } else if (direction > 0) {
579 for (; *y < term.row-1; *y += direction) {
580 if (!(term.line[*y][term.col-1].mode
594 int y, bufsize, lastx, linelen;
600 bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ;
601 ptr = str = xmalloc(bufsize);
603 /* append every set & selected glyph to the selection */
604 for (y = sel.nb.y; y <= sel.ne.y; y++) {
605 if ((linelen = tlinelen(y)) == 0) {
610 if (sel.type == SEL_RECTANGULAR) {
611 gp = &term.line[y][sel.nb.x];
614 gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0];
615 lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
617 last = &term.line[y][MIN(lastx, linelen-1)];
618 while (last >= gp && last->u == ' ')
621 for ( ; gp <= last; ++gp) {
622 if (gp->mode & ATTR_WDUMMY)
625 ptr += utf8encode(gp->u, ptr);
629 * Copy and pasting of line endings is inconsistent
630 * in the inconsistent terminal and GUI world.
631 * The best solution seems like to produce '\n' when
632 * something is copied from st and convert '\n' to
633 * '\r', when something to be pasted is received by
635 * FIXME: Fix the computer world.
637 if ((y < sel.ne.y || lastx >= linelen) && !(last->mode & ATTR_WRAP))
651 tsetdirt(sel.nb.y, sel.ne.y);
655 die(const char *errstr, ...)
659 va_start(ap, errstr);
660 vfprintf(stderr, errstr, ap);
666 execsh(char *cmd, char **args)
668 char *sh, *prog, *arg;
669 const struct passwd *pw;
672 if ((pw = getpwuid(getuid())) == NULL) {
674 die("getpwuid: %s\n", strerror(errno));
676 die("who are you?\n");
679 if ((sh = getenv("SHELL")) == NULL)
680 sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd;
687 arg = utmp ? utmp : sh;
695 DEFAULT(args, ((char *[]) {prog, arg, NULL}));
700 setenv("LOGNAME", pw->pw_name, 1);
701 setenv("USER", pw->pw_name, 1);
702 setenv("SHELL", sh, 1);
703 setenv("HOME", pw->pw_dir, 1);
704 setenv("TERM", termname, 1);
706 signal(SIGCHLD, SIG_DFL);
707 signal(SIGHUP, SIG_DFL);
708 signal(SIGINT, SIG_DFL);
709 signal(SIGQUIT, SIG_DFL);
710 signal(SIGTERM, SIG_DFL);
711 signal(SIGALRM, SIG_DFL);
723 if ((p = waitpid(pid, &stat, WNOHANG)) < 0)
724 die("waiting for pid %hd failed: %s\n", pid, strerror(errno));
729 if (WIFEXITED(stat) && WEXITSTATUS(stat))
730 die("child exited with status %d\n", WEXITSTATUS(stat));
731 else if (WIFSIGNALED(stat))
732 die("child terminated due to signal %d\n", WTERMSIG(stat));
739 char cmd[_POSIX_ARG_MAX], **p, *q, *s;
742 if ((n = strlen(stty_args)) > sizeof(cmd)-1)
743 die("incorrect stty parameters\n");
744 memcpy(cmd, stty_args, n);
746 siz = sizeof(cmd) - n;
747 for (p = args; p && (s = *p); ++p) {
748 if ((n = strlen(s)) > siz-1)
749 die("stty parameter length too long\n");
756 if (system(cmd) != 0)
757 perror("Couldn't call stty");
761 ttynew(char *line, char *cmd, char *out, char **args)
766 term.mode |= MODE_PRINT;
767 iofd = (!strcmp(out, "-")) ?
768 1 : open(out, O_WRONLY | O_CREAT, 0666);
770 fprintf(stderr, "Error opening %s:%s\n",
771 out, strerror(errno));
776 if ((cmdfd = open(line, O_RDWR)) < 0)
777 die("open line '%s' failed: %s\n",
778 line, strerror(errno));
784 /* seems to work fine on linux, openbsd and freebsd */
785 if (openpty(&m, &s, NULL, NULL, NULL) < 0)
786 die("openpty failed: %s\n", strerror(errno));
788 switch (pid = fork()) {
790 die("fork failed: %s\n", strerror(errno));
794 setsid(); /* create a new process group */
798 if (ioctl(s, TIOCSCTTY, NULL) < 0)
799 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno));
803 if (pledge("stdio getpw proc exec", NULL) == -1)
810 if (pledge("stdio rpath tty proc", NULL) == -1)
815 signal(SIGCHLD, sigchld);
824 static char buf[BUFSIZ];
825 static int buflen = 0;
828 /* append read bytes to unprocessed bytes */
829 ret = read(cmdfd, buf+buflen, LEN(buf)-buflen);
833 fputs("found EOF in input\n", stderr);
836 die("couldn't read from shell: %s\n", strerror(errno));
839 written = twrite(buf, buflen, 0);
841 /* keep any incomplete UTF-8 byte sequence for the next call */
843 memmove(buf, buf + written, buflen);
850 ttywrite(const char *s, size_t n, int may_echo)
854 if (may_echo && IS_SET(MODE_ECHO))
857 if (!IS_SET(MODE_CRLF)) {
862 /* This is similar to how the kernel handles ONLCR for ttys */
866 ttywriteraw("\r\n", 2);
868 next = memchr(s, '\r', n);
869 DEFAULT(next, s + n);
870 ttywriteraw(s, next - s);
878 ttywriteraw(const char *s, size_t n)
885 * Remember that we are using a pty, which might be a modem line.
886 * Writing too much will clog the line. That's why we are doing this
888 * FIXME: Migrate the world to Plan 9.
896 /* Check if we can write. */
897 if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) {
900 die("select failed: %s\n", strerror(errno));
902 if (FD_ISSET(cmdfd, &wfd)) {
904 * Only write the bytes written by ttywrite() or the
905 * default of 256. This seems to be a reasonable value
906 * for a serial line. Bigger values might clog the I/O.
908 if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0)
912 * We weren't able to write out everything.
913 * This means the buffer is getting full
921 /* All bytes have been written. */
925 if (FD_ISSET(cmdfd, &rfd))
931 die("write error on tty: %s\n", strerror(errno));
935 ttyresize(int tw, int th)
943 if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
944 fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
950 /* Send SIGHUP to shell */
959 for (i = 0; i < term.row-1; i++) {
960 for (j = 0; j < term.col-1; j++) {
961 if (term.line[i][j].mode & attr)
970 tsetdirt(int top, int bot)
974 LIMIT(top, 0, term.row-1);
975 LIMIT(bot, 0, term.row-1);
977 for (i = top; i <= bot; i++)
982 tsetdirtattr(int attr)
986 for (i = 0; i < term.row-1; i++) {
987 for (j = 0; j < term.col-1; j++) {
988 if (term.line[i][j].mode & attr) {
999 tsetdirt(0, term.row-1);
1005 static TCursor c[2];
1006 int alt = IS_SET(MODE_ALTSCREEN);
1008 if (mode == CURSOR_SAVE) {
1010 } else if (mode == CURSOR_LOAD) {
1012 tmoveto(c[alt].x, c[alt].y);
1021 term.c = (TCursor){{
1025 }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
1027 memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1028 for (i = tabspaces; i < term.col; i += tabspaces)
1031 term.bot = term.row - 1;
1032 term.mode = MODE_WRAP|MODE_UTF8;
1033 memset(term.trantbl, CS_USA, sizeof(term.trantbl));
1036 for (i = 0; i < 2; i++) {
1038 tcursor(CURSOR_SAVE);
1039 tclearregion(0, 0, term.col-1, term.row-1);
1045 tnew(int col, int row)
1047 term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
1055 Line *tmp = term.line;
1057 term.line = term.alt;
1059 term.mode ^= MODE_ALTSCREEN;
1064 tscrolldown(int orig, int n)
1069 LIMIT(n, 0, term.bot-orig+1);
1071 tsetdirt(orig, term.bot-n);
1072 tclearregion(0, term.bot-n+1, term.col-1, term.bot);
1074 for (i = term.bot; i >= orig+n; i--) {
1075 temp = term.line[i];
1076 term.line[i] = term.line[i-n];
1077 term.line[i-n] = temp;
1084 tscrollup(int orig, int n)
1089 LIMIT(n, 0, term.bot-orig+1);
1091 tclearregion(0, orig, term.col-1, orig+n-1);
1092 tsetdirt(orig+n, term.bot);
1094 for (i = orig; i <= term.bot-n; i++) {
1095 temp = term.line[i];
1096 term.line[i] = term.line[i+n];
1097 term.line[i+n] = temp;
1100 selscroll(orig, -n);
1104 selscroll(int orig, int n)
1109 if (BETWEEN(sel.ob.y, orig, term.bot) || BETWEEN(sel.oe.y, orig, term.bot)) {
1110 if ((sel.ob.y += n) > term.bot || (sel.oe.y += n) < term.top) {
1114 if (sel.type == SEL_RECTANGULAR) {
1115 if (sel.ob.y < term.top)
1116 sel.ob.y = term.top;
1117 if (sel.oe.y > term.bot)
1118 sel.oe.y = term.bot;
1120 if (sel.ob.y < term.top) {
1121 sel.ob.y = term.top;
1124 if (sel.oe.y > term.bot) {
1125 sel.oe.y = term.bot;
1126 sel.oe.x = term.col;
1134 tnewline(int first_col)
1138 if (y == term.bot) {
1139 tscrollup(term.top, 1);
1143 tmoveto(first_col ? 0 : term.c.x, y);
1149 char *p = csiescseq.buf, *np;
1158 csiescseq.buf[csiescseq.len] = '\0';
1159 while (p < csiescseq.buf+csiescseq.len) {
1161 v = strtol(p, &np, 10);
1164 if (v == LONG_MAX || v == LONG_MIN)
1166 csiescseq.arg[csiescseq.narg++] = v;
1168 if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ)
1172 csiescseq.mode[0] = *p++;
1173 csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0';
1176 /* for absolute user moves, when decom is set */
1178 tmoveato(int x, int y)
1180 tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0));
1184 tmoveto(int x, int y)
1188 if (term.c.state & CURSOR_ORIGIN) {
1193 maxy = term.row - 1;
1195 term.c.state &= ~CURSOR_WRAPNEXT;
1196 term.c.x = LIMIT(x, 0, term.col-1);
1197 term.c.y = LIMIT(y, miny, maxy);
1201 tsetchar(Rune u, Glyph *attr, int x, int y)
1203 static char *vt100_0[62] = { /* 0x41 - 0x7e */
1204 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1205 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1206 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1207 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1208 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1209 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1210 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1211 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1215 * The table is proudly stolen from rxvt.
1217 if (term.trantbl[term.charset] == CS_GRAPHIC0 &&
1218 BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
1219 utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ);
1221 if (term.line[y][x].mode & ATTR_WIDE) {
1222 if (x+1 < term.col) {
1223 term.line[y][x+1].u = ' ';
1224 term.line[y][x+1].mode &= ~ATTR_WDUMMY;
1226 } else if (term.line[y][x].mode & ATTR_WDUMMY) {
1227 term.line[y][x-1].u = ' ';
1228 term.line[y][x-1].mode &= ~ATTR_WIDE;
1232 term.line[y][x] = *attr;
1233 term.line[y][x].u = u;
1237 tclearregion(int x1, int y1, int x2, int y2)
1243 temp = x1, x1 = x2, x2 = temp;
1245 temp = y1, y1 = y2, y2 = temp;
1247 LIMIT(x1, 0, term.col-1);
1248 LIMIT(x2, 0, term.col-1);
1249 LIMIT(y1, 0, term.row-1);
1250 LIMIT(y2, 0, term.row-1);
1252 for (y = y1; y <= y2; y++) {
1254 for (x = x1; x <= x2; x++) {
1255 gp = &term.line[y][x];
1258 gp->fg = term.c.attr.fg;
1259 gp->bg = term.c.attr.bg;
1272 LIMIT(n, 0, term.col - term.c.x);
1276 size = term.col - src;
1277 line = term.line[term.c.y];
1279 memmove(&line[dst], &line[src], size * sizeof(Glyph));
1280 tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
1289 LIMIT(n, 0, term.col - term.c.x);
1293 size = term.col - dst;
1294 line = term.line[term.c.y];
1296 memmove(&line[dst], &line[src], size * sizeof(Glyph));
1297 tclearregion(src, term.c.y, dst - 1, term.c.y);
1301 tinsertblankline(int n)
1303 if (BETWEEN(term.c.y, term.top, term.bot))
1304 tscrolldown(term.c.y, n);
1310 if (BETWEEN(term.c.y, term.top, term.bot))
1311 tscrollup(term.c.y, n);
1315 tdefcolor(int *attr, int *npar, int l)
1320 switch (attr[*npar + 1]) {
1321 case 2: /* direct color in RGB space */
1322 if (*npar + 4 >= l) {
1324 "erresc(38): Incorrect number of parameters (%d)\n",
1328 r = attr[*npar + 2];
1329 g = attr[*npar + 3];
1330 b = attr[*npar + 4];
1332 if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255))
1333 fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n",
1336 idx = TRUECOLOR(r, g, b);
1338 case 5: /* indexed color */
1339 if (*npar + 2 >= l) {
1341 "erresc(38): Incorrect number of parameters (%d)\n",
1346 if (!BETWEEN(attr[*npar], 0, 255))
1347 fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]);
1351 case 0: /* implemented defined (only foreground) */
1352 case 1: /* transparent */
1353 case 3: /* direct color in CMY space */
1354 case 4: /* direct color in CMYK space */
1357 "erresc(38): gfx attr %d unknown\n", attr[*npar]);
1365 tsetattr(int *attr, int l)
1370 for (i = 0; i < l; i++) {
1373 term.c.attr.mode &= ~(
1382 term.c.attr.fg = defaultfg;
1383 term.c.attr.bg = defaultbg;
1386 term.c.attr.mode |= ATTR_BOLD;
1389 term.c.attr.mode |= ATTR_FAINT;
1392 term.c.attr.mode |= ATTR_ITALIC;
1395 term.c.attr.mode |= ATTR_UNDERLINE;
1397 case 5: /* slow blink */
1399 case 6: /* rapid blink */
1400 term.c.attr.mode |= ATTR_BLINK;
1403 term.c.attr.mode |= ATTR_REVERSE;
1406 term.c.attr.mode |= ATTR_INVISIBLE;
1409 term.c.attr.mode |= ATTR_STRUCK;
1412 term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT);
1415 term.c.attr.mode &= ~ATTR_ITALIC;
1418 term.c.attr.mode &= ~ATTR_UNDERLINE;
1421 term.c.attr.mode &= ~ATTR_BLINK;
1424 term.c.attr.mode &= ~ATTR_REVERSE;
1427 term.c.attr.mode &= ~ATTR_INVISIBLE;
1430 term.c.attr.mode &= ~ATTR_STRUCK;
1433 if ((idx = tdefcolor(attr, &i, l)) >= 0)
1434 term.c.attr.fg = idx;
1437 term.c.attr.fg = defaultfg;
1440 if ((idx = tdefcolor(attr, &i, l)) >= 0)
1441 term.c.attr.bg = idx;
1444 term.c.attr.bg = defaultbg;
1447 if (BETWEEN(attr[i], 30, 37)) {
1448 term.c.attr.fg = attr[i] - 30;
1449 } else if (BETWEEN(attr[i], 40, 47)) {
1450 term.c.attr.bg = attr[i] - 40;
1451 } else if (BETWEEN(attr[i], 90, 97)) {
1452 term.c.attr.fg = attr[i] - 90 + 8;
1453 } else if (BETWEEN(attr[i], 100, 107)) {
1454 term.c.attr.bg = attr[i] - 100 + 8;
1457 "erresc(default): gfx attr %d unknown\n",
1467 tsetscroll(int t, int b)
1471 LIMIT(t, 0, term.row-1);
1472 LIMIT(b, 0, term.row-1);
1483 tsetmode(int priv, int set, int *args, int narg)
1487 for (lim = args + narg; args < lim; ++args) {
1490 case 1: /* DECCKM -- Cursor key */
1491 xsetmode(set, MODE_APPCURSOR);
1493 case 5: /* DECSCNM -- Reverse video */
1494 xsetmode(set, MODE_REVERSE);
1496 case 6: /* DECOM -- Origin */
1497 MODBIT(term.c.state, set, CURSOR_ORIGIN);
1500 case 7: /* DECAWM -- Auto wrap */
1501 MODBIT(term.mode, set, MODE_WRAP);
1503 case 0: /* Error (IGNORED) */
1504 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1505 case 3: /* DECCOLM -- Column (IGNORED) */
1506 case 4: /* DECSCLM -- Scroll (IGNORED) */
1507 case 8: /* DECARM -- Auto repeat (IGNORED) */
1508 case 18: /* DECPFF -- Printer feed (IGNORED) */
1509 case 19: /* DECPEX -- Printer extent (IGNORED) */
1510 case 42: /* DECNRCM -- National characters (IGNORED) */
1511 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1513 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1514 xsetmode(!set, MODE_HIDE);
1516 case 9: /* X10 mouse compatibility mode */
1517 xsetpointermotion(0);
1518 xsetmode(0, MODE_MOUSE);
1519 xsetmode(set, MODE_MOUSEX10);
1521 case 1000: /* 1000: report button press */
1522 xsetpointermotion(0);
1523 xsetmode(0, MODE_MOUSE);
1524 xsetmode(set, MODE_MOUSEBTN);
1526 case 1002: /* 1002: report motion on button press */
1527 xsetpointermotion(0);
1528 xsetmode(0, MODE_MOUSE);
1529 xsetmode(set, MODE_MOUSEMOTION);
1531 case 1003: /* 1003: enable all mouse motions */
1532 xsetpointermotion(set);
1533 xsetmode(0, MODE_MOUSE);
1534 xsetmode(set, MODE_MOUSEMANY);
1536 case 1004: /* 1004: send focus events to tty */
1537 xsetmode(set, MODE_FOCUS);
1539 case 1006: /* 1006: extended reporting mode */
1540 xsetmode(set, MODE_MOUSESGR);
1543 xsetmode(set, MODE_8BIT);
1545 case 1049: /* swap screen & set/restore cursor as xterm */
1546 if (!allowaltscreen)
1548 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1550 case 47: /* swap screen */
1552 if (!allowaltscreen)
1554 alt = IS_SET(MODE_ALTSCREEN);
1556 tclearregion(0, 0, term.col-1,
1559 if (set ^ alt) /* set is always 1 or 0 */
1565 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1567 case 2004: /* 2004: bracketed paste mode */
1568 xsetmode(set, MODE_BRCKTPASTE);
1570 /* Not implemented mouse modes. See comments there. */
1571 case 1001: /* mouse highlight mode; can hang the
1572 terminal by design when implemented. */
1573 case 1005: /* UTF-8 mouse mode; will confuse
1574 applications not supporting UTF-8
1576 case 1015: /* urxvt mangled mouse mode; incompatible
1577 and can be mistaken for other control
1582 "erresc: unknown private set/reset mode %d\n",
1588 case 0: /* Error (IGNORED) */
1591 xsetmode(set, MODE_KBDLOCK);
1593 case 4: /* IRM -- Insertion-replacement */
1594 MODBIT(term.mode, set, MODE_INSERT);
1596 case 12: /* SRM -- Send/Receive */
1597 MODBIT(term.mode, !set, MODE_ECHO);
1599 case 20: /* LNM -- Linefeed/new line */
1600 MODBIT(term.mode, set, MODE_CRLF);
1604 "erresc: unknown set/reset mode %d\n",
1618 switch (csiescseq.mode[0]) {
1621 fprintf(stderr, "erresc: unknown csi ");
1625 case '@': /* ICH -- Insert <n> blank char */
1626 DEFAULT(csiescseq.arg[0], 1);
1627 tinsertblank(csiescseq.arg[0]);
1629 case 'A': /* CUU -- Cursor <n> Up */
1630 DEFAULT(csiescseq.arg[0], 1);
1631 tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
1633 case 'B': /* CUD -- Cursor <n> Down */
1634 case 'e': /* VPR --Cursor <n> Down */
1635 DEFAULT(csiescseq.arg[0], 1);
1636 tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
1638 case 'i': /* MC -- Media Copy */
1639 switch (csiescseq.arg[0]) {
1644 tdumpline(term.c.y);
1650 term.mode &= ~MODE_PRINT;
1653 term.mode |= MODE_PRINT;
1657 case 'c': /* DA -- Device Attributes */
1658 if (csiescseq.arg[0] == 0)
1659 ttywrite(vtiden, strlen(vtiden), 0);
1661 case 'C': /* CUF -- Cursor <n> Forward */
1662 case 'a': /* HPR -- Cursor <n> Forward */
1663 DEFAULT(csiescseq.arg[0], 1);
1664 tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
1666 case 'D': /* CUB -- Cursor <n> Backward */
1667 DEFAULT(csiescseq.arg[0], 1);
1668 tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
1670 case 'E': /* CNL -- Cursor <n> Down and first col */
1671 DEFAULT(csiescseq.arg[0], 1);
1672 tmoveto(0, term.c.y+csiescseq.arg[0]);
1674 case 'F': /* CPL -- Cursor <n> Up and first col */
1675 DEFAULT(csiescseq.arg[0], 1);
1676 tmoveto(0, term.c.y-csiescseq.arg[0]);
1678 case 'g': /* TBC -- Tabulation clear */
1679 switch (csiescseq.arg[0]) {
1680 case 0: /* clear current tab stop */
1681 term.tabs[term.c.x] = 0;
1683 case 3: /* clear all the tabs */
1684 memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1690 case 'G': /* CHA -- Move to <col> */
1692 DEFAULT(csiescseq.arg[0], 1);
1693 tmoveto(csiescseq.arg[0]-1, term.c.y);
1695 case 'H': /* CUP -- Move to <row> <col> */
1697 DEFAULT(csiescseq.arg[0], 1);
1698 DEFAULT(csiescseq.arg[1], 1);
1699 tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
1701 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1702 DEFAULT(csiescseq.arg[0], 1);
1703 tputtab(csiescseq.arg[0]);
1705 case 'J': /* ED -- Clear screen */
1706 switch (csiescseq.arg[0]) {
1708 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
1709 if (term.c.y < term.row-1) {
1710 tclearregion(0, term.c.y+1, term.col-1,
1716 tclearregion(0, 0, term.col-1, term.c.y-1);
1717 tclearregion(0, term.c.y, term.c.x, term.c.y);
1720 tclearregion(0, 0, term.col-1, term.row-1);
1726 case 'K': /* EL -- Clear line */
1727 switch (csiescseq.arg[0]) {
1729 tclearregion(term.c.x, term.c.y, term.col-1,
1733 tclearregion(0, term.c.y, term.c.x, term.c.y);
1736 tclearregion(0, term.c.y, term.col-1, term.c.y);
1740 case 'S': /* SU -- Scroll <n> line up */
1741 DEFAULT(csiescseq.arg[0], 1);
1742 tscrollup(term.top, csiescseq.arg[0]);
1744 case 'T': /* SD -- Scroll <n> line down */
1745 DEFAULT(csiescseq.arg[0], 1);
1746 tscrolldown(term.top, csiescseq.arg[0]);
1748 case 'L': /* IL -- Insert <n> blank lines */
1749 DEFAULT(csiescseq.arg[0], 1);
1750 tinsertblankline(csiescseq.arg[0]);
1752 case 'l': /* RM -- Reset Mode */
1753 tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
1755 case 'M': /* DL -- Delete <n> lines */
1756 DEFAULT(csiescseq.arg[0], 1);
1757 tdeleteline(csiescseq.arg[0]);
1759 case 'X': /* ECH -- Erase <n> char */
1760 DEFAULT(csiescseq.arg[0], 1);
1761 tclearregion(term.c.x, term.c.y,
1762 term.c.x + csiescseq.arg[0] - 1, term.c.y);
1764 case 'P': /* DCH -- Delete <n> char */
1765 DEFAULT(csiescseq.arg[0], 1);
1766 tdeletechar(csiescseq.arg[0]);
1768 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1769 DEFAULT(csiescseq.arg[0], 1);
1770 tputtab(-csiescseq.arg[0]);
1772 case 'd': /* VPA -- Move to <row> */
1773 DEFAULT(csiescseq.arg[0], 1);
1774 tmoveato(term.c.x, csiescseq.arg[0]-1);
1776 case 'h': /* SM -- Set terminal mode */
1777 tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
1779 case 'm': /* SGR -- Terminal attribute (color) */
1780 tsetattr(csiescseq.arg, csiescseq.narg);
1782 case 'n': /* DSR – Device Status Report (cursor position) */
1783 if (csiescseq.arg[0] == 6) {
1784 len = snprintf(buf, sizeof(buf),"\033[%i;%iR",
1785 term.c.y+1, term.c.x+1);
1786 ttywrite(buf, len, 0);
1789 case 'r': /* DECSTBM -- Set Scrolling Region */
1790 if (csiescseq.priv) {
1793 DEFAULT(csiescseq.arg[0], 1);
1794 DEFAULT(csiescseq.arg[1], term.row);
1795 tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
1799 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1800 tcursor(CURSOR_SAVE);
1802 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1803 tcursor(CURSOR_LOAD);
1806 switch (csiescseq.mode[1]) {
1807 case 'q': /* DECSCUSR -- Set Cursor Style */
1808 if (xsetcursor(csiescseq.arg[0]))
1824 fprintf(stderr, "ESC[");
1825 for (i = 0; i < csiescseq.len; i++) {
1826 c = csiescseq.buf[i] & 0xff;
1829 } else if (c == '\n') {
1830 fprintf(stderr, "(\\n)");
1831 } else if (c == '\r') {
1832 fprintf(stderr, "(\\r)");
1833 } else if (c == 0x1b) {
1834 fprintf(stderr, "(\\e)");
1836 fprintf(stderr, "(%02x)", c);
1845 memset(&csiescseq, 0, sizeof(csiescseq));
1851 char *p = NULL, *dec;
1854 term.esc &= ~(ESC_STR_END|ESC_STR);
1856 par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
1858 switch (strescseq.type) {
1859 case ']': /* OSC -- Operating System Command */
1865 xsettitle(strescseq.args[1]);
1869 dec = base64dec(strescseq.args[2]);
1874 fprintf(stderr, "erresc: invalid base64\n");
1878 case 4: /* color set */
1881 p = strescseq.args[2];
1883 case 104: /* color reset, here p = NULL */
1884 j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
1885 if (xsetcolorname(j, p)) {
1886 if (par == 104 && narg <= 1)
1887 return; /* color reset without parameter */
1888 fprintf(stderr, "erresc: invalid color j=%d, p=%s\n",
1889 j, p ? p : "(null)");
1892 * TODO if defaultbg color is changed, borders
1900 case 'k': /* old title set compatibility */
1901 xsettitle(strescseq.args[0]);
1903 case 'P': /* DCS -- Device Control String */
1904 term.mode |= ESC_DCS;
1905 case '_': /* APC -- Application Program Command */
1906 case '^': /* PM -- Privacy Message */
1910 fprintf(stderr, "erresc: unknown str ");
1918 char *p = strescseq.buf;
1921 strescseq.buf[strescseq.len] = '\0';
1926 while (strescseq.narg < STR_ARG_SIZ) {
1927 strescseq.args[strescseq.narg++] = p;
1928 while ((c = *p) != ';' && c != '\0')
1942 fprintf(stderr, "ESC%c", strescseq.type);
1943 for (i = 0; i < strescseq.len; i++) {
1944 c = strescseq.buf[i] & 0xff;
1948 } else if (isprint(c)) {
1950 } else if (c == '\n') {
1951 fprintf(stderr, "(\\n)");
1952 } else if (c == '\r') {
1953 fprintf(stderr, "(\\r)");
1954 } else if (c == 0x1b) {
1955 fprintf(stderr, "(\\e)");
1957 fprintf(stderr, "(%02x)", c);
1960 fprintf(stderr, "ESC\\\n");
1966 strescseq = (STREscape){
1967 .buf = xrealloc(strescseq.buf, STR_BUF_SIZ),
1973 sendbreak(const Arg *arg)
1975 if (tcsendbreak(cmdfd, 0))
1976 perror("Error sending break");
1980 tprinter(char *s, size_t len)
1982 if (iofd != -1 && xwrite(iofd, s, len) < 0) {
1983 perror("Error writing to output file");
1990 toggleprinter(const Arg *arg)
1992 term.mode ^= MODE_PRINT;
1996 printscreen(const Arg *arg)
2002 printsel(const Arg *arg)
2012 if ((ptr = getsel())) {
2013 tprinter(ptr, strlen(ptr));
2024 bp = &term.line[n][0];
2025 end = &bp[MIN(tlinelen(n), term.col) - 1];
2026 if (bp != end || bp->u != ' ') {
2027 for ( ;bp <= end; ++bp)
2028 tprinter(buf, utf8encode(bp->u, buf));
2038 for (i = 0; i < term.row; ++i)
2048 while (x < term.col && n--)
2049 for (++x; x < term.col && !term.tabs[x]; ++x)
2052 while (x > 0 && n++)
2053 for (--x; x > 0 && !term.tabs[x]; --x)
2056 term.c.x = LIMIT(x, 0, term.col-1);
2060 tdefutf8(char ascii)
2063 term.mode |= MODE_UTF8;
2064 else if (ascii == '@')
2065 term.mode &= ~MODE_UTF8;
2069 tdeftran(char ascii)
2071 static char cs[] = "0B";
2072 static int vcs[] = {CS_GRAPHIC0, CS_USA};
2075 if ((p = strchr(cs, ascii)) == NULL) {
2076 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
2078 term.trantbl[term.icharset] = vcs[p - cs];
2087 if (c == '8') { /* DEC screen alignment test. */
2088 for (x = 0; x < term.col; ++x) {
2089 for (y = 0; y < term.row; ++y)
2090 tsetchar('E', &term.c.attr, x, y);
2096 tstrsequence(uchar c)
2101 case 0x90: /* DCS -- Device Control String */
2103 term.esc |= ESC_DCS;
2105 case 0x9f: /* APC -- Application Program Command */
2108 case 0x9e: /* PM -- Privacy Message */
2111 case 0x9d: /* OSC -- Operating System Command */
2116 term.esc |= ESC_STR;
2120 tcontrolcode(uchar ascii)
2127 tmoveto(term.c.x-1, term.c.y);
2130 tmoveto(0, term.c.y);
2135 /* go to first col if the mode is set */
2136 tnewline(IS_SET(MODE_CRLF));
2138 case '\a': /* BEL */
2139 if (term.esc & ESC_STR_END) {
2140 /* backwards compatibility to xterm */
2146 case '\033': /* ESC */
2148 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
2149 term.esc |= ESC_START;
2151 case '\016': /* SO (LS1 -- Locking shift 1) */
2152 case '\017': /* SI (LS0 -- Locking shift 0) */
2153 term.charset = 1 - (ascii - '\016');
2155 case '\032': /* SUB */
2156 tsetchar('?', &term.c.attr, term.c.x, term.c.y);
2157 case '\030': /* CAN */
2160 case '\005': /* ENQ (IGNORED) */
2161 case '\000': /* NUL (IGNORED) */
2162 case '\021': /* XON (IGNORED) */
2163 case '\023': /* XOFF (IGNORED) */
2164 case 0177: /* DEL (IGNORED) */
2166 case 0x80: /* TODO: PAD */
2167 case 0x81: /* TODO: HOP */
2168 case 0x82: /* TODO: BPH */
2169 case 0x83: /* TODO: NBH */
2170 case 0x84: /* TODO: IND */
2172 case 0x85: /* NEL -- Next line */
2173 tnewline(1); /* always go to first col */
2175 case 0x86: /* TODO: SSA */
2176 case 0x87: /* TODO: ESA */
2178 case 0x88: /* HTS -- Horizontal tab stop */
2179 term.tabs[term.c.x] = 1;
2181 case 0x89: /* TODO: HTJ */
2182 case 0x8a: /* TODO: VTS */
2183 case 0x8b: /* TODO: PLD */
2184 case 0x8c: /* TODO: PLU */
2185 case 0x8d: /* TODO: RI */
2186 case 0x8e: /* TODO: SS2 */
2187 case 0x8f: /* TODO: SS3 */
2188 case 0x91: /* TODO: PU1 */
2189 case 0x92: /* TODO: PU2 */
2190 case 0x93: /* TODO: STS */
2191 case 0x94: /* TODO: CCH */
2192 case 0x95: /* TODO: MW */
2193 case 0x96: /* TODO: SPA */
2194 case 0x97: /* TODO: EPA */
2195 case 0x98: /* TODO: SOS */
2196 case 0x99: /* TODO: SGCI */
2198 case 0x9a: /* DECID -- Identify Terminal */
2199 ttywrite(vtiden, strlen(vtiden), 0);
2201 case 0x9b: /* TODO: CSI */
2202 case 0x9c: /* TODO: ST */
2204 case 0x90: /* DCS -- Device Control String */
2205 case 0x9d: /* OSC -- Operating System Command */
2206 case 0x9e: /* PM -- Privacy Message */
2207 case 0x9f: /* APC -- Application Program Command */
2208 tstrsequence(ascii);
2211 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2212 term.esc &= ~(ESC_STR_END|ESC_STR);
2216 * returns 1 when the sequence is finished and it hasn't to read
2217 * more characters for this sequence, otherwise 0
2220 eschandle(uchar ascii)
2224 term.esc |= ESC_CSI;
2227 term.esc |= ESC_TEST;
2230 term.esc |= ESC_UTF8;
2232 case 'P': /* DCS -- Device Control String */
2233 case '_': /* APC -- Application Program Command */
2234 case '^': /* PM -- Privacy Message */
2235 case ']': /* OSC -- Operating System Command */
2236 case 'k': /* old title set compatibility */
2237 tstrsequence(ascii);
2239 case 'n': /* LS2 -- Locking shift 2 */
2240 case 'o': /* LS3 -- Locking shift 3 */
2241 term.charset = 2 + (ascii - 'n');
2243 case '(': /* GZD4 -- set primary charset G0 */
2244 case ')': /* G1D4 -- set secondary charset G1 */
2245 case '*': /* G2D4 -- set tertiary charset G2 */
2246 case '+': /* G3D4 -- set quaternary charset G3 */
2247 term.icharset = ascii - '(';
2248 term.esc |= ESC_ALTCHARSET;
2250 case 'D': /* IND -- Linefeed */
2251 if (term.c.y == term.bot) {
2252 tscrollup(term.top, 1);
2254 tmoveto(term.c.x, term.c.y+1);
2257 case 'E': /* NEL -- Next line */
2258 tnewline(1); /* always go to first col */
2260 case 'H': /* HTS -- Horizontal tab stop */
2261 term.tabs[term.c.x] = 1;
2263 case 'M': /* RI -- Reverse index */
2264 if (term.c.y == term.top) {
2265 tscrolldown(term.top, 1);
2267 tmoveto(term.c.x, term.c.y-1);
2270 case 'Z': /* DECID -- Identify Terminal */
2271 ttywrite(vtiden, strlen(vtiden), 0);
2273 case 'c': /* RIS -- Reset to initial state */
2278 case '=': /* DECPAM -- Application keypad */
2279 xsetmode(1, MODE_APPKEYPAD);
2281 case '>': /* DECPNM -- Normal keypad */
2282 xsetmode(0, MODE_APPKEYPAD);
2284 case '7': /* DECSC -- Save Cursor */
2285 tcursor(CURSOR_SAVE);
2287 case '8': /* DECRC -- Restore Cursor */
2288 tcursor(CURSOR_LOAD);
2290 case '\\': /* ST -- String Terminator */
2291 if (term.esc & ESC_STR_END)
2295 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2296 (uchar) ascii, isprint(ascii)? ascii:'.');
2310 control = ISCONTROL(u);
2311 if (!IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
2315 len = utf8encode(u, c);
2316 if (!control && (width = wcwidth(u)) == -1) {
2317 memcpy(c, "\357\277\275", 4); /* UTF_INVALID */
2322 if (IS_SET(MODE_PRINT))
2326 * STR sequence must be checked before anything else
2327 * because it uses all following characters until it
2328 * receives a ESC, a SUB, a ST or any other C1 control
2331 if (term.esc & ESC_STR) {
2332 if (u == '\a' || u == 030 || u == 032 || u == 033 ||
2334 term.esc &= ~(ESC_START|ESC_STR|ESC_DCS);
2335 if (IS_SET(MODE_SIXEL)) {
2336 /* TODO: render sixel */;
2337 term.mode &= ~MODE_SIXEL;
2340 term.esc |= ESC_STR_END;
2341 goto check_control_code;
2344 if (IS_SET(MODE_SIXEL)) {
2345 /* TODO: implement sixel mode */
2348 if (term.esc&ESC_DCS && strescseq.len == 0 && u == 'q')
2349 term.mode |= MODE_SIXEL;
2351 if (strescseq.len+len >= strescseq.siz) {
2353 * Here is a bug in terminals. If the user never sends
2354 * some code to stop the str or esc command, then st
2355 * will stop responding. But this is better than
2356 * silently failing with unknown characters. At least
2357 * then users will report back.
2359 * In the case users ever get fixed, here is the code:
2365 if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2)
2368 strescseq.buf = xrealloc(strescseq.buf, strescseq.siz);
2371 memmove(&strescseq.buf[strescseq.len], c, len);
2372 strescseq.len += len;
2378 * Actions of control codes must be performed as soon they arrive
2379 * because they can be embedded inside a control sequence, and
2380 * they must not cause conflicts with sequences.
2385 * control codes are not shown ever
2388 } else if (term.esc & ESC_START) {
2389 if (term.esc & ESC_CSI) {
2390 csiescseq.buf[csiescseq.len++] = u;
2391 if (BETWEEN(u, 0x40, 0x7E)
2392 || csiescseq.len >= \
2393 sizeof(csiescseq.buf)-1) {
2399 } else if (term.esc & ESC_UTF8) {
2401 } else if (term.esc & ESC_ALTCHARSET) {
2403 } else if (term.esc & ESC_TEST) {
2408 /* sequence already finished */
2412 * All characters which form part of a sequence are not
2417 if (sel.ob.x != -1 && BETWEEN(term.c.y, sel.ob.y, sel.oe.y))
2420 gp = &term.line[term.c.y][term.c.x];
2421 if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
2422 gp->mode |= ATTR_WRAP;
2424 gp = &term.line[term.c.y][term.c.x];
2427 if (IS_SET(MODE_INSERT) && term.c.x+width < term.col)
2428 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
2430 if (term.c.x+width > term.col) {
2432 gp = &term.line[term.c.y][term.c.x];
2435 tsetchar(u, &term.c.attr, term.c.x, term.c.y);
2438 gp->mode |= ATTR_WIDE;
2439 if (term.c.x+1 < term.col) {
2441 gp[1].mode = ATTR_WDUMMY;
2444 if (term.c.x+width < term.col) {
2445 tmoveto(term.c.x+width, term.c.y);
2447 term.c.state |= CURSOR_WRAPNEXT;
2452 twrite(const char *buf, int buflen, int show_ctrl)
2458 for (n = 0; n < buflen; n += charsize) {
2459 if (IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
2460 /* process a complete utf8 char */
2461 charsize = utf8decode(buf + n, &u, buflen - n);
2468 if (show_ctrl && ISCONTROL(u)) {
2473 } else if (u != '\n' && u != '\r' && u != '\t') {
2484 tresize(int col, int row)
2487 int minrow = MIN(row, term.row);
2488 int mincol = MIN(col, term.col);
2492 if (col < 1 || row < 1) {
2494 "tresize: error resizing to %dx%d\n", col, row);
2499 * slide screen to keep cursor where we expect it -
2500 * tscrollup would work here, but we can optimize to
2501 * memmove because we're freeing the earlier lines
2503 for (i = 0; i <= term.c.y - row; i++) {
2507 /* ensure that both src and dst are not NULL */
2509 memmove(term.line, term.line + i, row * sizeof(Line));
2510 memmove(term.alt, term.alt + i, row * sizeof(Line));
2512 for (i += row; i < term.row; i++) {
2517 /* resize to new height */
2518 term.line = xrealloc(term.line, row * sizeof(Line));
2519 term.alt = xrealloc(term.alt, row * sizeof(Line));
2520 term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
2521 term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
2523 /* resize each row to new width, zero-pad if needed */
2524 for (i = 0; i < minrow; i++) {
2525 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
2526 term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph));
2529 /* allocate any new rows */
2530 for (/* i = minrow */; i < row; i++) {
2531 term.line[i] = xmalloc(col * sizeof(Glyph));
2532 term.alt[i] = xmalloc(col * sizeof(Glyph));
2534 if (col > term.col) {
2535 bp = term.tabs + term.col;
2537 memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
2538 while (--bp > term.tabs && !*bp)
2540 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
2543 /* update terminal size */
2546 /* reset scrolling region */
2547 tsetscroll(0, row-1);
2548 /* make use of the LIMIT in tmoveto */
2549 tmoveto(term.c.x, term.c.y);
2550 /* Clearing both screens (it makes dirty all lines) */
2552 for (i = 0; i < 2; i++) {
2553 if (mincol < col && 0 < minrow) {
2554 tclearregion(mincol, 0, col - 1, minrow - 1);
2556 if (0 < col && minrow < row) {
2557 tclearregion(0, minrow, col - 1, row - 1);
2560 tcursor(CURSOR_LOAD);
2572 drawregion(int x1, int y1, int x2, int y2)
2575 for (y = y1; y < y2; y++) {
2580 xdrawline(term.line[y], x1, y, x2);
2592 /* adjust cursor position */
2593 LIMIT(term.ocx, 0, term.col-1);
2594 LIMIT(term.ocy, 0, term.row-1);
2595 if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY)
2597 if (term.line[term.c.y][cx].mode & ATTR_WDUMMY)
2600 drawregion(0, 0, term.col, term.row);
2601 xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
2602 term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
2603 term.ocx = cx, term.ocy = term.c.y;
2605 xximspot(term.ocx, term.ocy);