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 NUMMAXLEN(x) ((int)(sizeof(x) * 2.56 + 0.5) + 1)
42 #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == '\177')
43 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
44 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
45 #define ISDELIM(u) (utf8strchr(worddelimiters, u) != NULL)
48 #define ISO14755CMD "dmenu -w \"$WINDOWID\" -p codepoint: </dev/null"
53 MODE_ALTSCREEN = 1 << 2,
61 enum cursor_movement {
85 ESC_STR = 4, /* OSC, PM, APC */
87 ESC_STR_END = 16, /* a final string was encountered */
88 ESC_TEST = 32, /* Enter in test mode */
94 Glyph attr; /* current char attributes */
105 * Selection variables:
106 * nb – normalized coordinates of the beginning of the selection
107 * ne – normalized coordinates of the end of the selection
108 * ob – original coordinates of the beginning of the selection
109 * oe – original coordinates of the end of the selection
118 /* Internal representation of the screen */
120 int row; /* nb row */
121 int col; /* nb col */
122 Line *line; /* screen */
123 Line *alt; /* alternate screen */
124 int *dirty; /* dirtyness of lines */
125 TCursor c; /* cursor */
126 int ocx; /* old cursor col */
127 int ocy; /* old cursor row */
128 int top; /* top scroll limit */
129 int bot; /* bottom scroll limit */
130 int mode; /* terminal mode flags */
131 int esc; /* escape state flags */
132 char trantbl[4]; /* charset table translation */
133 int charset; /* current charset */
134 int icharset; /* selected charset for sequence */
138 /* CSI Escape sequence structs */
139 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
141 char buf[ESC_BUF_SIZ]; /* raw string */
142 int len; /* raw string length */
144 int arg[ESC_ARG_SIZ];
145 int narg; /* nb of args */
149 /* STR Escape sequence structs */
150 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
152 char type; /* ESC type ... */
153 char buf[STR_BUF_SIZ]; /* raw string */
154 int len; /* raw string length */
155 char *args[STR_ARG_SIZ];
156 int narg; /* nb of args */
159 static void execsh(char *, char **);
160 static void stty(char **);
161 static void sigchld(int);
162 static void ttywriteraw(const char *, size_t);
164 static void csidump(void);
165 static void csihandle(void);
166 static void csiparse(void);
167 static void csireset(void);
168 static int eschandle(uchar);
169 static void strdump(void);
170 static void strhandle(void);
171 static void strparse(void);
172 static void strreset(void);
174 static void tprinter(char *, size_t);
175 static void tdumpsel(void);
176 static void tdumpline(int);
177 static void tdump(void);
178 static void tclearregion(int, int, int, int);
179 static void tcursor(int);
180 static void tdeletechar(int);
181 static void tdeleteline(int);
182 static void tinsertblank(int);
183 static void tinsertblankline(int);
184 static int tlinelen(int);
185 static void tmoveto(int, int);
186 static void tmoveato(int, int);
187 static void tnewline(int);
188 static void tputtab(int);
189 static void tputc(Rune);
190 static void treset(void);
191 static void tscrollup(int, int);
192 static void tscrolldown(int, int);
193 static void tsetattr(int *, int);
194 static void tsetchar(Rune, Glyph *, int, int);
195 static void tsetdirt(int, int);
196 static void tsetscroll(int, int);
197 static void tswapscreen(void);
198 static void tsetmode(int, int, int *, int);
199 static int twrite(const char *, int, int);
200 static void tfulldirt(void);
201 static void tcontrolcode(uchar );
202 static void tdectest(char );
203 static void tdefutf8(char);
204 static int32_t tdefcolor(int *, int *, int);
205 static void tdeftran(char);
206 static void tstrsequence(uchar);
208 static void drawregion(int, int, int, int);
210 static void selnormalize(void);
211 static void selscroll(int, int);
212 static void selsnap(int *, int *, int);
214 static size_t utf8decode(const char *, Rune *, size_t);
215 static Rune utf8decodebyte(char, size_t *);
216 static char utf8encodebyte(Rune, size_t);
217 static char *utf8strchr(char *, Rune);
218 static size_t utf8validate(Rune *, size_t);
220 static char *base64dec(const char *);
221 static char base64dec_getc(const char **);
223 static ssize_t xwrite(int, const char *, size_t);
227 static Selection sel;
228 static CSIEscape csiescseq;
229 static STREscape strescseq;
234 static uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
235 static uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
236 static Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000};
237 static Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
240 xwrite(int fd, const char *s, size_t len)
246 r = write(fd, s, len);
261 if (!(p = malloc(len)))
262 die("malloc: %s\n", strerror(errno));
268 xrealloc(void *p, size_t len)
270 if ((p = realloc(p, len)) == NULL)
271 die("realloc: %s\n", strerror(errno));
279 if ((s = strdup(s)) == NULL)
280 die("strdup: %s\n", strerror(errno));
286 utf8decode(const char *c, Rune *u, size_t clen)
288 size_t i, j, len, type;
294 udecoded = utf8decodebyte(c[0], &len);
295 if (!BETWEEN(len, 1, UTF_SIZ))
297 for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
298 udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
305 utf8validate(u, len);
311 utf8decodebyte(char c, size_t *i)
313 for (*i = 0; *i < LEN(utfmask); ++(*i))
314 if (((uchar)c & utfmask[*i]) == utfbyte[*i])
315 return (uchar)c & ~utfmask[*i];
321 utf8encode(Rune u, char *c)
325 len = utf8validate(&u, 0);
329 for (i = len - 1; i != 0; --i) {
330 c[i] = utf8encodebyte(u, 0);
333 c[0] = utf8encodebyte(u, len);
339 utf8encodebyte(Rune u, size_t i)
341 return utfbyte[i] | (u & ~utfmask[i]);
345 utf8strchr(char *s, Rune u)
351 for (i = 0, j = 0; i < len; i += j) {
352 if (!(j = utf8decode(&s[i], &r, len - i)))
362 utf8validate(Rune *u, size_t i)
364 if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF))
366 for (i = 1; *u > utfmax[i]; ++i)
372 static const char base64_digits[] = {
373 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
374 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0,
375 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
376 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
377 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
378 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
381 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
382 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
383 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
384 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
388 base64dec_getc(const char **src)
390 while (**src && !isprint(**src)) (*src)++;
395 base64dec(const char *src)
397 size_t in_len = strlen(src);
401 in_len += 4 - (in_len % 4);
402 result = dst = xmalloc(in_len / 4 * 3 + 1);
404 int a = base64_digits[(unsigned char) base64dec_getc(&src)];
405 int b = base64_digits[(unsigned char) base64dec_getc(&src)];
406 int c = base64_digits[(unsigned char) base64dec_getc(&src)];
407 int d = base64_digits[(unsigned char) base64dec_getc(&src)];
409 *dst++ = (a << 2) | ((b & 0x30) >> 4);
412 *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2);
415 *dst++ = ((c & 0x03) << 6) | d;
434 if (term.line[y][i - 1].mode & ATTR_WRAP)
437 while (i > 0 && term.line[y][i - 1].u == ' ')
444 selstart(int col, int row, int snap)
447 sel.mode = SEL_EMPTY;
448 sel.type = SEL_REGULAR;
449 sel.alt = IS_SET(MODE_ALTSCREEN);
451 sel.oe.x = sel.ob.x = col;
452 sel.oe.y = sel.ob.y = row;
456 sel.mode = SEL_READY;
457 tsetdirt(sel.nb.y, sel.ne.y);
461 selextend(int col, int row, int type, int done)
463 int oldey, oldex, oldsby, oldsey, oldtype;
465 if (sel.mode == SEL_IDLE)
467 if (done && sel.mode == SEL_EMPTY) {
483 if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type)
484 tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey));
486 sel.mode = done ? SEL_IDLE : SEL_READY;
494 if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) {
495 sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x;
496 sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x;
498 sel.nb.x = MIN(sel.ob.x, sel.oe.x);
499 sel.ne.x = MAX(sel.ob.x, sel.oe.x);
501 sel.nb.y = MIN(sel.ob.y, sel.oe.y);
502 sel.ne.y = MAX(sel.ob.y, sel.oe.y);
504 selsnap(&sel.nb.x, &sel.nb.y, -1);
505 selsnap(&sel.ne.x, &sel.ne.y, +1);
507 /* expand selection over line breaks */
508 if (sel.type == SEL_RECTANGULAR)
510 i = tlinelen(sel.nb.y);
513 if (tlinelen(sel.ne.y) <= sel.ne.x)
514 sel.ne.x = term.col - 1;
518 selected(int x, int y)
520 if (sel.mode == SEL_EMPTY || sel.ob.x == -1 ||
521 sel.alt != IS_SET(MODE_ALTSCREEN))
524 if (sel.type == SEL_RECTANGULAR)
525 return BETWEEN(y, sel.nb.y, sel.ne.y)
526 && BETWEEN(x, sel.nb.x, sel.ne.x);
528 return BETWEEN(y, sel.nb.y, sel.ne.y)
529 && (y != sel.nb.y || x >= sel.nb.x)
530 && (y != sel.ne.y || x <= sel.ne.x);
534 selsnap(int *x, int *y, int direction)
536 int newx, newy, xt, yt;
537 int delim, prevdelim;
543 * Snap around if the word wraps around at the end or
544 * beginning of a line.
546 prevgp = &term.line[*y][*x];
547 prevdelim = ISDELIM(prevgp->u);
549 newx = *x + direction;
551 if (!BETWEEN(newx, 0, term.col - 1)) {
553 newx = (newx + term.col) % term.col;
554 if (!BETWEEN(newy, 0, term.row - 1))
560 yt = newy, xt = newx;
561 if (!(term.line[yt][xt].mode & ATTR_WRAP))
565 if (newx >= tlinelen(newy))
568 gp = &term.line[newy][newx];
569 delim = ISDELIM(gp->u);
570 if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
571 || (delim && gp->u != prevgp->u)))
582 * Snap around if the the previous line or the current one
583 * has set ATTR_WRAP at its end. Then the whole next or
584 * previous line will be selected.
586 *x = (direction < 0) ? 0 : term.col - 1;
588 for (; *y > 0; *y += direction) {
589 if (!(term.line[*y-1][term.col-1].mode
594 } else if (direction > 0) {
595 for (; *y < term.row-1; *y += direction) {
596 if (!(term.line[*y][term.col-1].mode
610 int y, bufsize, lastx, linelen;
616 bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ;
617 ptr = str = xmalloc(bufsize);
619 /* append every set & selected glyph to the selection */
620 for (y = sel.nb.y; y <= sel.ne.y; y++) {
621 if ((linelen = tlinelen(y)) == 0) {
626 if (sel.type == SEL_RECTANGULAR) {
627 gp = &term.line[y][sel.nb.x];
630 gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0];
631 lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
633 last = &term.line[y][MIN(lastx, linelen-1)];
634 while (last >= gp && last->u == ' ')
637 for ( ; gp <= last; ++gp) {
638 if (gp->mode & ATTR_WDUMMY)
641 ptr += utf8encode(gp->u, ptr);
645 * Copy and pasting of line endings is inconsistent
646 * in the inconsistent terminal and GUI world.
647 * The best solution seems like to produce '\n' when
648 * something is copied from st and convert '\n' to
649 * '\r', when something to be pasted is received by
651 * FIXME: Fix the computer world.
653 if ((y < sel.ne.y || lastx >= linelen) && !(last->mode & ATTR_WRAP))
667 tsetdirt(sel.nb.y, sel.ne.y);
671 die(const char *errstr, ...)
675 va_start(ap, errstr);
676 vfprintf(stderr, errstr, ap);
682 execsh(char *cmd, char **args)
685 const struct passwd *pw;
688 if ((pw = getpwuid(getuid())) == NULL) {
690 die("getpwuid: %s\n", strerror(errno));
692 die("who are you?\n");
695 if ((sh = getenv("SHELL")) == NULL)
696 sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd;
704 DEFAULT(args, ((char *[]) {prog, NULL}));
709 setenv("LOGNAME", pw->pw_name, 1);
710 setenv("USER", pw->pw_name, 1);
711 setenv("SHELL", sh, 1);
712 setenv("HOME", pw->pw_dir, 1);
713 setenv("TERM", termname, 1);
715 signal(SIGCHLD, SIG_DFL);
716 signal(SIGHUP, SIG_DFL);
717 signal(SIGINT, SIG_DFL);
718 signal(SIGQUIT, SIG_DFL);
719 signal(SIGTERM, SIG_DFL);
720 signal(SIGALRM, SIG_DFL);
732 if ((p = waitpid(pid, &stat, WNOHANG)) < 0)
733 die("waiting for pid %hd failed: %s\n", pid, strerror(errno));
738 if (!WIFEXITED(stat) || WEXITSTATUS(stat))
739 die("child finished with error '%d'\n", stat);
746 char cmd[_POSIX_ARG_MAX], **p, *q, *s;
749 if ((n = strlen(stty_args)) > sizeof(cmd)-1)
750 die("incorrect stty parameters\n");
751 memcpy(cmd, stty_args, n);
753 siz = sizeof(cmd) - n;
754 for (p = args; p && (s = *p); ++p) {
755 if ((n = strlen(s)) > siz-1)
756 die("stty parameter length too long\n");
763 if (system(cmd) != 0)
764 perror("Couldn't call stty");
768 ttynew(char *line, char *cmd, char *out, char **args)
773 term.mode |= MODE_PRINT;
774 iofd = (!strcmp(out, "-")) ?
775 1 : open(out, O_WRONLY | O_CREAT, 0666);
777 fprintf(stderr, "Error opening %s:%s\n",
778 out, strerror(errno));
783 if ((cmdfd = open(line, O_RDWR)) < 0)
784 die("open line '%s' failed: %s\n",
785 line, strerror(errno));
791 /* seems to work fine on linux, openbsd and freebsd */
792 if (openpty(&m, &s, NULL, NULL, NULL) < 0)
793 die("openpty failed: %s\n", strerror(errno));
795 switch (pid = fork()) {
797 die("fork failed: %s\n", strerror(errno));
801 setsid(); /* create a new process group */
805 if (ioctl(s, TIOCSCTTY, NULL) < 0)
806 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno));
810 if (pledge("stdio getpw proc exec", NULL) == -1)
817 if (pledge("stdio rpath tty proc", NULL) == -1)
822 signal(SIGCHLD, sigchld);
831 static char buf[BUFSIZ];
832 static int buflen = 0;
836 /* append read bytes to unprocessed bytes */
837 if ((ret = read(cmdfd, buf+buflen, LEN(buf)-buflen)) < 0)
838 die("couldn't read from shell: %s\n", strerror(errno));
841 written = twrite(buf, buflen, 0);
843 /* keep any uncomplete utf8 char for the next call */
845 memmove(buf, buf + written, buflen);
851 ttywrite(const char *s, size_t n, int may_echo)
855 if (may_echo && IS_SET(MODE_ECHO))
858 if (!IS_SET(MODE_CRLF)) {
863 /* This is similar to how the kernel handles ONLCR for ttys */
867 ttywriteraw("\r\n", 2);
869 next = memchr(s, '\r', n);
870 DEFAULT(next, s + n);
871 ttywriteraw(s, next - s);
879 ttywriteraw(const char *s, size_t n)
886 * Remember that we are using a pty, which might be a modem line.
887 * Writing too much will clog the line. That's why we are doing this
889 * FIXME: Migrate the world to Plan 9.
897 /* Check if we can write. */
898 if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) {
901 die("select failed: %s\n", strerror(errno));
903 if (FD_ISSET(cmdfd, &wfd)) {
905 * Only write the bytes written by ttywrite() or the
906 * default of 256. This seems to be a reasonable value
907 * for a serial line. Bigger values might clog the I/O.
909 if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0)
913 * We weren't able to write out everything.
914 * This means the buffer is getting full
922 /* All bytes have been written. */
926 if (FD_ISSET(cmdfd, &rfd))
932 die("write error on tty: %s\n", strerror(errno));
936 ttyresize(int tw, int th)
944 if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
945 fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
951 /* Send SIGHUP to shell */
960 for (i = 0; i < term.row-1; i++) {
961 for (j = 0; j < term.col-1; j++) {
962 if (term.line[i][j].mode & attr)
971 tsetdirt(int top, int bot)
975 LIMIT(top, 0, term.row-1);
976 LIMIT(bot, 0, term.row-1);
978 for (i = top; i <= bot; i++)
983 tsetdirtattr(int attr)
987 for (i = 0; i < term.row-1; i++) {
988 for (j = 0; j < term.col-1; j++) {
989 if (term.line[i][j].mode & attr) {
1000 tsetdirt(0, term.row-1);
1006 static TCursor c[2];
1007 int alt = IS_SET(MODE_ALTSCREEN);
1009 if (mode == CURSOR_SAVE) {
1011 } else if (mode == CURSOR_LOAD) {
1013 tmoveto(c[alt].x, c[alt].y);
1022 term.c = (TCursor){{
1026 }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
1028 memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1029 for (i = tabspaces; i < term.col; i += tabspaces)
1032 term.bot = term.row - 1;
1033 term.mode = MODE_WRAP|MODE_UTF8;
1034 memset(term.trantbl, CS_USA, sizeof(term.trantbl));
1037 for (i = 0; i < 2; i++) {
1039 tcursor(CURSOR_SAVE);
1040 tclearregion(0, 0, term.col-1, term.row-1);
1046 tnew(int col, int row)
1048 term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
1056 Line *tmp = term.line;
1058 term.line = term.alt;
1060 term.mode ^= MODE_ALTSCREEN;
1065 tscrolldown(int orig, int n)
1070 LIMIT(n, 0, term.bot-orig+1);
1072 tsetdirt(orig, term.bot-n);
1073 tclearregion(0, term.bot-n+1, term.col-1, term.bot);
1075 for (i = term.bot; i >= orig+n; i--) {
1076 temp = term.line[i];
1077 term.line[i] = term.line[i-n];
1078 term.line[i-n] = temp;
1085 tscrollup(int orig, int n)
1090 LIMIT(n, 0, term.bot-orig+1);
1092 tclearregion(0, orig, term.col-1, orig+n-1);
1093 tsetdirt(orig+n, term.bot);
1095 for (i = orig; i <= term.bot-n; i++) {
1096 temp = term.line[i];
1097 term.line[i] = term.line[i+n];
1098 term.line[i+n] = temp;
1101 selscroll(orig, -n);
1105 selscroll(int orig, int n)
1110 if (BETWEEN(sel.ob.y, orig, term.bot) || BETWEEN(sel.oe.y, orig, term.bot)) {
1111 if ((sel.ob.y += n) > term.bot || (sel.oe.y += n) < term.top) {
1115 if (sel.type == SEL_RECTANGULAR) {
1116 if (sel.ob.y < term.top)
1117 sel.ob.y = term.top;
1118 if (sel.oe.y > term.bot)
1119 sel.oe.y = term.bot;
1121 if (sel.ob.y < term.top) {
1122 sel.ob.y = term.top;
1125 if (sel.oe.y > term.bot) {
1126 sel.oe.y = term.bot;
1127 sel.oe.x = term.col;
1135 tnewline(int first_col)
1139 if (y == term.bot) {
1140 tscrollup(term.top, 1);
1144 tmoveto(first_col ? 0 : term.c.x, y);
1150 char *p = csiescseq.buf, *np;
1159 csiescseq.buf[csiescseq.len] = '\0';
1160 while (p < csiescseq.buf+csiescseq.len) {
1162 v = strtol(p, &np, 10);
1165 if (v == LONG_MAX || v == LONG_MIN)
1167 csiescseq.arg[csiescseq.narg++] = v;
1169 if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ)
1173 csiescseq.mode[0] = *p++;
1174 csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0';
1177 /* for absolute user moves, when decom is set */
1179 tmoveato(int x, int y)
1181 tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0));
1185 tmoveto(int x, int y)
1189 if (term.c.state & CURSOR_ORIGIN) {
1194 maxy = term.row - 1;
1196 term.c.state &= ~CURSOR_WRAPNEXT;
1197 term.c.x = LIMIT(x, 0, term.col-1);
1198 term.c.y = LIMIT(y, miny, maxy);
1202 tsetchar(Rune u, Glyph *attr, int x, int y)
1204 static char *vt100_0[62] = { /* 0x41 - 0x7e */
1205 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1206 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1207 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1208 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1209 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1210 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1211 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1212 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1216 * The table is proudly stolen from rxvt.
1218 if (term.trantbl[term.charset] == CS_GRAPHIC0 &&
1219 BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
1220 utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ);
1222 if (term.line[y][x].mode & ATTR_WIDE) {
1223 if (x+1 < term.col) {
1224 term.line[y][x+1].u = ' ';
1225 term.line[y][x+1].mode &= ~ATTR_WDUMMY;
1227 } else if (term.line[y][x].mode & ATTR_WDUMMY) {
1228 term.line[y][x-1].u = ' ';
1229 term.line[y][x-1].mode &= ~ATTR_WIDE;
1233 term.line[y][x] = *attr;
1234 term.line[y][x].u = u;
1238 tclearregion(int x1, int y1, int x2, int y2)
1244 temp = x1, x1 = x2, x2 = temp;
1246 temp = y1, y1 = y2, y2 = temp;
1248 LIMIT(x1, 0, term.col-1);
1249 LIMIT(x2, 0, term.col-1);
1250 LIMIT(y1, 0, term.row-1);
1251 LIMIT(y2, 0, term.row-1);
1253 for (y = y1; y <= y2; y++) {
1255 for (x = x1; x <= x2; x++) {
1256 gp = &term.line[y][x];
1259 gp->fg = term.c.attr.fg;
1260 gp->bg = term.c.attr.bg;
1273 LIMIT(n, 0, term.col - term.c.x);
1277 size = term.col - src;
1278 line = term.line[term.c.y];
1280 memmove(&line[dst], &line[src], size * sizeof(Glyph));
1281 tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
1290 LIMIT(n, 0, term.col - term.c.x);
1294 size = term.col - dst;
1295 line = term.line[term.c.y];
1297 memmove(&line[dst], &line[src], size * sizeof(Glyph));
1298 tclearregion(src, term.c.y, dst - 1, term.c.y);
1302 tinsertblankline(int n)
1304 if (BETWEEN(term.c.y, term.top, term.bot))
1305 tscrolldown(term.c.y, n);
1311 if (BETWEEN(term.c.y, term.top, term.bot))
1312 tscrollup(term.c.y, n);
1316 tdefcolor(int *attr, int *npar, int l)
1321 switch (attr[*npar + 1]) {
1322 case 2: /* direct color in RGB space */
1323 if (*npar + 4 >= l) {
1325 "erresc(38): Incorrect number of parameters (%d)\n",
1329 r = attr[*npar + 2];
1330 g = attr[*npar + 3];
1331 b = attr[*npar + 4];
1333 if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255))
1334 fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n",
1337 idx = TRUECOLOR(r, g, b);
1339 case 5: /* indexed color */
1340 if (*npar + 2 >= l) {
1342 "erresc(38): Incorrect number of parameters (%d)\n",
1347 if (!BETWEEN(attr[*npar], 0, 255))
1348 fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]);
1352 case 0: /* implemented defined (only foreground) */
1353 case 1: /* transparent */
1354 case 3: /* direct color in CMY space */
1355 case 4: /* direct color in CMYK space */
1358 "erresc(38): gfx attr %d unknown\n", attr[*npar]);
1366 tsetattr(int *attr, int l)
1371 for (i = 0; i < l; i++) {
1374 term.c.attr.mode &= ~(
1383 term.c.attr.fg = defaultfg;
1384 term.c.attr.bg = defaultbg;
1387 term.c.attr.mode |= ATTR_BOLD;
1390 term.c.attr.mode |= ATTR_FAINT;
1393 term.c.attr.mode |= ATTR_ITALIC;
1396 term.c.attr.mode |= ATTR_UNDERLINE;
1398 case 5: /* slow blink */
1400 case 6: /* rapid blink */
1401 term.c.attr.mode |= ATTR_BLINK;
1404 term.c.attr.mode |= ATTR_REVERSE;
1407 term.c.attr.mode |= ATTR_INVISIBLE;
1410 term.c.attr.mode |= ATTR_STRUCK;
1413 term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT);
1416 term.c.attr.mode &= ~ATTR_ITALIC;
1419 term.c.attr.mode &= ~ATTR_UNDERLINE;
1422 term.c.attr.mode &= ~ATTR_BLINK;
1425 term.c.attr.mode &= ~ATTR_REVERSE;
1428 term.c.attr.mode &= ~ATTR_INVISIBLE;
1431 term.c.attr.mode &= ~ATTR_STRUCK;
1434 if ((idx = tdefcolor(attr, &i, l)) >= 0)
1435 term.c.attr.fg = idx;
1438 term.c.attr.fg = defaultfg;
1441 if ((idx = tdefcolor(attr, &i, l)) >= 0)
1442 term.c.attr.bg = idx;
1445 term.c.attr.bg = defaultbg;
1448 if (BETWEEN(attr[i], 30, 37)) {
1449 term.c.attr.fg = attr[i] - 30;
1450 } else if (BETWEEN(attr[i], 40, 47)) {
1451 term.c.attr.bg = attr[i] - 40;
1452 } else if (BETWEEN(attr[i], 90, 97)) {
1453 term.c.attr.fg = attr[i] - 90 + 8;
1454 } else if (BETWEEN(attr[i], 100, 107)) {
1455 term.c.attr.bg = attr[i] - 100 + 8;
1458 "erresc(default): gfx attr %d unknown\n",
1459 attr[i]), csidump();
1467 tsetscroll(int t, int b)
1471 LIMIT(t, 0, term.row-1);
1472 LIMIT(b, 0, term.row-1);
1483 tsetmode(int priv, int set, int *args, int narg)
1487 for (lim = args + narg; args < lim; ++args) {
1490 case 1: /* DECCKM -- Cursor key */
1491 xsetmode(set, MODE_APPCURSOR);
1493 case 5: /* DECSCNM -- Reverse video */
1494 xsetmode(set, MODE_REVERSE);
1496 case 6: /* DECOM -- Origin */
1497 MODBIT(term.c.state, set, CURSOR_ORIGIN);
1500 case 7: /* DECAWM -- Auto wrap */
1501 MODBIT(term.mode, set, MODE_WRAP);
1503 case 0: /* Error (IGNORED) */
1504 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1505 case 3: /* DECCOLM -- Column (IGNORED) */
1506 case 4: /* DECSCLM -- Scroll (IGNORED) */
1507 case 8: /* DECARM -- Auto repeat (IGNORED) */
1508 case 18: /* DECPFF -- Printer feed (IGNORED) */
1509 case 19: /* DECPEX -- Printer extent (IGNORED) */
1510 case 42: /* DECNRCM -- National characters (IGNORED) */
1511 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1513 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1514 xsetmode(!set, MODE_HIDE);
1516 case 9: /* X10 mouse compatibility mode */
1517 xsetpointermotion(0);
1518 xsetmode(0, MODE_MOUSE);
1519 xsetmode(set, MODE_MOUSEX10);
1521 case 1000: /* 1000: report button press */
1522 xsetpointermotion(0);
1523 xsetmode(0, MODE_MOUSE);
1524 xsetmode(set, MODE_MOUSEBTN);
1526 case 1002: /* 1002: report motion on button press */
1527 xsetpointermotion(0);
1528 xsetmode(0, MODE_MOUSE);
1529 xsetmode(set, MODE_MOUSEMOTION);
1531 case 1003: /* 1003: enable all mouse motions */
1532 xsetpointermotion(set);
1533 xsetmode(0, MODE_MOUSE);
1534 xsetmode(set, MODE_MOUSEMANY);
1536 case 1004: /* 1004: send focus events to tty */
1537 xsetmode(set, MODE_FOCUS);
1539 case 1006: /* 1006: extended reporting mode */
1540 xsetmode(set, MODE_MOUSESGR);
1543 xsetmode(set, MODE_8BIT);
1545 case 1049: /* swap screen & set/restore cursor as xterm */
1546 if (!allowaltscreen)
1548 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1550 case 47: /* swap screen */
1552 if (!allowaltscreen)
1554 alt = IS_SET(MODE_ALTSCREEN);
1556 tclearregion(0, 0, term.col-1,
1559 if (set ^ alt) /* set is always 1 or 0 */
1565 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1567 case 2004: /* 2004: bracketed paste mode */
1568 xsetmode(set, MODE_BRCKTPASTE);
1570 /* Not implemented mouse modes. See comments there. */
1571 case 1001: /* mouse highlight mode; can hang the
1572 terminal by design when implemented. */
1573 case 1005: /* UTF-8 mouse mode; will confuse
1574 applications not supporting UTF-8
1576 case 1015: /* urxvt mangled mouse mode; incompatible
1577 and can be mistaken for other control
1581 "erresc: unknown private set/reset mode %d\n",
1587 case 0: /* Error (IGNORED) */
1590 xsetmode(set, MODE_KBDLOCK);
1592 case 4: /* IRM -- Insertion-replacement */
1593 MODBIT(term.mode, set, MODE_INSERT);
1595 case 12: /* SRM -- Send/Receive */
1596 MODBIT(term.mode, !set, MODE_ECHO);
1598 case 20: /* LNM -- Linefeed/new line */
1599 MODBIT(term.mode, set, MODE_CRLF);
1603 "erresc: unknown set/reset mode %d\n",
1617 switch (csiescseq.mode[0]) {
1620 fprintf(stderr, "erresc: unknown csi ");
1624 case '@': /* ICH -- Insert <n> blank char */
1625 DEFAULT(csiescseq.arg[0], 1);
1626 tinsertblank(csiescseq.arg[0]);
1628 case 'A': /* CUU -- Cursor <n> Up */
1629 DEFAULT(csiescseq.arg[0], 1);
1630 tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
1632 case 'B': /* CUD -- Cursor <n> Down */
1633 case 'e': /* VPR --Cursor <n> Down */
1634 DEFAULT(csiescseq.arg[0], 1);
1635 tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
1637 case 'i': /* MC -- Media Copy */
1638 switch (csiescseq.arg[0]) {
1643 tdumpline(term.c.y);
1649 term.mode &= ~MODE_PRINT;
1652 term.mode |= MODE_PRINT;
1656 case 'c': /* DA -- Device Attributes */
1657 if (csiescseq.arg[0] == 0)
1658 ttywrite(vtiden, strlen(vtiden), 0);
1660 case 'C': /* CUF -- Cursor <n> Forward */
1661 case 'a': /* HPR -- Cursor <n> Forward */
1662 DEFAULT(csiescseq.arg[0], 1);
1663 tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
1665 case 'D': /* CUB -- Cursor <n> Backward */
1666 DEFAULT(csiescseq.arg[0], 1);
1667 tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
1669 case 'E': /* CNL -- Cursor <n> Down and first col */
1670 DEFAULT(csiescseq.arg[0], 1);
1671 tmoveto(0, term.c.y+csiescseq.arg[0]);
1673 case 'F': /* CPL -- Cursor <n> Up and first col */
1674 DEFAULT(csiescseq.arg[0], 1);
1675 tmoveto(0, term.c.y-csiescseq.arg[0]);
1677 case 'g': /* TBC -- Tabulation clear */
1678 switch (csiescseq.arg[0]) {
1679 case 0: /* clear current tab stop */
1680 term.tabs[term.c.x] = 0;
1682 case 3: /* clear all the tabs */
1683 memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1689 case 'G': /* CHA -- Move to <col> */
1691 DEFAULT(csiescseq.arg[0], 1);
1692 tmoveto(csiescseq.arg[0]-1, term.c.y);
1694 case 'H': /* CUP -- Move to <row> <col> */
1696 DEFAULT(csiescseq.arg[0], 1);
1697 DEFAULT(csiescseq.arg[1], 1);
1698 tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
1700 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1701 DEFAULT(csiescseq.arg[0], 1);
1702 tputtab(csiescseq.arg[0]);
1704 case 'J': /* ED -- Clear screen */
1705 switch (csiescseq.arg[0]) {
1707 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
1708 if (term.c.y < term.row-1) {
1709 tclearregion(0, term.c.y+1, term.col-1,
1715 tclearregion(0, 0, term.col-1, term.c.y-1);
1716 tclearregion(0, term.c.y, term.c.x, term.c.y);
1719 tclearregion(0, 0, term.col-1, term.row-1);
1725 case 'K': /* EL -- Clear line */
1726 switch (csiescseq.arg[0]) {
1728 tclearregion(term.c.x, term.c.y, term.col-1,
1732 tclearregion(0, term.c.y, term.c.x, term.c.y);
1735 tclearregion(0, term.c.y, term.col-1, term.c.y);
1739 case 'S': /* SU -- Scroll <n> line up */
1740 DEFAULT(csiescseq.arg[0], 1);
1741 tscrollup(term.top, csiescseq.arg[0]);
1743 case 'T': /* SD -- Scroll <n> line down */
1744 DEFAULT(csiescseq.arg[0], 1);
1745 tscrolldown(term.top, csiescseq.arg[0]);
1747 case 'L': /* IL -- Insert <n> blank lines */
1748 DEFAULT(csiescseq.arg[0], 1);
1749 tinsertblankline(csiescseq.arg[0]);
1751 case 'l': /* RM -- Reset Mode */
1752 tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
1754 case 'M': /* DL -- Delete <n> lines */
1755 DEFAULT(csiescseq.arg[0], 1);
1756 tdeleteline(csiescseq.arg[0]);
1758 case 'X': /* ECH -- Erase <n> char */
1759 DEFAULT(csiescseq.arg[0], 1);
1760 tclearregion(term.c.x, term.c.y,
1761 term.c.x + csiescseq.arg[0] - 1, term.c.y);
1763 case 'P': /* DCH -- Delete <n> char */
1764 DEFAULT(csiescseq.arg[0], 1);
1765 tdeletechar(csiescseq.arg[0]);
1767 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1768 DEFAULT(csiescseq.arg[0], 1);
1769 tputtab(-csiescseq.arg[0]);
1771 case 'd': /* VPA -- Move to <row> */
1772 DEFAULT(csiescseq.arg[0], 1);
1773 tmoveato(term.c.x, csiescseq.arg[0]-1);
1775 case 'h': /* SM -- Set terminal mode */
1776 tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
1778 case 'm': /* SGR -- Terminal attribute (color) */
1779 tsetattr(csiescseq.arg, csiescseq.narg);
1781 case 'n': /* DSR – Device Status Report (cursor position) */
1782 if (csiescseq.arg[0] == 6) {
1783 len = snprintf(buf, sizeof(buf),"\033[%i;%iR",
1784 term.c.y+1, term.c.x+1);
1785 ttywrite(buf, len, 0);
1788 case 'r': /* DECSTBM -- Set Scrolling Region */
1789 if (csiescseq.priv) {
1792 DEFAULT(csiescseq.arg[0], 1);
1793 DEFAULT(csiescseq.arg[1], term.row);
1794 tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
1798 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1799 tcursor(CURSOR_SAVE);
1801 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1802 tcursor(CURSOR_LOAD);
1805 switch (csiescseq.mode[1]) {
1806 case 'q': /* DECSCUSR -- Set Cursor Style */
1807 if (xsetcursor(csiescseq.arg[0]))
1823 fprintf(stderr, "ESC[");
1824 for (i = 0; i < csiescseq.len; i++) {
1825 c = csiescseq.buf[i] & 0xff;
1828 } else if (c == '\n') {
1829 fprintf(stderr, "(\\n)");
1830 } else if (c == '\r') {
1831 fprintf(stderr, "(\\r)");
1832 } else if (c == 0x1b) {
1833 fprintf(stderr, "(\\e)");
1835 fprintf(stderr, "(%02x)", c);
1844 memset(&csiescseq, 0, sizeof(csiescseq));
1853 term.esc &= ~(ESC_STR_END|ESC_STR);
1855 par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
1857 switch (strescseq.type) {
1858 case ']': /* OSC -- Operating System Command */
1864 xsettitle(strescseq.args[1]);
1870 dec = base64dec(strescseq.args[2]);
1875 fprintf(stderr, "erresc: invalid base64\n");
1879 case 4: /* color set */
1882 p = strescseq.args[2];
1884 case 104: /* color reset, here p = NULL */
1885 j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
1886 if (xsetcolorname(j, p)) {
1887 fprintf(stderr, "erresc: invalid color %s\n", p);
1890 * TODO if defaultbg color is changed, borders
1898 case 'k': /* old title set compatibility */
1899 xsettitle(strescseq.args[0]);
1901 case 'P': /* DCS -- Device Control String */
1902 term.mode |= ESC_DCS;
1903 case '_': /* APC -- Application Program Command */
1904 case '^': /* PM -- Privacy Message */
1908 fprintf(stderr, "erresc: unknown str ");
1916 char *p = strescseq.buf;
1919 strescseq.buf[strescseq.len] = '\0';
1924 while (strescseq.narg < STR_ARG_SIZ) {
1925 strescseq.args[strescseq.narg++] = p;
1926 while ((c = *p) != ';' && c != '\0')
1940 fprintf(stderr, "ESC%c", strescseq.type);
1941 for (i = 0; i < strescseq.len; i++) {
1942 c = strescseq.buf[i] & 0xff;
1946 } else if (isprint(c)) {
1948 } else if (c == '\n') {
1949 fprintf(stderr, "(\\n)");
1950 } else if (c == '\r') {
1951 fprintf(stderr, "(\\r)");
1952 } else if (c == 0x1b) {
1953 fprintf(stderr, "(\\e)");
1955 fprintf(stderr, "(%02x)", c);
1958 fprintf(stderr, "ESC\\\n");
1964 memset(&strescseq, 0, sizeof(strescseq));
1968 sendbreak(const Arg *arg)
1970 if (tcsendbreak(cmdfd, 0))
1971 perror("Error sending break");
1975 tprinter(char *s, size_t len)
1977 if (iofd != -1 && xwrite(iofd, s, len) < 0) {
1978 perror("Error writing to output file");
1985 iso14755(const Arg *arg)
1988 char *us, *e, codepoint[9], uc[UTF_SIZ];
1989 unsigned long utf32;
1991 if (!(p = popen(ISO14755CMD, "r")))
1994 us = fgets(codepoint, sizeof(codepoint), p);
1997 if (!us || *us == '\0' || *us == '-' || strlen(us) > 7)
1999 if ((utf32 = strtoul(us, &e, 16)) == ULONG_MAX ||
2000 (*e != '\n' && *e != '\0'))
2003 ttywrite(uc, utf8encode(utf32, uc), 1);
2007 toggleprinter(const Arg *arg)
2009 term.mode ^= MODE_PRINT;
2013 printscreen(const Arg *arg)
2019 printsel(const Arg *arg)
2029 if ((ptr = getsel())) {
2030 tprinter(ptr, strlen(ptr));
2041 bp = &term.line[n][0];
2042 end = &bp[MIN(tlinelen(n), term.col) - 1];
2043 if (bp != end || bp->u != ' ') {
2044 for ( ;bp <= end; ++bp)
2045 tprinter(buf, utf8encode(bp->u, buf));
2055 for (i = 0; i < term.row; ++i)
2065 while (x < term.col && n--)
2066 for (++x; x < term.col && !term.tabs[x]; ++x)
2069 while (x > 0 && n++)
2070 for (--x; x > 0 && !term.tabs[x]; --x)
2073 term.c.x = LIMIT(x, 0, term.col-1);
2077 tdefutf8(char ascii)
2080 term.mode |= MODE_UTF8;
2081 else if (ascii == '@')
2082 term.mode &= ~MODE_UTF8;
2086 tdeftran(char ascii)
2088 static char cs[] = "0B";
2089 static int vcs[] = {CS_GRAPHIC0, CS_USA};
2092 if ((p = strchr(cs, ascii)) == NULL) {
2093 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
2095 term.trantbl[term.icharset] = vcs[p - cs];
2104 if (c == '8') { /* DEC screen alignment test. */
2105 for (x = 0; x < term.col; ++x) {
2106 for (y = 0; y < term.row; ++y)
2107 tsetchar('E', &term.c.attr, x, y);
2113 tstrsequence(uchar c)
2118 case 0x90: /* DCS -- Device Control String */
2120 term.esc |= ESC_DCS;
2122 case 0x9f: /* APC -- Application Program Command */
2125 case 0x9e: /* PM -- Privacy Message */
2128 case 0x9d: /* OSC -- Operating System Command */
2133 term.esc |= ESC_STR;
2137 tcontrolcode(uchar ascii)
2144 tmoveto(term.c.x-1, term.c.y);
2147 tmoveto(0, term.c.y);
2152 /* go to first col if the mode is set */
2153 tnewline(IS_SET(MODE_CRLF));
2155 case '\a': /* BEL */
2156 if (term.esc & ESC_STR_END) {
2157 /* backwards compatibility to xterm */
2163 case '\033': /* ESC */
2165 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
2166 term.esc |= ESC_START;
2168 case '\016': /* SO (LS1 -- Locking shift 1) */
2169 case '\017': /* SI (LS0 -- Locking shift 0) */
2170 term.charset = 1 - (ascii - '\016');
2172 case '\032': /* SUB */
2173 tsetchar('?', &term.c.attr, term.c.x, term.c.y);
2174 case '\030': /* CAN */
2177 case '\005': /* ENQ (IGNORED) */
2178 case '\000': /* NUL (IGNORED) */
2179 case '\021': /* XON (IGNORED) */
2180 case '\023': /* XOFF (IGNORED) */
2181 case 0177: /* DEL (IGNORED) */
2183 case 0x80: /* TODO: PAD */
2184 case 0x81: /* TODO: HOP */
2185 case 0x82: /* TODO: BPH */
2186 case 0x83: /* TODO: NBH */
2187 case 0x84: /* TODO: IND */
2189 case 0x85: /* NEL -- Next line */
2190 tnewline(1); /* always go to first col */
2192 case 0x86: /* TODO: SSA */
2193 case 0x87: /* TODO: ESA */
2195 case 0x88: /* HTS -- Horizontal tab stop */
2196 term.tabs[term.c.x] = 1;
2198 case 0x89: /* TODO: HTJ */
2199 case 0x8a: /* TODO: VTS */
2200 case 0x8b: /* TODO: PLD */
2201 case 0x8c: /* TODO: PLU */
2202 case 0x8d: /* TODO: RI */
2203 case 0x8e: /* TODO: SS2 */
2204 case 0x8f: /* TODO: SS3 */
2205 case 0x91: /* TODO: PU1 */
2206 case 0x92: /* TODO: PU2 */
2207 case 0x93: /* TODO: STS */
2208 case 0x94: /* TODO: CCH */
2209 case 0x95: /* TODO: MW */
2210 case 0x96: /* TODO: SPA */
2211 case 0x97: /* TODO: EPA */
2212 case 0x98: /* TODO: SOS */
2213 case 0x99: /* TODO: SGCI */
2215 case 0x9a: /* DECID -- Identify Terminal */
2216 ttywrite(vtiden, strlen(vtiden), 0);
2218 case 0x9b: /* TODO: CSI */
2219 case 0x9c: /* TODO: ST */
2221 case 0x90: /* DCS -- Device Control String */
2222 case 0x9d: /* OSC -- Operating System Command */
2223 case 0x9e: /* PM -- Privacy Message */
2224 case 0x9f: /* APC -- Application Program Command */
2225 tstrsequence(ascii);
2228 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2229 term.esc &= ~(ESC_STR_END|ESC_STR);
2233 * returns 1 when the sequence is finished and it hasn't to read
2234 * more characters for this sequence, otherwise 0
2237 eschandle(uchar ascii)
2241 term.esc |= ESC_CSI;
2244 term.esc |= ESC_TEST;
2247 term.esc |= ESC_UTF8;
2249 case 'P': /* DCS -- Device Control String */
2250 case '_': /* APC -- Application Program Command */
2251 case '^': /* PM -- Privacy Message */
2252 case ']': /* OSC -- Operating System Command */
2253 case 'k': /* old title set compatibility */
2254 tstrsequence(ascii);
2256 case 'n': /* LS2 -- Locking shift 2 */
2257 case 'o': /* LS3 -- Locking shift 3 */
2258 term.charset = 2 + (ascii - 'n');
2260 case '(': /* GZD4 -- set primary charset G0 */
2261 case ')': /* G1D4 -- set secondary charset G1 */
2262 case '*': /* G2D4 -- set tertiary charset G2 */
2263 case '+': /* G3D4 -- set quaternary charset G3 */
2264 term.icharset = ascii - '(';
2265 term.esc |= ESC_ALTCHARSET;
2267 case 'D': /* IND -- Linefeed */
2268 if (term.c.y == term.bot) {
2269 tscrollup(term.top, 1);
2271 tmoveto(term.c.x, term.c.y+1);
2274 case 'E': /* NEL -- Next line */
2275 tnewline(1); /* always go to first col */
2277 case 'H': /* HTS -- Horizontal tab stop */
2278 term.tabs[term.c.x] = 1;
2280 case 'M': /* RI -- Reverse index */
2281 if (term.c.y == term.top) {
2282 tscrolldown(term.top, 1);
2284 tmoveto(term.c.x, term.c.y-1);
2287 case 'Z': /* DECID -- Identify Terminal */
2288 ttywrite(vtiden, strlen(vtiden), 0);
2290 case 'c': /* RIS -- Reset to inital state */
2295 case '=': /* DECPAM -- Application keypad */
2296 xsetmode(1, MODE_APPKEYPAD);
2298 case '>': /* DECPNM -- Normal keypad */
2299 xsetmode(0, MODE_APPKEYPAD);
2301 case '7': /* DECSC -- Save Cursor */
2302 tcursor(CURSOR_SAVE);
2304 case '8': /* DECRC -- Restore Cursor */
2305 tcursor(CURSOR_LOAD);
2307 case '\\': /* ST -- String Terminator */
2308 if (term.esc & ESC_STR_END)
2312 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2313 (uchar) ascii, isprint(ascii)? ascii:'.');
2327 control = ISCONTROL(u);
2328 if (!IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
2332 len = utf8encode(u, c);
2333 if (!control && (width = wcwidth(u)) == -1) {
2334 memcpy(c, "\357\277\275", 4); /* UTF_INVALID */
2339 if (IS_SET(MODE_PRINT))
2343 * STR sequence must be checked before anything else
2344 * because it uses all following characters until it
2345 * receives a ESC, a SUB, a ST or any other C1 control
2348 if (term.esc & ESC_STR) {
2349 if (u == '\a' || u == 030 || u == 032 || u == 033 ||
2351 term.esc &= ~(ESC_START|ESC_STR|ESC_DCS);
2352 if (IS_SET(MODE_SIXEL)) {
2353 /* TODO: render sixel */;
2354 term.mode &= ~MODE_SIXEL;
2357 term.esc |= ESC_STR_END;
2358 goto check_control_code;
2362 if (IS_SET(MODE_SIXEL)) {
2363 /* TODO: implement sixel mode */
2366 if (term.esc&ESC_DCS && strescseq.len == 0 && u == 'q')
2367 term.mode |= MODE_SIXEL;
2369 if (strescseq.len+len >= sizeof(strescseq.buf)-1) {
2371 * Here is a bug in terminals. If the user never sends
2372 * some code to stop the str or esc command, then st
2373 * will stop responding. But this is better than
2374 * silently failing with unknown characters. At least
2375 * then users will report back.
2377 * In the case users ever get fixed, here is the code:
2386 memmove(&strescseq.buf[strescseq.len], c, len);
2387 strescseq.len += len;
2393 * Actions of control codes must be performed as soon they arrive
2394 * because they can be embedded inside a control sequence, and
2395 * they must not cause conflicts with sequences.
2400 * control codes are not shown ever
2403 } else if (term.esc & ESC_START) {
2404 if (term.esc & ESC_CSI) {
2405 csiescseq.buf[csiescseq.len++] = u;
2406 if (BETWEEN(u, 0x40, 0x7E)
2407 || csiescseq.len >= \
2408 sizeof(csiescseq.buf)-1) {
2414 } else if (term.esc & ESC_UTF8) {
2416 } else if (term.esc & ESC_ALTCHARSET) {
2418 } else if (term.esc & ESC_TEST) {
2423 /* sequence already finished */
2427 * All characters which form part of a sequence are not
2432 if (sel.ob.x != -1 && BETWEEN(term.c.y, sel.ob.y, sel.oe.y))
2435 gp = &term.line[term.c.y][term.c.x];
2436 if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
2437 gp->mode |= ATTR_WRAP;
2439 gp = &term.line[term.c.y][term.c.x];
2442 if (IS_SET(MODE_INSERT) && term.c.x+width < term.col)
2443 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
2445 if (term.c.x+width > term.col) {
2447 gp = &term.line[term.c.y][term.c.x];
2450 tsetchar(u, &term.c.attr, term.c.x, term.c.y);
2453 gp->mode |= ATTR_WIDE;
2454 if (term.c.x+1 < term.col) {
2456 gp[1].mode = ATTR_WDUMMY;
2459 if (term.c.x+width < term.col) {
2460 tmoveto(term.c.x+width, term.c.y);
2462 term.c.state |= CURSOR_WRAPNEXT;
2467 twrite(const char *buf, int buflen, int show_ctrl)
2473 for (n = 0; n < buflen; n += charsize) {
2474 if (IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
2475 /* process a complete utf8 char */
2476 charsize = utf8decode(buf + n, &u, buflen - n);
2483 if (show_ctrl && ISCONTROL(u)) {
2488 } else if (u != '\n' && u != '\r' && u != '\t') {
2499 tresize(int col, int row)
2502 int minrow = MIN(row, term.row);
2503 int mincol = MIN(col, term.col);
2507 if (col < 1 || row < 1) {
2509 "tresize: error resizing to %dx%d\n", col, row);
2514 * slide screen to keep cursor where we expect it -
2515 * tscrollup would work here, but we can optimize to
2516 * memmove because we're freeing the earlier lines
2518 for (i = 0; i <= term.c.y - row; i++) {
2522 /* ensure that both src and dst are not NULL */
2524 memmove(term.line, term.line + i, row * sizeof(Line));
2525 memmove(term.alt, term.alt + i, row * sizeof(Line));
2527 for (i += row; i < term.row; i++) {
2532 /* resize to new height */
2533 term.line = xrealloc(term.line, row * sizeof(Line));
2534 term.alt = xrealloc(term.alt, row * sizeof(Line));
2535 term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
2536 term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
2538 /* resize each row to new width, zero-pad if needed */
2539 for (i = 0; i < minrow; i++) {
2540 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
2541 term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph));
2544 /* allocate any new rows */
2545 for (/* i = minrow */; i < row; i++) {
2546 term.line[i] = xmalloc(col * sizeof(Glyph));
2547 term.alt[i] = xmalloc(col * sizeof(Glyph));
2549 if (col > term.col) {
2550 bp = term.tabs + term.col;
2552 memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
2553 while (--bp > term.tabs && !*bp)
2555 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
2558 /* update terminal size */
2561 /* reset scrolling region */
2562 tsetscroll(0, row-1);
2563 /* make use of the LIMIT in tmoveto */
2564 tmoveto(term.c.x, term.c.y);
2565 /* Clearing both screens (it makes dirty all lines) */
2567 for (i = 0; i < 2; i++) {
2568 if (mincol < col && 0 < minrow) {
2569 tclearregion(mincol, 0, col - 1, minrow - 1);
2571 if (0 < col && minrow < row) {
2572 tclearregion(0, minrow, col - 1, row - 1);
2575 tcursor(CURSOR_LOAD);
2587 drawregion(int x1, int y1, int x2, int y2)
2590 for (y = y1; y < y2; y++) {
2595 xdrawline(term.line[y], x1, y, x2);
2607 /* adjust cursor position */
2608 LIMIT(term.ocx, 0, term.col-1);
2609 LIMIT(term.ocy, 0, term.row-1);
2610 if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY)
2612 if (term.line[term.c.y][cx].mode & ATTR_WDUMMY)
2615 drawregion(0, 0, term.col, term.row);
2616 xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
2617 term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
2618 term.ocx = cx, term.ocy = term.c.y;