1 /* See LICENSE for license details. */
12 #include <sys/ioctl.h>
13 #include <sys/select.h>
14 #include <sys/types.h>
25 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
27 #elif defined(__FreeBSD__) || defined(__DragonFly__)
32 #define UTF_INVALID 0xFFFD
34 #define ESC_BUF_SIZ (128*UTF_SIZ)
35 #define ESC_ARG_SIZ 16
36 #define STR_BUF_SIZ ESC_BUF_SIZ
37 #define STR_ARG_SIZ ESC_ARG_SIZ
40 #define IS_SET(flag) ((term.mode & (flag)) != 0)
41 #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f)
42 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
43 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
44 #define ISDELIM(u) (u && wcschr(worddelimiters, u))
49 MODE_ALTSCREEN = 1 << 2,
56 enum cursor_movement {
80 ESC_STR = 4, /* DCS, OSC, PM, APC */
82 ESC_STR_END = 16, /* a final string was encountered */
83 ESC_TEST = 32, /* Enter in test mode */
88 Glyph attr; /* current char attributes */
99 * Selection variables:
100 * nb – normalized coordinates of the beginning of the selection
101 * ne – normalized coordinates of the end of the selection
102 * ob – original coordinates of the beginning of the selection
103 * oe – original coordinates of the end of the selection
112 /* Internal representation of the screen */
114 int row; /* nb row */
115 int col; /* nb col */
116 Line *line; /* screen */
117 Line *alt; /* alternate screen */
118 int *dirty; /* dirtyness of lines */
119 TCursor c; /* cursor */
120 int ocx; /* old cursor col */
121 int ocy; /* old cursor row */
122 int top; /* top scroll limit */
123 int bot; /* bottom scroll limit */
124 int mode; /* terminal mode flags */
125 int esc; /* escape state flags */
126 char trantbl[4]; /* charset table translation */
127 int charset; /* current charset */
128 int icharset; /* selected charset for sequence */
130 Rune lastc; /* last printed char outside of sequence, 0 if control */
133 /* CSI Escape sequence structs */
134 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
136 char buf[ESC_BUF_SIZ]; /* raw string */
137 size_t len; /* raw string length */
139 int arg[ESC_ARG_SIZ];
140 int narg; /* nb of args */
144 /* STR Escape sequence structs */
145 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
147 char type; /* ESC type ... */
148 char *buf; /* allocated raw string */
149 size_t siz; /* allocation size */
150 size_t len; /* raw string length */
151 char *args[STR_ARG_SIZ];
152 int narg; /* nb of args */
155 static void execsh(char *, char **);
156 static void stty(char **);
157 static void sigchld(int);
158 static void ttywriteraw(const char *, size_t);
160 static void csidump(void);
161 static void csihandle(void);
162 static void csiparse(void);
163 static void csireset(void);
164 static int eschandle(uchar);
165 static void strdump(void);
166 static void strhandle(void);
167 static void strparse(void);
168 static void strreset(void);
170 static void tprinter(char *, size_t);
171 static void tdumpsel(void);
172 static void tdumpline(int);
173 static void tdump(void);
174 static void tclearregion(int, int, int, int);
175 static void tcursor(int);
176 static void tdeletechar(int);
177 static void tdeleteline(int);
178 static void tinsertblank(int);
179 static void tinsertblankline(int);
180 static int tlinelen(int);
181 static void tmoveto(int, int);
182 static void tmoveato(int, int);
183 static void tnewline(int);
184 static void tputtab(int);
185 static void tputc(Rune);
186 static void treset(void);
187 static void tscrollup(int, int);
188 static void tscrolldown(int, int);
189 static void tsetattr(const int *, int);
190 static void tsetchar(Rune, const Glyph *, int, int);
191 static void tsetdirt(int, int);
192 static void tsetscroll(int, int);
193 static void tswapscreen(void);
194 static void tsetmode(int, int, const int *, int);
195 static int twrite(const char *, int, int);
196 static void tfulldirt(void);
197 static void tcontrolcode(uchar );
198 static void tdectest(char );
199 static void tdefutf8(char);
200 static int32_t tdefcolor(const int *, int *, int);
201 static void tdeftran(char);
202 static void tstrsequence(uchar);
203 static void tsetcolor(int, int, int, uint32_t, uint32_t);
204 static char * findlastany(char *, const char**, size_t);
206 static void drawregion(int, int, int, int);
208 static void selnormalize(void);
209 static void selscroll(int, int);
210 static void selsnap(int *, int *, int);
212 static size_t utf8decode(const char *, Rune *, size_t);
213 static Rune utf8decodebyte(char, size_t *);
214 static char utf8encodebyte(Rune, size_t);
215 static size_t utf8validate(Rune *, size_t);
217 static char *base64dec(const char *);
218 static char base64dec_getc(const char **);
220 static ssize_t xwrite(int, const char *, size_t);
224 static Selection sel;
225 static CSIEscape csiescseq;
226 static STREscape strescseq;
231 static const uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
232 static const uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
233 static const Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000};
234 static const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
237 xwrite(int fd, const char *s, size_t len)
243 r = write(fd, s, len);
258 if (!(p = malloc(len)))
259 die("malloc: %s\n", strerror(errno));
265 xrealloc(void *p, size_t len)
267 if ((p = realloc(p, len)) == NULL)
268 die("realloc: %s\n", strerror(errno));
274 xstrdup(const char *s)
278 if ((p = strdup(s)) == NULL)
279 die("strdup: %s\n", strerror(errno));
285 utf8decode(const char *c, Rune *u, size_t clen)
287 size_t i, j, len, type;
293 udecoded = utf8decodebyte(c[0], &len);
294 if (!BETWEEN(len, 1, UTF_SIZ))
296 for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
297 udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
304 utf8validate(u, len);
310 utf8decodebyte(char c, size_t *i)
312 for (*i = 0; *i < LEN(utfmask); ++(*i))
313 if (((uchar)c & utfmask[*i]) == utfbyte[*i])
314 return (uchar)c & ~utfmask[*i];
320 utf8encode(Rune u, char *c)
324 len = utf8validate(&u, 0);
328 for (i = len - 1; i != 0; --i) {
329 c[i] = utf8encodebyte(u, 0);
332 c[0] = utf8encodebyte(u, len);
338 utf8encodebyte(Rune u, size_t i)
340 return utfbyte[i] | (u & ~utfmask[i]);
344 utf8validate(Rune *u, size_t i)
346 if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF))
348 for (i = 1; *u > utfmax[i]; ++i)
354 static const char base64_digits[] = {
355 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
356 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0,
357 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
358 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
359 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
360 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
364 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
365 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
366 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
370 base64dec_getc(const char **src)
372 while (**src && !isprint(**src))
374 return **src ? *((*src)++) : '='; /* emulate padding if string ends */
378 base64dec(const char *src)
380 size_t in_len = strlen(src);
384 in_len += 4 - (in_len % 4);
385 result = dst = xmalloc(in_len / 4 * 3 + 1);
387 int a = base64_digits[(unsigned char) base64dec_getc(&src)];
388 int b = base64_digits[(unsigned char) base64dec_getc(&src)];
389 int c = base64_digits[(unsigned char) base64dec_getc(&src)];
390 int d = base64_digits[(unsigned char) base64dec_getc(&src)];
392 /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */
393 if (a == -1 || b == -1)
396 *dst++ = (a << 2) | ((b & 0x30) >> 4);
399 *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2);
402 *dst++ = ((c & 0x03) << 6) | d;
421 if (term.line[y][i - 1].mode & ATTR_WRAP)
424 while (i > 0 && term.line[y][i - 1].u == ' ')
431 selstart(int col, int row, int snap)
434 sel.mode = SEL_EMPTY;
435 sel.type = SEL_REGULAR;
436 sel.alt = IS_SET(MODE_ALTSCREEN);
438 sel.oe.x = sel.ob.x = col;
439 sel.oe.y = sel.ob.y = row;
443 sel.mode = SEL_READY;
444 tsetdirt(sel.nb.y, sel.ne.y);
448 selextend(int col, int row, int type, int done)
450 int oldey, oldex, oldsby, oldsey, oldtype;
452 if (sel.mode == SEL_IDLE)
454 if (done && sel.mode == SEL_EMPTY) {
470 if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY)
471 tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey));
473 sel.mode = done ? SEL_IDLE : SEL_READY;
481 if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) {
482 sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x;
483 sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x;
485 sel.nb.x = MIN(sel.ob.x, sel.oe.x);
486 sel.ne.x = MAX(sel.ob.x, sel.oe.x);
488 sel.nb.y = MIN(sel.ob.y, sel.oe.y);
489 sel.ne.y = MAX(sel.ob.y, sel.oe.y);
491 selsnap(&sel.nb.x, &sel.nb.y, -1);
492 selsnap(&sel.ne.x, &sel.ne.y, +1);
494 /* expand selection over line breaks */
495 if (sel.type == SEL_RECTANGULAR)
497 i = tlinelen(sel.nb.y);
500 if (tlinelen(sel.ne.y) <= sel.ne.x)
501 sel.ne.x = term.col - 1;
505 selected(int x, int y)
507 if (sel.mode == SEL_EMPTY || sel.ob.x == -1 ||
508 sel.alt != IS_SET(MODE_ALTSCREEN))
511 if (sel.type == SEL_RECTANGULAR)
512 return BETWEEN(y, sel.nb.y, sel.ne.y)
513 && BETWEEN(x, sel.nb.x, sel.ne.x);
515 return BETWEEN(y, sel.nb.y, sel.ne.y)
516 && (y != sel.nb.y || x >= sel.nb.x)
517 && (y != sel.ne.y || x <= sel.ne.x);
521 selsnap(int *x, int *y, int direction)
523 int newx, newy, xt, yt;
524 int delim, prevdelim;
525 const Glyph *gp, *prevgp;
530 * Snap around if the word wraps around at the end or
531 * beginning of a line.
533 prevgp = &term.line[*y][*x];
534 prevdelim = ISDELIM(prevgp->u);
536 newx = *x + direction;
538 if (!BETWEEN(newx, 0, term.col - 1)) {
540 newx = (newx + term.col) % term.col;
541 if (!BETWEEN(newy, 0, term.row - 1))
547 yt = newy, xt = newx;
548 if (!(term.line[yt][xt].mode & ATTR_WRAP))
552 if (newx >= tlinelen(newy))
555 gp = &term.line[newy][newx];
556 delim = ISDELIM(gp->u);
557 if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
558 || (delim && gp->u != prevgp->u)))
569 * Snap around if the the previous line or the current one
570 * has set ATTR_WRAP at its end. Then the whole next or
571 * previous line will be selected.
573 *x = (direction < 0) ? 0 : term.col - 1;
575 for (; *y > 0; *y += direction) {
576 if (!(term.line[*y-1][term.col-1].mode
581 } else if (direction > 0) {
582 for (; *y < term.row-1; *y += direction) {
583 if (!(term.line[*y][term.col-1].mode
597 int y, bufsize, lastx, linelen;
598 const Glyph *gp, *last;
603 bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ;
604 ptr = str = xmalloc(bufsize);
606 /* append every set & selected glyph to the selection */
607 for (y = sel.nb.y; y <= sel.ne.y; y++) {
608 if ((linelen = tlinelen(y)) == 0) {
613 if (sel.type == SEL_RECTANGULAR) {
614 gp = &term.line[y][sel.nb.x];
617 gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0];
618 lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
620 last = &term.line[y][MIN(lastx, linelen-1)];
621 while (last >= gp && last->u == ' ')
624 for ( ; gp <= last; ++gp) {
625 if (gp->mode & ATTR_WDUMMY)
628 ptr += utf8encode(gp->u, ptr);
632 * Copy and pasting of line endings is inconsistent
633 * in the inconsistent terminal and GUI world.
634 * The best solution seems like to produce '\n' when
635 * something is copied from st and convert '\n' to
636 * '\r', when something to be pasted is received by
638 * FIXME: Fix the computer world.
640 if ((y < sel.ne.y || lastx >= linelen) &&
641 (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR))
655 tsetdirt(sel.nb.y, sel.ne.y);
659 die(const char *errstr, ...)
663 va_start(ap, errstr);
664 vfprintf(stderr, errstr, ap);
670 execsh(char *cmd, char **args)
672 char *sh, *prog, *arg;
673 const struct passwd *pw;
676 if ((pw = getpwuid(getuid())) == NULL) {
678 die("getpwuid: %s\n", strerror(errno));
680 die("who are you?\n");
683 if ((sh = getenv("SHELL")) == NULL)
684 sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd;
691 arg = utmp ? utmp : sh;
699 DEFAULT(args, ((char *[]) {prog, arg, NULL}));
704 setenv("LOGNAME", pw->pw_name, 1);
705 setenv("USER", pw->pw_name, 1);
706 setenv("SHELL", sh, 1);
707 setenv("HOME", pw->pw_dir, 1);
708 setenv("TERM", termname, 1);
710 signal(SIGCHLD, SIG_DFL);
711 signal(SIGHUP, SIG_DFL);
712 signal(SIGINT, SIG_DFL);
713 signal(SIGQUIT, SIG_DFL);
714 signal(SIGTERM, SIG_DFL);
715 signal(SIGALRM, SIG_DFL);
727 if ((p = waitpid(pid, &stat, WNOHANG)) < 0)
728 die("waiting for pid %hd failed: %s\n", pid, strerror(errno));
733 if (WIFEXITED(stat) && WEXITSTATUS(stat))
734 die("child exited with status %d\n", WEXITSTATUS(stat));
735 else if (WIFSIGNALED(stat))
736 die("child terminated due to signal %d\n", WTERMSIG(stat));
743 char cmd[_POSIX_ARG_MAX], **p, *q, *s;
746 if ((n = strlen(stty_args)) > sizeof(cmd)-1)
747 die("incorrect stty parameters\n");
748 memcpy(cmd, stty_args, n);
750 siz = sizeof(cmd) - n;
751 for (p = args; p && (s = *p); ++p) {
752 if ((n = strlen(s)) > siz-1)
753 die("stty parameter length too long\n");
760 if (system(cmd) != 0)
761 perror("Couldn't call stty");
765 ttynew(const char *line, char *cmd, const char *out, char **args)
770 term.mode |= MODE_PRINT;
771 iofd = (!strcmp(out, "-")) ?
772 1 : open(out, O_WRONLY | O_CREAT, 0666);
774 fprintf(stderr, "Error opening %s:%s\n",
775 out, strerror(errno));
780 if ((cmdfd = open(line, O_RDWR)) < 0)
781 die("open line '%s' failed: %s\n",
782 line, strerror(errno));
788 /* seems to work fine on linux, openbsd and freebsd */
789 if (openpty(&m, &s, NULL, NULL, NULL) < 0)
790 die("openpty failed: %s\n", strerror(errno));
792 switch (pid = fork()) {
794 die("fork failed: %s\n", strerror(errno));
798 setsid(); /* create a new process group */
802 if (ioctl(s, TIOCSCTTY, NULL) < 0)
803 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno));
807 if (pledge("stdio getpw proc exec", NULL) == -1)
814 if (pledge("stdio rpath tty proc", NULL) == -1)
819 signal(SIGCHLD, sigchld);
828 static char buf[BUFSIZ];
829 static int buflen = 0;
832 /* append read bytes to unprocessed bytes */
833 ret = read(cmdfd, buf+buflen, LEN(buf)-buflen);
839 die("couldn't read from shell: %s\n", strerror(errno));
842 written = twrite(buf, buflen, 0);
844 /* keep any incomplete UTF-8 byte sequence for the next call */
846 memmove(buf, buf + written, buflen);
852 ttywrite(const char *s, size_t n, int may_echo)
856 if (may_echo && IS_SET(MODE_ECHO))
859 if (!IS_SET(MODE_CRLF)) {
864 /* This is similar to how the kernel handles ONLCR for ttys */
868 ttywriteraw("\r\n", 2);
870 next = memchr(s, '\r', n);
871 DEFAULT(next, s + n);
872 ttywriteraw(s, next - s);
880 ttywriteraw(const char *s, size_t n)
887 * Remember that we are using a pty, which might be a modem line.
888 * Writing too much will clog the line. That's why we are doing this
890 * FIXME: Migrate the world to Plan 9.
898 /* Check if we can write. */
899 if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) {
902 die("select failed: %s\n", strerror(errno));
904 if (FD_ISSET(cmdfd, &wfd)) {
906 * Only write the bytes written by ttywrite() or the
907 * default of 256. This seems to be a reasonable value
908 * for a serial line. Bigger values might clog the I/O.
910 if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0)
914 * We weren't able to write out everything.
915 * This means the buffer is getting full
923 /* All bytes have been written. */
927 if (FD_ISSET(cmdfd, &rfd))
933 die("write error on tty: %s\n", strerror(errno));
937 ttyresize(int tw, int th)
945 if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
946 fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
952 /* Send SIGHUP to shell */
961 for (i = 0; i < term.row-1; i++) {
962 for (j = 0; j < term.col-1; j++) {
963 if (term.line[i][j].mode & attr)
972 tsetdirt(int top, int bot)
976 LIMIT(top, 0, term.row-1);
977 LIMIT(bot, 0, term.row-1);
979 for (i = top; i <= bot; i++)
984 tsetdirtattr(int attr)
988 for (i = 0; i < term.row-1; i++) {
989 for (j = 0; j < term.col-1; j++) {
990 if (term.line[i][j].mode & attr) {
1001 tsetdirt(0, term.row-1);
1007 static TCursor c[2];
1008 int alt = IS_SET(MODE_ALTSCREEN);
1010 if (mode == CURSOR_SAVE) {
1012 } else if (mode == CURSOR_LOAD) {
1014 tmoveto(c[alt].x, c[alt].y);
1023 term.c = (TCursor){{
1027 }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
1029 memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1030 for (i = tabspaces; i < term.col; i += tabspaces)
1033 term.bot = term.row - 1;
1034 term.mode = MODE_WRAP|MODE_UTF8;
1035 memset(term.trantbl, CS_USA, sizeof(term.trantbl));
1038 for (i = 0; i < 2; i++) {
1040 tcursor(CURSOR_SAVE);
1041 tclearregion(0, 0, term.col-1, term.row-1);
1047 tnew(int col, int row)
1049 term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
1057 Line *tmp = term.line;
1059 term.line = term.alt;
1061 term.mode ^= MODE_ALTSCREEN;
1066 tscrolldown(int orig, int n)
1071 LIMIT(n, 0, term.bot-orig+1);
1073 tsetdirt(orig, term.bot-n);
1074 tclearregion(0, term.bot-n+1, term.col-1, term.bot);
1076 for (i = term.bot; i >= orig+n; i--) {
1077 temp = term.line[i];
1078 term.line[i] = term.line[i-n];
1079 term.line[i-n] = temp;
1086 tscrollup(int orig, int n)
1091 LIMIT(n, 0, term.bot-orig+1);
1093 tclearregion(0, orig, term.col-1, orig+n-1);
1094 tsetdirt(orig+n, term.bot);
1096 for (i = orig; i <= term.bot-n; i++) {
1097 temp = term.line[i];
1098 term.line[i] = term.line[i+n];
1099 term.line[i+n] = temp;
1102 selscroll(orig, -n);
1106 selscroll(int orig, int n)
1111 if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) {
1113 } else if (BETWEEN(sel.nb.y, orig, term.bot)) {
1116 if (sel.ob.y < term.top || sel.ob.y > term.bot ||
1117 sel.oe.y < term.top || sel.oe.y > term.bot) {
1126 tnewline(int first_col)
1130 if (y == term.bot) {
1131 tscrollup(term.top, 1);
1135 tmoveto(first_col ? 0 : term.c.x, y);
1141 char *p = csiescseq.buf, *np;
1150 csiescseq.buf[csiescseq.len] = '\0';
1151 while (p < csiescseq.buf+csiescseq.len) {
1153 v = strtol(p, &np, 10);
1156 if (v == LONG_MAX || v == LONG_MIN)
1158 csiescseq.arg[csiescseq.narg++] = v;
1160 if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ)
1164 csiescseq.mode[0] = *p++;
1165 csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0';
1168 /* for absolute user moves, when decom is set */
1170 tmoveato(int x, int y)
1172 tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0));
1176 tmoveto(int x, int y)
1180 if (term.c.state & CURSOR_ORIGIN) {
1185 maxy = term.row - 1;
1187 term.c.state &= ~CURSOR_WRAPNEXT;
1188 term.c.x = LIMIT(x, 0, term.col-1);
1189 term.c.y = LIMIT(y, miny, maxy);
1193 tsetchar(Rune u, const Glyph *attr, int x, int y)
1195 static const char *vt100_0[62] = { /* 0x41 - 0x7e */
1196 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1197 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1198 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1199 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1200 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1201 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1202 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1203 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1207 * The table is proudly stolen from rxvt.
1209 if (term.trantbl[term.charset] == CS_GRAPHIC0 &&
1210 BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
1211 utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ);
1213 if (term.line[y][x].mode & ATTR_WIDE) {
1214 if (x+1 < term.col) {
1215 term.line[y][x+1].u = ' ';
1216 term.line[y][x+1].mode &= ~ATTR_WDUMMY;
1218 } else if (term.line[y][x].mode & ATTR_WDUMMY) {
1219 term.line[y][x-1].u = ' ';
1220 term.line[y][x-1].mode &= ~ATTR_WIDE;
1224 term.line[y][x] = *attr;
1225 term.line[y][x].u = u;
1229 tclearregion(int x1, int y1, int x2, int y2)
1235 temp = x1, x1 = x2, x2 = temp;
1237 temp = y1, y1 = y2, y2 = temp;
1239 LIMIT(x1, 0, term.col-1);
1240 LIMIT(x2, 0, term.col-1);
1241 LIMIT(y1, 0, term.row-1);
1242 LIMIT(y2, 0, term.row-1);
1244 for (y = y1; y <= y2; y++) {
1246 for (x = x1; x <= x2; x++) {
1247 gp = &term.line[y][x];
1250 gp->fg = term.c.attr.fg;
1251 gp->bg = term.c.attr.bg;
1264 LIMIT(n, 0, term.col - term.c.x);
1268 size = term.col - src;
1269 line = term.line[term.c.y];
1271 memmove(&line[dst], &line[src], size * sizeof(Glyph));
1272 tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
1281 LIMIT(n, 0, term.col - term.c.x);
1285 size = term.col - dst;
1286 line = term.line[term.c.y];
1288 memmove(&line[dst], &line[src], size * sizeof(Glyph));
1289 tclearregion(src, term.c.y, dst - 1, term.c.y);
1293 tinsertblankline(int n)
1295 if (BETWEEN(term.c.y, term.top, term.bot))
1296 tscrolldown(term.c.y, n);
1302 if (BETWEEN(term.c.y, term.top, term.bot))
1303 tscrollup(term.c.y, n);
1307 tdefcolor(const int *attr, int *npar, int l)
1312 switch (attr[*npar + 1]) {
1313 case 2: /* direct color in RGB space */
1314 if (*npar + 4 >= l) {
1316 "erresc(38): Incorrect number of parameters (%d)\n",
1320 r = attr[*npar + 2];
1321 g = attr[*npar + 3];
1322 b = attr[*npar + 4];
1324 if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255))
1325 fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n",
1328 idx = TRUECOLOR(r, g, b);
1330 case 5: /* indexed color */
1331 if (*npar + 2 >= l) {
1333 "erresc(38): Incorrect number of parameters (%d)\n",
1338 if (!BETWEEN(attr[*npar], 0, 255))
1339 fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]);
1343 case 0: /* implemented defined (only foreground) */
1344 case 1: /* transparent */
1345 case 3: /* direct color in CMY space */
1346 case 4: /* direct color in CMYK space */
1349 "erresc(38): gfx attr %d unknown\n", attr[*npar]);
1357 tsetattr(const int *attr, int l)
1362 for (i = 0; i < l; i++) {
1365 term.c.attr.mode &= ~(
1374 term.c.attr.fg = defaultfg;
1375 term.c.attr.bg = defaultbg;
1378 term.c.attr.mode |= ATTR_BOLD;
1381 term.c.attr.mode |= ATTR_FAINT;
1384 term.c.attr.mode |= ATTR_ITALIC;
1387 term.c.attr.mode |= ATTR_UNDERLINE;
1389 case 5: /* slow blink */
1391 case 6: /* rapid blink */
1392 term.c.attr.mode |= ATTR_BLINK;
1395 term.c.attr.mode |= ATTR_REVERSE;
1398 term.c.attr.mode |= ATTR_INVISIBLE;
1401 term.c.attr.mode |= ATTR_STRUCK;
1404 term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT);
1407 term.c.attr.mode &= ~ATTR_ITALIC;
1410 term.c.attr.mode &= ~ATTR_UNDERLINE;
1413 term.c.attr.mode &= ~ATTR_BLINK;
1416 term.c.attr.mode &= ~ATTR_REVERSE;
1419 term.c.attr.mode &= ~ATTR_INVISIBLE;
1422 term.c.attr.mode &= ~ATTR_STRUCK;
1425 if ((idx = tdefcolor(attr, &i, l)) >= 0)
1426 term.c.attr.fg = idx;
1429 term.c.attr.fg = defaultfg;
1432 if ((idx = tdefcolor(attr, &i, l)) >= 0)
1433 term.c.attr.bg = idx;
1436 term.c.attr.bg = defaultbg;
1439 if (BETWEEN(attr[i], 30, 37)) {
1440 term.c.attr.fg = attr[i] - 30;
1441 } else if (BETWEEN(attr[i], 40, 47)) {
1442 term.c.attr.bg = attr[i] - 40;
1443 } else if (BETWEEN(attr[i], 90, 97)) {
1444 term.c.attr.fg = attr[i] - 90 + 8;
1445 } else if (BETWEEN(attr[i], 100, 107)) {
1446 term.c.attr.bg = attr[i] - 100 + 8;
1449 "erresc(default): gfx attr %d unknown\n",
1459 tsetscroll(int t, int b)
1463 LIMIT(t, 0, term.row-1);
1464 LIMIT(b, 0, term.row-1);
1475 tsetmode(int priv, int set, const int *args, int narg)
1477 int alt; const int *lim;
1479 for (lim = args + narg; args < lim; ++args) {
1482 case 1: /* DECCKM -- Cursor key */
1483 xsetmode(set, MODE_APPCURSOR);
1485 case 5: /* DECSCNM -- Reverse video */
1486 xsetmode(set, MODE_REVERSE);
1488 case 6: /* DECOM -- Origin */
1489 MODBIT(term.c.state, set, CURSOR_ORIGIN);
1492 case 7: /* DECAWM -- Auto wrap */
1493 MODBIT(term.mode, set, MODE_WRAP);
1495 case 0: /* Error (IGNORED) */
1496 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1497 case 3: /* DECCOLM -- Column (IGNORED) */
1498 case 4: /* DECSCLM -- Scroll (IGNORED) */
1499 case 8: /* DECARM -- Auto repeat (IGNORED) */
1500 case 18: /* DECPFF -- Printer feed (IGNORED) */
1501 case 19: /* DECPEX -- Printer extent (IGNORED) */
1502 case 42: /* DECNRCM -- National characters (IGNORED) */
1503 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1505 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1506 xsetmode(!set, MODE_HIDE);
1508 case 9: /* X10 mouse compatibility mode */
1509 xsetpointermotion(0);
1510 xsetmode(0, MODE_MOUSE);
1511 xsetmode(set, MODE_MOUSEX10);
1513 case 1000: /* 1000: report button press */
1514 xsetpointermotion(0);
1515 xsetmode(0, MODE_MOUSE);
1516 xsetmode(set, MODE_MOUSEBTN);
1518 case 1002: /* 1002: report motion on button press */
1519 xsetpointermotion(0);
1520 xsetmode(0, MODE_MOUSE);
1521 xsetmode(set, MODE_MOUSEMOTION);
1523 case 1003: /* 1003: enable all mouse motions */
1524 xsetpointermotion(set);
1525 xsetmode(0, MODE_MOUSE);
1526 xsetmode(set, MODE_MOUSEMANY);
1528 case 1004: /* 1004: send focus events to tty */
1529 xsetmode(set, MODE_FOCUS);
1531 case 1006: /* 1006: extended reporting mode */
1532 xsetmode(set, MODE_MOUSESGR);
1535 xsetmode(set, MODE_8BIT);
1537 case 1049: /* swap screen & set/restore cursor as xterm */
1538 if (!allowaltscreen)
1540 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1542 case 47: /* swap screen */
1544 if (!allowaltscreen)
1546 alt = IS_SET(MODE_ALTSCREEN);
1548 tclearregion(0, 0, term.col-1,
1551 if (set ^ alt) /* set is always 1 or 0 */
1557 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1559 case 2004: /* 2004: bracketed paste mode */
1560 xsetmode(set, MODE_BRCKTPASTE);
1562 /* Not implemented mouse modes. See comments there. */
1563 case 1001: /* mouse highlight mode; can hang the
1564 terminal by design when implemented. */
1565 case 1005: /* UTF-8 mouse mode; will confuse
1566 applications not supporting UTF-8
1568 case 1015: /* urxvt mangled mouse mode; incompatible
1569 and can be mistaken for other control
1574 "erresc: unknown private set/reset mode %d\n",
1580 case 0: /* Error (IGNORED) */
1583 xsetmode(set, MODE_KBDLOCK);
1585 case 4: /* IRM -- Insertion-replacement */
1586 MODBIT(term.mode, set, MODE_INSERT);
1588 case 12: /* SRM -- Send/Receive */
1589 MODBIT(term.mode, !set, MODE_ECHO);
1591 case 20: /* LNM -- Linefeed/new line */
1592 MODBIT(term.mode, set, MODE_CRLF);
1596 "erresc: unknown set/reset mode %d\n",
1610 switch (csiescseq.mode[0]) {
1613 fprintf(stderr, "erresc: unknown csi ");
1617 case '@': /* ICH -- Insert <n> blank char */
1618 DEFAULT(csiescseq.arg[0], 1);
1619 tinsertblank(csiescseq.arg[0]);
1621 case 'A': /* CUU -- Cursor <n> Up */
1622 DEFAULT(csiescseq.arg[0], 1);
1623 tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
1625 case 'B': /* CUD -- Cursor <n> Down */
1626 case 'e': /* VPR --Cursor <n> Down */
1627 DEFAULT(csiescseq.arg[0], 1);
1628 tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
1630 case 'i': /* MC -- Media Copy */
1631 switch (csiescseq.arg[0]) {
1636 tdumpline(term.c.y);
1642 term.mode &= ~MODE_PRINT;
1645 term.mode |= MODE_PRINT;
1649 case 'c': /* DA -- Device Attributes */
1650 if (csiescseq.arg[0] == 0)
1651 ttywrite(vtiden, strlen(vtiden), 0);
1653 case 'b': /* REP -- if last char is printable print it <n> more times */
1654 DEFAULT(csiescseq.arg[0], 1);
1656 while (csiescseq.arg[0]-- > 0)
1659 case 'C': /* CUF -- Cursor <n> Forward */
1660 case 'a': /* HPR -- Cursor <n> Forward */
1661 DEFAULT(csiescseq.arg[0], 1);
1662 tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
1664 case 'D': /* CUB -- Cursor <n> Backward */
1665 DEFAULT(csiescseq.arg[0], 1);
1666 tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
1668 case 'E': /* CNL -- Cursor <n> Down and first col */
1669 DEFAULT(csiescseq.arg[0], 1);
1670 tmoveto(0, term.c.y+csiescseq.arg[0]);
1672 case 'F': /* CPL -- Cursor <n> Up and first col */
1673 DEFAULT(csiescseq.arg[0], 1);
1674 tmoveto(0, term.c.y-csiescseq.arg[0]);
1676 case 'g': /* TBC -- Tabulation clear */
1677 switch (csiescseq.arg[0]) {
1678 case 0: /* clear current tab stop */
1679 term.tabs[term.c.x] = 0;
1681 case 3: /* clear all the tabs */
1682 memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1688 case 'G': /* CHA -- Move to <col> */
1690 DEFAULT(csiescseq.arg[0], 1);
1691 tmoveto(csiescseq.arg[0]-1, term.c.y);
1693 case 'H': /* CUP -- Move to <row> <col> */
1695 DEFAULT(csiescseq.arg[0], 1);
1696 DEFAULT(csiescseq.arg[1], 1);
1697 tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
1699 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1700 DEFAULT(csiescseq.arg[0], 1);
1701 tputtab(csiescseq.arg[0]);
1703 case 'J': /* ED -- Clear screen */
1704 switch (csiescseq.arg[0]) {
1706 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
1707 if (term.c.y < term.row-1) {
1708 tclearregion(0, term.c.y+1, term.col-1,
1714 tclearregion(0, 0, term.col-1, term.c.y-1);
1715 tclearregion(0, term.c.y, term.c.x, term.c.y);
1718 tclearregion(0, 0, term.col-1, term.row-1);
1724 case 'K': /* EL -- Clear line */
1725 switch (csiescseq.arg[0]) {
1727 tclearregion(term.c.x, term.c.y, term.col-1,
1731 tclearregion(0, term.c.y, term.c.x, term.c.y);
1734 tclearregion(0, term.c.y, term.col-1, term.c.y);
1738 case 'S': /* SU -- Scroll <n> line up */
1739 DEFAULT(csiescseq.arg[0], 1);
1740 tscrollup(term.top, csiescseq.arg[0]);
1742 case 'T': /* SD -- Scroll <n> line down */
1743 DEFAULT(csiescseq.arg[0], 1);
1744 tscrolldown(term.top, csiescseq.arg[0]);
1746 case 'L': /* IL -- Insert <n> blank lines */
1747 DEFAULT(csiescseq.arg[0], 1);
1748 tinsertblankline(csiescseq.arg[0]);
1750 case 'l': /* RM -- Reset Mode */
1751 tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
1753 case 'M': /* DL -- Delete <n> lines */
1754 DEFAULT(csiescseq.arg[0], 1);
1755 tdeleteline(csiescseq.arg[0]);
1757 case 'X': /* ECH -- Erase <n> char */
1758 DEFAULT(csiescseq.arg[0], 1);
1759 tclearregion(term.c.x, term.c.y,
1760 term.c.x + csiescseq.arg[0] - 1, term.c.y);
1762 case 'P': /* DCH -- Delete <n> char */
1763 DEFAULT(csiescseq.arg[0], 1);
1764 tdeletechar(csiescseq.arg[0]);
1766 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1767 DEFAULT(csiescseq.arg[0], 1);
1768 tputtab(-csiescseq.arg[0]);
1770 case 'd': /* VPA -- Move to <row> */
1771 DEFAULT(csiescseq.arg[0], 1);
1772 tmoveato(term.c.x, csiescseq.arg[0]-1);
1774 case 'h': /* SM -- Set terminal mode */
1775 tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
1777 case 'm': /* SGR -- Terminal attribute (color) */
1778 tsetattr(csiescseq.arg, csiescseq.narg);
1780 case 'n': /* DSR – Device Status Report (cursor position) */
1781 if (csiescseq.arg[0] == 6) {
1782 len = snprintf(buf, sizeof(buf), "\033[%i;%iR",
1783 term.c.y+1, term.c.x+1);
1784 ttywrite(buf, len, 0);
1787 case 'r': /* DECSTBM -- Set Scrolling Region */
1788 if (csiescseq.priv) {
1791 DEFAULT(csiescseq.arg[0], 1);
1792 DEFAULT(csiescseq.arg[1], term.row);
1793 tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
1797 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1798 tcursor(CURSOR_SAVE);
1800 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1801 tcursor(CURSOR_LOAD);
1804 switch (csiescseq.mode[1]) {
1805 case 'q': /* DECSCUSR -- Set Cursor Style */
1806 if (xsetcursor(csiescseq.arg[0]))
1822 fprintf(stderr, "ESC[");
1823 for (i = 0; i < csiescseq.len; i++) {
1824 c = csiescseq.buf[i] & 0xff;
1827 } else if (c == '\n') {
1828 fprintf(stderr, "(\\n)");
1829 } else if (c == '\r') {
1830 fprintf(stderr, "(\\r)");
1831 } else if (c == 0x1b) {
1832 fprintf(stderr, "(\\e)");
1834 fprintf(stderr, "(%02x)", c);
1843 memset(&csiescseq, 0, sizeof(csiescseq));
1849 char *p = NULL, *dec;
1852 term.esc &= ~(ESC_STR_END|ESC_STR);
1854 par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
1856 switch (strescseq.type) {
1857 case ']': /* OSC -- Operating System Command */
1861 xsettitle(strescseq.args[1]);
1862 xseticontitle(strescseq.args[1]);
1867 xseticontitle(strescseq.args[1]);
1871 xsettitle(strescseq.args[1]);
1874 if (narg > 2 && allowwindowops) {
1875 dec = base64dec(strescseq.args[2]);
1880 fprintf(stderr, "erresc: invalid base64\n");
1884 case 4: /* color set */
1887 p = strescseq.args[2];
1889 case 104: /* color reset, here p = NULL */
1890 j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
1891 if (xsetcolorname(j, p)) {
1892 if (par == 104 && narg <= 1)
1893 return; /* color reset without parameter */
1894 fprintf(stderr, "erresc: invalid color j=%d, p=%s\n",
1895 j, p ? p : "(null)");
1898 * TODO if defaultbg color is changed, borders
1906 case 'k': /* old title set compatibility */
1907 xsettitle(strescseq.args[0]);
1909 case 'P': /* DCS -- Device Control String */
1910 case '_': /* APC -- Application Program Command */
1911 case '^': /* PM -- Privacy Message */
1915 fprintf(stderr, "erresc: unknown str ");
1923 char *p = strescseq.buf;
1926 strescseq.buf[strescseq.len] = '\0';
1931 while (strescseq.narg < STR_ARG_SIZ) {
1932 strescseq.args[strescseq.narg++] = p;
1933 while ((c = *p) != ';' && c != '\0')
1947 fprintf(stderr, "ESC%c", strescseq.type);
1948 for (i = 0; i < strescseq.len; i++) {
1949 c = strescseq.buf[i] & 0xff;
1953 } else if (isprint(c)) {
1955 } else if (c == '\n') {
1956 fprintf(stderr, "(\\n)");
1957 } else if (c == '\r') {
1958 fprintf(stderr, "(\\r)");
1959 } else if (c == 0x1b) {
1960 fprintf(stderr, "(\\e)");
1962 fprintf(stderr, "(%02x)", c);
1965 fprintf(stderr, "ESC\\\n");
1971 strescseq = (STREscape){
1972 .buf = xrealloc(strescseq.buf, STR_BUF_SIZ),
1978 sendbreak(const Arg *arg)
1980 if (tcsendbreak(cmdfd, 0))
1981 perror("Error sending break");
1985 tprinter(char *s, size_t len)
1987 if (iofd != -1 && xwrite(iofd, s, len) < 0) {
1988 perror("Error writing to output file");
1995 toggleprinter(const Arg *arg)
1997 term.mode ^= MODE_PRINT;
2001 printscreen(const Arg *arg)
2007 printsel(const Arg *arg)
2017 if ((ptr = getsel())) {
2018 tprinter(ptr, strlen(ptr));
2027 const Glyph *bp, *end;
2029 bp = &term.line[n][0];
2030 end = &bp[MIN(tlinelen(n), term.col) - 1];
2031 if (bp != end || bp->u != ' ') {
2032 for ( ; bp <= end; ++bp)
2033 tprinter(buf, utf8encode(bp->u, buf));
2043 for (i = 0; i < term.row; ++i)
2053 while (x < term.col && n--)
2054 for (++x; x < term.col && !term.tabs[x]; ++x)
2057 while (x > 0 && n++)
2058 for (--x; x > 0 && !term.tabs[x]; --x)
2061 term.c.x = LIMIT(x, 0, term.col-1);
2065 tdefutf8(char ascii)
2068 term.mode |= MODE_UTF8;
2069 else if (ascii == '@')
2070 term.mode &= ~MODE_UTF8;
2074 tdeftran(char ascii)
2076 static char cs[] = "0B";
2077 static int vcs[] = {CS_GRAPHIC0, CS_USA};
2080 if ((p = strchr(cs, ascii)) == NULL) {
2081 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
2083 term.trantbl[term.icharset] = vcs[p - cs];
2092 if (c == '8') { /* DEC screen alignment test. */
2093 for (x = 0; x < term.col; ++x) {
2094 for (y = 0; y < term.row; ++y)
2095 tsetchar('E', &term.c.attr, x, y);
2101 tstrsequence(uchar c)
2104 case 0x90: /* DCS -- Device Control String */
2107 case 0x9f: /* APC -- Application Program Command */
2110 case 0x9e: /* PM -- Privacy Message */
2113 case 0x9d: /* OSC -- Operating System Command */
2119 term.esc |= ESC_STR;
2123 tcontrolcode(uchar ascii)
2130 tmoveto(term.c.x-1, term.c.y);
2133 tmoveto(0, term.c.y);
2138 /* go to first col if the mode is set */
2139 tnewline(IS_SET(MODE_CRLF));
2141 case '\a': /* BEL */
2142 if (term.esc & ESC_STR_END) {
2143 /* backwards compatibility to xterm */
2149 case '\033': /* ESC */
2151 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
2152 term.esc |= ESC_START;
2154 case '\016': /* SO (LS1 -- Locking shift 1) */
2155 case '\017': /* SI (LS0 -- Locking shift 0) */
2156 term.charset = 1 - (ascii - '\016');
2158 case '\032': /* SUB */
2159 tsetchar('?', &term.c.attr, term.c.x, term.c.y);
2161 case '\030': /* CAN */
2164 case '\005': /* ENQ (IGNORED) */
2165 case '\000': /* NUL (IGNORED) */
2166 case '\021': /* XON (IGNORED) */
2167 case '\023': /* XOFF (IGNORED) */
2168 case 0177: /* DEL (IGNORED) */
2170 case 0x80: /* TODO: PAD */
2171 case 0x81: /* TODO: HOP */
2172 case 0x82: /* TODO: BPH */
2173 case 0x83: /* TODO: NBH */
2174 case 0x84: /* TODO: IND */
2176 case 0x85: /* NEL -- Next line */
2177 tnewline(1); /* always go to first col */
2179 case 0x86: /* TODO: SSA */
2180 case 0x87: /* TODO: ESA */
2182 case 0x88: /* HTS -- Horizontal tab stop */
2183 term.tabs[term.c.x] = 1;
2185 case 0x89: /* TODO: HTJ */
2186 case 0x8a: /* TODO: VTS */
2187 case 0x8b: /* TODO: PLD */
2188 case 0x8c: /* TODO: PLU */
2189 case 0x8d: /* TODO: RI */
2190 case 0x8e: /* TODO: SS2 */
2191 case 0x8f: /* TODO: SS3 */
2192 case 0x91: /* TODO: PU1 */
2193 case 0x92: /* TODO: PU2 */
2194 case 0x93: /* TODO: STS */
2195 case 0x94: /* TODO: CCH */
2196 case 0x95: /* TODO: MW */
2197 case 0x96: /* TODO: SPA */
2198 case 0x97: /* TODO: EPA */
2199 case 0x98: /* TODO: SOS */
2200 case 0x99: /* TODO: SGCI */
2202 case 0x9a: /* DECID -- Identify Terminal */
2203 ttywrite(vtiden, strlen(vtiden), 0);
2205 case 0x9b: /* TODO: CSI */
2206 case 0x9c: /* TODO: ST */
2208 case 0x90: /* DCS -- Device Control String */
2209 case 0x9d: /* OSC -- Operating System Command */
2210 case 0x9e: /* PM -- Privacy Message */
2211 case 0x9f: /* APC -- Application Program Command */
2212 tstrsequence(ascii);
2215 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2216 term.esc &= ~(ESC_STR_END|ESC_STR);
2220 * returns 1 when the sequence is finished and it hasn't to read
2221 * more characters for this sequence, otherwise 0
2224 eschandle(uchar ascii)
2228 term.esc |= ESC_CSI;
2231 term.esc |= ESC_TEST;
2234 term.esc |= ESC_UTF8;
2236 case 'P': /* DCS -- Device Control String */
2237 case '_': /* APC -- Application Program Command */
2238 case '^': /* PM -- Privacy Message */
2239 case ']': /* OSC -- Operating System Command */
2240 case 'k': /* old title set compatibility */
2241 tstrsequence(ascii);
2243 case 'n': /* LS2 -- Locking shift 2 */
2244 case 'o': /* LS3 -- Locking shift 3 */
2245 term.charset = 2 + (ascii - 'n');
2247 case '(': /* GZD4 -- set primary charset G0 */
2248 case ')': /* G1D4 -- set secondary charset G1 */
2249 case '*': /* G2D4 -- set tertiary charset G2 */
2250 case '+': /* G3D4 -- set quaternary charset G3 */
2251 term.icharset = ascii - '(';
2252 term.esc |= ESC_ALTCHARSET;
2254 case 'D': /* IND -- Linefeed */
2255 if (term.c.y == term.bot) {
2256 tscrollup(term.top, 1);
2258 tmoveto(term.c.x, term.c.y+1);
2261 case 'E': /* NEL -- Next line */
2262 tnewline(1); /* always go to first col */
2264 case 'H': /* HTS -- Horizontal tab stop */
2265 term.tabs[term.c.x] = 1;
2267 case 'M': /* RI -- Reverse index */
2268 if (term.c.y == term.top) {
2269 tscrolldown(term.top, 1);
2271 tmoveto(term.c.x, term.c.y-1);
2274 case 'Z': /* DECID -- Identify Terminal */
2275 ttywrite(vtiden, strlen(vtiden), 0);
2277 case 'c': /* RIS -- Reset to initial state */
2282 case '=': /* DECPAM -- Application keypad */
2283 xsetmode(1, MODE_APPKEYPAD);
2285 case '>': /* DECPNM -- Normal keypad */
2286 xsetmode(0, MODE_APPKEYPAD);
2288 case '7': /* DECSC -- Save Cursor */
2289 tcursor(CURSOR_SAVE);
2291 case '8': /* DECRC -- Restore Cursor */
2292 tcursor(CURSOR_LOAD);
2294 case '\\': /* ST -- String Terminator */
2295 if (term.esc & ESC_STR_END)
2299 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2300 (uchar) ascii, isprint(ascii)? ascii:'.');
2314 control = ISCONTROL(u);
2315 if (u < 127 || !IS_SET(MODE_UTF8)) {
2319 len = utf8encode(u, c);
2320 if (!control && (width = wcwidth(u)) == -1)
2324 if (IS_SET(MODE_PRINT))
2328 * STR sequence must be checked before anything else
2329 * because it uses all following characters until it
2330 * receives a ESC, a SUB, a ST or any other C1 control
2333 if (term.esc & ESC_STR) {
2334 if (u == '\a' || u == 030 || u == 032 || u == 033 ||
2336 term.esc &= ~(ESC_START|ESC_STR);
2337 term.esc |= ESC_STR_END;
2338 goto check_control_code;
2341 if (strescseq.len+len >= strescseq.siz) {
2343 * Here is a bug in terminals. If the user never sends
2344 * some code to stop the str or esc command, then st
2345 * will stop responding. But this is better than
2346 * silently failing with unknown characters. At least
2347 * then users will report back.
2349 * In the case users ever get fixed, here is the code:
2355 if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2)
2358 strescseq.buf = xrealloc(strescseq.buf, strescseq.siz);
2361 memmove(&strescseq.buf[strescseq.len], c, len);
2362 strescseq.len += len;
2368 * Actions of control codes must be performed as soon they arrive
2369 * because they can be embedded inside a control sequence, and
2370 * they must not cause conflicts with sequences.
2375 * control codes are not shown ever
2380 } else if (term.esc & ESC_START) {
2381 if (term.esc & ESC_CSI) {
2382 csiescseq.buf[csiescseq.len++] = u;
2383 if (BETWEEN(u, 0x40, 0x7E)
2384 || csiescseq.len >= \
2385 sizeof(csiescseq.buf)-1) {
2391 } else if (term.esc & ESC_UTF8) {
2393 } else if (term.esc & ESC_ALTCHARSET) {
2395 } else if (term.esc & ESC_TEST) {
2400 /* sequence already finished */
2404 * All characters which form part of a sequence are not
2409 if (selected(term.c.x, term.c.y))
2412 gp = &term.line[term.c.y][term.c.x];
2413 if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
2414 gp->mode |= ATTR_WRAP;
2416 gp = &term.line[term.c.y][term.c.x];
2419 if (IS_SET(MODE_INSERT) && term.c.x+width < term.col)
2420 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
2422 if (term.c.x+width > term.col) {
2424 gp = &term.line[term.c.y][term.c.x];
2427 tsetchar(u, &term.c.attr, term.c.x, term.c.y);
2431 gp->mode |= ATTR_WIDE;
2432 if (term.c.x+1 < term.col) {
2434 gp[1].mode = ATTR_WDUMMY;
2437 if (term.c.x+width < term.col) {
2438 tmoveto(term.c.x+width, term.c.y);
2440 term.c.state |= CURSOR_WRAPNEXT;
2445 twrite(const char *buf, int buflen, int show_ctrl)
2451 for (n = 0; n < buflen; n += charsize) {
2452 if (IS_SET(MODE_UTF8)) {
2453 /* process a complete utf8 char */
2454 charsize = utf8decode(buf + n, &u, buflen - n);
2461 if (show_ctrl && ISCONTROL(u)) {
2466 } else if (u != '\n' && u != '\r' && u != '\t') {
2477 tresize(int col, int row)
2480 int minrow = MIN(row, term.row);
2481 int mincol = MIN(col, term.col);
2485 if (col < 1 || row < 1) {
2487 "tresize: error resizing to %dx%d\n", col, row);
2492 * slide screen to keep cursor where we expect it -
2493 * tscrollup would work here, but we can optimize to
2494 * memmove because we're freeing the earlier lines
2496 for (i = 0; i <= term.c.y - row; i++) {
2500 /* ensure that both src and dst are not NULL */
2502 memmove(term.line, term.line + i, row * sizeof(Line));
2503 memmove(term.alt, term.alt + i, row * sizeof(Line));
2505 for (i += row; i < term.row; i++) {
2510 /* resize to new height */
2511 term.line = xrealloc(term.line, row * sizeof(Line));
2512 term.alt = xrealloc(term.alt, row * sizeof(Line));
2513 term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
2514 term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
2516 /* resize each row to new width, zero-pad if needed */
2517 for (i = 0; i < minrow; i++) {
2518 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
2519 term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph));
2522 /* allocate any new rows */
2523 for (/* i = minrow */; i < row; i++) {
2524 term.line[i] = xmalloc(col * sizeof(Glyph));
2525 term.alt[i] = xmalloc(col * sizeof(Glyph));
2527 if (col > term.col) {
2528 bp = term.tabs + term.col;
2530 memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
2531 while (--bp > term.tabs && !*bp)
2533 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
2536 /* update terminal size */
2539 /* reset scrolling region */
2540 tsetscroll(0, row-1);
2541 /* make use of the LIMIT in tmoveto */
2542 tmoveto(term.c.x, term.c.y);
2543 /* Clearing both screens (it makes dirty all lines) */
2545 for (i = 0; i < 2; i++) {
2546 if (mincol < col && 0 < minrow) {
2547 tclearregion(mincol, 0, col - 1, minrow - 1);
2549 if (0 < col && minrow < row) {
2550 tclearregion(0, minrow, col - 1, row - 1);
2553 tcursor(CURSOR_LOAD);
2565 drawregion(int x1, int y1, int x2, int y2)
2569 for (y = y1; y < y2; y++) {
2574 xdrawline(term.line[y], x1, y, x2);
2581 int cx = term.c.x, ocx = term.ocx, ocy = term.ocy;
2586 /* adjust cursor position */
2587 LIMIT(term.ocx, 0, term.col-1);
2588 LIMIT(term.ocy, 0, term.row-1);
2589 if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY)
2591 if (term.line[term.c.y][cx].mode & ATTR_WDUMMY)
2594 drawregion(0, 0, term.col, term.row);
2595 xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
2596 term.ocx, term.ocy, term.line[term.ocy][term.ocx],
2597 term.line[term.ocy], term.col);
2599 term.ocy = term.c.y;
2601 if (ocx != term.ocx || ocy != term.ocy)
2602 xximspot(term.ocx, term.ocy);
2612 /* select and copy the previous url on screen (do nothing if there's no url).
2613 * known bug: doesn't handle urls that span multiple lines (wontfix)
2614 * known bug: only finds first url on line (mightfix)
2618 tsetcolor( int row, int start, int end, uint32_t fg, uint32_t bg )
2621 for( ; i < end; ++i )
2623 term.line[row][i].fg = fg;
2624 term.line[row][i].bg = bg;
2629 findlastany(char *str, const char** find, size_t len)
2633 for(found = str + strlen(str) - 1; found >= str; --found) {
2634 for(i = 0; i < len; i++) {
2635 if(strncmp(found, find[i], strlen(find[i])) == 0) {
2645 ** Select and copy the previous url on screen (do nothing if there's no url).
2647 ** FIXME: doesn't handle urls that span multiple lines; will need to add support
2648 ** for multiline "getsel()" first
2651 copyurl(const Arg *arg) {
2652 /* () and [] can appear in urls, but excluding them here will reduce false
2653 * positives when figuring out where a given url ends.
2655 static char URLCHARS[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
2656 "abcdefghijklmnopqrstuvwxyz"
2657 "0123456789-._~:/?#@!$&'*+,;=%";
2659 static const char* URLSTRINGS[] = {"http://", "https://"};
2661 /* remove highlighting from previous selection if any */
2662 if(sel.ob.x >= 0 && sel.oe.x >= 0)
2663 tsetcolor(sel.nb.y, sel.ob.x, sel.oe.x + 1, defaultfg, defaultbg);
2666 row = 0, /* row of current URL */
2667 col = 0, /* column of current URL start */
2668 startrow = 0, /* row of last occurrence */
2669 colend = 0, /* column of last occurrence */
2670 passes = 0; /* how many rows have been scanned */
2672 char *linestr = calloc(term.col+1, sizeof(Rune));
2676 row = (sel.ob.x >= 0 && sel.nb.y > 0) ? sel.nb.y : term.bot;
2677 LIMIT(row, term.top, term.bot);
2680 colend = (sel.ob.x >= 0 && sel.nb.y > 0) ? sel.nb.x : term.col;
2681 LIMIT(colend, 0, term.col);
2684 ** Scan from (term.bot,term.col) to (0,0) and find
2685 ** next occurrance of a URL
2687 while(passes !=term.bot + 2) {
2688 /* Read in each column of every row until
2689 ** we hit previous occurrence of URL
2691 for (col = 0, i = 0; col < colend; ++col,++i) {
2692 linestr[i] = term.line[row][col].u;
2694 linestr[term.col] = '\0';
2696 if ((match = findlastany(linestr, URLSTRINGS,
2697 sizeof(URLSTRINGS)/sizeof(URLSTRINGS[0]))))
2700 if (--row < term.top)
2708 /* must happen before trim */
2710 sel.ob.x = strlen(linestr) - strlen(match);
2712 /* trim the rest of the line from the url match */
2713 for (c = match; *c != '\0'; ++c)
2714 if (!strchr(URLCHARS, *c)) {
2719 /* highlight selection by inverting terminal colors */
2720 tsetcolor(row, sel.ob.x, sel.ob.x + strlen( match ), defaultbg, defaultfg);
2722 /* select and copy */
2724 sel.type = SEL_REGULAR;
2725 sel.oe.x = sel.ob.x + strlen(match)-1;
2726 sel.ob.y = sel.oe.y = row;
2728 tsetdirt(sel.nb.y, sel.ne.y);