1 /* See LICENSE for license details. */
12 #include <sys/ioctl.h>
13 #include <sys/select.h>
14 #include <sys/types.h>
25 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
27 #elif defined(__FreeBSD__) || defined(__DragonFly__)
32 #define UTF_INVALID 0xFFFD
34 #define ESC_BUF_SIZ (128*UTF_SIZ)
35 #define ESC_ARG_SIZ 16
36 #define STR_BUF_SIZ ESC_BUF_SIZ
37 #define STR_ARG_SIZ ESC_ARG_SIZ
40 #define IS_SET(flag) ((term.mode & (flag)) != 0)
41 #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == '\177')
42 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
43 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
44 #define ISDELIM(u) (utf8strchr(worddelimiters, u) != NULL)
49 MODE_ALTSCREEN = 1 << 2,
57 enum cursor_movement {
81 ESC_STR = 4, /* OSC, PM, APC */
83 ESC_STR_END = 16, /* a final string was encountered */
84 ESC_TEST = 32, /* Enter in test mode */
90 Glyph attr; /* current char attributes */
101 * Selection variables:
102 * nb – normalized coordinates of the beginning of the selection
103 * ne – normalized coordinates of the end of the selection
104 * ob – original coordinates of the beginning of the selection
105 * oe – original coordinates of the end of the selection
114 /* Internal representation of the screen */
116 int row; /* nb row */
117 int col; /* nb col */
118 Line *line; /* screen */
119 Line *alt; /* alternate screen */
120 int *dirty; /* dirtyness of lines */
121 TCursor c; /* cursor */
122 int ocx; /* old cursor col */
123 int ocy; /* old cursor row */
124 int top; /* top scroll limit */
125 int bot; /* bottom scroll limit */
126 int mode; /* terminal mode flags */
127 int esc; /* escape state flags */
128 char trantbl[4]; /* charset table translation */
129 int charset; /* current charset */
130 int icharset; /* selected charset for sequence */
134 /* CSI Escape sequence structs */
135 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
137 char buf[ESC_BUF_SIZ]; /* raw string */
138 int len; /* raw string length */
140 int arg[ESC_ARG_SIZ];
141 int narg; /* nb of args */
145 /* STR Escape sequence structs */
146 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
148 char type; /* ESC type ... */
149 char buf[STR_BUF_SIZ]; /* raw string */
150 int 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(int *, int);
190 static void tsetchar(Rune, 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, 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(int *, int *, int);
201 static void tdeftran(char);
202 static void tstrsequence(uchar);
204 static void drawregion(int, int, int, int);
206 static void selnormalize(void);
207 static void selscroll(int, int);
208 static void selsnap(int *, int *, int);
210 static size_t utf8decode(const char *, Rune *, size_t);
211 static Rune utf8decodebyte(char, size_t *);
212 static char utf8encodebyte(Rune, size_t);
213 static char *utf8strchr(char *, Rune);
214 static size_t utf8validate(Rune *, size_t);
216 static char *base64dec(const char *);
217 static char base64dec_getc(const char **);
219 static ssize_t xwrite(int, const char *, size_t);
223 static Selection sel;
224 static CSIEscape csiescseq;
225 static STREscape strescseq;
230 static uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
231 static uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
232 static Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000};
233 static Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
236 xwrite(int fd, const char *s, size_t len)
242 r = write(fd, s, len);
257 if (!(p = malloc(len)))
258 die("malloc: %s\n", strerror(errno));
264 xrealloc(void *p, size_t len)
266 if ((p = realloc(p, len)) == NULL)
267 die("realloc: %s\n", strerror(errno));
275 if ((s = strdup(s)) == NULL)
276 die("strdup: %s\n", strerror(errno));
282 utf8decode(const char *c, Rune *u, size_t clen)
284 size_t i, j, len, type;
290 udecoded = utf8decodebyte(c[0], &len);
291 if (!BETWEEN(len, 1, UTF_SIZ))
293 for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
294 udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
301 utf8validate(u, len);
307 utf8decodebyte(char c, size_t *i)
309 for (*i = 0; *i < LEN(utfmask); ++(*i))
310 if (((uchar)c & utfmask[*i]) == utfbyte[*i])
311 return (uchar)c & ~utfmask[*i];
317 utf8encode(Rune u, char *c)
321 len = utf8validate(&u, 0);
325 for (i = len - 1; i != 0; --i) {
326 c[i] = utf8encodebyte(u, 0);
329 c[0] = utf8encodebyte(u, len);
335 utf8encodebyte(Rune u, size_t i)
337 return utfbyte[i] | (u & ~utfmask[i]);
341 utf8strchr(char *s, Rune u)
347 for (i = 0, j = 0; i < len; i += j) {
348 if (!(j = utf8decode(&s[i], &r, len - i)))
358 utf8validate(Rune *u, size_t i)
360 if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF))
362 for (i = 1; *u > utfmax[i]; ++i)
368 static const char base64_digits[] = {
369 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
370 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0,
371 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
372 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
373 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
374 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0,
375 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
376 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
377 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
378 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
379 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
380 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
384 base64dec_getc(const char **src)
386 while (**src && !isprint(**src)) (*src)++;
391 base64dec(const char *src)
393 size_t in_len = strlen(src);
397 in_len += 4 - (in_len % 4);
398 result = dst = xmalloc(in_len / 4 * 3 + 1);
400 int a = base64_digits[(unsigned char) base64dec_getc(&src)];
401 int b = base64_digits[(unsigned char) base64dec_getc(&src)];
402 int c = base64_digits[(unsigned char) base64dec_getc(&src)];
403 int d = base64_digits[(unsigned char) base64dec_getc(&src)];
405 *dst++ = (a << 2) | ((b & 0x30) >> 4);
408 *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2);
411 *dst++ = ((c & 0x03) << 6) | d;
430 if (term.line[y][i - 1].mode & ATTR_WRAP)
433 while (i > 0 && term.line[y][i - 1].u == ' ')
440 selstart(int col, int row, int snap)
443 sel.mode = SEL_EMPTY;
444 sel.type = SEL_REGULAR;
445 sel.alt = IS_SET(MODE_ALTSCREEN);
447 sel.oe.x = sel.ob.x = col;
448 sel.oe.y = sel.ob.y = row;
452 sel.mode = SEL_READY;
453 tsetdirt(sel.nb.y, sel.ne.y);
457 selextend(int col, int row, int type, int done)
459 int oldey, oldex, oldsby, oldsey, oldtype;
461 if (sel.mode == SEL_IDLE)
463 if (done && sel.mode == SEL_EMPTY) {
479 if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type)
480 tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey));
482 sel.mode = done ? SEL_IDLE : SEL_READY;
490 if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) {
491 sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x;
492 sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x;
494 sel.nb.x = MIN(sel.ob.x, sel.oe.x);
495 sel.ne.x = MAX(sel.ob.x, sel.oe.x);
497 sel.nb.y = MIN(sel.ob.y, sel.oe.y);
498 sel.ne.y = MAX(sel.ob.y, sel.oe.y);
500 selsnap(&sel.nb.x, &sel.nb.y, -1);
501 selsnap(&sel.ne.x, &sel.ne.y, +1);
503 /* expand selection over line breaks */
504 if (sel.type == SEL_RECTANGULAR)
506 i = tlinelen(sel.nb.y);
509 if (tlinelen(sel.ne.y) <= sel.ne.x)
510 sel.ne.x = term.col - 1;
514 selected(int x, int y)
516 if (sel.mode == SEL_EMPTY || sel.ob.x == -1 ||
517 sel.alt != IS_SET(MODE_ALTSCREEN))
520 if (sel.type == SEL_RECTANGULAR)
521 return BETWEEN(y, sel.nb.y, sel.ne.y)
522 && BETWEEN(x, sel.nb.x, sel.ne.x);
524 return BETWEEN(y, sel.nb.y, sel.ne.y)
525 && (y != sel.nb.y || x >= sel.nb.x)
526 && (y != sel.ne.y || x <= sel.ne.x);
530 selsnap(int *x, int *y, int direction)
532 int newx, newy, xt, yt;
533 int delim, prevdelim;
539 * Snap around if the word wraps around at the end or
540 * beginning of a line.
542 prevgp = &term.line[*y][*x];
543 prevdelim = ISDELIM(prevgp->u);
545 newx = *x + direction;
547 if (!BETWEEN(newx, 0, term.col - 1)) {
549 newx = (newx + term.col) % term.col;
550 if (!BETWEEN(newy, 0, term.row - 1))
556 yt = newy, xt = newx;
557 if (!(term.line[yt][xt].mode & ATTR_WRAP))
561 if (newx >= tlinelen(newy))
564 gp = &term.line[newy][newx];
565 delim = ISDELIM(gp->u);
566 if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
567 || (delim && gp->u != prevgp->u)))
578 * Snap around if the the previous line or the current one
579 * has set ATTR_WRAP at its end. Then the whole next or
580 * previous line will be selected.
582 *x = (direction < 0) ? 0 : term.col - 1;
584 for (; *y > 0; *y += direction) {
585 if (!(term.line[*y-1][term.col-1].mode
590 } else if (direction > 0) {
591 for (; *y < term.row-1; *y += direction) {
592 if (!(term.line[*y][term.col-1].mode
606 int y, bufsize, lastx, linelen;
612 bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ;
613 ptr = str = xmalloc(bufsize);
615 /* append every set & selected glyph to the selection */
616 for (y = sel.nb.y; y <= sel.ne.y; y++) {
617 if ((linelen = tlinelen(y)) == 0) {
622 if (sel.type == SEL_RECTANGULAR) {
623 gp = &term.line[y][sel.nb.x];
626 gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0];
627 lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
629 last = &term.line[y][MIN(lastx, linelen-1)];
630 while (last >= gp && last->u == ' ')
633 for ( ; gp <= last; ++gp) {
634 if (gp->mode & ATTR_WDUMMY)
637 ptr += utf8encode(gp->u, ptr);
641 * Copy and pasting of line endings is inconsistent
642 * in the inconsistent terminal and GUI world.
643 * The best solution seems like to produce '\n' when
644 * something is copied from st and convert '\n' to
645 * '\r', when something to be pasted is received by
647 * FIXME: Fix the computer world.
649 if ((y < sel.ne.y || lastx >= linelen) && !(last->mode & ATTR_WRAP))
663 tsetdirt(sel.nb.y, sel.ne.y);
667 die(const char *errstr, ...)
671 va_start(ap, errstr);
672 vfprintf(stderr, errstr, ap);
678 execsh(char *cmd, char **args)
681 const struct passwd *pw;
684 if ((pw = getpwuid(getuid())) == NULL) {
686 die("getpwuid: %s\n", strerror(errno));
688 die("who are you?\n");
691 if ((sh = getenv("SHELL")) == NULL)
692 sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd;
700 DEFAULT(args, ((char *[]) {prog, NULL}));
705 setenv("LOGNAME", pw->pw_name, 1);
706 setenv("USER", pw->pw_name, 1);
707 setenv("SHELL", sh, 1);
708 setenv("HOME", pw->pw_dir, 1);
709 setenv("TERM", termname, 1);
711 signal(SIGCHLD, SIG_DFL);
712 signal(SIGHUP, SIG_DFL);
713 signal(SIGINT, SIG_DFL);
714 signal(SIGQUIT, SIG_DFL);
715 signal(SIGTERM, SIG_DFL);
716 signal(SIGALRM, SIG_DFL);
728 if ((p = waitpid(pid, &stat, WNOHANG)) < 0)
729 die("waiting for pid %hd failed: %s\n", pid, strerror(errno));
734 if (!WIFEXITED(stat) || WEXITSTATUS(stat))
735 die("child finished with error '%d'\n", stat);
742 char cmd[_POSIX_ARG_MAX], **p, *q, *s;
745 if ((n = strlen(stty_args)) > sizeof(cmd)-1)
746 die("incorrect stty parameters\n");
747 memcpy(cmd, stty_args, n);
749 siz = sizeof(cmd) - n;
750 for (p = args; p && (s = *p); ++p) {
751 if ((n = strlen(s)) > siz-1)
752 die("stty parameter length too long\n");
759 if (system(cmd) != 0)
760 perror("Couldn't call stty");
764 ttynew(char *line, char *cmd, char *out, char **args)
769 term.mode |= MODE_PRINT;
770 iofd = (!strcmp(out, "-")) ?
771 1 : open(out, O_WRONLY | O_CREAT, 0666);
773 fprintf(stderr, "Error opening %s:%s\n",
774 out, strerror(errno));
779 if ((cmdfd = open(line, O_RDWR)) < 0)
780 die("open line '%s' failed: %s\n",
781 line, strerror(errno));
787 /* seems to work fine on linux, openbsd and freebsd */
788 if (openpty(&m, &s, NULL, NULL, NULL) < 0)
789 die("openpty failed: %s\n", strerror(errno));
791 switch (pid = fork()) {
793 die("fork failed: %s\n", strerror(errno));
797 setsid(); /* create a new process group */
801 if (ioctl(s, TIOCSCTTY, NULL) < 0)
802 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno));
806 if (pledge("stdio getpw proc exec", NULL) == -1)
813 if (pledge("stdio rpath tty proc", NULL) == -1)
818 signal(SIGCHLD, sigchld);
827 static char buf[BUFSIZ];
828 static int buflen = 0;
832 /* append read bytes to unprocessed bytes */
833 if ((ret = read(cmdfd, buf+buflen, LEN(buf)-buflen)) < 0)
834 die("couldn't read from shell: %s\n", strerror(errno));
837 written = twrite(buf, buflen, 0);
839 /* keep any uncomplete utf8 char for the next call */
841 memmove(buf, buf + written, buflen);
847 ttywrite(const char *s, size_t n, int may_echo)
851 if (may_echo && IS_SET(MODE_ECHO))
854 if (!IS_SET(MODE_CRLF)) {
859 /* This is similar to how the kernel handles ONLCR for ttys */
863 ttywriteraw("\r\n", 2);
865 next = memchr(s, '\r', n);
866 DEFAULT(next, s + n);
867 ttywriteraw(s, next - s);
875 ttywriteraw(const char *s, size_t n)
882 * Remember that we are using a pty, which might be a modem line.
883 * Writing too much will clog the line. That's why we are doing this
885 * FIXME: Migrate the world to Plan 9.
893 /* Check if we can write. */
894 if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) {
897 die("select failed: %s\n", strerror(errno));
899 if (FD_ISSET(cmdfd, &wfd)) {
901 * Only write the bytes written by ttywrite() or the
902 * default of 256. This seems to be a reasonable value
903 * for a serial line. Bigger values might clog the I/O.
905 if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0)
909 * We weren't able to write out everything.
910 * This means the buffer is getting full
918 /* All bytes have been written. */
922 if (FD_ISSET(cmdfd, &rfd))
928 die("write error on tty: %s\n", strerror(errno));
932 ttyresize(int tw, int th)
940 if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
941 fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
947 /* Send SIGHUP to shell */
956 for (i = 0; i < term.row-1; i++) {
957 for (j = 0; j < term.col-1; j++) {
958 if (term.line[i][j].mode & attr)
967 tsetdirt(int top, int bot)
971 LIMIT(top, 0, term.row-1);
972 LIMIT(bot, 0, term.row-1);
974 for (i = top; i <= bot; i++)
979 tsetdirtattr(int attr)
983 for (i = 0; i < term.row-1; i++) {
984 for (j = 0; j < term.col-1; j++) {
985 if (term.line[i][j].mode & attr) {
996 tsetdirt(0, term.row-1);
1002 static TCursor c[2];
1003 int alt = IS_SET(MODE_ALTSCREEN);
1005 if (mode == CURSOR_SAVE) {
1007 } else if (mode == CURSOR_LOAD) {
1009 tmoveto(c[alt].x, c[alt].y);
1018 term.c = (TCursor){{
1022 }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
1024 memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1025 for (i = tabspaces; i < term.col; i += tabspaces)
1028 term.bot = term.row - 1;
1029 term.mode = MODE_WRAP|MODE_UTF8;
1030 memset(term.trantbl, CS_USA, sizeof(term.trantbl));
1033 for (i = 0; i < 2; i++) {
1035 tcursor(CURSOR_SAVE);
1036 tclearregion(0, 0, term.col-1, term.row-1);
1042 tnew(int col, int row)
1044 term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
1052 Line *tmp = term.line;
1054 term.line = term.alt;
1056 term.mode ^= MODE_ALTSCREEN;
1061 tscrolldown(int orig, int n)
1066 LIMIT(n, 0, term.bot-orig+1);
1068 tsetdirt(orig, term.bot-n);
1069 tclearregion(0, term.bot-n+1, term.col-1, term.bot);
1071 for (i = term.bot; i >= orig+n; i--) {
1072 temp = term.line[i];
1073 term.line[i] = term.line[i-n];
1074 term.line[i-n] = temp;
1081 tscrollup(int orig, int n)
1086 LIMIT(n, 0, term.bot-orig+1);
1088 tclearregion(0, orig, term.col-1, orig+n-1);
1089 tsetdirt(orig+n, term.bot);
1091 for (i = orig; i <= term.bot-n; i++) {
1092 temp = term.line[i];
1093 term.line[i] = term.line[i+n];
1094 term.line[i+n] = temp;
1097 selscroll(orig, -n);
1101 selscroll(int orig, int n)
1106 if (BETWEEN(sel.ob.y, orig, term.bot) || BETWEEN(sel.oe.y, orig, term.bot)) {
1107 if ((sel.ob.y += n) > term.bot || (sel.oe.y += n) < term.top) {
1111 if (sel.type == SEL_RECTANGULAR) {
1112 if (sel.ob.y < term.top)
1113 sel.ob.y = term.top;
1114 if (sel.oe.y > term.bot)
1115 sel.oe.y = term.bot;
1117 if (sel.ob.y < term.top) {
1118 sel.ob.y = term.top;
1121 if (sel.oe.y > term.bot) {
1122 sel.oe.y = term.bot;
1123 sel.oe.x = term.col;
1131 tnewline(int first_col)
1135 if (y == term.bot) {
1136 tscrollup(term.top, 1);
1140 tmoveto(first_col ? 0 : term.c.x, y);
1146 char *p = csiescseq.buf, *np;
1155 csiescseq.buf[csiescseq.len] = '\0';
1156 while (p < csiescseq.buf+csiescseq.len) {
1158 v = strtol(p, &np, 10);
1161 if (v == LONG_MAX || v == LONG_MIN)
1163 csiescseq.arg[csiescseq.narg++] = v;
1165 if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ)
1169 csiescseq.mode[0] = *p++;
1170 csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0';
1173 /* for absolute user moves, when decom is set */
1175 tmoveato(int x, int y)
1177 tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0));
1181 tmoveto(int x, int y)
1185 if (term.c.state & CURSOR_ORIGIN) {
1190 maxy = term.row - 1;
1192 term.c.state &= ~CURSOR_WRAPNEXT;
1193 term.c.x = LIMIT(x, 0, term.col-1);
1194 term.c.y = LIMIT(y, miny, maxy);
1198 tsetchar(Rune u, Glyph *attr, int x, int y)
1200 static char *vt100_0[62] = { /* 0x41 - 0x7e */
1201 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1202 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1203 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1204 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1205 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1206 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1207 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1208 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1212 * The table is proudly stolen from rxvt.
1214 if (term.trantbl[term.charset] == CS_GRAPHIC0 &&
1215 BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
1216 utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ);
1218 if (term.line[y][x].mode & ATTR_WIDE) {
1219 if (x+1 < term.col) {
1220 term.line[y][x+1].u = ' ';
1221 term.line[y][x+1].mode &= ~ATTR_WDUMMY;
1223 } else if (term.line[y][x].mode & ATTR_WDUMMY) {
1224 term.line[y][x-1].u = ' ';
1225 term.line[y][x-1].mode &= ~ATTR_WIDE;
1229 term.line[y][x] = *attr;
1230 term.line[y][x].u = u;
1234 tclearregion(int x1, int y1, int x2, int y2)
1240 temp = x1, x1 = x2, x2 = temp;
1242 temp = y1, y1 = y2, y2 = temp;
1244 LIMIT(x1, 0, term.col-1);
1245 LIMIT(x2, 0, term.col-1);
1246 LIMIT(y1, 0, term.row-1);
1247 LIMIT(y2, 0, term.row-1);
1249 for (y = y1; y <= y2; y++) {
1251 for (x = x1; x <= x2; x++) {
1252 gp = &term.line[y][x];
1255 gp->fg = term.c.attr.fg;
1256 gp->bg = term.c.attr.bg;
1269 LIMIT(n, 0, term.col - term.c.x);
1273 size = term.col - src;
1274 line = term.line[term.c.y];
1276 memmove(&line[dst], &line[src], size * sizeof(Glyph));
1277 tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
1286 LIMIT(n, 0, term.col - term.c.x);
1290 size = term.col - dst;
1291 line = term.line[term.c.y];
1293 memmove(&line[dst], &line[src], size * sizeof(Glyph));
1294 tclearregion(src, term.c.y, dst - 1, term.c.y);
1298 tinsertblankline(int n)
1300 if (BETWEEN(term.c.y, term.top, term.bot))
1301 tscrolldown(term.c.y, n);
1307 if (BETWEEN(term.c.y, term.top, term.bot))
1308 tscrollup(term.c.y, n);
1312 tdefcolor(int *attr, int *npar, int l)
1317 switch (attr[*npar + 1]) {
1318 case 2: /* direct color in RGB space */
1319 if (*npar + 4 >= l) {
1321 "erresc(38): Incorrect number of parameters (%d)\n",
1325 r = attr[*npar + 2];
1326 g = attr[*npar + 3];
1327 b = attr[*npar + 4];
1329 if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255))
1330 fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n",
1333 idx = TRUECOLOR(r, g, b);
1335 case 5: /* indexed color */
1336 if (*npar + 2 >= l) {
1338 "erresc(38): Incorrect number of parameters (%d)\n",
1343 if (!BETWEEN(attr[*npar], 0, 255))
1344 fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]);
1348 case 0: /* implemented defined (only foreground) */
1349 case 1: /* transparent */
1350 case 3: /* direct color in CMY space */
1351 case 4: /* direct color in CMYK space */
1354 "erresc(38): gfx attr %d unknown\n", attr[*npar]);
1362 tsetattr(int *attr, int l)
1367 for (i = 0; i < l; i++) {
1370 term.c.attr.mode &= ~(
1379 term.c.attr.fg = defaultfg;
1380 term.c.attr.bg = defaultbg;
1383 term.c.attr.mode |= ATTR_BOLD;
1386 term.c.attr.mode |= ATTR_FAINT;
1389 term.c.attr.mode |= ATTR_ITALIC;
1392 term.c.attr.mode |= ATTR_UNDERLINE;
1394 case 5: /* slow blink */
1396 case 6: /* rapid blink */
1397 term.c.attr.mode |= ATTR_BLINK;
1400 term.c.attr.mode |= ATTR_REVERSE;
1403 term.c.attr.mode |= ATTR_INVISIBLE;
1406 term.c.attr.mode |= ATTR_STRUCK;
1409 term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT);
1412 term.c.attr.mode &= ~ATTR_ITALIC;
1415 term.c.attr.mode &= ~ATTR_UNDERLINE;
1418 term.c.attr.mode &= ~ATTR_BLINK;
1421 term.c.attr.mode &= ~ATTR_REVERSE;
1424 term.c.attr.mode &= ~ATTR_INVISIBLE;
1427 term.c.attr.mode &= ~ATTR_STRUCK;
1430 if ((idx = tdefcolor(attr, &i, l)) >= 0)
1431 term.c.attr.fg = idx;
1434 term.c.attr.fg = defaultfg;
1437 if ((idx = tdefcolor(attr, &i, l)) >= 0)
1438 term.c.attr.bg = idx;
1441 term.c.attr.bg = defaultbg;
1444 if (BETWEEN(attr[i], 30, 37)) {
1445 term.c.attr.fg = attr[i] - 30;
1446 } else if (BETWEEN(attr[i], 40, 47)) {
1447 term.c.attr.bg = attr[i] - 40;
1448 } else if (BETWEEN(attr[i], 90, 97)) {
1449 term.c.attr.fg = attr[i] - 90 + 8;
1450 } else if (BETWEEN(attr[i], 100, 107)) {
1451 term.c.attr.bg = attr[i] - 100 + 8;
1454 "erresc(default): gfx attr %d unknown\n",
1464 tsetscroll(int t, int b)
1468 LIMIT(t, 0, term.row-1);
1469 LIMIT(b, 0, term.row-1);
1480 tsetmode(int priv, int set, int *args, int narg)
1484 for (lim = args + narg; args < lim; ++args) {
1487 case 1: /* DECCKM -- Cursor key */
1488 xsetmode(set, MODE_APPCURSOR);
1490 case 5: /* DECSCNM -- Reverse video */
1491 xsetmode(set, MODE_REVERSE);
1493 case 6: /* DECOM -- Origin */
1494 MODBIT(term.c.state, set, CURSOR_ORIGIN);
1497 case 7: /* DECAWM -- Auto wrap */
1498 MODBIT(term.mode, set, MODE_WRAP);
1500 case 0: /* Error (IGNORED) */
1501 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1502 case 3: /* DECCOLM -- Column (IGNORED) */
1503 case 4: /* DECSCLM -- Scroll (IGNORED) */
1504 case 8: /* DECARM -- Auto repeat (IGNORED) */
1505 case 18: /* DECPFF -- Printer feed (IGNORED) */
1506 case 19: /* DECPEX -- Printer extent (IGNORED) */
1507 case 42: /* DECNRCM -- National characters (IGNORED) */
1508 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1510 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1511 xsetmode(!set, MODE_HIDE);
1513 case 9: /* X10 mouse compatibility mode */
1514 xsetpointermotion(0);
1515 xsetmode(0, MODE_MOUSE);
1516 xsetmode(set, MODE_MOUSEX10);
1518 case 1000: /* 1000: report button press */
1519 xsetpointermotion(0);
1520 xsetmode(0, MODE_MOUSE);
1521 xsetmode(set, MODE_MOUSEBTN);
1523 case 1002: /* 1002: report motion on button press */
1524 xsetpointermotion(0);
1525 xsetmode(0, MODE_MOUSE);
1526 xsetmode(set, MODE_MOUSEMOTION);
1528 case 1003: /* 1003: enable all mouse motions */
1529 xsetpointermotion(set);
1530 xsetmode(0, MODE_MOUSE);
1531 xsetmode(set, MODE_MOUSEMANY);
1533 case 1004: /* 1004: send focus events to tty */
1534 xsetmode(set, MODE_FOCUS);
1536 case 1006: /* 1006: extended reporting mode */
1537 xsetmode(set, MODE_MOUSESGR);
1540 xsetmode(set, MODE_8BIT);
1542 case 1049: /* swap screen & set/restore cursor as xterm */
1543 if (!allowaltscreen)
1545 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1547 case 47: /* swap screen */
1549 if (!allowaltscreen)
1551 alt = IS_SET(MODE_ALTSCREEN);
1553 tclearregion(0, 0, term.col-1,
1556 if (set ^ alt) /* set is always 1 or 0 */
1562 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1564 case 2004: /* 2004: bracketed paste mode */
1565 xsetmode(set, MODE_BRCKTPASTE);
1567 /* Not implemented mouse modes. See comments there. */
1568 case 1001: /* mouse highlight mode; can hang the
1569 terminal by design when implemented. */
1570 case 1005: /* UTF-8 mouse mode; will confuse
1571 applications not supporting UTF-8
1573 case 1015: /* urxvt mangled mouse mode; incompatible
1574 and can be mistaken for other control
1578 "erresc: unknown private set/reset mode %d\n",
1584 case 0: /* Error (IGNORED) */
1587 xsetmode(set, MODE_KBDLOCK);
1589 case 4: /* IRM -- Insertion-replacement */
1590 MODBIT(term.mode, set, MODE_INSERT);
1592 case 12: /* SRM -- Send/Receive */
1593 MODBIT(term.mode, !set, MODE_ECHO);
1595 case 20: /* LNM -- Linefeed/new line */
1596 MODBIT(term.mode, set, MODE_CRLF);
1600 "erresc: unknown set/reset mode %d\n",
1614 switch (csiescseq.mode[0]) {
1617 fprintf(stderr, "erresc: unknown csi ");
1621 case '@': /* ICH -- Insert <n> blank char */
1622 DEFAULT(csiescseq.arg[0], 1);
1623 tinsertblank(csiescseq.arg[0]);
1625 case 'A': /* CUU -- Cursor <n> Up */
1626 DEFAULT(csiescseq.arg[0], 1);
1627 tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
1629 case 'B': /* CUD -- Cursor <n> Down */
1630 case 'e': /* VPR --Cursor <n> Down */
1631 DEFAULT(csiescseq.arg[0], 1);
1632 tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
1634 case 'i': /* MC -- Media Copy */
1635 switch (csiescseq.arg[0]) {
1640 tdumpline(term.c.y);
1646 term.mode &= ~MODE_PRINT;
1649 term.mode |= MODE_PRINT;
1653 case 'c': /* DA -- Device Attributes */
1654 if (csiescseq.arg[0] == 0)
1655 ttywrite(vtiden, strlen(vtiden), 0);
1657 case 'C': /* CUF -- Cursor <n> Forward */
1658 case 'a': /* HPR -- Cursor <n> Forward */
1659 DEFAULT(csiescseq.arg[0], 1);
1660 tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
1662 case 'D': /* CUB -- Cursor <n> Backward */
1663 DEFAULT(csiescseq.arg[0], 1);
1664 tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
1666 case 'E': /* CNL -- Cursor <n> Down and first col */
1667 DEFAULT(csiescseq.arg[0], 1);
1668 tmoveto(0, term.c.y+csiescseq.arg[0]);
1670 case 'F': /* CPL -- Cursor <n> Up and first col */
1671 DEFAULT(csiescseq.arg[0], 1);
1672 tmoveto(0, term.c.y-csiescseq.arg[0]);
1674 case 'g': /* TBC -- Tabulation clear */
1675 switch (csiescseq.arg[0]) {
1676 case 0: /* clear current tab stop */
1677 term.tabs[term.c.x] = 0;
1679 case 3: /* clear all the tabs */
1680 memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1686 case 'G': /* CHA -- Move to <col> */
1688 DEFAULT(csiescseq.arg[0], 1);
1689 tmoveto(csiescseq.arg[0]-1, term.c.y);
1691 case 'H': /* CUP -- Move to <row> <col> */
1693 DEFAULT(csiescseq.arg[0], 1);
1694 DEFAULT(csiescseq.arg[1], 1);
1695 tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
1697 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1698 DEFAULT(csiescseq.arg[0], 1);
1699 tputtab(csiescseq.arg[0]);
1701 case 'J': /* ED -- Clear screen */
1702 switch (csiescseq.arg[0]) {
1704 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
1705 if (term.c.y < term.row-1) {
1706 tclearregion(0, term.c.y+1, term.col-1,
1712 tclearregion(0, 0, term.col-1, term.c.y-1);
1713 tclearregion(0, term.c.y, term.c.x, term.c.y);
1716 tclearregion(0, 0, term.col-1, term.row-1);
1722 case 'K': /* EL -- Clear line */
1723 switch (csiescseq.arg[0]) {
1725 tclearregion(term.c.x, term.c.y, term.col-1,
1729 tclearregion(0, term.c.y, term.c.x, term.c.y);
1732 tclearregion(0, term.c.y, term.col-1, term.c.y);
1736 case 'S': /* SU -- Scroll <n> line up */
1737 DEFAULT(csiescseq.arg[0], 1);
1738 tscrollup(term.top, csiescseq.arg[0]);
1740 case 'T': /* SD -- Scroll <n> line down */
1741 DEFAULT(csiescseq.arg[0], 1);
1742 tscrolldown(term.top, csiescseq.arg[0]);
1744 case 'L': /* IL -- Insert <n> blank lines */
1745 DEFAULT(csiescseq.arg[0], 1);
1746 tinsertblankline(csiescseq.arg[0]);
1748 case 'l': /* RM -- Reset Mode */
1749 tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
1751 case 'M': /* DL -- Delete <n> lines */
1752 DEFAULT(csiescseq.arg[0], 1);
1753 tdeleteline(csiescseq.arg[0]);
1755 case 'X': /* ECH -- Erase <n> char */
1756 DEFAULT(csiescseq.arg[0], 1);
1757 tclearregion(term.c.x, term.c.y,
1758 term.c.x + csiescseq.arg[0] - 1, term.c.y);
1760 case 'P': /* DCH -- Delete <n> char */
1761 DEFAULT(csiescseq.arg[0], 1);
1762 tdeletechar(csiescseq.arg[0]);
1764 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1765 DEFAULT(csiescseq.arg[0], 1);
1766 tputtab(-csiescseq.arg[0]);
1768 case 'd': /* VPA -- Move to <row> */
1769 DEFAULT(csiescseq.arg[0], 1);
1770 tmoveato(term.c.x, csiescseq.arg[0]-1);
1772 case 'h': /* SM -- Set terminal mode */
1773 tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
1775 case 'm': /* SGR -- Terminal attribute (color) */
1776 tsetattr(csiescseq.arg, csiescseq.narg);
1778 case 'n': /* DSR – Device Status Report (cursor position) */
1779 if (csiescseq.arg[0] == 6) {
1780 len = snprintf(buf, sizeof(buf),"\033[%i;%iR",
1781 term.c.y+1, term.c.x+1);
1782 ttywrite(buf, len, 0);
1785 case 'r': /* DECSTBM -- Set Scrolling Region */
1786 if (csiescseq.priv) {
1789 DEFAULT(csiescseq.arg[0], 1);
1790 DEFAULT(csiescseq.arg[1], term.row);
1791 tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
1795 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1796 tcursor(CURSOR_SAVE);
1798 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1799 tcursor(CURSOR_LOAD);
1802 switch (csiescseq.mode[1]) {
1803 case 'q': /* DECSCUSR -- Set Cursor Style */
1804 if (xsetcursor(csiescseq.arg[0]))
1820 fprintf(stderr, "ESC[");
1821 for (i = 0; i < csiescseq.len; i++) {
1822 c = csiescseq.buf[i] & 0xff;
1825 } else if (c == '\n') {
1826 fprintf(stderr, "(\\n)");
1827 } else if (c == '\r') {
1828 fprintf(stderr, "(\\r)");
1829 } else if (c == 0x1b) {
1830 fprintf(stderr, "(\\e)");
1832 fprintf(stderr, "(%02x)", c);
1841 memset(&csiescseq, 0, sizeof(csiescseq));
1850 term.esc &= ~(ESC_STR_END|ESC_STR);
1852 par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
1854 switch (strescseq.type) {
1855 case ']': /* OSC -- Operating System Command */
1861 xsettitle(strescseq.args[1]);
1867 dec = base64dec(strescseq.args[2]);
1872 fprintf(stderr, "erresc: invalid base64\n");
1876 case 4: /* color set */
1879 p = strescseq.args[2];
1881 case 104: /* color reset, here p = NULL */
1882 j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
1883 if (xsetcolorname(j, p)) {
1884 fprintf(stderr, "erresc: invalid color %s\n", p);
1887 * TODO if defaultbg color is changed, borders
1895 case 'k': /* old title set compatibility */
1896 xsettitle(strescseq.args[0]);
1898 case 'P': /* DCS -- Device Control String */
1899 term.mode |= ESC_DCS;
1900 case '_': /* APC -- Application Program Command */
1901 case '^': /* PM -- Privacy Message */
1905 fprintf(stderr, "erresc: unknown str ");
1913 char *p = strescseq.buf;
1916 strescseq.buf[strescseq.len] = '\0';
1921 while (strescseq.narg < STR_ARG_SIZ) {
1922 strescseq.args[strescseq.narg++] = p;
1923 while ((c = *p) != ';' && c != '\0')
1937 fprintf(stderr, "ESC%c", strescseq.type);
1938 for (i = 0; i < strescseq.len; i++) {
1939 c = strescseq.buf[i] & 0xff;
1943 } else if (isprint(c)) {
1945 } else if (c == '\n') {
1946 fprintf(stderr, "(\\n)");
1947 } else if (c == '\r') {
1948 fprintf(stderr, "(\\r)");
1949 } else if (c == 0x1b) {
1950 fprintf(stderr, "(\\e)");
1952 fprintf(stderr, "(%02x)", c);
1955 fprintf(stderr, "ESC\\\n");
1961 memset(&strescseq, 0, sizeof(strescseq));
1965 sendbreak(const Arg *arg)
1967 if (tcsendbreak(cmdfd, 0))
1968 perror("Error sending break");
1972 tprinter(char *s, size_t len)
1974 if (iofd != -1 && xwrite(iofd, s, len) < 0) {
1975 perror("Error writing to output file");
1982 toggleprinter(const Arg *arg)
1984 term.mode ^= MODE_PRINT;
1988 printscreen(const Arg *arg)
1994 printsel(const Arg *arg)
2004 if ((ptr = getsel())) {
2005 tprinter(ptr, strlen(ptr));
2016 bp = &term.line[n][0];
2017 end = &bp[MIN(tlinelen(n), term.col) - 1];
2018 if (bp != end || bp->u != ' ') {
2019 for ( ;bp <= end; ++bp)
2020 tprinter(buf, utf8encode(bp->u, buf));
2030 for (i = 0; i < term.row; ++i)
2040 while (x < term.col && n--)
2041 for (++x; x < term.col && !term.tabs[x]; ++x)
2044 while (x > 0 && n++)
2045 for (--x; x > 0 && !term.tabs[x]; --x)
2048 term.c.x = LIMIT(x, 0, term.col-1);
2052 tdefutf8(char ascii)
2055 term.mode |= MODE_UTF8;
2056 else if (ascii == '@')
2057 term.mode &= ~MODE_UTF8;
2061 tdeftran(char ascii)
2063 static char cs[] = "0B";
2064 static int vcs[] = {CS_GRAPHIC0, CS_USA};
2067 if ((p = strchr(cs, ascii)) == NULL) {
2068 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
2070 term.trantbl[term.icharset] = vcs[p - cs];
2079 if (c == '8') { /* DEC screen alignment test. */
2080 for (x = 0; x < term.col; ++x) {
2081 for (y = 0; y < term.row; ++y)
2082 tsetchar('E', &term.c.attr, x, y);
2088 tstrsequence(uchar c)
2093 case 0x90: /* DCS -- Device Control String */
2095 term.esc |= ESC_DCS;
2097 case 0x9f: /* APC -- Application Program Command */
2100 case 0x9e: /* PM -- Privacy Message */
2103 case 0x9d: /* OSC -- Operating System Command */
2108 term.esc |= ESC_STR;
2112 tcontrolcode(uchar ascii)
2119 tmoveto(term.c.x-1, term.c.y);
2122 tmoveto(0, term.c.y);
2127 /* go to first col if the mode is set */
2128 tnewline(IS_SET(MODE_CRLF));
2130 case '\a': /* BEL */
2131 if (term.esc & ESC_STR_END) {
2132 /* backwards compatibility to xterm */
2138 case '\033': /* ESC */
2140 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
2141 term.esc |= ESC_START;
2143 case '\016': /* SO (LS1 -- Locking shift 1) */
2144 case '\017': /* SI (LS0 -- Locking shift 0) */
2145 term.charset = 1 - (ascii - '\016');
2147 case '\032': /* SUB */
2148 tsetchar('?', &term.c.attr, term.c.x, term.c.y);
2149 case '\030': /* CAN */
2152 case '\005': /* ENQ (IGNORED) */
2153 case '\000': /* NUL (IGNORED) */
2154 case '\021': /* XON (IGNORED) */
2155 case '\023': /* XOFF (IGNORED) */
2156 case 0177: /* DEL (IGNORED) */
2158 case 0x80: /* TODO: PAD */
2159 case 0x81: /* TODO: HOP */
2160 case 0x82: /* TODO: BPH */
2161 case 0x83: /* TODO: NBH */
2162 case 0x84: /* TODO: IND */
2164 case 0x85: /* NEL -- Next line */
2165 tnewline(1); /* always go to first col */
2167 case 0x86: /* TODO: SSA */
2168 case 0x87: /* TODO: ESA */
2170 case 0x88: /* HTS -- Horizontal tab stop */
2171 term.tabs[term.c.x] = 1;
2173 case 0x89: /* TODO: HTJ */
2174 case 0x8a: /* TODO: VTS */
2175 case 0x8b: /* TODO: PLD */
2176 case 0x8c: /* TODO: PLU */
2177 case 0x8d: /* TODO: RI */
2178 case 0x8e: /* TODO: SS2 */
2179 case 0x8f: /* TODO: SS3 */
2180 case 0x91: /* TODO: PU1 */
2181 case 0x92: /* TODO: PU2 */
2182 case 0x93: /* TODO: STS */
2183 case 0x94: /* TODO: CCH */
2184 case 0x95: /* TODO: MW */
2185 case 0x96: /* TODO: SPA */
2186 case 0x97: /* TODO: EPA */
2187 case 0x98: /* TODO: SOS */
2188 case 0x99: /* TODO: SGCI */
2190 case 0x9a: /* DECID -- Identify Terminal */
2191 ttywrite(vtiden, strlen(vtiden), 0);
2193 case 0x9b: /* TODO: CSI */
2194 case 0x9c: /* TODO: ST */
2196 case 0x90: /* DCS -- Device Control String */
2197 case 0x9d: /* OSC -- Operating System Command */
2198 case 0x9e: /* PM -- Privacy Message */
2199 case 0x9f: /* APC -- Application Program Command */
2200 tstrsequence(ascii);
2203 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2204 term.esc &= ~(ESC_STR_END|ESC_STR);
2208 * returns 1 when the sequence is finished and it hasn't to read
2209 * more characters for this sequence, otherwise 0
2212 eschandle(uchar ascii)
2216 term.esc |= ESC_CSI;
2219 term.esc |= ESC_TEST;
2222 term.esc |= ESC_UTF8;
2224 case 'P': /* DCS -- Device Control String */
2225 case '_': /* APC -- Application Program Command */
2226 case '^': /* PM -- Privacy Message */
2227 case ']': /* OSC -- Operating System Command */
2228 case 'k': /* old title set compatibility */
2229 tstrsequence(ascii);
2231 case 'n': /* LS2 -- Locking shift 2 */
2232 case 'o': /* LS3 -- Locking shift 3 */
2233 term.charset = 2 + (ascii - 'n');
2235 case '(': /* GZD4 -- set primary charset G0 */
2236 case ')': /* G1D4 -- set secondary charset G1 */
2237 case '*': /* G2D4 -- set tertiary charset G2 */
2238 case '+': /* G3D4 -- set quaternary charset G3 */
2239 term.icharset = ascii - '(';
2240 term.esc |= ESC_ALTCHARSET;
2242 case 'D': /* IND -- Linefeed */
2243 if (term.c.y == term.bot) {
2244 tscrollup(term.top, 1);
2246 tmoveto(term.c.x, term.c.y+1);
2249 case 'E': /* NEL -- Next line */
2250 tnewline(1); /* always go to first col */
2252 case 'H': /* HTS -- Horizontal tab stop */
2253 term.tabs[term.c.x] = 1;
2255 case 'M': /* RI -- Reverse index */
2256 if (term.c.y == term.top) {
2257 tscrolldown(term.top, 1);
2259 tmoveto(term.c.x, term.c.y-1);
2262 case 'Z': /* DECID -- Identify Terminal */
2263 ttywrite(vtiden, strlen(vtiden), 0);
2265 case 'c': /* RIS -- Reset to initial state */
2270 case '=': /* DECPAM -- Application keypad */
2271 xsetmode(1, MODE_APPKEYPAD);
2273 case '>': /* DECPNM -- Normal keypad */
2274 xsetmode(0, MODE_APPKEYPAD);
2276 case '7': /* DECSC -- Save Cursor */
2277 tcursor(CURSOR_SAVE);
2279 case '8': /* DECRC -- Restore Cursor */
2280 tcursor(CURSOR_LOAD);
2282 case '\\': /* ST -- String Terminator */
2283 if (term.esc & ESC_STR_END)
2287 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2288 (uchar) ascii, isprint(ascii)? ascii:'.');
2302 control = ISCONTROL(u);
2303 if (!IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
2307 len = utf8encode(u, c);
2308 if (!control && (width = wcwidth(u)) == -1) {
2309 memcpy(c, "\357\277\275", 4); /* UTF_INVALID */
2314 if (IS_SET(MODE_PRINT))
2318 * STR sequence must be checked before anything else
2319 * because it uses all following characters until it
2320 * receives a ESC, a SUB, a ST or any other C1 control
2323 if (term.esc & ESC_STR) {
2324 if (u == '\a' || u == 030 || u == 032 || u == 033 ||
2326 term.esc &= ~(ESC_START|ESC_STR|ESC_DCS);
2327 if (IS_SET(MODE_SIXEL)) {
2328 /* TODO: render sixel */;
2329 term.mode &= ~MODE_SIXEL;
2332 term.esc |= ESC_STR_END;
2333 goto check_control_code;
2337 if (IS_SET(MODE_SIXEL)) {
2338 /* TODO: implement sixel mode */
2341 if (term.esc&ESC_DCS && strescseq.len == 0 && u == 'q')
2342 term.mode |= MODE_SIXEL;
2344 if (strescseq.len+len >= sizeof(strescseq.buf)-1) {
2346 * Here is a bug in terminals. If the user never sends
2347 * some code to stop the str or esc command, then st
2348 * will stop responding. But this is better than
2349 * silently failing with unknown characters. At least
2350 * then users will report back.
2352 * In the case users ever get fixed, here is the code:
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
2378 } else if (term.esc & ESC_START) {
2379 if (term.esc & ESC_CSI) {
2380 csiescseq.buf[csiescseq.len++] = u;
2381 if (BETWEEN(u, 0x40, 0x7E)
2382 || csiescseq.len >= \
2383 sizeof(csiescseq.buf)-1) {
2389 } else if (term.esc & ESC_UTF8) {
2391 } else if (term.esc & ESC_ALTCHARSET) {
2393 } else if (term.esc & ESC_TEST) {
2398 /* sequence already finished */
2402 * All characters which form part of a sequence are not
2407 if (sel.ob.x != -1 && BETWEEN(term.c.y, sel.ob.y, sel.oe.y))
2410 gp = &term.line[term.c.y][term.c.x];
2411 if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
2412 gp->mode |= ATTR_WRAP;
2414 gp = &term.line[term.c.y][term.c.x];
2417 if (IS_SET(MODE_INSERT) && term.c.x+width < term.col)
2418 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
2420 if (term.c.x+width > term.col) {
2422 gp = &term.line[term.c.y][term.c.x];
2425 tsetchar(u, &term.c.attr, term.c.x, term.c.y);
2428 gp->mode |= ATTR_WIDE;
2429 if (term.c.x+1 < term.col) {
2431 gp[1].mode = ATTR_WDUMMY;
2434 if (term.c.x+width < term.col) {
2435 tmoveto(term.c.x+width, term.c.y);
2437 term.c.state |= CURSOR_WRAPNEXT;
2442 twrite(const char *buf, int buflen, int show_ctrl)
2448 for (n = 0; n < buflen; n += charsize) {
2449 if (IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
2450 /* process a complete utf8 char */
2451 charsize = utf8decode(buf + n, &u, buflen - n);
2458 if (show_ctrl && ISCONTROL(u)) {
2463 } else if (u != '\n' && u != '\r' && u != '\t') {
2474 tresize(int col, int row)
2477 int minrow = MIN(row, term.row);
2478 int mincol = MIN(col, term.col);
2482 if (col < 1 || row < 1) {
2484 "tresize: error resizing to %dx%d\n", col, row);
2489 * slide screen to keep cursor where we expect it -
2490 * tscrollup would work here, but we can optimize to
2491 * memmove because we're freeing the earlier lines
2493 for (i = 0; i <= term.c.y - row; i++) {
2497 /* ensure that both src and dst are not NULL */
2499 memmove(term.line, term.line + i, row * sizeof(Line));
2500 memmove(term.alt, term.alt + i, row * sizeof(Line));
2502 for (i += row; i < term.row; i++) {
2507 /* resize to new height */
2508 term.line = xrealloc(term.line, row * sizeof(Line));
2509 term.alt = xrealloc(term.alt, row * sizeof(Line));
2510 term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
2511 term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
2513 /* resize each row to new width, zero-pad if needed */
2514 for (i = 0; i < minrow; i++) {
2515 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
2516 term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph));
2519 /* allocate any new rows */
2520 for (/* i = minrow */; i < row; i++) {
2521 term.line[i] = xmalloc(col * sizeof(Glyph));
2522 term.alt[i] = xmalloc(col * sizeof(Glyph));
2524 if (col > term.col) {
2525 bp = term.tabs + term.col;
2527 memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
2528 while (--bp > term.tabs && !*bp)
2530 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
2533 /* update terminal size */
2536 /* reset scrolling region */
2537 tsetscroll(0, row-1);
2538 /* make use of the LIMIT in tmoveto */
2539 tmoveto(term.c.x, term.c.y);
2540 /* Clearing both screens (it makes dirty all lines) */
2542 for (i = 0; i < 2; i++) {
2543 if (mincol < col && 0 < minrow) {
2544 tclearregion(mincol, 0, col - 1, minrow - 1);
2546 if (0 < col && minrow < row) {
2547 tclearregion(0, minrow, col - 1, row - 1);
2550 tcursor(CURSOR_LOAD);
2562 drawregion(int x1, int y1, int x2, int y2)
2565 for (y = y1; y < y2; y++) {
2570 xdrawline(term.line[y], x1, y, x2);
2582 /* adjust cursor position */
2583 LIMIT(term.ocx, 0, term.col-1);
2584 LIMIT(term.ocy, 0, term.row-1);
2585 if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY)
2587 if (term.line[term.c.y][cx].mode & ATTR_WDUMMY)
2590 drawregion(0, 0, term.col, term.row);
2591 xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
2592 term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
2593 term.ocx = cx, term.ocy = term.c.y;