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
37 #define STR_BUF_SIZ ESC_BUF_SIZ
38 #define STR_ARG_SIZ ESC_ARG_SIZ
42 #define IS_SET(flag) ((term.mode & (flag)) != 0)
43 #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f)
44 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
45 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
46 #define ISDELIM(u) (u && wcschr(worddelimiters, u))
47 #define TLINE(y) ((y) < term.scr ? term.hist[((y) + term.histi - \
48 term.scr + HISTSIZE + 1) % HISTSIZE] : \
49 term.line[(y) - term.scr])
54 MODE_ALTSCREEN = 1 << 2,
61 enum cursor_movement {
85 ESC_STR = 4, /* DCS, OSC, PM, APC */
87 ESC_STR_END = 16, /* a final string was encountered */
88 ESC_TEST = 32, /* Enter in test mode */
93 Glyph attr; /* current char attributes */
104 * Selection variables:
105 * nb – normalized coordinates of the beginning of the selection
106 * ne – normalized coordinates of the end of the selection
107 * ob – original coordinates of the beginning of the selection
108 * oe – original coordinates of the end of the selection
117 /* Internal representation of the screen */
119 int row; /* nb row */
120 int col; /* nb col */
121 Line *line; /* screen */
122 Line *alt; /* alternate screen */
123 Line hist[HISTSIZE]; /* history buffer */
124 int histi; /* history index */
125 int scr; /* scroll back */
126 int *dirty; /* dirtyness of lines */
127 TCursor c; /* cursor */
128 int ocx; /* old cursor col */
129 int ocy; /* old cursor row */
130 int top; /* top scroll limit */
131 int bot; /* bottom scroll limit */
132 int mode; /* terminal mode flags */
133 int esc; /* escape state flags */
134 char trantbl[4]; /* charset table translation */
135 int charset; /* current charset */
136 int icharset; /* selected charset for sequence */
138 Rune lastc; /* last printed char outside of sequence, 0 if control */
141 /* CSI Escape sequence structs */
142 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
144 char buf[ESC_BUF_SIZ]; /* raw string */
145 size_t len; /* raw string length */
147 int arg[ESC_ARG_SIZ];
148 int narg; /* nb of args */
150 int carg[ESC_ARG_SIZ][CAR_PER_ARG]; /* colon args */
153 /* STR Escape sequence structs */
154 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
156 char type; /* ESC type ... */
157 char *buf; /* allocated raw string */
158 size_t siz; /* allocation size */
159 size_t len; /* raw string length */
160 char *args[STR_ARG_SIZ];
161 int narg; /* nb of args */
164 static void execsh(char *, char **);
165 static void stty(char **);
166 static void sigchld(int);
167 static void ttywriteraw(const char *, size_t);
169 static void csidump(void);
170 static void csihandle(void);
171 static void readcolonargs(char **, int, int[][CAR_PER_ARG]);
172 static void csiparse(void);
173 static void csireset(void);
174 static int eschandle(uchar);
175 static void strdump(void);
176 static void strhandle(void);
177 static void strparse(void);
178 static void strreset(void);
180 static void tprinter(char *, size_t);
181 static void tdumpsel(void);
182 static void tdumpline(int);
183 static void tdump(void);
184 static void tclearregion(int, int, int, int);
185 static void tcursor(int);
186 static void tdeletechar(int);
187 static void tdeleteline(int);
188 static void tinsertblank(int);
189 static void tinsertblankline(int);
190 static int tlinelen(int);
191 static void tmoveto(int, int);
192 static void tmoveato(int, int);
193 static void tnewline(int);
194 static void tputtab(int);
195 static void tputc(Rune);
196 static void treset(void);
197 static void tscrollup(int, int, int);
198 static void tscrolldown(int, int, int);
199 static void tsetattr(const int *, int);
200 static void tsetchar(Rune, const Glyph *, int, int);
201 static void tsetdirt(int, int);
202 static void tsetscroll(int, int);
203 static void tswapscreen(void);
204 static void tsetmode(int, int, const int *, int);
205 static int twrite(const char *, int, int);
206 static void tfulldirt(void);
207 static void tcontrolcode(uchar );
208 static void tdectest(char );
209 static void tdefutf8(char);
210 static int32_t tdefcolor(const int *, int *, int);
211 static void tdeftran(char);
212 static void tstrsequence(uchar);
214 static void drawregion(int, int, int, int);
216 static void selnormalize(void);
217 static void selscroll(int, int);
218 static void selsnap(int *, int *, int);
220 static size_t utf8decode(const char *, Rune *, size_t);
221 static Rune utf8decodebyte(char, size_t *);
222 static char utf8encodebyte(Rune, size_t);
223 static size_t utf8validate(Rune *, size_t);
225 static char *base64dec(const char *);
226 static char base64dec_getc(const char **);
228 static ssize_t xwrite(int, const char *, size_t);
232 static Selection sel;
233 static CSIEscape csiescseq;
234 static STREscape strescseq;
239 static const uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
240 static const uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
241 static const Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000};
242 static const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
246 struct timespec sutv;
251 clock_gettime(CLOCK_MONOTONIC, &sutv);
262 tinsync(uint timeout)
265 if (su && !clock_gettime(CLOCK_MONOTONIC, &now)
266 && TIMEDIFF(now, sutv) >= timeout)
272 xwrite(int fd, const char *s, size_t len)
278 r = write(fd, s, len);
293 if (!(p = malloc(len)))
294 die("malloc: %s\n", strerror(errno));
300 xrealloc(void *p, size_t len)
302 if ((p = realloc(p, len)) == NULL)
303 die("realloc: %s\n", strerror(errno));
309 xstrdup(const char *s)
313 if ((p = strdup(s)) == NULL)
314 die("strdup: %s\n", strerror(errno));
320 utf8decode(const char *c, Rune *u, size_t clen)
322 size_t i, j, len, type;
328 udecoded = utf8decodebyte(c[0], &len);
329 if (!BETWEEN(len, 1, UTF_SIZ))
331 for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
332 udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
339 utf8validate(u, len);
345 utf8decodebyte(char c, size_t *i)
347 for (*i = 0; *i < LEN(utfmask); ++(*i))
348 if (((uchar)c & utfmask[*i]) == utfbyte[*i])
349 return (uchar)c & ~utfmask[*i];
355 utf8encode(Rune u, char *c)
359 len = utf8validate(&u, 0);
363 for (i = len - 1; i != 0; --i) {
364 c[i] = utf8encodebyte(u, 0);
367 c[0] = utf8encodebyte(u, len);
373 utf8encodebyte(Rune u, size_t i)
375 return utfbyte[i] | (u & ~utfmask[i]);
379 utf8validate(Rune *u, size_t i)
381 if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF))
383 for (i = 1; *u > utfmax[i]; ++i)
389 static const char base64_digits[] = {
390 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
391 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0,
392 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
393 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
394 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
395 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0,
396 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
397 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
398 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
399 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
400 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
401 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
405 base64dec_getc(const char **src)
407 while (**src && !isprint(**src))
409 return **src ? *((*src)++) : '='; /* emulate padding if string ends */
413 base64dec(const char *src)
415 size_t in_len = strlen(src);
419 in_len += 4 - (in_len % 4);
420 result = dst = xmalloc(in_len / 4 * 3 + 1);
422 int a = base64_digits[(unsigned char) base64dec_getc(&src)];
423 int b = base64_digits[(unsigned char) base64dec_getc(&src)];
424 int c = base64_digits[(unsigned char) base64dec_getc(&src)];
425 int d = base64_digits[(unsigned char) base64dec_getc(&src)];
427 /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */
428 if (a == -1 || b == -1)
431 *dst++ = (a << 2) | ((b & 0x30) >> 4);
434 *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2);
437 *dst++ = ((c & 0x03) << 6) | d;
456 if (TLINE(y)[i - 1].mode & ATTR_WRAP)
459 while (i > 0 && TLINE(y)[i - 1].u == ' ')
466 selstart(int col, int row, int snap)
469 sel.mode = SEL_EMPTY;
470 sel.type = SEL_REGULAR;
471 sel.alt = IS_SET(MODE_ALTSCREEN);
473 sel.oe.x = sel.ob.x = col;
474 sel.oe.y = sel.ob.y = row;
478 sel.mode = SEL_READY;
479 tsetdirt(sel.nb.y, sel.ne.y);
483 selextend(int col, int row, int type, int done)
485 int oldey, oldex, oldsby, oldsey, oldtype;
487 if (sel.mode == SEL_IDLE)
489 if (done && sel.mode == SEL_EMPTY) {
505 if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY)
506 tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey));
508 sel.mode = done ? SEL_IDLE : SEL_READY;
516 if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) {
517 sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x;
518 sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x;
520 sel.nb.x = MIN(sel.ob.x, sel.oe.x);
521 sel.ne.x = MAX(sel.ob.x, sel.oe.x);
523 sel.nb.y = MIN(sel.ob.y, sel.oe.y);
524 sel.ne.y = MAX(sel.ob.y, sel.oe.y);
526 selsnap(&sel.nb.x, &sel.nb.y, -1);
527 selsnap(&sel.ne.x, &sel.ne.y, +1);
529 /* expand selection over line breaks */
530 if (sel.type == SEL_RECTANGULAR)
532 i = tlinelen(sel.nb.y);
535 if (tlinelen(sel.ne.y) <= sel.ne.x)
536 sel.ne.x = term.col - 1;
540 selected(int x, int y)
542 if (sel.mode == SEL_EMPTY || sel.ob.x == -1 ||
543 sel.alt != IS_SET(MODE_ALTSCREEN))
546 if (sel.type == SEL_RECTANGULAR)
547 return BETWEEN(y, sel.nb.y, sel.ne.y)
548 && BETWEEN(x, sel.nb.x, sel.ne.x);
550 return BETWEEN(y, sel.nb.y, sel.ne.y)
551 && (y != sel.nb.y || x >= sel.nb.x)
552 && (y != sel.ne.y || x <= sel.ne.x);
556 selsnap(int *x, int *y, int direction)
558 int newx, newy, xt, yt;
559 int delim, prevdelim;
560 const Glyph *gp, *prevgp;
565 * Snap around if the word wraps around at the end or
566 * beginning of a line.
568 prevgp = &TLINE(*y)[*x];
569 prevdelim = ISDELIM(prevgp->u);
571 newx = *x + direction;
573 if (!BETWEEN(newx, 0, term.col - 1)) {
575 newx = (newx + term.col) % term.col;
576 if (!BETWEEN(newy, 0, term.row - 1))
582 yt = newy, xt = newx;
583 if (!(TLINE(yt)[xt].mode & ATTR_WRAP))
587 if (newx >= tlinelen(newy))
590 gp = &TLINE(newy)[newx];
591 delim = ISDELIM(gp->u);
592 if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
593 || (delim && gp->u != prevgp->u)))
604 * Snap around if the the previous line or the current one
605 * has set ATTR_WRAP at its end. Then the whole next or
606 * previous line will be selected.
608 *x = (direction < 0) ? 0 : term.col - 1;
610 for (; *y > 0; *y += direction) {
611 if (!(TLINE(*y-1)[term.col-1].mode
616 } else if (direction > 0) {
617 for (; *y < term.row-1; *y += direction) {
618 if (!(TLINE(*y)[term.col-1].mode
632 int y, bufsize, lastx, linelen;
633 const Glyph *gp, *last;
638 bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ;
639 ptr = str = xmalloc(bufsize);
641 /* append every set & selected glyph to the selection */
642 for (y = sel.nb.y; y <= sel.ne.y; y++) {
643 if ((linelen = tlinelen(y)) == 0) {
648 if (sel.type == SEL_RECTANGULAR) {
649 gp = &TLINE(y)[sel.nb.x];
652 gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0];
653 lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
655 last = &TLINE(y)[MIN(lastx, linelen-1)];
656 while (last >= gp && last->u == ' ')
659 for ( ; gp <= last; ++gp) {
660 if (gp->mode & ATTR_WDUMMY)
663 ptr += utf8encode(gp->u, ptr);
667 * Copy and pasting of line endings is inconsistent
668 * in the inconsistent terminal and GUI world.
669 * The best solution seems like to produce '\n' when
670 * something is copied from st and convert '\n' to
671 * '\r', when something to be pasted is received by
673 * FIXME: Fix the computer world.
675 if ((y < sel.ne.y || lastx >= linelen) &&
676 (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR))
690 tsetdirt(sel.nb.y, sel.ne.y);
694 die(const char *errstr, ...)
698 va_start(ap, errstr);
699 vfprintf(stderr, errstr, ap);
705 execsh(char *cmd, char **args)
707 char *sh, *prog, *arg;
708 const struct passwd *pw;
711 if ((pw = getpwuid(getuid())) == NULL) {
713 die("getpwuid: %s\n", strerror(errno));
715 die("who are you?\n");
718 if ((sh = getenv("SHELL")) == NULL)
719 sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd;
726 arg = utmp ? utmp : sh;
734 DEFAULT(args, ((char *[]) {prog, arg, NULL}));
739 setenv("LOGNAME", pw->pw_name, 1);
740 setenv("USER", pw->pw_name, 1);
741 setenv("SHELL", sh, 1);
742 setenv("HOME", pw->pw_dir, 1);
743 setenv("TERM", termname, 1);
745 signal(SIGCHLD, SIG_DFL);
746 signal(SIGHUP, SIG_DFL);
747 signal(SIGINT, SIG_DFL);
748 signal(SIGQUIT, SIG_DFL);
749 signal(SIGTERM, SIG_DFL);
750 signal(SIGALRM, SIG_DFL);
762 if ((p = waitpid(pid, &stat, WNOHANG)) < 0)
763 die("waiting for pid %hd failed: %s\n", pid, strerror(errno));
768 if (WIFEXITED(stat) && WEXITSTATUS(stat))
769 die("child exited with status %d\n", WEXITSTATUS(stat));
770 else if (WIFSIGNALED(stat))
771 die("child terminated due to signal %d\n", WTERMSIG(stat));
778 char cmd[_POSIX_ARG_MAX], **p, *q, *s;
781 if ((n = strlen(stty_args)) > sizeof(cmd)-1)
782 die("incorrect stty parameters\n");
783 memcpy(cmd, stty_args, n);
785 siz = sizeof(cmd) - n;
786 for (p = args; p && (s = *p); ++p) {
787 if ((n = strlen(s)) > siz-1)
788 die("stty parameter length too long\n");
795 if (system(cmd) != 0)
796 perror("Couldn't call stty");
800 ttynew(const char *line, char *cmd, const char *out, char **args)
805 term.mode |= MODE_PRINT;
806 iofd = (!strcmp(out, "-")) ?
807 1 : open(out, O_WRONLY | O_CREAT, 0666);
809 fprintf(stderr, "Error opening %s:%s\n",
810 out, strerror(errno));
815 if ((cmdfd = open(line, O_RDWR)) < 0)
816 die("open line '%s' failed: %s\n",
817 line, strerror(errno));
823 /* seems to work fine on linux, openbsd and freebsd */
824 if (openpty(&m, &s, NULL, NULL, NULL) < 0)
825 die("openpty failed: %s\n", strerror(errno));
827 switch (pid = fork()) {
829 die("fork failed: %s\n", strerror(errno));
834 setsid(); /* create a new process group */
838 if (ioctl(s, TIOCSCTTY, NULL) < 0)
839 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno));
843 if (pledge("stdio getpw proc exec", NULL) == -1)
850 if (pledge("stdio rpath tty proc", NULL) == -1)
855 signal(SIGCHLD, sigchld);
861 static int twrite_aborted = 0;
862 int ttyread_pending() { return twrite_aborted; }
867 static char buf[BUFSIZ];
868 static int buflen = 0;
871 /* append read bytes to unprocessed bytes */
872 ret = twrite_aborted ? 1 : read(cmdfd, buf+buflen, LEN(buf)-buflen);
878 die("couldn't read from shell: %s\n", strerror(errno));
880 buflen += twrite_aborted ? 0 : ret;
881 written = twrite(buf, buflen, 0);
883 /* keep any incomplete UTF-8 byte sequence for the next call */
885 memmove(buf, buf + written, buflen);
891 ttywrite(const char *s, size_t n, int may_echo)
894 Arg arg = (Arg) { .i = term.scr };
898 if (may_echo && IS_SET(MODE_ECHO))
901 if (!IS_SET(MODE_CRLF)) {
906 /* This is similar to how the kernel handles ONLCR for ttys */
910 ttywriteraw("\r\n", 2);
912 next = memchr(s, '\r', n);
913 DEFAULT(next, s + n);
914 ttywriteraw(s, next - s);
922 ttywriteraw(const char *s, size_t n)
929 * Remember that we are using a pty, which might be a modem line.
930 * Writing too much will clog the line. That's why we are doing this
932 * FIXME: Migrate the world to Plan 9.
940 /* Check if we can write. */
941 if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) {
944 die("select failed: %s\n", strerror(errno));
946 if (FD_ISSET(cmdfd, &wfd)) {
948 * Only write the bytes written by ttywrite() or the
949 * default of 256. This seems to be a reasonable value
950 * for a serial line. Bigger values might clog the I/O.
952 if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0)
956 * We weren't able to write out everything.
957 * This means the buffer is getting full
965 /* All bytes have been written. */
969 if (FD_ISSET(cmdfd, &rfd))
975 die("write error on tty: %s\n", strerror(errno));
979 ttyresize(int tw, int th)
987 if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
988 fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
994 /* Send SIGHUP to shell */
1003 for (i = 0; i < term.row-1; i++) {
1004 for (j = 0; j < term.col-1; j++) {
1005 if (term.line[i][j].mode & attr)
1014 tsetdirt(int top, int bot)
1018 LIMIT(top, 0, term.row-1);
1019 LIMIT(bot, 0, term.row-1);
1021 for (i = top; i <= bot; i++)
1026 tsetdirtattr(int attr)
1030 for (i = 0; i < term.row-1; i++) {
1031 for (j = 0; j < term.col-1; j++) {
1032 if (term.line[i][j].mode & attr) {
1044 tsetdirt(0, term.row-1);
1050 static TCursor c[2];
1051 int alt = IS_SET(MODE_ALTSCREEN);
1053 if (mode == CURSOR_SAVE) {
1055 } else if (mode == CURSOR_LOAD) {
1057 tmoveto(c[alt].x, c[alt].y);
1066 term.c = (TCursor){{
1070 }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
1072 memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1073 for (i = tabspaces; i < term.col; i += tabspaces)
1076 term.bot = term.row - 1;
1077 term.mode = MODE_WRAP|MODE_UTF8;
1078 memset(term.trantbl, CS_USA, sizeof(term.trantbl));
1081 for (i = 0; i < 2; i++) {
1083 tcursor(CURSOR_SAVE);
1084 tclearregion(0, 0, term.col-1, term.row-1);
1090 tnew(int col, int row)
1092 term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
1099 return IS_SET(MODE_ALTSCREEN);
1105 Line *tmp = term.line;
1107 term.line = term.alt;
1109 term.mode ^= MODE_ALTSCREEN;
1114 kscrolldown(const Arg* a)
1132 kscrollup(const Arg* a)
1139 if (term.scr <= HISTSIZE-n) {
1147 tscrolldown(int orig, int n, int copyhist)
1152 LIMIT(n, 0, term.bot-orig+1);
1155 term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE;
1156 temp = term.hist[term.histi];
1157 term.hist[term.histi] = term.line[term.bot];
1158 term.line[term.bot] = temp;
1161 tsetdirt(orig, term.bot-n);
1162 tclearregion(0, term.bot-n+1, term.col-1, term.bot);
1164 for (i = term.bot; i >= orig+n; i--) {
1165 temp = term.line[i];
1166 term.line[i] = term.line[i-n];
1167 term.line[i-n] = temp;
1175 tscrollup(int orig, int n, int copyhist)
1180 LIMIT(n, 0, term.bot-orig+1);
1183 term.histi = (term.histi + 1) % HISTSIZE;
1184 temp = term.hist[term.histi];
1185 term.hist[term.histi] = term.line[orig];
1186 term.line[orig] = temp;
1189 if (term.scr > 0 && term.scr < HISTSIZE)
1190 term.scr = MIN(term.scr + n, HISTSIZE-1);
1192 tclearregion(0, orig, term.col-1, orig+n-1);
1193 tsetdirt(orig+n, term.bot);
1195 for (i = orig; i <= term.bot-n; i++) {
1196 temp = term.line[i];
1197 term.line[i] = term.line[i+n];
1198 term.line[i+n] = temp;
1202 selscroll(orig, -n);
1206 selscroll(int orig, int n)
1211 if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) {
1213 } else if (BETWEEN(sel.nb.y, orig, term.bot)) {
1216 if (sel.ob.y < term.top || sel.ob.y > term.bot ||
1217 sel.oe.y < term.top || sel.oe.y > term.bot) {
1226 tnewline(int first_col)
1230 if (y == term.bot) {
1231 tscrollup(term.top, 1, 1);
1235 tmoveto(first_col ? 0 : term.c.x, y);
1239 readcolonargs(char **p, int cursor, int params[][CAR_PER_ARG])
1242 for (; i < CAR_PER_ARG; i++)
1243 params[cursor][i] = -1;
1251 while (**p == ':' && i < CAR_PER_ARG) {
1254 params[cursor][i] = strtol(*p, &np, 10);
1263 char *p = csiescseq.buf, *np;
1272 csiescseq.buf[csiescseq.len] = '\0';
1273 while (p < csiescseq.buf+csiescseq.len) {
1275 v = strtol(p, &np, 10);
1278 if (v == LONG_MAX || v == LONG_MIN)
1280 csiescseq.arg[csiescseq.narg++] = v;
1282 readcolonargs(&p, csiescseq.narg-1, csiescseq.carg);
1283 if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ)
1287 csiescseq.mode[0] = *p++;
1288 csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0';
1291 /* for absolute user moves, when decom is set */
1293 tmoveato(int x, int y)
1295 tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0));
1299 tmoveto(int x, int y)
1303 if (term.c.state & CURSOR_ORIGIN) {
1308 maxy = term.row - 1;
1310 term.c.state &= ~CURSOR_WRAPNEXT;
1311 term.c.x = LIMIT(x, 0, term.col-1);
1312 term.c.y = LIMIT(y, miny, maxy);
1316 tsetchar(Rune u, const Glyph *attr, int x, int y)
1318 static const char *vt100_0[62] = { /* 0x41 - 0x7e */
1319 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1320 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1321 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1322 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1323 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1324 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1325 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1326 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1330 * The table is proudly stolen from rxvt.
1332 if (term.trantbl[term.charset] == CS_GRAPHIC0 &&
1333 BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
1334 utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ);
1336 if (term.line[y][x].mode & ATTR_WIDE) {
1337 if (x+1 < term.col) {
1338 term.line[y][x+1].u = ' ';
1339 term.line[y][x+1].mode &= ~ATTR_WDUMMY;
1341 } else if (term.line[y][x].mode & ATTR_WDUMMY) {
1342 term.line[y][x-1].u = ' ';
1343 term.line[y][x-1].mode &= ~ATTR_WIDE;
1347 term.line[y][x] = *attr;
1348 term.line[y][x].u = u;
1352 tclearregion(int x1, int y1, int x2, int y2)
1358 temp = x1, x1 = x2, x2 = temp;
1360 temp = y1, y1 = y2, y2 = temp;
1362 LIMIT(x1, 0, term.col-1);
1363 LIMIT(x2, 0, term.col-1);
1364 LIMIT(y1, 0, term.row-1);
1365 LIMIT(y2, 0, term.row-1);
1367 for (y = y1; y <= y2; y++) {
1369 for (x = x1; x <= x2; x++) {
1370 gp = &term.line[y][x];
1373 gp->fg = term.c.attr.fg;
1374 gp->bg = term.c.attr.bg;
1387 LIMIT(n, 0, term.col - term.c.x);
1391 size = term.col - src;
1392 line = term.line[term.c.y];
1394 memmove(&line[dst], &line[src], size * sizeof(Glyph));
1395 tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
1404 LIMIT(n, 0, term.col - term.c.x);
1408 size = term.col - dst;
1409 line = term.line[term.c.y];
1411 memmove(&line[dst], &line[src], size * sizeof(Glyph));
1412 tclearregion(src, term.c.y, dst - 1, term.c.y);
1416 tinsertblankline(int n)
1418 if (BETWEEN(term.c.y, term.top, term.bot))
1419 tscrolldown(term.c.y, n, 0);
1425 if (BETWEEN(term.c.y, term.top, term.bot))
1426 tscrollup(term.c.y, n, 0);
1430 tdefcolor(const int *attr, int *npar, int l)
1435 switch (attr[*npar + 1]) {
1436 case 2: /* direct color in RGB space */
1437 if (*npar + 4 >= l) {
1439 "erresc(38): Incorrect number of parameters (%d)\n",
1443 r = attr[*npar + 2];
1444 g = attr[*npar + 3];
1445 b = attr[*npar + 4];
1447 if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255))
1448 fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n",
1451 idx = TRUECOLOR(r, g, b);
1453 case 5: /* indexed color */
1454 if (*npar + 2 >= l) {
1456 "erresc(38): Incorrect number of parameters (%d)\n",
1461 if (!BETWEEN(attr[*npar], 0, 255))
1462 fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]);
1466 case 0: /* implemented defined (only foreground) */
1467 case 1: /* transparent */
1468 case 3: /* direct color in CMY space */
1469 case 4: /* direct color in CMYK space */
1472 "erresc(38): gfx attr %d unknown\n", attr[*npar]);
1480 tsetattr(const int *attr, int l)
1485 for (i = 0; i < l; i++) {
1488 term.c.attr.mode &= ~(
1497 term.c.attr.fg = defaultfg;
1498 term.c.attr.bg = defaultbg;
1499 term.c.attr.ustyle = -1;
1500 term.c.attr.ucolor[0] = -1;
1501 term.c.attr.ucolor[1] = -1;
1502 term.c.attr.ucolor[2] = -1;
1505 term.c.attr.mode |= ATTR_BOLD;
1508 term.c.attr.mode |= ATTR_FAINT;
1511 term.c.attr.mode |= ATTR_ITALIC;
1514 term.c.attr.ustyle = csiescseq.carg[i][0];
1516 if (term.c.attr.ustyle != 0)
1517 term.c.attr.mode |= ATTR_UNDERLINE;
1519 term.c.attr.mode &= ~ATTR_UNDERLINE;
1521 term.c.attr.mode ^= ATTR_DIRTYUNDERLINE;
1523 case 5: /* slow blink */
1525 case 6: /* rapid blink */
1526 term.c.attr.mode |= ATTR_BLINK;
1529 term.c.attr.mode |= ATTR_REVERSE;
1532 term.c.attr.mode |= ATTR_INVISIBLE;
1535 term.c.attr.mode |= ATTR_STRUCK;
1538 term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT);
1541 term.c.attr.mode &= ~ATTR_ITALIC;
1544 term.c.attr.mode &= ~ATTR_UNDERLINE;
1547 term.c.attr.mode &= ~ATTR_BLINK;
1550 term.c.attr.mode &= ~ATTR_REVERSE;
1553 term.c.attr.mode &= ~ATTR_INVISIBLE;
1556 term.c.attr.mode &= ~ATTR_STRUCK;
1559 if ((idx = tdefcolor(attr, &i, l)) >= 0)
1560 term.c.attr.fg = idx;
1563 term.c.attr.fg = defaultfg;
1566 if ((idx = tdefcolor(attr, &i, l)) >= 0)
1567 term.c.attr.bg = idx;
1570 term.c.attr.bg = defaultbg;
1573 term.c.attr.ucolor[0] = csiescseq.carg[i][1];
1574 term.c.attr.ucolor[1] = csiescseq.carg[i][2];
1575 term.c.attr.ucolor[2] = csiescseq.carg[i][3];
1576 term.c.attr.mode ^= ATTR_DIRTYUNDERLINE;
1579 term.c.attr.ucolor[0] = -1;
1580 term.c.attr.ucolor[1] = -1;
1581 term.c.attr.ucolor[2] = -1;
1582 term.c.attr.mode ^= ATTR_DIRTYUNDERLINE;
1585 if (BETWEEN(attr[i], 30, 37)) {
1586 term.c.attr.fg = attr[i] - 30;
1587 } else if (BETWEEN(attr[i], 40, 47)) {
1588 term.c.attr.bg = attr[i] - 40;
1589 } else if (BETWEEN(attr[i], 90, 97)) {
1590 term.c.attr.fg = attr[i] - 90 + 8;
1591 } else if (BETWEEN(attr[i], 100, 107)) {
1592 term.c.attr.bg = attr[i] - 100 + 8;
1595 "erresc(default): gfx attr %d unknown\n",
1605 tsetscroll(int t, int b)
1609 LIMIT(t, 0, term.row-1);
1610 LIMIT(b, 0, term.row-1);
1621 tsetmode(int priv, int set, const int *args, int narg)
1623 int alt; const int *lim;
1625 for (lim = args + narg; args < lim; ++args) {
1628 case 1: /* DECCKM -- Cursor key */
1629 xsetmode(set, MODE_APPCURSOR);
1631 case 5: /* DECSCNM -- Reverse video */
1632 xsetmode(set, MODE_REVERSE);
1634 case 6: /* DECOM -- Origin */
1635 MODBIT(term.c.state, set, CURSOR_ORIGIN);
1638 case 7: /* DECAWM -- Auto wrap */
1639 MODBIT(term.mode, set, MODE_WRAP);
1641 case 0: /* Error (IGNORED) */
1642 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1643 case 3: /* DECCOLM -- Column (IGNORED) */
1644 case 4: /* DECSCLM -- Scroll (IGNORED) */
1645 case 8: /* DECARM -- Auto repeat (IGNORED) */
1646 case 18: /* DECPFF -- Printer feed (IGNORED) */
1647 case 19: /* DECPEX -- Printer extent (IGNORED) */
1648 case 42: /* DECNRCM -- National characters (IGNORED) */
1649 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1651 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1652 xsetmode(!set, MODE_HIDE);
1654 case 9: /* X10 mouse compatibility mode */
1655 xsetpointermotion(0);
1656 xsetmode(0, MODE_MOUSE);
1657 xsetmode(set, MODE_MOUSEX10);
1659 case 1000: /* 1000: report button press */
1660 xsetpointermotion(0);
1661 xsetmode(0, MODE_MOUSE);
1662 xsetmode(set, MODE_MOUSEBTN);
1664 case 1002: /* 1002: report motion on button press */
1665 xsetpointermotion(0);
1666 xsetmode(0, MODE_MOUSE);
1667 xsetmode(set, MODE_MOUSEMOTION);
1669 case 1003: /* 1003: enable all mouse motions */
1670 xsetpointermotion(set);
1671 xsetmode(0, MODE_MOUSE);
1672 xsetmode(set, MODE_MOUSEMANY);
1674 case 1004: /* 1004: send focus events to tty */
1675 xsetmode(set, MODE_FOCUS);
1677 case 1006: /* 1006: extended reporting mode */
1678 xsetmode(set, MODE_MOUSESGR);
1681 xsetmode(set, MODE_8BIT);
1683 case 1049: /* swap screen & set/restore cursor as xterm */
1684 if (!allowaltscreen)
1686 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1688 case 47: /* swap screen */
1690 if (!allowaltscreen)
1692 alt = IS_SET(MODE_ALTSCREEN);
1694 tclearregion(0, 0, term.col-1,
1697 if (set ^ alt) /* set is always 1 or 0 */
1703 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1705 case 2004: /* 2004: bracketed paste mode */
1706 xsetmode(set, MODE_BRCKTPASTE);
1708 /* Not implemented mouse modes. See comments there. */
1709 case 1001: /* mouse highlight mode; can hang the
1710 terminal by design when implemented. */
1711 case 1005: /* UTF-8 mouse mode; will confuse
1712 applications not supporting UTF-8
1714 case 1015: /* urxvt mangled mouse mode; incompatible
1715 and can be mistaken for other control
1720 "erresc: unknown private set/reset mode %d\n",
1726 case 0: /* Error (IGNORED) */
1729 xsetmode(set, MODE_KBDLOCK);
1731 case 4: /* IRM -- Insertion-replacement */
1732 MODBIT(term.mode, set, MODE_INSERT);
1734 case 12: /* SRM -- Send/Receive */
1735 MODBIT(term.mode, !set, MODE_ECHO);
1737 case 20: /* LNM -- Linefeed/new line */
1738 MODBIT(term.mode, set, MODE_CRLF);
1742 "erresc: unknown set/reset mode %d\n",
1756 switch (csiescseq.mode[0]) {
1759 fprintf(stderr, "erresc: unknown csi ");
1763 case '@': /* ICH -- Insert <n> blank char */
1764 DEFAULT(csiescseq.arg[0], 1);
1765 tinsertblank(csiescseq.arg[0]);
1767 case 'A': /* CUU -- Cursor <n> Up */
1768 DEFAULT(csiescseq.arg[0], 1);
1769 tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
1771 case 'B': /* CUD -- Cursor <n> Down */
1772 case 'e': /* VPR --Cursor <n> Down */
1773 DEFAULT(csiescseq.arg[0], 1);
1774 tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
1776 case 'i': /* MC -- Media Copy */
1777 switch (csiescseq.arg[0]) {
1782 tdumpline(term.c.y);
1788 term.mode &= ~MODE_PRINT;
1791 term.mode |= MODE_PRINT;
1795 case 'c': /* DA -- Device Attributes */
1796 if (csiescseq.arg[0] == 0)
1797 ttywrite(vtiden, strlen(vtiden), 0);
1799 case 'b': /* REP -- if last char is printable print it <n> more times */
1800 DEFAULT(csiescseq.arg[0], 1);
1802 while (csiescseq.arg[0]-- > 0)
1805 case 'C': /* CUF -- Cursor <n> Forward */
1806 case 'a': /* HPR -- Cursor <n> Forward */
1807 DEFAULT(csiescseq.arg[0], 1);
1808 tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
1810 case 'D': /* CUB -- Cursor <n> Backward */
1811 DEFAULT(csiescseq.arg[0], 1);
1812 tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
1814 case 'E': /* CNL -- Cursor <n> Down and first col */
1815 DEFAULT(csiescseq.arg[0], 1);
1816 tmoveto(0, term.c.y+csiescseq.arg[0]);
1818 case 'F': /* CPL -- Cursor <n> Up and first col */
1819 DEFAULT(csiescseq.arg[0], 1);
1820 tmoveto(0, term.c.y-csiescseq.arg[0]);
1822 case 'g': /* TBC -- Tabulation clear */
1823 switch (csiescseq.arg[0]) {
1824 case 0: /* clear current tab stop */
1825 term.tabs[term.c.x] = 0;
1827 case 3: /* clear all the tabs */
1828 memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1834 case 'G': /* CHA -- Move to <col> */
1836 DEFAULT(csiescseq.arg[0], 1);
1837 tmoveto(csiescseq.arg[0]-1, term.c.y);
1839 case 'H': /* CUP -- Move to <row> <col> */
1841 DEFAULT(csiescseq.arg[0], 1);
1842 DEFAULT(csiescseq.arg[1], 1);
1843 tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
1845 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1846 DEFAULT(csiescseq.arg[0], 1);
1847 tputtab(csiescseq.arg[0]);
1849 case 'J': /* ED -- Clear screen */
1850 switch (csiescseq.arg[0]) {
1852 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
1853 if (term.c.y < term.row-1) {
1854 tclearregion(0, term.c.y+1, term.col-1,
1860 tclearregion(0, 0, term.col-1, term.c.y-1);
1861 tclearregion(0, term.c.y, term.c.x, term.c.y);
1864 tclearregion(0, 0, term.col-1, term.row-1);
1870 case 'K': /* EL -- Clear line */
1871 switch (csiescseq.arg[0]) {
1873 tclearregion(term.c.x, term.c.y, term.col-1,
1877 tclearregion(0, term.c.y, term.c.x, term.c.y);
1880 tclearregion(0, term.c.y, term.col-1, term.c.y);
1884 case 'S': /* SU -- Scroll <n> line up */
1885 DEFAULT(csiescseq.arg[0], 1);
1886 tscrollup(term.top, csiescseq.arg[0], 0);
1888 case 'T': /* SD -- Scroll <n> line down */
1889 DEFAULT(csiescseq.arg[0], 1);
1890 tscrolldown(term.top, csiescseq.arg[0], 0);
1892 case 'L': /* IL -- Insert <n> blank lines */
1893 DEFAULT(csiescseq.arg[0], 1);
1894 tinsertblankline(csiescseq.arg[0]);
1896 case 'l': /* RM -- Reset Mode */
1897 tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
1899 case 'M': /* DL -- Delete <n> lines */
1900 DEFAULT(csiescseq.arg[0], 1);
1901 tdeleteline(csiescseq.arg[0]);
1903 case 'X': /* ECH -- Erase <n> char */
1904 DEFAULT(csiescseq.arg[0], 1);
1905 tclearregion(term.c.x, term.c.y,
1906 term.c.x + csiescseq.arg[0] - 1, term.c.y);
1908 case 'P': /* DCH -- Delete <n> char */
1909 DEFAULT(csiescseq.arg[0], 1);
1910 tdeletechar(csiescseq.arg[0]);
1912 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1913 DEFAULT(csiescseq.arg[0], 1);
1914 tputtab(-csiescseq.arg[0]);
1916 case 'd': /* VPA -- Move to <row> */
1917 DEFAULT(csiescseq.arg[0], 1);
1918 tmoveato(term.c.x, csiescseq.arg[0]-1);
1920 case 'h': /* SM -- Set terminal mode */
1921 tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
1923 case 'm': /* SGR -- Terminal attribute (color) */
1924 tsetattr(csiescseq.arg, csiescseq.narg);
1926 case 'n': /* DSR – Device Status Report (cursor position) */
1927 if (csiescseq.arg[0] == 6) {
1928 len = snprintf(buf, sizeof(buf), "\033[%i;%iR",
1929 term.c.y+1, term.c.x+1);
1930 ttywrite(buf, len, 0);
1933 case 'r': /* DECSTBM -- Set Scrolling Region */
1934 if (csiescseq.priv) {
1937 DEFAULT(csiescseq.arg[0], 1);
1938 DEFAULT(csiescseq.arg[1], term.row);
1939 tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
1943 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1944 tcursor(CURSOR_SAVE);
1946 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1947 tcursor(CURSOR_LOAD);
1950 switch (csiescseq.mode[1]) {
1951 case 'q': /* DECSCUSR -- Set Cursor Style */
1952 if (xsetcursor(csiescseq.arg[0]))
1968 fprintf(stderr, "ESC[");
1969 for (i = 0; i < csiescseq.len; i++) {
1970 c = csiescseq.buf[i] & 0xff;
1973 } else if (c == '\n') {
1974 fprintf(stderr, "(\\n)");
1975 } else if (c == '\r') {
1976 fprintf(stderr, "(\\r)");
1977 } else if (c == 0x1b) {
1978 fprintf(stderr, "(\\e)");
1980 fprintf(stderr, "(%02x)", c);
1989 memset(&csiescseq, 0, sizeof(csiescseq));
1993 osc4_color_response(int num)
1997 unsigned char r, g, b;
1999 if (xgetcolor(num, &r, &g, &b)) {
2000 fprintf(stderr, "erresc: failed to fetch osc4 color %d\n", num);
2004 n = snprintf(buf, sizeof buf, "\033]4;%d;rgb:%02x%02x/%02x%02x/%02x%02x\007",
2005 num, r, r, g, g, b, b);
2007 ttywrite(buf, n, 1);
2011 osc_color_response(int index, int num)
2015 unsigned char r, g, b;
2017 if (xgetcolor(index, &r, &g, &b)) {
2018 fprintf(stderr, "erresc: failed to fetch osc color %d\n", index);
2022 n = snprintf(buf, sizeof buf, "\033]%d;rgb:%02x%02x/%02x%02x/%02x%02x\007",
2023 num, r, r, g, g, b, b);
2025 ttywrite(buf, n, 1);
2031 char *p = NULL, *dec;
2034 term.esc &= ~(ESC_STR_END|ESC_STR);
2036 par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
2038 switch (strescseq.type) {
2039 case ']': /* OSC -- Operating System Command */
2043 xsettitle(strescseq.args[1]);
2044 xseticontitle(strescseq.args[1]);
2049 xseticontitle(strescseq.args[1]);
2053 xsettitle(strescseq.args[1]);
2056 if (narg > 2 && allowwindowops) {
2057 dec = base64dec(strescseq.args[2]);
2062 fprintf(stderr, "erresc: invalid base64\n");
2070 p = strescseq.args[1];
2072 if (!strcmp(p, "?"))
2073 osc_color_response(defaultfg, 10);
2074 else if (xsetcolorname(defaultfg, p))
2075 fprintf(stderr, "erresc: invalid foreground color: %s\n", p);
2083 p = strescseq.args[1];
2085 if (!strcmp(p, "?"))
2086 osc_color_response(defaultbg, 11);
2087 else if (xsetcolorname(defaultbg, p))
2088 fprintf(stderr, "erresc: invalid background color: %s\n", p);
2096 p = strescseq.args[1];
2098 if (!strcmp(p, "?"))
2099 osc_color_response(defaultcs, 12);
2100 else if (xsetcolorname(defaultcs, p))
2101 fprintf(stderr, "erresc: invalid cursor color: %s\n", p);
2105 case 4: /* color set */
2108 p = strescseq.args[2];
2110 case 104: /* color reset */
2111 j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
2113 if (p && !strcmp(p, "?"))
2114 osc4_color_response(j);
2115 else if (xsetcolorname(j, p)) {
2116 if (par == 104 && narg <= 1)
2117 return; /* color reset without parameter */
2118 fprintf(stderr, "erresc: invalid color j=%d, p=%s\n",
2119 j, p ? p : "(null)");
2122 * TODO if defaultbg color is changed, borders
2130 case 'k': /* old title set compatibility */
2131 xsettitle(strescseq.args[0]);
2133 case 'P': /* DCS -- Device Control String */
2134 /* https://gitlab.com/gnachman/iterm2/-/wikis/synchronized-updates-spec */
2135 if (strstr(strescseq.buf, "=1s") == strescseq.buf)
2136 tsync_begin(); /* BSU */
2137 else if (strstr(strescseq.buf, "=2s") == strescseq.buf)
2138 tsync_end(); /* ESU */
2140 case '_': /* APC -- Application Program Command */
2141 case '^': /* PM -- Privacy Message */
2145 fprintf(stderr, "erresc: unknown str ");
2153 char *p = strescseq.buf;
2156 strescseq.buf[strescseq.len] = '\0';
2161 while (strescseq.narg < STR_ARG_SIZ) {
2162 strescseq.args[strescseq.narg++] = p;
2163 while ((c = *p) != ';' && c != '\0')
2177 fprintf(stderr, "ESC%c", strescseq.type);
2178 for (i = 0; i < strescseq.len; i++) {
2179 c = strescseq.buf[i] & 0xff;
2183 } else if (isprint(c)) {
2185 } else if (c == '\n') {
2186 fprintf(stderr, "(\\n)");
2187 } else if (c == '\r') {
2188 fprintf(stderr, "(\\r)");
2189 } else if (c == 0x1b) {
2190 fprintf(stderr, "(\\e)");
2192 fprintf(stderr, "(%02x)", c);
2195 fprintf(stderr, "ESC\\\n");
2201 strescseq = (STREscape){
2202 .buf = xrealloc(strescseq.buf, STR_BUF_SIZ),
2208 sendbreak(const Arg *arg)
2210 if (tcsendbreak(cmdfd, 0))
2211 perror("Error sending break");
2215 tprinter(char *s, size_t len)
2217 if (iofd != -1 && xwrite(iofd, s, len) < 0) {
2218 perror("Error writing to output file");
2225 toggleprinter(const Arg *arg)
2227 term.mode ^= MODE_PRINT;
2231 printscreen(const Arg *arg)
2237 printsel(const Arg *arg)
2247 if ((ptr = getsel())) {
2248 tprinter(ptr, strlen(ptr));
2257 const Glyph *bp, *end;
2259 bp = &term.line[n][0];
2260 end = &bp[MIN(tlinelen(n), term.col) - 1];
2261 if (bp != end || bp->u != ' ') {
2262 for ( ; bp <= end; ++bp)
2263 tprinter(buf, utf8encode(bp->u, buf));
2273 for (i = 0; i < term.row; ++i)
2283 while (x < term.col && n--)
2284 for (++x; x < term.col && !term.tabs[x]; ++x)
2287 while (x > 0 && n++)
2288 for (--x; x > 0 && !term.tabs[x]; --x)
2291 term.c.x = LIMIT(x, 0, term.col-1);
2295 tdefutf8(char ascii)
2298 term.mode |= MODE_UTF8;
2299 else if (ascii == '@')
2300 term.mode &= ~MODE_UTF8;
2304 tdeftran(char ascii)
2306 static char cs[] = "0B";
2307 static int vcs[] = {CS_GRAPHIC0, CS_USA};
2310 if ((p = strchr(cs, ascii)) == NULL) {
2311 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
2313 term.trantbl[term.icharset] = vcs[p - cs];
2322 if (c == '8') { /* DEC screen alignment test. */
2323 for (x = 0; x < term.col; ++x) {
2324 for (y = 0; y < term.row; ++y)
2325 tsetchar('E', &term.c.attr, x, y);
2331 tstrsequence(uchar c)
2334 case 0x90: /* DCS -- Device Control String */
2337 case 0x9f: /* APC -- Application Program Command */
2340 case 0x9e: /* PM -- Privacy Message */
2343 case 0x9d: /* OSC -- Operating System Command */
2349 term.esc |= ESC_STR;
2353 tcontrolcode(uchar ascii)
2360 tmoveto(term.c.x-1, term.c.y);
2363 tmoveto(0, term.c.y);
2368 /* go to first col if the mode is set */
2369 tnewline(IS_SET(MODE_CRLF));
2371 case '\a': /* BEL */
2372 if (term.esc & ESC_STR_END) {
2373 /* backwards compatibility to xterm */
2379 case '\033': /* ESC */
2381 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
2382 term.esc |= ESC_START;
2384 case '\016': /* SO (LS1 -- Locking shift 1) */
2385 case '\017': /* SI (LS0 -- Locking shift 0) */
2386 term.charset = 1 - (ascii - '\016');
2388 case '\032': /* SUB */
2389 tsetchar('?', &term.c.attr, term.c.x, term.c.y);
2391 case '\030': /* CAN */
2394 case '\005': /* ENQ (IGNORED) */
2395 case '\000': /* NUL (IGNORED) */
2396 case '\021': /* XON (IGNORED) */
2397 case '\023': /* XOFF (IGNORED) */
2398 case 0177: /* DEL (IGNORED) */
2400 case 0x80: /* TODO: PAD */
2401 case 0x81: /* TODO: HOP */
2402 case 0x82: /* TODO: BPH */
2403 case 0x83: /* TODO: NBH */
2404 case 0x84: /* TODO: IND */
2406 case 0x85: /* NEL -- Next line */
2407 tnewline(1); /* always go to first col */
2409 case 0x86: /* TODO: SSA */
2410 case 0x87: /* TODO: ESA */
2412 case 0x88: /* HTS -- Horizontal tab stop */
2413 term.tabs[term.c.x] = 1;
2415 case 0x89: /* TODO: HTJ */
2416 case 0x8a: /* TODO: VTS */
2417 case 0x8b: /* TODO: PLD */
2418 case 0x8c: /* TODO: PLU */
2419 case 0x8d: /* TODO: RI */
2420 case 0x8e: /* TODO: SS2 */
2421 case 0x8f: /* TODO: SS3 */
2422 case 0x91: /* TODO: PU1 */
2423 case 0x92: /* TODO: PU2 */
2424 case 0x93: /* TODO: STS */
2425 case 0x94: /* TODO: CCH */
2426 case 0x95: /* TODO: MW */
2427 case 0x96: /* TODO: SPA */
2428 case 0x97: /* TODO: EPA */
2429 case 0x98: /* TODO: SOS */
2430 case 0x99: /* TODO: SGCI */
2432 case 0x9a: /* DECID -- Identify Terminal */
2433 ttywrite(vtiden, strlen(vtiden), 0);
2435 case 0x9b: /* TODO: CSI */
2436 case 0x9c: /* TODO: ST */
2438 case 0x90: /* DCS -- Device Control String */
2439 case 0x9d: /* OSC -- Operating System Command */
2440 case 0x9e: /* PM -- Privacy Message */
2441 case 0x9f: /* APC -- Application Program Command */
2442 tstrsequence(ascii);
2445 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2446 term.esc &= ~(ESC_STR_END|ESC_STR);
2450 * returns 1 when the sequence is finished and it hasn't to read
2451 * more characters for this sequence, otherwise 0
2454 eschandle(uchar ascii)
2458 term.esc |= ESC_CSI;
2461 term.esc |= ESC_TEST;
2464 term.esc |= ESC_UTF8;
2466 case 'P': /* DCS -- Device Control String */
2467 case '_': /* APC -- Application Program Command */
2468 case '^': /* PM -- Privacy Message */
2469 case ']': /* OSC -- Operating System Command */
2470 case 'k': /* old title set compatibility */
2471 tstrsequence(ascii);
2473 case 'n': /* LS2 -- Locking shift 2 */
2474 case 'o': /* LS3 -- Locking shift 3 */
2475 term.charset = 2 + (ascii - 'n');
2477 case '(': /* GZD4 -- set primary charset G0 */
2478 case ')': /* G1D4 -- set secondary charset G1 */
2479 case '*': /* G2D4 -- set tertiary charset G2 */
2480 case '+': /* G3D4 -- set quaternary charset G3 */
2481 term.icharset = ascii - '(';
2482 term.esc |= ESC_ALTCHARSET;
2484 case 'D': /* IND -- Linefeed */
2485 if (term.c.y == term.bot) {
2486 tscrollup(term.top, 1, 1);
2488 tmoveto(term.c.x, term.c.y+1);
2491 case 'E': /* NEL -- Next line */
2492 tnewline(1); /* always go to first col */
2494 case 'H': /* HTS -- Horizontal tab stop */
2495 term.tabs[term.c.x] = 1;
2497 case 'M': /* RI -- Reverse index */
2498 if (term.c.y == term.top) {
2499 tscrolldown(term.top, 1, 1);
2501 tmoveto(term.c.x, term.c.y-1);
2504 case 'Z': /* DECID -- Identify Terminal */
2505 ttywrite(vtiden, strlen(vtiden), 0);
2507 case 'c': /* RIS -- Reset to initial state */
2512 case '=': /* DECPAM -- Application keypad */
2513 xsetmode(1, MODE_APPKEYPAD);
2515 case '>': /* DECPNM -- Normal keypad */
2516 xsetmode(0, MODE_APPKEYPAD);
2518 case '7': /* DECSC -- Save Cursor */
2519 tcursor(CURSOR_SAVE);
2521 case '8': /* DECRC -- Restore Cursor */
2522 tcursor(CURSOR_LOAD);
2524 case '\\': /* ST -- String Terminator */
2525 if (term.esc & ESC_STR_END)
2529 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2530 (uchar) ascii, isprint(ascii)? ascii:'.');
2544 control = ISCONTROL(u);
2545 if (u < 127 || !IS_SET(MODE_UTF8)) {
2549 len = utf8encode(u, c);
2550 if (!control && (width = wcwidth(u)) == -1)
2554 if (IS_SET(MODE_PRINT))
2558 * STR sequence must be checked before anything else
2559 * because it uses all following characters until it
2560 * receives a ESC, a SUB, a ST or any other C1 control
2563 if (term.esc & ESC_STR) {
2564 if (u == '\a' || u == 030 || u == 032 || u == 033 ||
2566 term.esc &= ~(ESC_START|ESC_STR);
2567 term.esc |= ESC_STR_END;
2568 goto check_control_code;
2571 if (strescseq.len+len >= strescseq.siz) {
2573 * Here is a bug in terminals. If the user never sends
2574 * some code to stop the str or esc command, then st
2575 * will stop responding. But this is better than
2576 * silently failing with unknown characters. At least
2577 * then users will report back.
2579 * In the case users ever get fixed, here is the code:
2585 if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2)
2588 strescseq.buf = xrealloc(strescseq.buf, strescseq.siz);
2591 memmove(&strescseq.buf[strescseq.len], c, len);
2592 strescseq.len += len;
2598 * Actions of control codes must be performed as soon they arrive
2599 * because they can be embedded inside a control sequence, and
2600 * they must not cause conflicts with sequences.
2605 * control codes are not shown ever
2610 } else if (term.esc & ESC_START) {
2611 if (term.esc & ESC_CSI) {
2612 csiescseq.buf[csiescseq.len++] = u;
2613 if (BETWEEN(u, 0x40, 0x7E)
2614 || csiescseq.len >= \
2615 sizeof(csiescseq.buf)-1) {
2621 } else if (term.esc & ESC_UTF8) {
2623 } else if (term.esc & ESC_ALTCHARSET) {
2625 } else if (term.esc & ESC_TEST) {
2630 /* sequence already finished */
2634 * All characters which form part of a sequence are not
2639 if (selected(term.c.x, term.c.y))
2642 gp = &term.line[term.c.y][term.c.x];
2643 if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
2644 gp->mode |= ATTR_WRAP;
2646 gp = &term.line[term.c.y][term.c.x];
2649 if (IS_SET(MODE_INSERT) && term.c.x+width < term.col)
2650 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
2652 if (term.c.x+width > term.col) {
2654 gp = &term.line[term.c.y][term.c.x];
2657 tsetchar(u, &term.c.attr, term.c.x, term.c.y);
2661 gp->mode |= ATTR_WIDE;
2662 if (term.c.x+1 < term.col) {
2663 if (gp[1].mode == ATTR_WIDE && term.c.x+2 < term.col) {
2665 gp[2].mode &= ~ATTR_WDUMMY;
2668 gp[1].mode = ATTR_WDUMMY;
2671 if (term.c.x+width < term.col) {
2672 tmoveto(term.c.x+width, term.c.y);
2674 term.c.state |= CURSOR_WRAPNEXT;
2679 twrite(const char *buf, int buflen, int show_ctrl)
2688 for (n = 0; n < buflen; n += charsize) {
2689 if (IS_SET(MODE_UTF8)) {
2690 /* process a complete utf8 char */
2691 charsize = utf8decode(buf + n, &u, buflen - n);
2700 break; // ESU - allow rendering before a new BSU
2702 if (show_ctrl && ISCONTROL(u)) {
2707 } else if (u != '\n' && u != '\r' && u != '\t') {
2718 tresize(int col, int row)
2721 int minrow = MIN(row, term.row);
2722 int mincol = MIN(col, term.col);
2726 if (col < 1 || row < 1) {
2728 "tresize: error resizing to %dx%d\n", col, row);
2733 * slide screen to keep cursor where we expect it -
2734 * tscrollup would work here, but we can optimize to
2735 * memmove because we're freeing the earlier lines
2737 for (i = 0; i <= term.c.y - row; i++) {
2741 /* ensure that both src and dst are not NULL */
2743 memmove(term.line, term.line + i, row * sizeof(Line));
2744 memmove(term.alt, term.alt + i, row * sizeof(Line));
2746 for (i += row; i < term.row; i++) {
2751 /* resize to new height */
2752 term.line = xrealloc(term.line, row * sizeof(Line));
2753 term.alt = xrealloc(term.alt, row * sizeof(Line));
2754 term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
2755 term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
2757 for (i = 0; i < HISTSIZE; i++) {
2758 term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph));
2759 for (j = mincol; j < col; j++) {
2760 term.hist[i][j] = term.c.attr;
2761 term.hist[i][j].u = ' ';
2765 /* resize each row to new width, zero-pad if needed */
2766 for (i = 0; i < minrow; i++) {
2767 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
2768 term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph));
2771 /* allocate any new rows */
2772 for (/* i = minrow */; i < row; i++) {
2773 term.line[i] = xmalloc(col * sizeof(Glyph));
2774 term.alt[i] = xmalloc(col * sizeof(Glyph));
2776 if (col > term.col) {
2777 bp = term.tabs + term.col;
2779 memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
2780 while (--bp > term.tabs && !*bp)
2782 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
2785 /* update terminal size */
2788 /* reset scrolling region */
2789 tsetscroll(0, row-1);
2790 /* make use of the LIMIT in tmoveto */
2791 tmoveto(term.c.x, term.c.y);
2792 /* Clearing both screens (it makes dirty all lines) */
2794 for (i = 0; i < 2; i++) {
2795 if (mincol < col && 0 < minrow) {
2796 tclearregion(mincol, 0, col - 1, minrow - 1);
2798 if (0 < col && minrow < row) {
2799 tclearregion(0, minrow, col - 1, row - 1);
2802 tcursor(CURSOR_LOAD);
2814 drawregion(int x1, int y1, int x2, int y2)
2818 for (y = y1; y < y2; y++) {
2823 xdrawline(TLINE(y), x1, y, x2);
2830 int cx = term.c.x, ocx = term.ocx, ocy = term.ocy;
2835 /* adjust cursor position */
2836 LIMIT(term.ocx, 0, term.col-1);
2837 LIMIT(term.ocy, 0, term.row-1);
2838 if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY)
2840 if (term.line[term.c.y][cx].mode & ATTR_WDUMMY)
2843 drawregion(0, 0, term.col, term.row);
2845 xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
2846 term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
2848 term.ocy = term.c.y;
2850 if (ocx != term.ocx || ocy != term.ocy)
2851 xximspot(term.ocx, term.ocy);