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);
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 size_t utf8validate(Rune *, size_t);
215 static char *base64dec(const char *);
216 static char base64dec_getc(const char **);
218 static ssize_t xwrite(int, const char *, size_t);
222 static Selection sel;
223 static CSIEscape csiescseq;
224 static STREscape strescseq;
229 static const uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
230 static const uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
231 static const Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000};
232 static const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
235 xwrite(int fd, const char *s, size_t len)
241 r = write(fd, s, len);
256 if (!(p = malloc(len)))
257 die("malloc: %s\n", strerror(errno));
263 xrealloc(void *p, size_t len)
265 if ((p = realloc(p, len)) == NULL)
266 die("realloc: %s\n", strerror(errno));
272 xstrdup(const char *s)
276 if ((p = strdup(s)) == NULL)
277 die("strdup: %s\n", strerror(errno));
283 utf8decode(const char *c, Rune *u, size_t clen)
285 size_t i, j, len, type;
291 udecoded = utf8decodebyte(c[0], &len);
292 if (!BETWEEN(len, 1, UTF_SIZ))
294 for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
295 udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
302 utf8validate(u, len);
308 utf8decodebyte(char c, size_t *i)
310 for (*i = 0; *i < LEN(utfmask); ++(*i))
311 if (((uchar)c & utfmask[*i]) == utfbyte[*i])
312 return (uchar)c & ~utfmask[*i];
318 utf8encode(Rune u, char *c)
322 len = utf8validate(&u, 0);
326 for (i = len - 1; i != 0; --i) {
327 c[i] = utf8encodebyte(u, 0);
330 c[0] = utf8encodebyte(u, len);
336 utf8encodebyte(Rune u, size_t i)
338 return utfbyte[i] | (u & ~utfmask[i]);
342 utf8validate(Rune *u, size_t i)
344 if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF))
346 for (i = 1; *u > utfmax[i]; ++i)
353 base64dec_getc(const char **src)
355 while (**src && !isprint((unsigned char)**src))
357 return **src ? *((*src)++) : '='; /* emulate padding if string ends */
361 base64dec(const char *src)
363 size_t in_len = strlen(src);
365 static const char base64_digits[256] = {
366 [43] = 62, 0, 0, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
367 0, 0, 0, -1, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
368 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0,
369 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
370 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
374 in_len += 4 - (in_len % 4);
375 result = dst = xmalloc(in_len / 4 * 3 + 1);
377 int a = base64_digits[(unsigned char) base64dec_getc(&src)];
378 int b = base64_digits[(unsigned char) base64dec_getc(&src)];
379 int c = base64_digits[(unsigned char) base64dec_getc(&src)];
380 int d = base64_digits[(unsigned char) base64dec_getc(&src)];
382 /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */
383 if (a == -1 || b == -1)
386 *dst++ = (a << 2) | ((b & 0x30) >> 4);
389 *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2);
392 *dst++ = ((c & 0x03) << 6) | d;
411 if (term.line[y][i - 1].mode & ATTR_WRAP)
414 while (i > 0 && term.line[y][i - 1].u == ' ')
421 selstart(int col, int row, int snap)
424 sel.mode = SEL_EMPTY;
425 sel.type = SEL_REGULAR;
426 sel.alt = IS_SET(MODE_ALTSCREEN);
428 sel.oe.x = sel.ob.x = col;
429 sel.oe.y = sel.ob.y = row;
433 sel.mode = SEL_READY;
434 tsetdirt(sel.nb.y, sel.ne.y);
438 selextend(int col, int row, int type, int done)
440 int oldey, oldex, oldsby, oldsey, oldtype;
442 if (sel.mode == SEL_IDLE)
444 if (done && sel.mode == SEL_EMPTY) {
460 if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY)
461 tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey));
463 sel.mode = done ? SEL_IDLE : SEL_READY;
471 if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) {
472 sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x;
473 sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x;
475 sel.nb.x = MIN(sel.ob.x, sel.oe.x);
476 sel.ne.x = MAX(sel.ob.x, sel.oe.x);
478 sel.nb.y = MIN(sel.ob.y, sel.oe.y);
479 sel.ne.y = MAX(sel.ob.y, sel.oe.y);
481 selsnap(&sel.nb.x, &sel.nb.y, -1);
482 selsnap(&sel.ne.x, &sel.ne.y, +1);
484 /* expand selection over line breaks */
485 if (sel.type == SEL_RECTANGULAR)
487 i = tlinelen(sel.nb.y);
490 if (tlinelen(sel.ne.y) <= sel.ne.x)
491 sel.ne.x = term.col - 1;
495 selected(int x, int y)
497 if (sel.mode == SEL_EMPTY || sel.ob.x == -1 ||
498 sel.alt != IS_SET(MODE_ALTSCREEN))
501 if (sel.type == SEL_RECTANGULAR)
502 return BETWEEN(y, sel.nb.y, sel.ne.y)
503 && BETWEEN(x, sel.nb.x, sel.ne.x);
505 return BETWEEN(y, sel.nb.y, sel.ne.y)
506 && (y != sel.nb.y || x >= sel.nb.x)
507 && (y != sel.ne.y || x <= sel.ne.x);
511 selsnap(int *x, int *y, int direction)
513 int newx, newy, xt, yt;
514 int delim, prevdelim;
515 const Glyph *gp, *prevgp;
520 * Snap around if the word wraps around at the end or
521 * beginning of a line.
523 prevgp = &term.line[*y][*x];
524 prevdelim = ISDELIM(prevgp->u);
526 newx = *x + direction;
528 if (!BETWEEN(newx, 0, term.col - 1)) {
530 newx = (newx + term.col) % term.col;
531 if (!BETWEEN(newy, 0, term.row - 1))
537 yt = newy, xt = newx;
538 if (!(term.line[yt][xt].mode & ATTR_WRAP))
542 if (newx >= tlinelen(newy))
545 gp = &term.line[newy][newx];
546 delim = ISDELIM(gp->u);
547 if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
548 || (delim && gp->u != prevgp->u)))
559 * Snap around if the the previous line or the current one
560 * has set ATTR_WRAP at its end. Then the whole next or
561 * previous line will be selected.
563 *x = (direction < 0) ? 0 : term.col - 1;
565 for (; *y > 0; *y += direction) {
566 if (!(term.line[*y-1][term.col-1].mode
571 } else if (direction > 0) {
572 for (; *y < term.row-1; *y += direction) {
573 if (!(term.line[*y][term.col-1].mode
587 int y, bufsize, lastx, linelen;
588 const Glyph *gp, *last;
593 bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ;
594 ptr = str = xmalloc(bufsize);
596 /* append every set & selected glyph to the selection */
597 for (y = sel.nb.y; y <= sel.ne.y; y++) {
598 if ((linelen = tlinelen(y)) == 0) {
603 if (sel.type == SEL_RECTANGULAR) {
604 gp = &term.line[y][sel.nb.x];
607 gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0];
608 lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
610 last = &term.line[y][MIN(lastx, linelen-1)];
611 while (last >= gp && last->u == ' ')
614 for ( ; gp <= last; ++gp) {
615 if (gp->mode & ATTR_WDUMMY)
618 ptr += utf8encode(gp->u, ptr);
622 * Copy and pasting of line endings is inconsistent
623 * in the inconsistent terminal and GUI world.
624 * The best solution seems like to produce '\n' when
625 * something is copied from st and convert '\n' to
626 * '\r', when something to be pasted is received by
628 * FIXME: Fix the computer world.
630 if ((y < sel.ne.y || lastx >= linelen) &&
631 (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR))
645 tsetdirt(sel.nb.y, sel.ne.y);
649 die(const char *errstr, ...)
653 va_start(ap, errstr);
654 vfprintf(stderr, errstr, ap);
660 execsh(char *cmd, char **args)
662 char *sh, *prog, *arg;
663 const struct passwd *pw;
666 if ((pw = getpwuid(getuid())) == NULL) {
668 die("getpwuid: %s\n", strerror(errno));
670 die("who are you?\n");
673 if ((sh = getenv("SHELL")) == NULL)
674 sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd;
681 arg = utmp ? utmp : sh;
689 DEFAULT(args, ((char *[]) {prog, arg, NULL}));
694 setenv("LOGNAME", pw->pw_name, 1);
695 setenv("USER", pw->pw_name, 1);
696 setenv("SHELL", sh, 1);
697 setenv("HOME", pw->pw_dir, 1);
698 setenv("TERM", termname, 1);
700 signal(SIGCHLD, SIG_DFL);
701 signal(SIGHUP, SIG_DFL);
702 signal(SIGINT, SIG_DFL);
703 signal(SIGQUIT, SIG_DFL);
704 signal(SIGTERM, SIG_DFL);
705 signal(SIGALRM, SIG_DFL);
717 if ((p = waitpid(pid, &stat, WNOHANG)) < 0)
718 die("waiting for pid %hd failed: %s\n", pid, strerror(errno));
723 if (WIFEXITED(stat) && WEXITSTATUS(stat))
724 die("child exited with status %d\n", WEXITSTATUS(stat));
725 else if (WIFSIGNALED(stat))
726 die("child terminated due to signal %d\n", WTERMSIG(stat));
733 char cmd[_POSIX_ARG_MAX], **p, *q, *s;
736 if ((n = strlen(stty_args)) > sizeof(cmd)-1)
737 die("incorrect stty parameters\n");
738 memcpy(cmd, stty_args, n);
740 siz = sizeof(cmd) - n;
741 for (p = args; p && (s = *p); ++p) {
742 if ((n = strlen(s)) > siz-1)
743 die("stty parameter length too long\n");
750 if (system(cmd) != 0)
751 perror("Couldn't call stty");
755 ttynew(const char *line, char *cmd, const char *out, char **args)
760 term.mode |= MODE_PRINT;
761 iofd = (!strcmp(out, "-")) ?
762 1 : open(out, O_WRONLY | O_CREAT, 0666);
764 fprintf(stderr, "Error opening %s:%s\n",
765 out, strerror(errno));
770 if ((cmdfd = open(line, O_RDWR)) < 0)
771 die("open line '%s' failed: %s\n",
772 line, strerror(errno));
778 /* seems to work fine on linux, openbsd and freebsd */
779 if (openpty(&m, &s, NULL, NULL, NULL) < 0)
780 die("openpty failed: %s\n", strerror(errno));
782 switch (pid = fork()) {
784 die("fork failed: %s\n", strerror(errno));
789 setsid(); /* create a new process group */
793 if (ioctl(s, TIOCSCTTY, NULL) < 0)
794 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno));
798 if (pledge("stdio getpw proc exec", NULL) == -1)
805 if (pledge("stdio rpath tty proc", NULL) == -1)
810 signal(SIGCHLD, sigchld);
819 static char buf[BUFSIZ];
820 static int buflen = 0;
823 /* append read bytes to unprocessed bytes */
824 ret = read(cmdfd, buf+buflen, LEN(buf)-buflen);
830 die("couldn't read from shell: %s\n", strerror(errno));
833 written = twrite(buf, buflen, 0);
835 /* keep any incomplete UTF-8 byte sequence for the next call */
837 memmove(buf, buf + written, buflen);
843 ttywrite(const char *s, size_t n, int may_echo)
847 if (may_echo && IS_SET(MODE_ECHO))
850 if (!IS_SET(MODE_CRLF)) {
855 /* This is similar to how the kernel handles ONLCR for ttys */
859 ttywriteraw("\r\n", 2);
861 next = memchr(s, '\r', n);
862 DEFAULT(next, s + n);
863 ttywriteraw(s, next - s);
871 ttywriteraw(const char *s, size_t n)
878 * Remember that we are using a pty, which might be a modem line.
879 * Writing too much will clog the line. That's why we are doing this
881 * FIXME: Migrate the world to Plan 9.
889 /* Check if we can write. */
890 if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) {
893 die("select failed: %s\n", strerror(errno));
895 if (FD_ISSET(cmdfd, &wfd)) {
897 * Only write the bytes written by ttywrite() or the
898 * default of 256. This seems to be a reasonable value
899 * for a serial line. Bigger values might clog the I/O.
901 if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0)
905 * We weren't able to write out everything.
906 * This means the buffer is getting full
914 /* All bytes have been written. */
918 if (FD_ISSET(cmdfd, &rfd))
924 die("write error on tty: %s\n", strerror(errno));
928 ttyresize(int tw, int th)
936 if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
937 fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
943 /* Send SIGHUP to shell */
952 for (i = 0; i < term.row-1; i++) {
953 for (j = 0; j < term.col-1; j++) {
954 if (term.line[i][j].mode & attr)
963 tsetdirt(int top, int bot)
967 LIMIT(top, 0, term.row-1);
968 LIMIT(bot, 0, term.row-1);
970 for (i = top; i <= bot; i++)
975 tsetdirtattr(int attr)
979 for (i = 0; i < term.row-1; i++) {
980 for (j = 0; j < term.col-1; j++) {
981 if (term.line[i][j].mode & attr) {
992 tsetdirt(0, term.row-1);
999 int alt = IS_SET(MODE_ALTSCREEN);
1001 if (mode == CURSOR_SAVE) {
1003 } else if (mode == CURSOR_LOAD) {
1005 tmoveto(c[alt].x, c[alt].y);
1014 term.c = (TCursor){{
1018 }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
1020 memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1021 for (i = tabspaces; i < term.col; i += tabspaces)
1024 term.bot = term.row - 1;
1025 term.mode = MODE_WRAP|MODE_UTF8;
1026 memset(term.trantbl, CS_USA, sizeof(term.trantbl));
1029 for (i = 0; i < 2; i++) {
1031 tcursor(CURSOR_SAVE);
1032 tclearregion(0, 0, term.col-1, term.row-1);
1038 tnew(int col, int row)
1040 term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
1048 Line *tmp = term.line;
1050 term.line = term.alt;
1052 term.mode ^= MODE_ALTSCREEN;
1057 tscrolldown(int orig, int n)
1062 LIMIT(n, 0, term.bot-orig+1);
1064 tsetdirt(orig, term.bot-n);
1065 tclearregion(0, term.bot-n+1, term.col-1, term.bot);
1067 for (i = term.bot; i >= orig+n; i--) {
1068 temp = term.line[i];
1069 term.line[i] = term.line[i-n];
1070 term.line[i-n] = temp;
1077 tscrollup(int orig, int n)
1082 LIMIT(n, 0, term.bot-orig+1);
1084 tclearregion(0, orig, term.col-1, orig+n-1);
1085 tsetdirt(orig+n, term.bot);
1087 for (i = orig; i <= term.bot-n; i++) {
1088 temp = term.line[i];
1089 term.line[i] = term.line[i+n];
1090 term.line[i+n] = temp;
1093 selscroll(orig, -n);
1097 selscroll(int orig, int n)
1102 if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) {
1104 } else if (BETWEEN(sel.nb.y, orig, term.bot)) {
1107 if (sel.ob.y < term.top || sel.ob.y > term.bot ||
1108 sel.oe.y < term.top || sel.oe.y > term.bot) {
1117 tnewline(int first_col)
1121 if (y == term.bot) {
1122 tscrollup(term.top, 1);
1126 tmoveto(first_col ? 0 : term.c.x, y);
1132 char *p = csiescseq.buf, *np;
1141 csiescseq.buf[csiescseq.len] = '\0';
1142 while (p < csiescseq.buf+csiescseq.len) {
1144 v = strtol(p, &np, 10);
1147 if (v == LONG_MAX || v == LONG_MIN)
1149 csiescseq.arg[csiescseq.narg++] = v;
1151 if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ)
1155 csiescseq.mode[0] = *p++;
1156 csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0';
1159 /* for absolute user moves, when decom is set */
1161 tmoveato(int x, int y)
1163 tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0));
1167 tmoveto(int x, int y)
1171 if (term.c.state & CURSOR_ORIGIN) {
1176 maxy = term.row - 1;
1178 term.c.state &= ~CURSOR_WRAPNEXT;
1179 term.c.x = LIMIT(x, 0, term.col-1);
1180 term.c.y = LIMIT(y, miny, maxy);
1184 tsetchar(Rune u, const Glyph *attr, int x, int y)
1186 static const char *vt100_0[62] = { /* 0x41 - 0x7e */
1187 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1188 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1189 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1190 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1191 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1192 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1193 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1194 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1198 * The table is proudly stolen from rxvt.
1200 if (term.trantbl[term.charset] == CS_GRAPHIC0 &&
1201 BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
1202 utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ);
1204 if (term.line[y][x].mode & ATTR_WIDE) {
1205 if (x+1 < term.col) {
1206 term.line[y][x+1].u = ' ';
1207 term.line[y][x+1].mode &= ~ATTR_WDUMMY;
1209 } else if (term.line[y][x].mode & ATTR_WDUMMY) {
1210 term.line[y][x-1].u = ' ';
1211 term.line[y][x-1].mode &= ~ATTR_WIDE;
1215 term.line[y][x] = *attr;
1216 term.line[y][x].u = u;
1220 tclearregion(int x1, int y1, int x2, int y2)
1226 temp = x1, x1 = x2, x2 = temp;
1228 temp = y1, y1 = y2, y2 = temp;
1230 LIMIT(x1, 0, term.col-1);
1231 LIMIT(x2, 0, term.col-1);
1232 LIMIT(y1, 0, term.row-1);
1233 LIMIT(y2, 0, term.row-1);
1235 for (y = y1; y <= y2; y++) {
1237 for (x = x1; x <= x2; x++) {
1238 gp = &term.line[y][x];
1241 gp->fg = term.c.attr.fg;
1242 gp->bg = term.c.attr.bg;
1255 LIMIT(n, 0, term.col - term.c.x);
1259 size = term.col - src;
1260 line = term.line[term.c.y];
1262 memmove(&line[dst], &line[src], size * sizeof(Glyph));
1263 tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
1272 LIMIT(n, 0, term.col - term.c.x);
1276 size = term.col - dst;
1277 line = term.line[term.c.y];
1279 memmove(&line[dst], &line[src], size * sizeof(Glyph));
1280 tclearregion(src, term.c.y, dst - 1, term.c.y);
1284 tinsertblankline(int n)
1286 if (BETWEEN(term.c.y, term.top, term.bot))
1287 tscrolldown(term.c.y, n);
1293 if (BETWEEN(term.c.y, term.top, term.bot))
1294 tscrollup(term.c.y, n);
1298 tdefcolor(const int *attr, int *npar, int l)
1303 switch (attr[*npar + 1]) {
1304 case 2: /* direct color in RGB space */
1305 if (*npar + 4 >= l) {
1307 "erresc(38): Incorrect number of parameters (%d)\n",
1311 r = attr[*npar + 2];
1312 g = attr[*npar + 3];
1313 b = attr[*npar + 4];
1315 if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255))
1316 fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n",
1319 idx = TRUECOLOR(r, g, b);
1321 case 5: /* indexed color */
1322 if (*npar + 2 >= l) {
1324 "erresc(38): Incorrect number of parameters (%d)\n",
1329 if (!BETWEEN(attr[*npar], 0, 255))
1330 fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]);
1334 case 0: /* implemented defined (only foreground) */
1335 case 1: /* transparent */
1336 case 3: /* direct color in CMY space */
1337 case 4: /* direct color in CMYK space */
1340 "erresc(38): gfx attr %d unknown\n", attr[*npar]);
1348 tsetattr(const int *attr, int l)
1353 for (i = 0; i < l; i++) {
1356 term.c.attr.mode &= ~(
1365 term.c.attr.fg = defaultfg;
1366 term.c.attr.bg = defaultbg;
1369 term.c.attr.mode |= ATTR_BOLD;
1372 term.c.attr.mode |= ATTR_FAINT;
1375 term.c.attr.mode |= ATTR_ITALIC;
1378 term.c.attr.mode |= ATTR_UNDERLINE;
1380 case 5: /* slow blink */
1382 case 6: /* rapid blink */
1383 term.c.attr.mode |= ATTR_BLINK;
1386 term.c.attr.mode |= ATTR_REVERSE;
1389 term.c.attr.mode |= ATTR_INVISIBLE;
1392 term.c.attr.mode |= ATTR_STRUCK;
1395 term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT);
1398 term.c.attr.mode &= ~ATTR_ITALIC;
1401 term.c.attr.mode &= ~ATTR_UNDERLINE;
1404 term.c.attr.mode &= ~ATTR_BLINK;
1407 term.c.attr.mode &= ~ATTR_REVERSE;
1410 term.c.attr.mode &= ~ATTR_INVISIBLE;
1413 term.c.attr.mode &= ~ATTR_STRUCK;
1416 if ((idx = tdefcolor(attr, &i, l)) >= 0)
1417 term.c.attr.fg = idx;
1420 term.c.attr.fg = defaultfg;
1423 if ((idx = tdefcolor(attr, &i, l)) >= 0)
1424 term.c.attr.bg = idx;
1427 term.c.attr.bg = defaultbg;
1430 if (BETWEEN(attr[i], 30, 37)) {
1431 term.c.attr.fg = attr[i] - 30;
1432 } else if (BETWEEN(attr[i], 40, 47)) {
1433 term.c.attr.bg = attr[i] - 40;
1434 } else if (BETWEEN(attr[i], 90, 97)) {
1435 term.c.attr.fg = attr[i] - 90 + 8;
1436 } else if (BETWEEN(attr[i], 100, 107)) {
1437 term.c.attr.bg = attr[i] - 100 + 8;
1440 "erresc(default): gfx attr %d unknown\n",
1450 tsetscroll(int t, int b)
1454 LIMIT(t, 0, term.row-1);
1455 LIMIT(b, 0, term.row-1);
1466 tsetmode(int priv, int set, const int *args, int narg)
1468 int alt; const int *lim;
1470 for (lim = args + narg; args < lim; ++args) {
1473 case 1: /* DECCKM -- Cursor key */
1474 xsetmode(set, MODE_APPCURSOR);
1476 case 5: /* DECSCNM -- Reverse video */
1477 xsetmode(set, MODE_REVERSE);
1479 case 6: /* DECOM -- Origin */
1480 MODBIT(term.c.state, set, CURSOR_ORIGIN);
1483 case 7: /* DECAWM -- Auto wrap */
1484 MODBIT(term.mode, set, MODE_WRAP);
1486 case 0: /* Error (IGNORED) */
1487 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1488 case 3: /* DECCOLM -- Column (IGNORED) */
1489 case 4: /* DECSCLM -- Scroll (IGNORED) */
1490 case 8: /* DECARM -- Auto repeat (IGNORED) */
1491 case 18: /* DECPFF -- Printer feed (IGNORED) */
1492 case 19: /* DECPEX -- Printer extent (IGNORED) */
1493 case 42: /* DECNRCM -- National characters (IGNORED) */
1494 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1496 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1497 xsetmode(!set, MODE_HIDE);
1499 case 9: /* X10 mouse compatibility mode */
1500 xsetpointermotion(0);
1501 xsetmode(0, MODE_MOUSE);
1502 xsetmode(set, MODE_MOUSEX10);
1504 case 1000: /* 1000: report button press */
1505 xsetpointermotion(0);
1506 xsetmode(0, MODE_MOUSE);
1507 xsetmode(set, MODE_MOUSEBTN);
1509 case 1002: /* 1002: report motion on button press */
1510 xsetpointermotion(0);
1511 xsetmode(0, MODE_MOUSE);
1512 xsetmode(set, MODE_MOUSEMOTION);
1514 case 1003: /* 1003: enable all mouse motions */
1515 xsetpointermotion(set);
1516 xsetmode(0, MODE_MOUSE);
1517 xsetmode(set, MODE_MOUSEMANY);
1519 case 1004: /* 1004: send focus events to tty */
1520 xsetmode(set, MODE_FOCUS);
1522 case 1006: /* 1006: extended reporting mode */
1523 xsetmode(set, MODE_MOUSESGR);
1526 xsetmode(set, MODE_8BIT);
1528 case 1049: /* swap screen & set/restore cursor as xterm */
1529 if (!allowaltscreen)
1531 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1533 case 47: /* swap screen */
1535 if (!allowaltscreen)
1537 alt = IS_SET(MODE_ALTSCREEN);
1539 tclearregion(0, 0, term.col-1,
1542 if (set ^ alt) /* set is always 1 or 0 */
1548 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1550 case 2004: /* 2004: bracketed paste mode */
1551 xsetmode(set, MODE_BRCKTPASTE);
1553 /* Not implemented mouse modes. See comments there. */
1554 case 1001: /* mouse highlight mode; can hang the
1555 terminal by design when implemented. */
1556 case 1005: /* UTF-8 mouse mode; will confuse
1557 applications not supporting UTF-8
1559 case 1015: /* urxvt mangled mouse mode; incompatible
1560 and can be mistaken for other control
1565 "erresc: unknown private set/reset mode %d\n",
1571 case 0: /* Error (IGNORED) */
1574 xsetmode(set, MODE_KBDLOCK);
1576 case 4: /* IRM -- Insertion-replacement */
1577 MODBIT(term.mode, set, MODE_INSERT);
1579 case 12: /* SRM -- Send/Receive */
1580 MODBIT(term.mode, !set, MODE_ECHO);
1582 case 20: /* LNM -- Linefeed/new line */
1583 MODBIT(term.mode, set, MODE_CRLF);
1587 "erresc: unknown set/reset mode %d\n",
1601 switch (csiescseq.mode[0]) {
1604 fprintf(stderr, "erresc: unknown csi ");
1608 case '@': /* ICH -- Insert <n> blank char */
1609 DEFAULT(csiescseq.arg[0], 1);
1610 tinsertblank(csiescseq.arg[0]);
1612 case 'A': /* CUU -- Cursor <n> Up */
1613 DEFAULT(csiescseq.arg[0], 1);
1614 tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
1616 case 'B': /* CUD -- Cursor <n> Down */
1617 case 'e': /* VPR --Cursor <n> Down */
1618 DEFAULT(csiescseq.arg[0], 1);
1619 tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
1621 case 'i': /* MC -- Media Copy */
1622 switch (csiescseq.arg[0]) {
1627 tdumpline(term.c.y);
1633 term.mode &= ~MODE_PRINT;
1636 term.mode |= MODE_PRINT;
1640 case 'c': /* DA -- Device Attributes */
1641 if (csiescseq.arg[0] == 0)
1642 ttywrite(vtiden, strlen(vtiden), 0);
1644 case 'b': /* REP -- if last char is printable print it <n> more times */
1645 DEFAULT(csiescseq.arg[0], 1);
1647 while (csiescseq.arg[0]-- > 0)
1650 case 'C': /* CUF -- Cursor <n> Forward */
1651 case 'a': /* HPR -- Cursor <n> Forward */
1652 DEFAULT(csiescseq.arg[0], 1);
1653 tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
1655 case 'D': /* CUB -- Cursor <n> Backward */
1656 DEFAULT(csiescseq.arg[0], 1);
1657 tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
1659 case 'E': /* CNL -- Cursor <n> Down and first col */
1660 DEFAULT(csiescseq.arg[0], 1);
1661 tmoveto(0, term.c.y+csiescseq.arg[0]);
1663 case 'F': /* CPL -- Cursor <n> Up and first col */
1664 DEFAULT(csiescseq.arg[0], 1);
1665 tmoveto(0, term.c.y-csiescseq.arg[0]);
1667 case 'g': /* TBC -- Tabulation clear */
1668 switch (csiescseq.arg[0]) {
1669 case 0: /* clear current tab stop */
1670 term.tabs[term.c.x] = 0;
1672 case 3: /* clear all the tabs */
1673 memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1679 case 'G': /* CHA -- Move to <col> */
1681 DEFAULT(csiescseq.arg[0], 1);
1682 tmoveto(csiescseq.arg[0]-1, term.c.y);
1684 case 'H': /* CUP -- Move to <row> <col> */
1686 DEFAULT(csiescseq.arg[0], 1);
1687 DEFAULT(csiescseq.arg[1], 1);
1688 tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
1690 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1691 DEFAULT(csiescseq.arg[0], 1);
1692 tputtab(csiescseq.arg[0]);
1694 case 'J': /* ED -- Clear screen */
1695 switch (csiescseq.arg[0]) {
1697 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
1698 if (term.c.y < term.row-1) {
1699 tclearregion(0, term.c.y+1, term.col-1,
1705 tclearregion(0, 0, term.col-1, term.c.y-1);
1706 tclearregion(0, term.c.y, term.c.x, term.c.y);
1709 tclearregion(0, 0, term.col-1, term.row-1);
1715 case 'K': /* EL -- Clear line */
1716 switch (csiescseq.arg[0]) {
1718 tclearregion(term.c.x, term.c.y, term.col-1,
1722 tclearregion(0, term.c.y, term.c.x, term.c.y);
1725 tclearregion(0, term.c.y, term.col-1, term.c.y);
1729 case 'S': /* SU -- Scroll <n> line up */
1730 DEFAULT(csiescseq.arg[0], 1);
1731 tscrollup(term.top, csiescseq.arg[0]);
1733 case 'T': /* SD -- Scroll <n> line down */
1734 DEFAULT(csiescseq.arg[0], 1);
1735 tscrolldown(term.top, csiescseq.arg[0]);
1737 case 'L': /* IL -- Insert <n> blank lines */
1738 DEFAULT(csiescseq.arg[0], 1);
1739 tinsertblankline(csiescseq.arg[0]);
1741 case 'l': /* RM -- Reset Mode */
1742 tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
1744 case 'M': /* DL -- Delete <n> lines */
1745 DEFAULT(csiescseq.arg[0], 1);
1746 tdeleteline(csiescseq.arg[0]);
1748 case 'X': /* ECH -- Erase <n> char */
1749 DEFAULT(csiescseq.arg[0], 1);
1750 tclearregion(term.c.x, term.c.y,
1751 term.c.x + csiescseq.arg[0] - 1, term.c.y);
1753 case 'P': /* DCH -- Delete <n> char */
1754 DEFAULT(csiescseq.arg[0], 1);
1755 tdeletechar(csiescseq.arg[0]);
1757 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1758 DEFAULT(csiescseq.arg[0], 1);
1759 tputtab(-csiescseq.arg[0]);
1761 case 'd': /* VPA -- Move to <row> */
1762 DEFAULT(csiescseq.arg[0], 1);
1763 tmoveato(term.c.x, csiescseq.arg[0]-1);
1765 case 'h': /* SM -- Set terminal mode */
1766 tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
1768 case 'm': /* SGR -- Terminal attribute (color) */
1769 tsetattr(csiescseq.arg, csiescseq.narg);
1771 case 'n': /* DSR – Device Status Report (cursor position) */
1772 if (csiescseq.arg[0] == 6) {
1773 len = snprintf(buf, sizeof(buf), "\033[%i;%iR",
1774 term.c.y+1, term.c.x+1);
1775 ttywrite(buf, len, 0);
1778 case 'r': /* DECSTBM -- Set Scrolling Region */
1779 if (csiescseq.priv) {
1782 DEFAULT(csiescseq.arg[0], 1);
1783 DEFAULT(csiescseq.arg[1], term.row);
1784 tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
1788 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1789 tcursor(CURSOR_SAVE);
1791 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1792 tcursor(CURSOR_LOAD);
1795 switch (csiescseq.mode[1]) {
1796 case 'q': /* DECSCUSR -- Set Cursor Style */
1797 if (xsetcursor(csiescseq.arg[0]))
1813 fprintf(stderr, "ESC[");
1814 for (i = 0; i < csiescseq.len; i++) {
1815 c = csiescseq.buf[i] & 0xff;
1818 } else if (c == '\n') {
1819 fprintf(stderr, "(\\n)");
1820 } else if (c == '\r') {
1821 fprintf(stderr, "(\\r)");
1822 } else if (c == 0x1b) {
1823 fprintf(stderr, "(\\e)");
1825 fprintf(stderr, "(%02x)", c);
1834 memset(&csiescseq, 0, sizeof(csiescseq));
1838 osc4_color_response(int num)
1842 unsigned char r, g, b;
1844 if (xgetcolor(num, &r, &g, &b)) {
1845 fprintf(stderr, "erresc: failed to fetch osc4 color %d\n", num);
1849 n = snprintf(buf, sizeof buf, "\033]4;%d;rgb:%02x%02x/%02x%02x/%02x%02x\007",
1850 num, r, r, g, g, b, b);
1852 ttywrite(buf, n, 1);
1856 osc_color_response(int index, int num)
1860 unsigned char r, g, b;
1862 if (xgetcolor(index, &r, &g, &b)) {
1863 fprintf(stderr, "erresc: failed to fetch osc color %d\n", index);
1867 n = snprintf(buf, sizeof buf, "\033]%d;rgb:%02x%02x/%02x%02x/%02x%02x\007",
1868 num, r, r, g, g, b, b);
1870 ttywrite(buf, n, 1);
1876 char *p = NULL, *dec;
1879 term.esc &= ~(ESC_STR_END|ESC_STR);
1881 par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
1883 switch (strescseq.type) {
1884 case ']': /* OSC -- Operating System Command */
1888 xsettitle(strescseq.args[1]);
1889 xseticontitle(strescseq.args[1]);
1894 xseticontitle(strescseq.args[1]);
1898 xsettitle(strescseq.args[1]);
1901 if (narg > 2 && allowwindowops) {
1902 dec = base64dec(strescseq.args[2]);
1907 fprintf(stderr, "erresc: invalid base64\n");
1915 p = strescseq.args[1];
1917 if (!strcmp(p, "?"))
1918 osc_color_response(defaultfg, 10);
1919 else if (xsetcolorname(defaultfg, p))
1920 fprintf(stderr, "erresc: invalid foreground color: %s\n", p);
1928 p = strescseq.args[1];
1930 if (!strcmp(p, "?"))
1931 osc_color_response(defaultbg, 11);
1932 else if (xsetcolorname(defaultbg, p))
1933 fprintf(stderr, "erresc: invalid background color: %s\n", p);
1941 p = strescseq.args[1];
1943 if (!strcmp(p, "?"))
1944 osc_color_response(defaultcs, 12);
1945 else if (xsetcolorname(defaultcs, p))
1946 fprintf(stderr, "erresc: invalid cursor color: %s\n", p);
1950 case 4: /* color set */
1953 p = strescseq.args[2];
1955 case 104: /* color reset */
1956 j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
1958 if (p && !strcmp(p, "?"))
1959 osc4_color_response(j);
1960 else if (xsetcolorname(j, p)) {
1961 if (par == 104 && narg <= 1)
1962 return; /* color reset without parameter */
1963 fprintf(stderr, "erresc: invalid color j=%d, p=%s\n",
1964 j, p ? p : "(null)");
1967 * TODO if defaultbg color is changed, borders
1975 case 'k': /* old title set compatibility */
1976 xsettitle(strescseq.args[0]);
1978 case 'P': /* DCS -- Device Control String */
1979 case '_': /* APC -- Application Program Command */
1980 case '^': /* PM -- Privacy Message */
1984 fprintf(stderr, "erresc: unknown str ");
1992 char *p = strescseq.buf;
1995 strescseq.buf[strescseq.len] = '\0';
2000 while (strescseq.narg < STR_ARG_SIZ) {
2001 strescseq.args[strescseq.narg++] = p;
2002 while ((c = *p) != ';' && c != '\0')
2016 fprintf(stderr, "ESC%c", strescseq.type);
2017 for (i = 0; i < strescseq.len; i++) {
2018 c = strescseq.buf[i] & 0xff;
2022 } else if (isprint(c)) {
2024 } else if (c == '\n') {
2025 fprintf(stderr, "(\\n)");
2026 } else if (c == '\r') {
2027 fprintf(stderr, "(\\r)");
2028 } else if (c == 0x1b) {
2029 fprintf(stderr, "(\\e)");
2031 fprintf(stderr, "(%02x)", c);
2034 fprintf(stderr, "ESC\\\n");
2040 strescseq = (STREscape){
2041 .buf = xrealloc(strescseq.buf, STR_BUF_SIZ),
2047 sendbreak(const Arg *arg)
2049 if (tcsendbreak(cmdfd, 0))
2050 perror("Error sending break");
2054 tprinter(char *s, size_t len)
2056 if (iofd != -1 && xwrite(iofd, s, len) < 0) {
2057 perror("Error writing to output file");
2064 toggleprinter(const Arg *arg)
2066 term.mode ^= MODE_PRINT;
2070 printscreen(const Arg *arg)
2076 printsel(const Arg *arg)
2086 if ((ptr = getsel())) {
2087 tprinter(ptr, strlen(ptr));
2096 const Glyph *bp, *end;
2098 bp = &term.line[n][0];
2099 end = &bp[MIN(tlinelen(n), term.col) - 1];
2100 if (bp != end || bp->u != ' ') {
2101 for ( ; bp <= end; ++bp)
2102 tprinter(buf, utf8encode(bp->u, buf));
2112 for (i = 0; i < term.row; ++i)
2122 while (x < term.col && n--)
2123 for (++x; x < term.col && !term.tabs[x]; ++x)
2126 while (x > 0 && n++)
2127 for (--x; x > 0 && !term.tabs[x]; --x)
2130 term.c.x = LIMIT(x, 0, term.col-1);
2134 tdefutf8(char ascii)
2137 term.mode |= MODE_UTF8;
2138 else if (ascii == '@')
2139 term.mode &= ~MODE_UTF8;
2143 tdeftran(char ascii)
2145 static char cs[] = "0B";
2146 static int vcs[] = {CS_GRAPHIC0, CS_USA};
2149 if ((p = strchr(cs, ascii)) == NULL) {
2150 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
2152 term.trantbl[term.icharset] = vcs[p - cs];
2161 if (c == '8') { /* DEC screen alignment test. */
2162 for (x = 0; x < term.col; ++x) {
2163 for (y = 0; y < term.row; ++y)
2164 tsetchar('E', &term.c.attr, x, y);
2170 tstrsequence(uchar c)
2173 case 0x90: /* DCS -- Device Control String */
2176 case 0x9f: /* APC -- Application Program Command */
2179 case 0x9e: /* PM -- Privacy Message */
2182 case 0x9d: /* OSC -- Operating System Command */
2188 term.esc |= ESC_STR;
2192 tcontrolcode(uchar ascii)
2199 tmoveto(term.c.x-1, term.c.y);
2202 tmoveto(0, term.c.y);
2207 /* go to first col if the mode is set */
2208 tnewline(IS_SET(MODE_CRLF));
2210 case '\a': /* BEL */
2211 if (term.esc & ESC_STR_END) {
2212 /* backwards compatibility to xterm */
2218 case '\033': /* ESC */
2220 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
2221 term.esc |= ESC_START;
2223 case '\016': /* SO (LS1 -- Locking shift 1) */
2224 case '\017': /* SI (LS0 -- Locking shift 0) */
2225 term.charset = 1 - (ascii - '\016');
2227 case '\032': /* SUB */
2228 tsetchar('?', &term.c.attr, term.c.x, term.c.y);
2230 case '\030': /* CAN */
2233 case '\005': /* ENQ (IGNORED) */
2234 case '\000': /* NUL (IGNORED) */
2235 case '\021': /* XON (IGNORED) */
2236 case '\023': /* XOFF (IGNORED) */
2237 case 0177: /* DEL (IGNORED) */
2239 case 0x80: /* TODO: PAD */
2240 case 0x81: /* TODO: HOP */
2241 case 0x82: /* TODO: BPH */
2242 case 0x83: /* TODO: NBH */
2243 case 0x84: /* TODO: IND */
2245 case 0x85: /* NEL -- Next line */
2246 tnewline(1); /* always go to first col */
2248 case 0x86: /* TODO: SSA */
2249 case 0x87: /* TODO: ESA */
2251 case 0x88: /* HTS -- Horizontal tab stop */
2252 term.tabs[term.c.x] = 1;
2254 case 0x89: /* TODO: HTJ */
2255 case 0x8a: /* TODO: VTS */
2256 case 0x8b: /* TODO: PLD */
2257 case 0x8c: /* TODO: PLU */
2258 case 0x8d: /* TODO: RI */
2259 case 0x8e: /* TODO: SS2 */
2260 case 0x8f: /* TODO: SS3 */
2261 case 0x91: /* TODO: PU1 */
2262 case 0x92: /* TODO: PU2 */
2263 case 0x93: /* TODO: STS */
2264 case 0x94: /* TODO: CCH */
2265 case 0x95: /* TODO: MW */
2266 case 0x96: /* TODO: SPA */
2267 case 0x97: /* TODO: EPA */
2268 case 0x98: /* TODO: SOS */
2269 case 0x99: /* TODO: SGCI */
2271 case 0x9a: /* DECID -- Identify Terminal */
2272 ttywrite(vtiden, strlen(vtiden), 0);
2274 case 0x9b: /* TODO: CSI */
2275 case 0x9c: /* TODO: ST */
2277 case 0x90: /* DCS -- Device Control String */
2278 case 0x9d: /* OSC -- Operating System Command */
2279 case 0x9e: /* PM -- Privacy Message */
2280 case 0x9f: /* APC -- Application Program Command */
2281 tstrsequence(ascii);
2284 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2285 term.esc &= ~(ESC_STR_END|ESC_STR);
2289 * returns 1 when the sequence is finished and it hasn't to read
2290 * more characters for this sequence, otherwise 0
2293 eschandle(uchar ascii)
2297 term.esc |= ESC_CSI;
2300 term.esc |= ESC_TEST;
2303 term.esc |= ESC_UTF8;
2305 case 'P': /* DCS -- Device Control String */
2306 case '_': /* APC -- Application Program Command */
2307 case '^': /* PM -- Privacy Message */
2308 case ']': /* OSC -- Operating System Command */
2309 case 'k': /* old title set compatibility */
2310 tstrsequence(ascii);
2312 case 'n': /* LS2 -- Locking shift 2 */
2313 case 'o': /* LS3 -- Locking shift 3 */
2314 term.charset = 2 + (ascii - 'n');
2316 case '(': /* GZD4 -- set primary charset G0 */
2317 case ')': /* G1D4 -- set secondary charset G1 */
2318 case '*': /* G2D4 -- set tertiary charset G2 */
2319 case '+': /* G3D4 -- set quaternary charset G3 */
2320 term.icharset = ascii - '(';
2321 term.esc |= ESC_ALTCHARSET;
2323 case 'D': /* IND -- Linefeed */
2324 if (term.c.y == term.bot) {
2325 tscrollup(term.top, 1);
2327 tmoveto(term.c.x, term.c.y+1);
2330 case 'E': /* NEL -- Next line */
2331 tnewline(1); /* always go to first col */
2333 case 'H': /* HTS -- Horizontal tab stop */
2334 term.tabs[term.c.x] = 1;
2336 case 'M': /* RI -- Reverse index */
2337 if (term.c.y == term.top) {
2338 tscrolldown(term.top, 1);
2340 tmoveto(term.c.x, term.c.y-1);
2343 case 'Z': /* DECID -- Identify Terminal */
2344 ttywrite(vtiden, strlen(vtiden), 0);
2346 case 'c': /* RIS -- Reset to initial state */
2351 case '=': /* DECPAM -- Application keypad */
2352 xsetmode(1, MODE_APPKEYPAD);
2354 case '>': /* DECPNM -- Normal keypad */
2355 xsetmode(0, MODE_APPKEYPAD);
2357 case '7': /* DECSC -- Save Cursor */
2358 tcursor(CURSOR_SAVE);
2360 case '8': /* DECRC -- Restore Cursor */
2361 tcursor(CURSOR_LOAD);
2363 case '\\': /* ST -- String Terminator */
2364 if (term.esc & ESC_STR_END)
2368 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2369 (uchar) ascii, isprint(ascii)? ascii:'.');
2383 control = ISCONTROL(u);
2384 if (u < 127 || !IS_SET(MODE_UTF8)) {
2388 len = utf8encode(u, c);
2389 if (!control && (width = wcwidth(u)) == -1)
2393 if (IS_SET(MODE_PRINT))
2397 * STR sequence must be checked before anything else
2398 * because it uses all following characters until it
2399 * receives a ESC, a SUB, a ST or any other C1 control
2402 if (term.esc & ESC_STR) {
2403 if (u == '\a' || u == 030 || u == 032 || u == 033 ||
2405 term.esc &= ~(ESC_START|ESC_STR);
2406 term.esc |= ESC_STR_END;
2407 goto check_control_code;
2410 if (strescseq.len+len >= strescseq.siz) {
2412 * Here is a bug in terminals. If the user never sends
2413 * some code to stop the str or esc command, then st
2414 * will stop responding. But this is better than
2415 * silently failing with unknown characters. At least
2416 * then users will report back.
2418 * In the case users ever get fixed, here is the code:
2424 if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2)
2427 strescseq.buf = xrealloc(strescseq.buf, strescseq.siz);
2430 memmove(&strescseq.buf[strescseq.len], c, len);
2431 strescseq.len += len;
2437 * Actions of control codes must be performed as soon they arrive
2438 * because they can be embedded inside a control sequence, and
2439 * they must not cause conflicts with sequences.
2444 * control codes are not shown ever
2449 } else if (term.esc & ESC_START) {
2450 if (term.esc & ESC_CSI) {
2451 csiescseq.buf[csiescseq.len++] = u;
2452 if (BETWEEN(u, 0x40, 0x7E)
2453 || csiescseq.len >= \
2454 sizeof(csiescseq.buf)-1) {
2460 } else if (term.esc & ESC_UTF8) {
2462 } else if (term.esc & ESC_ALTCHARSET) {
2464 } else if (term.esc & ESC_TEST) {
2469 /* sequence already finished */
2473 * All characters which form part of a sequence are not
2478 if (selected(term.c.x, term.c.y))
2481 gp = &term.line[term.c.y][term.c.x];
2482 if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
2483 gp->mode |= ATTR_WRAP;
2485 gp = &term.line[term.c.y][term.c.x];
2488 if (IS_SET(MODE_INSERT) && term.c.x+width < term.col)
2489 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
2491 if (term.c.x+width > term.col) {
2493 gp = &term.line[term.c.y][term.c.x];
2496 tsetchar(u, &term.c.attr, term.c.x, term.c.y);
2500 gp->mode |= ATTR_WIDE;
2501 if (term.c.x+1 < term.col) {
2502 if (gp[1].mode == ATTR_WIDE && term.c.x+2 < term.col) {
2504 gp[2].mode &= ~ATTR_WDUMMY;
2507 gp[1].mode = ATTR_WDUMMY;
2510 if (term.c.x+width < term.col) {
2511 tmoveto(term.c.x+width, term.c.y);
2513 term.c.state |= CURSOR_WRAPNEXT;
2518 twrite(const char *buf, int buflen, int show_ctrl)
2524 for (n = 0; n < buflen; n += charsize) {
2525 if (IS_SET(MODE_UTF8)) {
2526 /* process a complete utf8 char */
2527 charsize = utf8decode(buf + n, &u, buflen - n);
2534 if (show_ctrl && ISCONTROL(u)) {
2539 } else if (u != '\n' && u != '\r' && u != '\t') {
2550 tresize(int col, int row)
2553 int minrow = MIN(row, term.row);
2554 int mincol = MIN(col, term.col);
2558 if (col < 1 || row < 1) {
2560 "tresize: error resizing to %dx%d\n", col, row);
2565 * slide screen to keep cursor where we expect it -
2566 * tscrollup would work here, but we can optimize to
2567 * memmove because we're freeing the earlier lines
2569 for (i = 0; i <= term.c.y - row; i++) {
2573 /* ensure that both src and dst are not NULL */
2575 memmove(term.line, term.line + i, row * sizeof(Line));
2576 memmove(term.alt, term.alt + i, row * sizeof(Line));
2578 for (i += row; i < term.row; i++) {
2583 /* resize to new height */
2584 term.line = xrealloc(term.line, row * sizeof(Line));
2585 term.alt = xrealloc(term.alt, row * sizeof(Line));
2586 term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
2587 term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
2589 /* resize each row to new width, zero-pad if needed */
2590 for (i = 0; i < minrow; i++) {
2591 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
2592 term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph));
2595 /* allocate any new rows */
2596 for (/* i = minrow */; i < row; i++) {
2597 term.line[i] = xmalloc(col * sizeof(Glyph));
2598 term.alt[i] = xmalloc(col * sizeof(Glyph));
2600 if (col > term.col) {
2601 bp = term.tabs + term.col;
2603 memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
2604 while (--bp > term.tabs && !*bp)
2606 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
2609 /* update terminal size */
2612 /* reset scrolling region */
2613 tsetscroll(0, row-1);
2614 /* make use of the LIMIT in tmoveto */
2615 tmoveto(term.c.x, term.c.y);
2616 /* Clearing both screens (it makes dirty all lines) */
2618 for (i = 0; i < 2; i++) {
2619 if (mincol < col && 0 < minrow) {
2620 tclearregion(mincol, 0, col - 1, minrow - 1);
2622 if (0 < col && minrow < row) {
2623 tclearregion(0, minrow, col - 1, row - 1);
2626 tcursor(CURSOR_LOAD);
2638 drawregion(int x1, int y1, int x2, int y2)
2642 for (y = y1; y < y2; y++) {
2647 xdrawline(term.line[y], x1, y, x2);
2654 int cx = term.c.x, ocx = term.ocx, ocy = term.ocy;
2659 /* adjust cursor position */
2660 LIMIT(term.ocx, 0, term.col-1);
2661 LIMIT(term.ocy, 0, term.row-1);
2662 if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY)
2664 if (term.line[term.c.y][cx].mode & ATTR_WDUMMY)
2667 drawregion(0, 0, term.col, term.row);
2668 xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
2669 term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
2671 term.ocy = term.c.y;
2673 if (ocx != term.ocx || ocy != term.ocy)
2674 xximspot(term.ocx, term.ocy);