1 /* See LICENSE for license details. */
13 #include <sys/ioctl.h>
14 #include <sys/select.h>
15 #include <sys/types.h>
26 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
28 #elif defined(__FreeBSD__) || defined(__DragonFly__)
33 #define UTF_INVALID 0xFFFD
35 #define ESC_BUF_SIZ (128*UTF_SIZ)
36 #define ESC_ARG_SIZ 16
37 #define STR_BUF_SIZ ESC_BUF_SIZ
38 #define STR_ARG_SIZ ESC_ARG_SIZ
41 #define IS_SET(flag) ((term.mode & (flag)) != 0)
42 #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f)
43 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
44 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
45 #define ISDELIM(u) (u && wcschr(worddelimiters, u))
46 static inline int max(int a, int b) { return a > b ? a : b; }
47 static inline int min(int a, int b) { return a < b ? a : b; }
52 MODE_ALTSCREEN = 1 << 2,
59 enum cursor_movement {
83 ESC_STR = 4, /* DCS, OSC, PM, APC */
85 ESC_STR_END = 16, /* a final string was encountered */
86 ESC_TEST = 32, /* Enter in test mode */
91 Glyph attr; /* current char attributes */
103 * Selection variables:
104 * nb – normalized coordinates of the beginning of the selection
105 * ne – normalized coordinates of the end of the selection
106 * ob – original coordinates of the beginning of the selection
107 * oe – original coordinates of the end of the selection
116 /* Internal representation of the screen */
118 int row; /* nb row */
119 int col; /* nb col */
120 Line *line; /* screen */
121 Line *alt; /* alternate screen */
122 int *dirty; /* dirtyness of lines */
123 TCursor c; /* cursor */
124 int ocx; /* old cursor col */
125 int ocy; /* old cursor row */
126 int top; /* top scroll limit */
127 int bot; /* bottom scroll limit */
128 int mode; /* terminal mode flags */
129 int esc; /* escape state flags */
130 char trantbl[4]; /* charset table translation */
131 int charset; /* current charset */
132 int icharset; /* selected charset for sequence */
134 Rune lastc; /* last printed char outside of sequence, 0 if control */
137 /* CSI Escape sequence structs */
138 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
140 char buf[ESC_BUF_SIZ]; /* raw string */
141 size_t len; /* raw string length */
143 int arg[ESC_ARG_SIZ];
144 int narg; /* nb of args */
148 /* STR Escape sequence structs */
149 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
151 char type; /* ESC type ... */
152 char *buf; /* allocated raw string */
153 size_t siz; /* allocation size */
154 size_t len; /* raw string length */
155 char *args[STR_ARG_SIZ];
156 int narg; /* nb of args */
159 static void execsh(char *, char **);
160 static char *getcwd_by_pid(pid_t pid);
161 static void stty(char **);
162 static void sigchld(int);
163 static void ttywriteraw(const char *, size_t);
165 static void csidump(void);
166 static void csihandle(void);
167 static void csiparse(void);
168 static void csireset(void);
169 static int eschandle(uchar);
170 static void strdump(void);
171 static void strhandle(void);
172 static void strparse(void);
173 static void strreset(void);
175 static void tprinter(char *, size_t);
176 static void tdumpsel(void);
177 static void tdumpline(int);
178 static void tdump(void);
179 static void tclearregion(int, int, int, int);
180 static void tcursor(int);
181 static void tdeletechar(int);
182 static void tdeleteline(int);
183 static void tinsertblank(int);
184 static void tinsertblankline(int);
185 static int tlinelen(int);
186 static void tmoveto(int, int);
187 static void tmoveato(int, int);
188 static void tnewline(int);
189 static void tputtab(int);
190 static void tputc(Rune);
191 static void treset(void);
192 static void tscrollup(int, int);
193 static void tscrolldown(int, int);
194 static void tsetattr(int *, int);
195 static void tsetchar(Rune, Glyph *, int, int);
196 static void tsetdirt(int, int);
197 static void tsetscroll(int, int);
198 static void tswapscreen(void);
199 static void tsetmode(int, int, int *, int);
200 static int twrite(const char *, int, int);
201 static void tfulldirt(void);
202 static void tcontrolcode(uchar );
203 static void tdectest(char );
204 static void tdefutf8(char);
205 static int32_t tdefcolor(int *, int *, int);
206 static void tdeftran(char);
207 static void tstrsequence(uchar);
209 static void drawregion(int, int, int, int);
211 static void selscroll(int, int);
212 static void selnormalize(void);
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 size_t utf8validate(Rune *, size_t);
219 static char *base64dec(const char *);
220 static char base64dec_getc(const char **);
222 static ssize_t xwrite(int, const char *, size_t);
226 static Selection sel;
227 static CSIEscape csiescseq;
228 static STREscape strescseq;
233 static uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
234 static uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
235 static Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000};
236 static Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
239 extern int const buffSize;
240 int histOp, histMode, histOff, histOffX, insertOff, altToggle, *mark;
243 static inline int rows() { return IS_SET(MODE_ALTSCREEN) ? term.row : buffSize;}
244 static inline int rangeY(int i) { while (i < 0) i += rows(); return i % rows();}
247 xwrite(int fd, const char *s, size_t len)
253 r = write(fd, s, len);
268 if (!(p = malloc(len)))
269 die("malloc: %s\n", strerror(errno));
275 xrealloc(void *p, size_t len)
277 if ((p = realloc(p, len)) == NULL)
278 die("realloc: %s\n", strerror(errno));
286 if ((s = strdup(s)) == NULL)
287 die("strdup: %s\n", strerror(errno));
293 utf8decode(const char *c, Rune *u, size_t clen)
295 size_t i, j, len, type;
301 udecoded = utf8decodebyte(c[0], &len);
302 if (!BETWEEN(len, 1, UTF_SIZ))
304 for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
305 udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
312 utf8validate(u, len);
318 utf8decodebyte(char c, size_t *i)
320 for (*i = 0; *i < LEN(utfmask); ++(*i))
321 if (((uchar)c & utfmask[*i]) == utfbyte[*i])
322 return (uchar)c & ~utfmask[*i];
328 utf8encode(Rune u, char *c)
332 len = utf8validate(&u, 0);
336 for (i = len - 1; i != 0; --i) {
337 c[i] = utf8encodebyte(u, 0);
340 c[0] = utf8encodebyte(u, len);
346 utf8encodebyte(Rune u, size_t i)
348 return utfbyte[i] | (u & ~utfmask[i]);
352 utf8validate(Rune *u, size_t i)
354 if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF))
356 for (i = 1; *u > utfmax[i]; ++i)
362 static const char base64_digits[] = {
363 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
364 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0,
365 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
366 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
367 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
368 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0,
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, 0, 0, 0, 0, 0,
371 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
372 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
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
378 base64dec_getc(const char **src)
380 while (**src && !isprint(**src))
382 return **src ? *((*src)++) : '='; /* emulate padding if string ends */
386 base64dec(const char *src)
388 size_t in_len = strlen(src);
392 in_len += 4 - (in_len % 4);
393 result = dst = xmalloc(in_len / 4 * 3 + 1);
395 int a = base64_digits[(unsigned char) base64dec_getc(&src)];
396 int b = base64_digits[(unsigned char) base64dec_getc(&src)];
397 int c = base64_digits[(unsigned char) base64dec_getc(&src)];
398 int d = base64_digits[(unsigned char) base64dec_getc(&src)];
400 /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */
401 if (a == -1 || b == -1)
404 *dst++ = (a << 2) | ((b & 0x30) >> 4);
407 *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2);
410 *dst++ = ((c & 0x03) << 6) | d;
429 if (term.line[y][i - 1].mode & ATTR_WRAP)
432 while (i > 0 && term.line[y][i - 1].u == ' ')
438 void historyOpToggle(int start, int paint) {
439 if (!histOp == !(histOp + start)) if ((histOp += start) || 1) return;
440 if (histMode && paint && (!IS_SET(MODE_ALTSCREEN) || altToggle)) draw();
441 tcursor(CURSOR_SAVE);
443 if (histMode && altToggle) {
445 memset(term.dirty,0,sizeof(*term.dirty)*term.row);
447 tcursor(CURSOR_LOAD);
448 *(!IS_SET(MODE_ALTSCREEN)?&term.line:&term.alt)=&buf[histOp?histOff:insertOff];
451 void historyModeToggle(int start) {
452 if (!(histMode = (histOp = !!start))) {
456 tcursor(CURSOR_SAVE);
462 int historyBufferScroll(int n) {
463 if (IS_SET(MODE_ALTSCREEN) || !n) return histOp;
464 int p=abs(n=(n<0) ? max(n,-term.row) : min(n,term.row)), r=term.row-p,
465 s=sizeof(*term.dirty), *ptr=histOp?&histOff:&insertOff;
466 if (!histMode || histOp) tfulldirt(); else {
467 memmove(&term.dirty[-min(n,0)], &term.dirty[max(n,0)], s*r);
468 memset(&term.dirty[n>0 ? r : 0], 0, s * p);
470 int const prevOffBuf = sel.alt ? 0 : insertOff + term.row;
471 term.line = &buf[*ptr = (buffSize+*ptr+n) % buffSize];
472 // Cut part of selection removed from buffer, and update sel.ne/b.
473 if (sel.ob.x != -1 && !histOp && n) {
474 int const offBuf = sel.alt ? 0 : insertOff + term.row,
475 pb = rangeY(sel.ob.y - prevOffBuf),
476 pe = rangeY(sel.oe.y - prevOffBuf);
477 int const b = rangeY(sel.ob.y - offBuf), nln = n < 0,
478 e = rangeY(sel.oe.y - offBuf), last = offBuf - nln;
479 if (pb != b && ((pb < b) != nln)) sel.ob.y = last;
480 if (pe != e && ((pe < e) != nln)) sel.oe.y = last;
481 if (sel.oe.y == last && sel.ob.y == last) selclear();
484 // Clear the new region exposed by the shift.
485 if (!histOp) tclearregion(0, n>0?r+1:0, buffCols-1, n>0?term.row:p-1);
489 int historyMove(int x, int y, int ly) {
490 historyOpToggle(1, 1);
491 y += ((term.c.x += x) < 0 ?term.c.x-term.col :term.c.x) / term.col;//< x
492 if ((term.c.x %= term.col) < 0) term.c.x += term.col;
493 if ((term.c.y += y) >= term.row) ly += term.c.y - term.row + 1; //< y
494 else if (term.c.y < 0) ly += term.c.y;
495 term.c.y = MIN(MAX(term.c.y, 0), term.row - 1);
496 int off=insertOff-histOff, bot=rangeY(off), top=-rangeY(-term.row-off),
497 pTop = (-ly>-top), pBot = (ly > bot), fin=histMode&&(pTop||pBot);
498 if (fin && (x||y)) term.c.x = pBot ? term.col-1 : 0;
499 historyBufferScroll(fin ? (pBot ? bot : top) : ly);
500 historyOpToggle(-1, 1);
504 #include "normalMode.c"
506 void selnormalize(void) {
507 historyOpToggle(1, 1);
509 int const oldb = sel.nb.y, olde = sel.ne.y;
510 if (sel.ob.x == -1) {
511 sel.ne.y = sel.nb.y = -1;
513 int const offsetBuffer = sel.alt ? 0 : insertOff + term.row;
514 int const off = sel.alt ? 0 : (histMode ? histOff : insertOff);
515 int const nby = rangeY(sel.ob.y - off),
516 ney = rangeY(sel.oe.y - off);
517 sel.swap = rangeY(sel.ob.y - offsetBuffer)
518 > rangeY(sel.oe.y - offsetBuffer);
519 sel.nb.y = sel.swap ? ney : nby;
520 sel.ne.y = !sel.swap ? ney : nby;
521 int const cnb = sel.nb.y < term.row, cne = sel.ne.y < term.row;
522 if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) {
523 if (cnb) sel.nb.x = (!sel.swap) ? sel.ob.x : sel.oe.x;
524 if (cne) sel.ne.x = (!sel.swap) ? sel.oe.x : sel.ob.x;
526 if (cnb) sel.nb.x = MIN(sel.ob.x, sel.oe.x);
527 if (cne) sel.ne.x = MAX(sel.ob.x, sel.oe.x);
530 int const nBet=sel.nb.y<=sel.ne.y, oBet=oldb<=olde;
531 for (int i = 0; i < term.row; ++i) {
532 int const n = nBet ? BETWEEN(i, sel.nb.y, sel.ne.y)
533 : OUT(i, sel.nb.y, sel.ne.y);
534 term.dirty[i] |= (sel.type == SEL_RECTANGULAR && n) ||
535 (n != (oBet ? BETWEEN(i,oldb,olde) : OUT(i,oldb,olde)));
538 if (BETWEEN(oldb, 0, term.row - 1)) term.dirty[oldb] = 1;
539 if (BETWEEN(olde, 0, term.row - 1)) term.dirty[olde] = 1;
540 if (BETWEEN(sel.nb.y, 0, term.row - 1)) term.dirty[sel.nb.y] = 1;
541 if (BETWEEN(sel.ne.y, 0, term.row - 1)) term.dirty[sel.ne.y] = 1;
543 historyOpToggle(-1, 1);
547 selstart(int col, int row, int snap)
550 sel.mode = SEL_EMPTY;
551 sel.type = SEL_REGULAR;
552 sel.alt = IS_SET(MODE_ALTSCREEN);
554 sel.oe.x = sel.ob.x = col;
555 sel.oe.y = sel.ob.y = row + !sel.alt * (histMode ? histOff : insertOff);
556 if (sel.snap != 0) sel.mode = SEL_READY;
561 selextend(int col, int row, int type, int done)
563 if (sel.mode == SEL_IDLE)
565 if (done && sel.mode == SEL_EMPTY) {
571 sel.oe.y = row + (sel.alt ? 0 : (histMode ? histOff : insertOff));
574 sel.mode = done ? SEL_IDLE : SEL_READY;
578 selected(int x, int y)
580 if (sel.mode == SEL_EMPTY || sel.ob.x == -1 ||
581 sel.alt != IS_SET(MODE_ALTSCREEN))
584 if (sel.type == SEL_RECTANGULAR)
585 return BETWEEN(y, sel.nb.y, sel.ne.y)
586 && BETWEEN(x, sel.nb.x, sel.ne.x);
588 return ((sel.nb.y > sel.ne.y) ? OUT(y, sel.nb.y, sel.ne.y)
589 : BETWEEN(y, sel.nb.y, sel.ne.y)) &&
590 (y != sel.nb.y || x >= sel.nb.x) &&
591 (y != sel.ne.y || x <= sel.ne.x);
598 int y, yy, bufsize, lastx;
604 int const start = sel.swap ? sel.oe.y : sel.ob.y, h = rows();
605 int endy = (sel.swap ? sel.ob.y : sel.oe.y);
606 for (; endy < start; endy += h);
607 Line * const cbuf = IS_SET(MODE_ALTSCREEN) ? term.line : buf;
608 bufsize = (term.col+1) * (endy-start+1 ) * UTF_SIZ;
610 ptr = str = xmalloc(bufsize);
612 /* append every set & selected glyph to the selection */
613 for (y = start; y <= endy; y++) {
616 if (sel.type == SEL_RECTANGULAR) {
617 gp = &cbuf[yy][sel.nb.x];
620 gp = &cbuf[yy][start == y ? sel.nb.x : 0];
621 lastx = (endy == y) ? sel.ne.x : term.col-1;
623 last = &cbuf[yy][lastx];
624 if (!(cbuf[yy][term.col - 1].mode & ATTR_WRAP))
625 while (last > gp && last->u == ' ') --last;
627 for ( ; gp <= last; ++gp) {
628 if (gp->mode & ATTR_WDUMMY) continue;
629 ptr += utf8encode(gp->u, ptr);
633 * Copy and pasting of line endings is inconsistent
634 * in the inconsistent terminal and GUI world.
635 * The best solution seems like to produce '\n' when
636 * something is copied from st and convert '\n' to
637 * '\r', when something to be pasted is received by
639 * FIXME: Fix the computer world.
641 if ((y < endy || lastx >= term.col - 1) &&
642 (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR))
660 die(const char *errstr, ...)
664 va_start(ap, errstr);
665 vfprintf(stderr, errstr, ap);
671 execsh(char *cmd, char **args)
673 char *sh, *prog, *arg;
674 const struct passwd *pw;
677 if ((pw = getpwuid(getuid())) == NULL) {
679 die("getpwuid: %s\n", strerror(errno));
681 die("who are you?\n");
684 if ((sh = getenv("SHELL")) == NULL)
685 sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd;
692 arg = utmp ? utmp : sh;
700 DEFAULT(args, ((char *[]) {prog, arg, 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 exited with status %d\n", WEXITSTATUS(stat));
736 else if (WIFSIGNALED(stat))
737 die("child terminated due to signal %d\n", WTERMSIG(stat));
744 char cmd[_POSIX_ARG_MAX], **p, *q, *s;
747 if ((n = strlen(stty_args)) > sizeof(cmd)-1)
748 die("incorrect stty parameters\n");
749 memcpy(cmd, stty_args, n);
751 siz = sizeof(cmd) - n;
752 for (p = args; p && (s = *p); ++p) {
753 if ((n = strlen(s)) > siz-1)
754 die("stty parameter length too long\n");
761 if (system(cmd) != 0)
762 perror("Couldn't call stty");
766 ttynew(char *line, char *cmd, char *out, char **args)
771 term.mode |= MODE_PRINT;
772 iofd = (!strcmp(out, "-")) ?
773 1 : open(out, O_WRONLY | O_CREAT, 0666);
775 fprintf(stderr, "Error opening %s:%s\n",
776 out, strerror(errno));
781 if ((cmdfd = open(line, O_RDWR)) < 0)
782 die("open line '%s' failed: %s\n",
783 line, strerror(errno));
789 /* seems to work fine on linux, openbsd and freebsd */
790 if (openpty(&m, &s, NULL, NULL, NULL) < 0)
791 die("openpty failed: %s\n", strerror(errno));
793 switch (pid = fork()) {
795 die("fork failed: %s\n", strerror(errno));
799 setsid(); /* create a new process group */
803 if (ioctl(s, TIOCSCTTY, NULL) < 0)
804 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno));
808 if (pledge("stdio getpw proc exec", NULL) == -1)
815 if (pledge("stdio rpath tty proc", NULL) == -1)
820 signal(SIGCHLD, sigchld);
829 static char buf[BUFSIZ];
830 static int buflen = 0;
833 /* append read bytes to unprocessed bytes */
834 ret = read(cmdfd, buf+buflen, LEN(buf)-buflen);
840 die("couldn't read from shell: %s\n", strerror(errno));
843 written = twrite(buf, buflen, 0);
845 /* keep any incomplete UTF-8 byte sequence for the next call */
847 memmove(buf, buf + written, buflen);
853 ttywrite(const char *s, size_t n, int may_echo)
857 if (may_echo && IS_SET(MODE_ECHO))
860 if (!IS_SET(MODE_CRLF)) {
865 /* This is similar to how the kernel handles ONLCR for ttys */
869 ttywriteraw("\r\n", 2);
871 next = memchr(s, '\r', n);
872 DEFAULT(next, s + n);
873 ttywriteraw(s, next - s);
881 ttywriteraw(const char *s, size_t n)
888 * Remember that we are using a pty, which might be a modem line.
889 * Writing too much will clog the line. That's why we are doing this
891 * FIXME: Migrate the world to Plan 9.
899 /* Check if we can write. */
900 if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) {
903 die("select failed: %s\n", strerror(errno));
905 if (FD_ISSET(cmdfd, &wfd)) {
907 * Only write the bytes written by ttywrite() or the
908 * default of 256. This seems to be a reasonable value
909 * for a serial line. Bigger values might clog the I/O.
911 if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0)
915 * We weren't able to write out everything.
916 * This means the buffer is getting full
924 /* All bytes have been written. */
928 if (FD_ISSET(cmdfd, &rfd))
934 die("write error on tty: %s\n", strerror(errno));
938 ttyresize(int tw, int th)
946 if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
947 fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
953 /* Send SIGHUP to shell */
962 for (i = 0; i < term.row-1; i++) {
963 for (j = 0; j < term.col-1; j++) {
964 if (term.line[i][j].mode & attr)
973 tsetdirt(int top, int bot)
977 LIMIT(top, 0, term.row-1);
978 LIMIT(bot, 0, term.row-1);
980 for (i = top; i <= bot; i++)
985 tsetdirtattr(int attr)
989 for (i = 0; i < term.row-1; i++) {
990 for (j = 0; j < term.col-1; j++) {
991 if (term.line[i][j].mode & attr) {
1002 tsetdirt(0, term.row-1);
1008 int alt = (histOp) ? 0 : (IS_SET(MODE_ALTSCREEN) + 1);
1010 if (mode == CURSOR_SAVE) {
1012 } else if (mode == CURSOR_LOAD) {
1014 tmoveto(c[alt].x, c[alt].y);
1023 term.c = (TCursor){{
1027 }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
1029 memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1030 for (i = tabspaces; i < term.col; i += tabspaces)
1033 term.bot = term.row - 1;
1034 term.mode = MODE_WRAP|MODE_UTF8;
1035 memset(term.trantbl, CS_USA, sizeof(term.trantbl));
1038 for (i = 0; i < 2; i++) {
1040 tcursor(CURSOR_SAVE);
1041 tclearregion(0, 0, term.col-1, term.row-1);
1047 tnew(int col, int row)
1049 term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
1057 Line *tmp = term.line;
1059 term.line = term.alt;
1061 term.mode ^= MODE_ALTSCREEN;
1066 newterm(const Arg* a)
1070 die("fork failed: %s\n", strerror(errno));
1073 chdir(getcwd_by_pid(pid));
1074 execlp("st", "./st", NULL);
1079 static char *getcwd_by_pid(pid_t pid) {
1081 snprintf(buf, sizeof buf, "/proc/%d/cwd", pid);
1082 return realpath(buf, NULL);
1086 tscrolldown(int orig, int n)
1088 if (historyBufferScroll(-n)) return;
1092 LIMIT(n, 0, term.bot-orig+1);
1094 tsetdirt(orig, term.bot-n);
1095 tclearregion(0, term.bot-n+1, term.col-1, term.bot);
1097 for (i = term.bot; i >= orig+n; i--) {
1098 temp = term.line[i];
1099 term.line[i] = term.line[i-n];
1100 term.line[i-n] = temp;
1107 tscrollup(int orig, int n)
1109 if (historyBufferScroll(n)) return;
1113 LIMIT(n, 0, term.bot-orig+1);
1115 tclearregion(0, orig, term.col-1, orig+n-1);
1116 tsetdirt(orig+n, term.bot);
1118 for (i = orig; i <= term.bot-n; i++) {
1119 temp = term.line[i];
1120 term.line[i] = term.line[i+n];
1121 term.line[i+n] = temp;
1124 selscroll(orig, -n);
1128 selscroll(int orig, int n)
1133 if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) {
1135 } else if (BETWEEN(sel.nb.y, orig, term.bot)) {
1138 if (sel.ob.y < term.top || sel.ob.y > term.bot ||
1139 sel.oe.y < term.top || sel.oe.y > term.bot) {
1148 tnewline(int first_col)
1152 if (y == term.bot) {
1153 tscrollup(term.top, 1);
1157 tmoveto(first_col ? 0 : term.c.x, y);
1163 char *p = csiescseq.buf, *np;
1172 csiescseq.buf[csiescseq.len] = '\0';
1173 while (p < csiescseq.buf+csiescseq.len) {
1175 v = strtol(p, &np, 10);
1178 if (v == LONG_MAX || v == LONG_MIN)
1180 csiescseq.arg[csiescseq.narg++] = v;
1182 if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ)
1186 csiescseq.mode[0] = *p++;
1187 csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0';
1190 /* for absolute user moves, when decom is set */
1192 tmoveato(int x, int y)
1194 tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0));
1198 tmoveto(int x, int y)
1202 if (term.c.state & CURSOR_ORIGIN) {
1207 maxy = term.row - 1;
1209 term.c.state &= ~CURSOR_WRAPNEXT;
1210 term.c.x = LIMIT(x, 0, term.col-1);
1211 term.c.y = LIMIT(y, miny, maxy);
1215 tsetchar(Rune u, Glyph *attr, int x, int y)
1217 static char *vt100_0[62] = { /* 0x41 - 0x7e */
1218 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1219 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1220 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1221 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1222 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1223 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1224 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1225 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1229 * The table is proudly stolen from rxvt.
1231 if (term.trantbl[term.charset] == CS_GRAPHIC0 &&
1232 BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
1233 utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ);
1235 if (term.line[y][x].mode & ATTR_WIDE) {
1236 if (x+1 < term.col) {
1237 term.line[y][x+1].u = ' ';
1238 term.line[y][x+1].mode &= ~ATTR_WDUMMY;
1240 } else if (term.line[y][x].mode & ATTR_WDUMMY) {
1241 term.line[y][x-1].u = ' ';
1242 term.line[y][x-1].mode &= ~ATTR_WIDE;
1246 term.line[y][x] = *attr;
1247 term.line[y][x].u = u;
1251 tclearregion(int x1, int y1, int x2, int y2)
1257 temp = x1, x1 = x2, x2 = temp;
1259 temp = y1, y1 = y2, y2 = temp;
1261 LIMIT(x1, 0, buffCols-1);
1262 LIMIT(x2, 0, buffCols-1);
1263 LIMIT(y1, 0, term.row-1);
1264 LIMIT(y2, 0, term.row-1);
1266 for (y = y1; y <= y2; y++) {
1268 for (x = x1; x <= x2; x++) {
1269 gp = &term.line[y][x];
1270 gp->fg = term.c.attr.fg;
1271 gp->bg = term.c.attr.bg;
1284 LIMIT(n, 0, term.col - term.c.x);
1288 size = term.col - src;
1289 line = term.line[term.c.y];
1291 memmove(&line[dst], &line[src], size * sizeof(Glyph));
1292 tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
1301 LIMIT(n, 0, term.col - term.c.x);
1305 size = term.col - dst;
1306 line = term.line[term.c.y];
1308 memmove(&line[dst], &line[src], size * sizeof(Glyph));
1309 tclearregion(src, term.c.y, dst - 1, term.c.y);
1313 tinsertblankline(int n)
1315 if (BETWEEN(term.c.y, term.top, term.bot))
1316 tscrolldown(term.c.y, n);
1322 if (BETWEEN(term.c.y, term.top, term.bot))
1323 tscrollup(term.c.y, n);
1327 tdefcolor(int *attr, int *npar, int l)
1332 switch (attr[*npar + 1]) {
1333 case 2: /* direct color in RGB space */
1334 if (*npar + 4 >= l) {
1336 "erresc(38): Incorrect number of parameters (%d)\n",
1340 r = attr[*npar + 2];
1341 g = attr[*npar + 3];
1342 b = attr[*npar + 4];
1344 if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255))
1345 fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n",
1348 idx = TRUECOLOR(r, g, b);
1350 case 5: /* indexed color */
1351 if (*npar + 2 >= l) {
1353 "erresc(38): Incorrect number of parameters (%d)\n",
1358 if (!BETWEEN(attr[*npar], 0, 255))
1359 fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]);
1363 case 0: /* implemented defined (only foreground) */
1364 case 1: /* transparent */
1365 case 3: /* direct color in CMY space */
1366 case 4: /* direct color in CMYK space */
1369 "erresc(38): gfx attr %d unknown\n", attr[*npar]);
1377 tsetattr(int *attr, int l)
1382 for (i = 0; i < l; i++) {
1385 term.c.attr.mode &= ~(
1394 term.c.attr.fg = defaultfg;
1395 term.c.attr.bg = defaultbg;
1398 term.c.attr.mode |= ATTR_BOLD;
1401 term.c.attr.mode |= ATTR_FAINT;
1404 term.c.attr.mode |= ATTR_ITALIC;
1407 term.c.attr.mode |= ATTR_UNDERLINE;
1409 case 5: /* slow blink */
1411 case 6: /* rapid blink */
1412 term.c.attr.mode |= ATTR_BLINK;
1415 term.c.attr.mode |= ATTR_REVERSE;
1418 term.c.attr.mode |= ATTR_INVISIBLE;
1421 term.c.attr.mode |= ATTR_STRUCK;
1424 term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT);
1427 term.c.attr.mode &= ~ATTR_ITALIC;
1430 term.c.attr.mode &= ~ATTR_UNDERLINE;
1433 term.c.attr.mode &= ~ATTR_BLINK;
1436 term.c.attr.mode &= ~ATTR_REVERSE;
1439 term.c.attr.mode &= ~ATTR_INVISIBLE;
1442 term.c.attr.mode &= ~ATTR_STRUCK;
1445 if ((idx = tdefcolor(attr, &i, l)) >= 0)
1446 term.c.attr.fg = idx;
1449 term.c.attr.fg = defaultfg;
1452 if ((idx = tdefcolor(attr, &i, l)) >= 0)
1453 term.c.attr.bg = idx;
1456 term.c.attr.bg = defaultbg;
1459 if (BETWEEN(attr[i], 30, 37)) {
1460 term.c.attr.fg = attr[i] - 30;
1461 } else if (BETWEEN(attr[i], 40, 47)) {
1462 term.c.attr.bg = attr[i] - 40;
1463 } else if (BETWEEN(attr[i], 90, 97)) {
1464 term.c.attr.fg = attr[i] - 90 + 8;
1465 } else if (BETWEEN(attr[i], 100, 107)) {
1466 term.c.attr.bg = attr[i] - 100 + 8;
1469 "erresc(default): gfx attr %d unknown\n",
1479 tsetscroll(int t, int b)
1483 LIMIT(t, 0, term.row-1);
1484 LIMIT(b, 0, term.row-1);
1495 tsetmode(int priv, int set, int *args, int narg)
1499 for (lim = args + narg; args < lim; ++args) {
1502 case 1: /* DECCKM -- Cursor key */
1503 xsetmode(set, MODE_APPCURSOR);
1505 case 5: /* DECSCNM -- Reverse video */
1506 xsetmode(set, MODE_REVERSE);
1508 case 6: /* DECOM -- Origin */
1509 MODBIT(term.c.state, set, CURSOR_ORIGIN);
1512 case 7: /* DECAWM -- Auto wrap */
1513 MODBIT(term.mode, set, MODE_WRAP);
1515 case 0: /* Error (IGNORED) */
1516 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1517 case 3: /* DECCOLM -- Column (IGNORED) */
1518 case 4: /* DECSCLM -- Scroll (IGNORED) */
1519 case 8: /* DECARM -- Auto repeat (IGNORED) */
1520 case 18: /* DECPFF -- Printer feed (IGNORED) */
1521 case 19: /* DECPEX -- Printer extent (IGNORED) */
1522 case 42: /* DECNRCM -- National characters (IGNORED) */
1523 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1525 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1526 xsetmode(!set, MODE_HIDE);
1528 case 9: /* X10 mouse compatibility mode */
1529 xsetpointermotion(0);
1530 xsetmode(0, MODE_MOUSE);
1531 xsetmode(set, MODE_MOUSEX10);
1533 case 1000: /* 1000: report button press */
1534 xsetpointermotion(0);
1535 xsetmode(0, MODE_MOUSE);
1536 xsetmode(set, MODE_MOUSEBTN);
1538 case 1002: /* 1002: report motion on button press */
1539 xsetpointermotion(0);
1540 xsetmode(0, MODE_MOUSE);
1541 xsetmode(set, MODE_MOUSEMOTION);
1543 case 1003: /* 1003: enable all mouse motions */
1544 xsetpointermotion(set);
1545 xsetmode(0, MODE_MOUSE);
1546 xsetmode(set, MODE_MOUSEMANY);
1548 case 1004: /* 1004: send focus events to tty */
1549 xsetmode(set, MODE_FOCUS);
1551 case 1006: /* 1006: extended reporting mode */
1552 xsetmode(set, MODE_MOUSESGR);
1555 xsetmode(set, MODE_8BIT);
1557 case 1049: /* swap screen & set/restore cursor as xterm */
1558 if (!allowaltscreen)
1560 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1562 case 47: /* swap screen */
1564 if (!allowaltscreen)
1566 alt = IS_SET(MODE_ALTSCREEN);
1568 tclearregion(0, 0, term.col-1,
1571 if (set ^ alt) /* set is always 1 or 0 */
1577 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1579 case 2004: /* 2004: bracketed paste mode */
1580 xsetmode(set, MODE_BRCKTPASTE);
1582 /* Not implemented mouse modes. See comments there. */
1583 case 1001: /* mouse highlight mode; can hang the
1584 terminal by design when implemented. */
1585 case 1005: /* UTF-8 mouse mode; will confuse
1586 applications not supporting UTF-8
1588 case 1015: /* urxvt mangled mouse mode; incompatible
1589 and can be mistaken for other control
1594 "erresc: unknown private set/reset mode %d\n",
1600 case 0: /* Error (IGNORED) */
1603 xsetmode(set, MODE_KBDLOCK);
1605 case 4: /* IRM -- Insertion-replacement */
1606 MODBIT(term.mode, set, MODE_INSERT);
1608 case 12: /* SRM -- Send/Receive */
1609 MODBIT(term.mode, !set, MODE_ECHO);
1611 case 20: /* LNM -- Linefeed/new line */
1612 MODBIT(term.mode, set, MODE_CRLF);
1616 "erresc: unknown set/reset mode %d\n",
1630 switch (csiescseq.mode[0]) {
1633 fprintf(stderr, "erresc: unknown csi ");
1637 case '@': /* ICH -- Insert <n> blank char */
1638 DEFAULT(csiescseq.arg[0], 1);
1639 tinsertblank(csiescseq.arg[0]);
1641 case 'A': /* CUU -- Cursor <n> Up */
1642 DEFAULT(csiescseq.arg[0], 1);
1643 tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
1645 case 'B': /* CUD -- Cursor <n> Down */
1646 case 'e': /* VPR --Cursor <n> Down */
1647 DEFAULT(csiescseq.arg[0], 1);
1648 tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
1650 case 'i': /* MC -- Media Copy */
1651 switch (csiescseq.arg[0]) {
1656 tdumpline(term.c.y);
1662 term.mode &= ~MODE_PRINT;
1665 term.mode |= MODE_PRINT;
1669 case 'c': /* DA -- Device Attributes */
1670 if (csiescseq.arg[0] == 0)
1671 ttywrite(vtiden, strlen(vtiden), 0);
1673 case 'b': /* REP -- if last char is printable print it <n> more times */
1674 DEFAULT(csiescseq.arg[0], 1);
1676 while (csiescseq.arg[0]-- > 0)
1679 case 'C': /* CUF -- Cursor <n> Forward */
1680 case 'a': /* HPR -- Cursor <n> Forward */
1681 DEFAULT(csiescseq.arg[0], 1);
1682 tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
1684 case 'D': /* CUB -- Cursor <n> Backward */
1685 DEFAULT(csiescseq.arg[0], 1);
1686 tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
1688 case 'E': /* CNL -- Cursor <n> Down and first col */
1689 DEFAULT(csiescseq.arg[0], 1);
1690 tmoveto(0, term.c.y+csiescseq.arg[0]);
1692 case 'F': /* CPL -- Cursor <n> Up and first col */
1693 DEFAULT(csiescseq.arg[0], 1);
1694 tmoveto(0, term.c.y-csiescseq.arg[0]);
1696 case 'g': /* TBC -- Tabulation clear */
1697 switch (csiescseq.arg[0]) {
1698 case 0: /* clear current tab stop */
1699 term.tabs[term.c.x] = 0;
1701 case 3: /* clear all the tabs */
1702 memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1708 case 'G': /* CHA -- Move to <col> */
1710 DEFAULT(csiescseq.arg[0], 1);
1711 tmoveto(csiescseq.arg[0]-1, term.c.y);
1713 case 'H': /* CUP -- Move to <row> <col> */
1715 DEFAULT(csiescseq.arg[0], 1);
1716 DEFAULT(csiescseq.arg[1], 1);
1717 tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
1719 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1720 DEFAULT(csiescseq.arg[0], 1);
1721 tputtab(csiescseq.arg[0]);
1723 case 'J': /* ED -- Clear screen */
1724 switch (csiescseq.arg[0]) {
1726 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
1727 if (term.c.y < term.row-1) {
1728 tclearregion(0, term.c.y+1, term.col-1,
1734 tclearregion(0, 0, term.col-1, term.c.y-1);
1735 tclearregion(0, term.c.y, term.c.x, term.c.y);
1738 tclearregion(0, 0, term.col-1, term.row-1);
1744 case 'K': /* EL -- Clear line */
1745 switch (csiescseq.arg[0]) {
1747 tclearregion(term.c.x, term.c.y, term.col-1,
1751 tclearregion(0, term.c.y, term.c.x, term.c.y);
1754 tclearregion(0, term.c.y, term.col-1, term.c.y);
1758 case 'S': /* SU -- Scroll <n> line up */
1759 DEFAULT(csiescseq.arg[0], 1);
1760 tscrollup(term.top, csiescseq.arg[0]);
1762 case 'T': /* SD -- Scroll <n> line down */
1763 DEFAULT(csiescseq.arg[0], 1);
1764 tscrolldown(term.top, csiescseq.arg[0]);
1766 case 'L': /* IL -- Insert <n> blank lines */
1767 DEFAULT(csiescseq.arg[0], 1);
1768 tinsertblankline(csiescseq.arg[0]);
1770 case 'l': /* RM -- Reset Mode */
1771 tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
1773 case 'M': /* DL -- Delete <n> lines */
1774 DEFAULT(csiescseq.arg[0], 1);
1775 tdeleteline(csiescseq.arg[0]);
1777 case 'X': /* ECH -- Erase <n> char */
1778 DEFAULT(csiescseq.arg[0], 1);
1779 tclearregion(term.c.x, term.c.y,
1780 term.c.x + csiescseq.arg[0] - 1, term.c.y);
1782 case 'P': /* DCH -- Delete <n> char */
1783 DEFAULT(csiescseq.arg[0], 1);
1784 tdeletechar(csiescseq.arg[0]);
1786 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1787 DEFAULT(csiescseq.arg[0], 1);
1788 tputtab(-csiescseq.arg[0]);
1790 case 'd': /* VPA -- Move to <row> */
1791 DEFAULT(csiescseq.arg[0], 1);
1792 tmoveato(term.c.x, csiescseq.arg[0]-1);
1794 case 'h': /* SM -- Set terminal mode */
1795 tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
1797 case 'm': /* SGR -- Terminal attribute (color) */
1798 tsetattr(csiescseq.arg, csiescseq.narg);
1800 case 'n': /* DSR – Device Status Report (cursor position) */
1801 if (csiescseq.arg[0] == 6) {
1802 len = snprintf(buf, sizeof(buf), "\033[%i;%iR",
1803 term.c.y+1, term.c.x+1);
1804 ttywrite(buf, len, 0);
1807 case 'r': /* DECSTBM -- Set Scrolling Region */
1808 if (csiescseq.priv) {
1811 DEFAULT(csiescseq.arg[0], 1);
1812 DEFAULT(csiescseq.arg[1], term.row);
1813 tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
1817 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1818 tcursor(CURSOR_SAVE);
1820 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1821 tcursor(CURSOR_LOAD);
1824 switch (csiescseq.mode[1]) {
1825 case 'q': /* DECSCUSR -- Set Cursor Style */
1826 if (xsetcursor(csiescseq.arg[0]))
1842 fprintf(stderr, "ESC[");
1843 for (i = 0; i < csiescseq.len; i++) {
1844 c = csiescseq.buf[i] & 0xff;
1847 } else if (c == '\n') {
1848 fprintf(stderr, "(\\n)");
1849 } else if (c == '\r') {
1850 fprintf(stderr, "(\\r)");
1851 } else if (c == 0x1b) {
1852 fprintf(stderr, "(\\e)");
1854 fprintf(stderr, "(%02x)", c);
1863 memset(&csiescseq, 0, sizeof(csiescseq));
1869 char *p = NULL, *dec;
1872 term.esc &= ~(ESC_STR_END|ESC_STR);
1874 par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
1876 switch (strescseq.type) {
1877 case ']': /* OSC -- Operating System Command */
1883 xsettitle(strescseq.args[1]);
1886 if (narg > 2 && allowwindowops) {
1887 dec = base64dec(strescseq.args[2]);
1892 fprintf(stderr, "erresc: invalid base64\n");
1896 case 4: /* color set */
1899 p = strescseq.args[2];
1901 case 104: /* color reset, here p = NULL */
1902 j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
1903 if (xsetcolorname(j, p)) {
1904 if (par == 104 && narg <= 1)
1905 return; /* color reset without parameter */
1906 fprintf(stderr, "erresc: invalid color j=%d, p=%s\n",
1907 j, p ? p : "(null)");
1910 * TODO if defaultbg color is changed, borders
1918 case 'k': /* old title set compatibility */
1919 xsettitle(strescseq.args[0]);
1921 case 'P': /* DCS -- Device Control String */
1922 case '_': /* APC -- Application Program Command */
1923 case '^': /* PM -- Privacy Message */
1927 fprintf(stderr, "erresc: unknown str ");
1935 char *p = strescseq.buf;
1938 strescseq.buf[strescseq.len] = '\0';
1943 while (strescseq.narg < STR_ARG_SIZ) {
1944 strescseq.args[strescseq.narg++] = p;
1945 while ((c = *p) != ';' && c != '\0')
1959 fprintf(stderr, "ESC%c", strescseq.type);
1960 for (i = 0; i < strescseq.len; i++) {
1961 c = strescseq.buf[i] & 0xff;
1965 } else if (isprint(c)) {
1967 } else if (c == '\n') {
1968 fprintf(stderr, "(\\n)");
1969 } else if (c == '\r') {
1970 fprintf(stderr, "(\\r)");
1971 } else if (c == 0x1b) {
1972 fprintf(stderr, "(\\e)");
1974 fprintf(stderr, "(%02x)", c);
1977 fprintf(stderr, "ESC\\\n");
1983 strescseq = (STREscape){
1984 .buf = xrealloc(strescseq.buf, STR_BUF_SIZ),
1990 sendbreak(const Arg *arg)
1992 if (tcsendbreak(cmdfd, 0))
1993 perror("Error sending break");
1997 tprinter(char *s, size_t len)
1999 if (iofd != -1 && xwrite(iofd, s, len) < 0) {
2000 perror("Error writing to output file");
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)
2116 case 0x90: /* DCS -- Device Control String */
2119 case 0x9f: /* APC -- Application Program Command */
2122 case 0x9e: /* PM -- Privacy Message */
2125 case 0x9d: /* OSC -- Operating System Command */
2131 term.esc |= ESC_STR;
2135 tcontrolcode(uchar ascii)
2142 tmoveto(term.c.x-1, term.c.y);
2145 tmoveto(0, term.c.y);
2150 /* go to first col if the mode is set */
2151 tnewline(IS_SET(MODE_CRLF));
2153 case '\a': /* BEL */
2154 if (term.esc & ESC_STR_END) {
2155 /* backwards compatibility to xterm */
2161 case '\033': /* ESC */
2163 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
2164 term.esc |= ESC_START;
2166 case '\016': /* SO (LS1 -- Locking shift 1) */
2167 case '\017': /* SI (LS0 -- Locking shift 0) */
2168 term.charset = 1 - (ascii - '\016');
2170 case '\032': /* SUB */
2171 tsetchar('?', &term.c.attr, term.c.x, term.c.y);
2173 case '\030': /* CAN */
2176 case '\005': /* ENQ (IGNORED) */
2177 case '\000': /* NUL (IGNORED) */
2178 case '\021': /* XON (IGNORED) */
2179 case '\023': /* XOFF (IGNORED) */
2180 case 0177: /* DEL (IGNORED) */
2182 case 0x80: /* TODO: PAD */
2183 case 0x81: /* TODO: HOP */
2184 case 0x82: /* TODO: BPH */
2185 case 0x83: /* TODO: NBH */
2186 case 0x84: /* TODO: IND */
2188 case 0x85: /* NEL -- Next line */
2189 tnewline(1); /* always go to first col */
2191 case 0x86: /* TODO: SSA */
2192 case 0x87: /* TODO: ESA */
2194 case 0x88: /* HTS -- Horizontal tab stop */
2195 term.tabs[term.c.x] = 1;
2197 case 0x89: /* TODO: HTJ */
2198 case 0x8a: /* TODO: VTS */
2199 case 0x8b: /* TODO: PLD */
2200 case 0x8c: /* TODO: PLU */
2201 case 0x8d: /* TODO: RI */
2202 case 0x8e: /* TODO: SS2 */
2203 case 0x8f: /* TODO: SS3 */
2204 case 0x91: /* TODO: PU1 */
2205 case 0x92: /* TODO: PU2 */
2206 case 0x93: /* TODO: STS */
2207 case 0x94: /* TODO: CCH */
2208 case 0x95: /* TODO: MW */
2209 case 0x96: /* TODO: SPA */
2210 case 0x97: /* TODO: EPA */
2211 case 0x98: /* TODO: SOS */
2212 case 0x99: /* TODO: SGCI */
2214 case 0x9a: /* DECID -- Identify Terminal */
2215 ttywrite(vtiden, strlen(vtiden), 0);
2217 case 0x9b: /* TODO: CSI */
2218 case 0x9c: /* TODO: ST */
2220 case 0x90: /* DCS -- Device Control String */
2221 case 0x9d: /* OSC -- Operating System Command */
2222 case 0x9e: /* PM -- Privacy Message */
2223 case 0x9f: /* APC -- Application Program Command */
2224 tstrsequence(ascii);
2227 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2228 term.esc &= ~(ESC_STR_END|ESC_STR);
2232 * returns 1 when the sequence is finished and it hasn't to read
2233 * more characters for this sequence, otherwise 0
2236 eschandle(uchar ascii)
2240 term.esc |= ESC_CSI;
2243 term.esc |= ESC_TEST;
2246 term.esc |= ESC_UTF8;
2248 case 'P': /* DCS -- Device Control String */
2249 case '_': /* APC -- Application Program Command */
2250 case '^': /* PM -- Privacy Message */
2251 case ']': /* OSC -- Operating System Command */
2252 case 'k': /* old title set compatibility */
2253 tstrsequence(ascii);
2255 case 'n': /* LS2 -- Locking shift 2 */
2256 case 'o': /* LS3 -- Locking shift 3 */
2257 term.charset = 2 + (ascii - 'n');
2259 case '(': /* GZD4 -- set primary charset G0 */
2260 case ')': /* G1D4 -- set secondary charset G1 */
2261 case '*': /* G2D4 -- set tertiary charset G2 */
2262 case '+': /* G3D4 -- set quaternary charset G3 */
2263 term.icharset = ascii - '(';
2264 term.esc |= ESC_ALTCHARSET;
2266 case 'D': /* IND -- Linefeed */
2267 if (term.c.y == term.bot) {
2268 tscrollup(term.top, 1);
2270 tmoveto(term.c.x, term.c.y+1);
2273 case 'E': /* NEL -- Next line */
2274 tnewline(1); /* always go to first col */
2276 case 'H': /* HTS -- Horizontal tab stop */
2277 term.tabs[term.c.x] = 1;
2279 case 'M': /* RI -- Reverse index */
2280 if (term.c.y == term.top) {
2281 tscrolldown(term.top, 1);
2283 tmoveto(term.c.x, term.c.y-1);
2286 case 'Z': /* DECID -- Identify Terminal */
2287 ttywrite(vtiden, strlen(vtiden), 0);
2289 case 'c': /* RIS -- Reset to initial state */
2294 case '=': /* DECPAM -- Application keypad */
2295 xsetmode(1, MODE_APPKEYPAD);
2297 case '>': /* DECPNM -- Normal keypad */
2298 xsetmode(0, MODE_APPKEYPAD);
2300 case '7': /* DECSC -- Save Cursor */
2301 tcursor(CURSOR_SAVE);
2303 case '8': /* DECRC -- Restore Cursor */
2304 tcursor(CURSOR_LOAD);
2306 case '\\': /* ST -- String Terminator */
2307 if (term.esc & ESC_STR_END)
2311 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2312 (uchar) ascii, isprint(ascii)? ascii:'.');
2326 control = ISCONTROL(u);
2327 if (u < 127 || !IS_SET(MODE_UTF8)) {
2331 len = utf8encode(u, c);
2332 if (!control && (width = wcwidth(u)) == -1)
2336 if (IS_SET(MODE_PRINT))
2340 * STR sequence must be checked before anything else
2341 * because it uses all following characters until it
2342 * receives a ESC, a SUB, a ST or any other C1 control
2345 if (term.esc & ESC_STR) {
2346 if (u == '\a' || u == 030 || u == 032 || u == 033 ||
2348 term.esc &= ~(ESC_START|ESC_STR);
2349 term.esc |= ESC_STR_END;
2350 goto check_control_code;
2353 if (strescseq.len+len >= strescseq.siz) {
2355 * Here is a bug in terminals. If the user never sends
2356 * some code to stop the str or esc command, then st
2357 * will stop responding. But this is better than
2358 * silently failing with unknown characters. At least
2359 * then users will report back.
2361 * In the case users ever get fixed, here is the code:
2367 if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2)
2370 strescseq.buf = xrealloc(strescseq.buf, strescseq.siz);
2373 memmove(&strescseq.buf[strescseq.len], c, len);
2374 strescseq.len += len;
2380 * Actions of control codes must be performed as soon they arrive
2381 * because they can be embedded inside a control sequence, and
2382 * they must not cause conflicts with sequences.
2387 * control codes are not shown ever
2392 } else if (term.esc & ESC_START) {
2393 if (term.esc & ESC_CSI) {
2394 csiescseq.buf[csiescseq.len++] = u;
2395 if (BETWEEN(u, 0x40, 0x7E)
2396 || csiescseq.len >= \
2397 sizeof(csiescseq.buf)-1) {
2403 } else if (term.esc & ESC_UTF8) {
2405 } else if (term.esc & ESC_ALTCHARSET) {
2407 } else if (term.esc & ESC_TEST) {
2412 /* sequence already finished */
2416 * All characters which form part of a sequence are not
2421 //if (selected(term.c.x, term.c.y))
2424 gp = &term.line[term.c.y][term.c.x];
2425 if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
2426 gp->mode |= ATTR_WRAP;
2428 gp = &term.line[term.c.y][term.c.x];
2431 if (IS_SET(MODE_INSERT) && term.c.x+width < term.col)
2432 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
2434 if (term.c.x+width > term.col) {
2436 gp = &term.line[term.c.y][term.c.x];
2439 tsetchar(u, &term.c.attr, term.c.x, term.c.y);
2443 gp->mode |= ATTR_WIDE;
2444 if (term.c.x+1 < term.col) {
2446 gp[1].mode = ATTR_WDUMMY;
2449 if (term.c.x+width < term.col) {
2450 tmoveto(term.c.x+width, term.c.y);
2452 term.c.state |= CURSOR_WRAPNEXT;
2457 twrite(const char *buf, int buflen, int show_ctrl)
2463 for (n = 0; n < buflen; n += charsize) {
2464 if (IS_SET(MODE_UTF8)) {
2465 /* process a complete utf8 char */
2466 charsize = utf8decode(buf + n, &u, buflen - n);
2473 if (show_ctrl && ISCONTROL(u)) {
2478 } else if (u != '\n' && u != '\r' && u != '\t') {
2489 tresize(int col, int row)
2492 int const colSet = col, alt = IS_SET(MODE_ALTSCREEN), ini = buf == NULL;
2493 col = MAX(col, buffCols);
2494 row = MIN(row, buffSize);
2495 int const minrow = MIN(row, term.row), mincol = MIN(col, buffCols);
2499 if (col < 1 || row < 1) {
2501 "tresize: error resizing to %dx%d\n", col, row);
2504 if (alt) tswapscreen();
2507 * slide screen to keep cursor where we expect it -
2508 * tscrollup would work here, but we can optimize to
2509 * memmove because we're freeing the earlier lines
2511 for (i = 0; i <= term.c.y - row; i++) {
2514 /* ensure that both src and dst are not NULL */
2516 memmove(term.alt, term.alt + i, row * sizeof(Line));
2518 for (i += row; i < term.row; i++) {
2522 /* resize to new height */
2523 buf = xrealloc(buf, (buffSize + row) * sizeof(Line));
2524 term.alt = xrealloc(term.alt, row * sizeof(Line));
2525 term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
2526 mark = xrealloc(mark, col * row * sizeof(*mark));
2527 term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
2529 /* resize each row to new width, zero-pad if needed */
2530 for (i = 0; i < minrow; i++) {
2531 term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph));
2534 /* allocate any new rows */
2535 for (/* i = minrow */; i < row; i++) {
2536 term.alt[i] = xmalloc(col * sizeof(Glyph));
2538 if (col > buffCols) {
2539 bp = term.tabs + buffCols;
2541 memset(bp, 0, sizeof(*term.tabs) * (col - buffCols));
2542 while (--bp > term.tabs && !*bp)
2544 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
2547 Glyph g=(Glyph){.bg=term.c.attr.bg, .fg=term.c.attr.fg, .u=' ', .mode=0};
2548 for (i = 0; i < buffSize; ++i) {
2549 buf[i] = xrealloc(ini ? NULL : buf[i], col*sizeof(Glyph));
2550 for (int j = ini ? 0 : buffCols; j < col; ++j) buf[i][j] = g;
2552 for (i = 0; i < row; ++i) buf[buffSize + i] = buf[i];
2553 term.line = &buf[*(histOp?&histOff:&insertOff) +=MAX(term.c.y-row+1,0)];
2554 memset(mark, 0, col * row * sizeof(*mark));
2555 /* update terminal size */
2559 if (alt) tswapscreen();
2560 /* reset scrolling region */
2561 tsetscroll(0, row-1);
2562 /* make use of the LIMIT in tmoveto */
2563 tmoveto(term.c.x, term.c.y);
2564 /* Clearing both screens (it makes dirty all lines) */
2566 for (i = 0; i < 2; i++) {
2567 if (mincol < col && 0 < minrow) {
2568 tclearregion(mincol, 0, col - 1, minrow - 1);
2570 if (0 < col && minrow < row) {
2571 tclearregion(0, minrow, col - 1, row - 1);
2574 tcursor(CURSOR_LOAD);
2586 drawregion(int x1, int y1, int x2, int y2)
2588 if (altToggle && histMode && !histOp)
2589 memset(term.dirty, 0, sizeof(*term.dirty) * term.row);
2590 int const o = !IS_SET(MODE_ALTSCREEN) && histMode && !histOp, h =rows();
2593 for (y = y1; y < y2; y++) {
2594 int const oy = o ? (y + insertOff - histOff + h) % h : y;
2595 if (!BETWEEN(oy, 0, term.row-1) || !term.dirty[y]) continue;
2596 xdrawline(term.line[y], x1, oy, x2);
2598 memset(&term.dirty[y1], 0, sizeof(*term.dirty) * (y2 - y1));
2604 int cx = term.c.x, ocx = term.ocx, ocy = term.ocy;
2609 /* adjust cursor position */
2610 LIMIT(term.ocx, 0, term.col-1);
2611 LIMIT(term.ocy, 0, term.row-1);
2612 if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY)
2614 if (term.line[term.c.y][cx].mode & ATTR_WDUMMY)
2617 if (histMode) historyPreDraw();
2618 drawregion(0, 0, term.col, term.row);
2620 xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
2621 term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
2623 term.ocy = term.c.y;
2625 if (ocx != term.ocx || ocy != term.ocy)
2626 xximspot(term.ocx, term.ocy);