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__)
31 #if defined(__OpenBSD__)
32 #include <sys/sysctl.h>
36 #define UTF_INVALID 0xFFFD
38 #define ESC_BUF_SIZ (128*UTF_SIZ)
39 #define ESC_ARG_SIZ 16
40 #define STR_BUF_SIZ ESC_BUF_SIZ
41 #define STR_ARG_SIZ ESC_ARG_SIZ
44 #define IS_SET(flag) ((term.mode & (flag)) != 0)
45 #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f)
46 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
47 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
48 #define ISDELIM(u) (u && wcschr(worddelimiters, u))
49 static inline int max(int a, int b) { return a > b ? a : b; }
50 static inline int min(int a, int b) { return a < b ? a : b; }
55 MODE_ALTSCREEN = 1 << 2,
62 enum cursor_movement {
86 ESC_STR = 4, /* DCS, OSC, PM, APC */
88 ESC_STR_END = 16, /* a final string was encountered */
89 ESC_TEST = 32, /* Enter in test mode */
94 Glyph attr; /* current char attributes */
106 * Selection variables:
107 * nb – normalized coordinates of the beginning of the selection
108 * ne – normalized coordinates of the end of the selection
109 * ob – original coordinates of the beginning of the selection
110 * oe – original coordinates of the end of the selection
119 /* Internal representation of the screen */
121 int row; /* nb row */
122 int col; /* nb col */
123 Line *line; /* screen */
124 Line *alt; /* alternate screen */
125 int *dirty; /* dirtyness of lines */
126 TCursor c; /* cursor */
127 int ocx; /* old cursor col */
128 int ocy; /* old cursor row */
129 int top; /* top scroll limit */
130 int bot; /* bottom scroll limit */
131 int mode; /* terminal mode flags */
132 int esc; /* escape state flags */
133 char trantbl[4]; /* charset table translation */
134 int charset; /* current charset */
135 int icharset; /* selected charset for sequence */
137 Rune lastc; /* last printed char outside of sequence, 0 if control */
140 /* CSI Escape sequence structs */
141 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
143 char buf[ESC_BUF_SIZ]; /* raw string */
144 size_t len; /* raw string length */
146 int arg[ESC_ARG_SIZ];
147 int narg; /* nb of args */
151 /* STR Escape sequence structs */
152 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
154 char type; /* ESC type ... */
155 char *buf; /* allocated raw string */
156 size_t siz; /* allocation size */
157 size_t len; /* raw string length */
158 char *args[STR_ARG_SIZ];
159 int narg; /* nb of args */
162 static void execsh(char *, char **);
163 static char *getcwd_by_pid(pid_t pid);
164 static void stty(char **);
165 static void sigchld(int);
166 static void ttywriteraw(const char *, size_t);
168 static void csidump(void);
169 static void csihandle(void);
170 static void csiparse(void);
171 static void csireset(void);
172 static int eschandle(uchar);
173 static void strdump(void);
174 static void strhandle(void);
175 static void strparse(void);
176 static void strreset(void);
178 static void tprinter(char *, size_t);
179 static void tdumpsel(void);
180 static void tdumpline(int);
181 static void tdump(void);
182 static void tclearregion(int, int, int, int);
183 static void tcursor(int);
184 static void tdeletechar(int);
185 static void tdeleteline(int);
186 static void tinsertblank(int);
187 static void tinsertblankline(int);
188 static int tlinelen(int);
189 static void tmoveto(int, int);
190 static void tmoveato(int, int);
191 static void tnewline(int);
192 static void tputtab(int);
193 static void tputc(Rune);
194 static void treset(void);
195 static void tscrollup(int, int);
196 static void tscrolldown(int, int);
197 static void tsetattr(int *, int);
198 static void tsetchar(Rune, Glyph *, int, int);
199 static void tsetdirt(int, int);
200 static void tsetscroll(int, int);
201 static void tswapscreen(void);
202 static void tsetmode(int, int, int *, int);
203 static int twrite(const char *, int, int);
204 static void tfulldirt(void);
205 static void tcontrolcode(uchar );
206 static void tdectest(char );
207 static void tdefutf8(char);
208 static int32_t tdefcolor(int *, int *, int);
209 static void tdeftran(char);
210 static void tstrsequence(uchar);
212 static void drawregion(int, int, int, int);
214 static void selscroll(int, int);
215 static void selnormalize(void);
217 static size_t utf8decode(const char *, Rune *, size_t);
218 static Rune utf8decodebyte(char, size_t *);
219 static char utf8encodebyte(Rune, size_t);
220 static size_t utf8validate(Rune *, size_t);
222 static char *base64dec(const char *);
223 static char base64dec_getc(const char **);
225 static ssize_t xwrite(int, const char *, size_t);
229 static Selection sel;
230 static CSIEscape csiescseq;
231 static STREscape strescseq;
236 static uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
237 static uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
238 static Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000};
239 static Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
242 extern int const buffSize;
243 int histOp, histMode, histOff, histOffX, insertOff, altToggle, *mark;
246 static inline int rows() { return IS_SET(MODE_ALTSCREEN) ? term.row : buffSize;}
247 static inline int rangeY(int i) { while (i < 0) i += rows(); return i % rows();}
250 subprocwd(char *path)
253 if (snprintf(path, PATH_MAX, "/proc/%d/cwd", pid) < 0)
256 #elif defined(__OpenBSD__)
257 size_t sz = PATH_MAX;
258 int name[3] = {CTL_KERN, KERN_PROC_CWD, pid};
259 if (sysctl(name, 3, path, &sz, 0, 0) == -1)
266 xwrite(int fd, const char *s, size_t len)
272 r = write(fd, s, len);
287 if (!(p = malloc(len)))
288 die("malloc: %s\n", strerror(errno));
294 xrealloc(void *p, size_t len)
296 if ((p = realloc(p, len)) == NULL)
297 die("realloc: %s\n", strerror(errno));
305 if ((s = strdup(s)) == NULL)
306 die("strdup: %s\n", strerror(errno));
312 utf8decode(const char *c, Rune *u, size_t clen)
314 size_t i, j, len, type;
320 udecoded = utf8decodebyte(c[0], &len);
321 if (!BETWEEN(len, 1, UTF_SIZ))
323 for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
324 udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
331 utf8validate(u, len);
337 utf8decodebyte(char c, size_t *i)
339 for (*i = 0; *i < LEN(utfmask); ++(*i))
340 if (((uchar)c & utfmask[*i]) == utfbyte[*i])
341 return (uchar)c & ~utfmask[*i];
347 utf8encode(Rune u, char *c)
351 len = utf8validate(&u, 0);
355 for (i = len - 1; i != 0; --i) {
356 c[i] = utf8encodebyte(u, 0);
359 c[0] = utf8encodebyte(u, len);
365 utf8encodebyte(Rune u, size_t i)
367 return utfbyte[i] | (u & ~utfmask[i]);
371 utf8validate(Rune *u, size_t i)
373 if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF))
375 for (i = 1; *u > utfmax[i]; ++i)
381 static const char base64_digits[] = {
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, 62, 0, 0, 0,
384 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
385 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
386 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
387 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0,
388 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
389 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
390 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
391 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
392 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
393 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
397 base64dec_getc(const char **src)
399 while (**src && !isprint(**src))
401 return **src ? *((*src)++) : '='; /* emulate padding if string ends */
405 base64dec(const char *src)
407 size_t in_len = strlen(src);
411 in_len += 4 - (in_len % 4);
412 result = dst = xmalloc(in_len / 4 * 3 + 1);
414 int a = base64_digits[(unsigned char) base64dec_getc(&src)];
415 int b = base64_digits[(unsigned char) base64dec_getc(&src)];
416 int c = base64_digits[(unsigned char) base64dec_getc(&src)];
417 int d = base64_digits[(unsigned char) base64dec_getc(&src)];
419 /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */
420 if (a == -1 || b == -1)
423 *dst++ = (a << 2) | ((b & 0x30) >> 4);
426 *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2);
429 *dst++ = ((c & 0x03) << 6) | d;
448 if (term.line[y][i - 1].mode & ATTR_WRAP)
451 while (i > 0 && term.line[y][i - 1].u == ' ')
457 void historyOpToggle(int start, int paint) {
458 if (!histOp == !(histOp + start)) if ((histOp += start) || 1) return;
459 if (histMode && paint && (!IS_SET(MODE_ALTSCREEN) || altToggle)) draw();
460 tcursor(CURSOR_SAVE);
462 if (histMode && altToggle) {
464 memset(term.dirty,0,sizeof(*term.dirty)*term.row);
466 tcursor(CURSOR_LOAD);
467 *(!IS_SET(MODE_ALTSCREEN)?&term.line:&term.alt)=&buf[histOp?histOff:insertOff];
470 void historyModeToggle(int start) {
471 if (!(histMode = (histOp = !!start))) {
475 tcursor(CURSOR_SAVE);
481 int historyBufferScroll(int n) {
482 if (IS_SET(MODE_ALTSCREEN) || !n) return histOp;
483 int p=abs(n=(n<0) ? max(n,-term.row) : min(n,term.row)), r=term.row-p,
484 s=sizeof(*term.dirty), *ptr=histOp?&histOff:&insertOff;
485 if (!histMode || histOp) tfulldirt(); else {
486 memmove(&term.dirty[-min(n,0)], &term.dirty[max(n,0)], s*r);
487 memset(&term.dirty[n>0 ? r : 0], 0, s * p);
489 int const prevOffBuf = sel.alt ? 0 : insertOff + term.row;
490 term.line = &buf[*ptr = (buffSize+*ptr+n) % buffSize];
491 // Cut part of selection removed from buffer, and update sel.ne/b.
492 if (sel.ob.x != -1 && !histOp && n) {
493 int const offBuf = sel.alt ? 0 : insertOff + term.row,
494 pb = rangeY(sel.ob.y - prevOffBuf),
495 pe = rangeY(sel.oe.y - prevOffBuf);
496 int const b = rangeY(sel.ob.y - offBuf), nln = n < 0,
497 e = rangeY(sel.oe.y - offBuf), last = offBuf - nln;
498 if (pb != b && ((pb < b) != nln)) sel.ob.y = last;
499 if (pe != e && ((pe < e) != nln)) sel.oe.y = last;
500 if (sel.oe.y == last && sel.ob.y == last) selclear();
503 // Clear the new region exposed by the shift.
504 if (!histOp) tclearregion(0, n>0?r+1:0, buffCols-1, n>0?term.row:p-1);
508 int historyMove(int x, int y, int ly) {
509 historyOpToggle(1, 1);
510 y += ((term.c.x += x) < 0 ?term.c.x-term.col :term.c.x) / term.col;//< x
511 if ((term.c.x %= term.col) < 0) term.c.x += term.col;
512 if ((term.c.y += y) >= term.row) ly += term.c.y - term.row + 1; //< y
513 else if (term.c.y < 0) ly += term.c.y;
514 term.c.y = MIN(MAX(term.c.y, 0), term.row - 1);
515 int off=insertOff-histOff, bot=rangeY(off), top=-rangeY(-term.row-off),
516 pTop = (-ly>-top), pBot = (ly > bot), fin=histMode&&(pTop||pBot);
517 if (fin && (x||y)) term.c.x = pBot ? term.col-1 : 0;
518 historyBufferScroll(fin ? (pBot ? bot : top) : ly);
519 historyOpToggle(-1, 1);
523 #include "normalMode.c"
525 void selnormalize(void) {
526 historyOpToggle(1, 1);
528 int const oldb = sel.nb.y, olde = sel.ne.y;
529 if (sel.ob.x == -1) {
530 sel.ne.y = sel.nb.y = -1;
532 int const offsetBuffer = sel.alt ? 0 : insertOff + term.row;
533 int const off = sel.alt ? 0 : (histMode ? histOff : insertOff);
534 int const nby = rangeY(sel.ob.y - off),
535 ney = rangeY(sel.oe.y - off);
536 sel.swap = rangeY(sel.ob.y - offsetBuffer)
537 > rangeY(sel.oe.y - offsetBuffer);
538 sel.nb.y = sel.swap ? ney : nby;
539 sel.ne.y = !sel.swap ? ney : nby;
540 int const cnb = sel.nb.y < term.row, cne = sel.ne.y < term.row;
541 if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) {
542 if (cnb) sel.nb.x = (!sel.swap) ? sel.ob.x : sel.oe.x;
543 if (cne) sel.ne.x = (!sel.swap) ? sel.oe.x : sel.ob.x;
545 if (cnb) sel.nb.x = MIN(sel.ob.x, sel.oe.x);
546 if (cne) sel.ne.x = MAX(sel.ob.x, sel.oe.x);
549 int const nBet=sel.nb.y<=sel.ne.y, oBet=oldb<=olde;
550 for (int i = 0; i < term.row; ++i) {
551 int const n = nBet ? BETWEEN(i, sel.nb.y, sel.ne.y)
552 : OUT(i, sel.nb.y, sel.ne.y);
553 term.dirty[i] |= (sel.type == SEL_RECTANGULAR && n) ||
554 (n != (oBet ? BETWEEN(i,oldb,olde) : OUT(i,oldb,olde)));
557 if (BETWEEN(oldb, 0, term.row - 1)) term.dirty[oldb] = 1;
558 if (BETWEEN(olde, 0, term.row - 1)) term.dirty[olde] = 1;
559 if (BETWEEN(sel.nb.y, 0, term.row - 1)) term.dirty[sel.nb.y] = 1;
560 if (BETWEEN(sel.ne.y, 0, term.row - 1)) term.dirty[sel.ne.y] = 1;
562 historyOpToggle(-1, 1);
566 selstart(int col, int row, int snap)
569 sel.mode = SEL_EMPTY;
570 sel.type = SEL_REGULAR;
571 sel.alt = IS_SET(MODE_ALTSCREEN);
573 sel.oe.x = sel.ob.x = col;
574 sel.oe.y = sel.ob.y = row + !sel.alt * (histMode ? histOff : insertOff);
575 if (sel.snap != 0) sel.mode = SEL_READY;
580 selextend(int col, int row, int type, int done)
582 if (sel.mode == SEL_IDLE)
584 if (done && sel.mode == SEL_EMPTY) {
590 sel.oe.y = row + (sel.alt ? 0 : (histMode ? histOff : insertOff));
593 sel.mode = done ? SEL_IDLE : SEL_READY;
597 selected(int x, int y)
599 if (sel.mode == SEL_EMPTY || sel.ob.x == -1 ||
600 sel.alt != IS_SET(MODE_ALTSCREEN))
603 if (sel.type == SEL_RECTANGULAR)
604 return BETWEEN(y, sel.nb.y, sel.ne.y)
605 && BETWEEN(x, sel.nb.x, sel.ne.x);
607 return ((sel.nb.y > sel.ne.y) ? OUT(y, sel.nb.y, sel.ne.y)
608 : BETWEEN(y, sel.nb.y, sel.ne.y)) &&
609 (y != sel.nb.y || x >= sel.nb.x) &&
610 (y != sel.ne.y || x <= sel.ne.x);
617 int y, yy, bufsize, lastx;
623 int const start = sel.swap ? sel.oe.y : sel.ob.y, h = rows();
624 int endy = (sel.swap ? sel.ob.y : sel.oe.y);
625 for (; endy < start; endy += h);
626 Line * const cbuf = IS_SET(MODE_ALTSCREEN) ? term.line : buf;
627 bufsize = (term.col+1) * (endy-start+1 ) * UTF_SIZ;
629 ptr = str = xmalloc(bufsize);
631 /* append every set & selected glyph to the selection */
632 for (y = start; y <= endy; y++) {
635 if (sel.type == SEL_RECTANGULAR) {
636 gp = &cbuf[yy][sel.nb.x];
639 gp = &cbuf[yy][start == y ? sel.nb.x : 0];
640 lastx = (endy == y) ? sel.ne.x : term.col-1;
642 last = &cbuf[yy][lastx];
643 if (!(cbuf[yy][term.col - 1].mode & ATTR_WRAP))
644 while (last > gp && last->u == ' ') --last;
646 for ( ; gp <= last; ++gp) {
647 if (gp->mode & ATTR_WDUMMY) continue;
648 ptr += utf8encode(gp->u, ptr);
652 * Copy and pasting of line endings is inconsistent
653 * in the inconsistent terminal and GUI world.
654 * The best solution seems like to produce '\n' when
655 * something is copied from st and convert '\n' to
656 * '\r', when something to be pasted is received by
658 * FIXME: Fix the computer world.
660 if ((y < endy || lastx >= term.col - 1) &&
661 (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR))
679 die(const char *errstr, ...)
683 va_start(ap, errstr);
684 vfprintf(stderr, errstr, ap);
690 execsh(char *cmd, char **args)
692 char *sh, *prog, *arg;
693 const struct passwd *pw;
696 if ((pw = getpwuid(getuid())) == NULL) {
698 die("getpwuid: %s\n", strerror(errno));
700 die("who are you?\n");
703 if ((sh = getenv("SHELL")) == NULL)
704 sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd;
711 arg = utmp ? utmp : sh;
719 DEFAULT(args, ((char *[]) {prog, arg, NULL}));
724 setenv("LOGNAME", pw->pw_name, 1);
725 setenv("USER", pw->pw_name, 1);
726 setenv("SHELL", sh, 1);
727 setenv("HOME", pw->pw_dir, 1);
728 setenv("TERM", termname, 1);
730 signal(SIGCHLD, SIG_DFL);
731 signal(SIGHUP, SIG_DFL);
732 signal(SIGINT, SIG_DFL);
733 signal(SIGQUIT, SIG_DFL);
734 signal(SIGTERM, SIG_DFL);
735 signal(SIGALRM, SIG_DFL);
747 if ((p = waitpid(pid, &stat, WNOHANG)) < 0)
748 die("waiting for pid %hd failed: %s\n", pid, strerror(errno));
753 if (WIFEXITED(stat) && WEXITSTATUS(stat))
754 die("child exited with status %d\n", WEXITSTATUS(stat));
755 else if (WIFSIGNALED(stat))
756 die("child terminated due to signal %d\n", WTERMSIG(stat));
763 char cmd[_POSIX_ARG_MAX], **p, *q, *s;
766 if ((n = strlen(stty_args)) > sizeof(cmd)-1)
767 die("incorrect stty parameters\n");
768 memcpy(cmd, stty_args, n);
770 siz = sizeof(cmd) - n;
771 for (p = args; p && (s = *p); ++p) {
772 if ((n = strlen(s)) > siz-1)
773 die("stty parameter length too long\n");
780 if (system(cmd) != 0)
781 perror("Couldn't call stty");
785 ttynew(char *line, char *cmd, char *out, char **args)
790 term.mode |= MODE_PRINT;
791 iofd = (!strcmp(out, "-")) ?
792 1 : open(out, O_WRONLY | O_CREAT, 0666);
794 fprintf(stderr, "Error opening %s:%s\n",
795 out, strerror(errno));
800 if ((cmdfd = open(line, O_RDWR)) < 0)
801 die("open line '%s' failed: %s\n",
802 line, strerror(errno));
808 /* seems to work fine on linux, openbsd and freebsd */
809 if (openpty(&m, &s, NULL, NULL, NULL) < 0)
810 die("openpty failed: %s\n", strerror(errno));
812 switch (pid = fork()) {
814 die("fork failed: %s\n", strerror(errno));
818 setsid(); /* create a new process group */
822 if (ioctl(s, TIOCSCTTY, NULL) < 0)
823 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno));
827 if (pledge("stdio getpw proc exec", NULL) == -1)
834 if (pledge("stdio rpath tty proc ps exec", NULL) == -1)
839 signal(SIGCHLD, sigchld);
848 static char buf[BUFSIZ];
849 static int buflen = 0;
852 /* append read bytes to unprocessed bytes */
853 ret = read(cmdfd, buf+buflen, LEN(buf)-buflen);
859 die("couldn't read from shell: %s\n", strerror(errno));
862 written = twrite(buf, buflen, 0);
864 /* keep any incomplete UTF-8 byte sequence for the next call */
866 memmove(buf, buf + written, buflen);
872 ttywrite(const char *s, size_t n, int may_echo)
876 if (may_echo && IS_SET(MODE_ECHO))
879 if (!IS_SET(MODE_CRLF)) {
884 /* This is similar to how the kernel handles ONLCR for ttys */
888 ttywriteraw("\r\n", 2);
890 next = memchr(s, '\r', n);
891 DEFAULT(next, s + n);
892 ttywriteraw(s, next - s);
900 ttywriteraw(const char *s, size_t n)
907 * Remember that we are using a pty, which might be a modem line.
908 * Writing too much will clog the line. That's why we are doing this
910 * FIXME: Migrate the world to Plan 9.
918 /* Check if we can write. */
919 if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) {
922 die("select failed: %s\n", strerror(errno));
924 if (FD_ISSET(cmdfd, &wfd)) {
926 * Only write the bytes written by ttywrite() or the
927 * default of 256. This seems to be a reasonable value
928 * for a serial line. Bigger values might clog the I/O.
930 if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0)
934 * We weren't able to write out everything.
935 * This means the buffer is getting full
943 /* All bytes have been written. */
947 if (FD_ISSET(cmdfd, &rfd))
953 die("write error on tty: %s\n", strerror(errno));
957 ttyresize(int tw, int th)
965 if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
966 fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
972 /* Send SIGHUP to shell */
981 for (i = 0; i < term.row-1; i++) {
982 for (j = 0; j < term.col-1; j++) {
983 if (term.line[i][j].mode & attr)
992 tsetdirt(int top, int bot)
996 LIMIT(top, 0, term.row-1);
997 LIMIT(bot, 0, term.row-1);
999 for (i = top; i <= bot; i++)
1004 tsetdirtattr(int attr)
1008 for (i = 0; i < term.row-1; i++) {
1009 for (j = 0; j < term.col-1; j++) {
1010 if (term.line[i][j].mode & attr) {
1021 tsetdirt(0, term.row-1);
1027 int alt = (histOp) ? 0 : (IS_SET(MODE_ALTSCREEN) + 1);
1029 if (mode == CURSOR_SAVE) {
1031 } else if (mode == CURSOR_LOAD) {
1033 tmoveto(c[alt].x, c[alt].y);
1042 term.c = (TCursor){{
1046 }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
1048 memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1049 for (i = tabspaces; i < term.col; i += tabspaces)
1052 term.bot = term.row - 1;
1053 term.mode = MODE_WRAP|MODE_UTF8;
1054 memset(term.trantbl, CS_USA, sizeof(term.trantbl));
1057 for (i = 0; i < 2; i++) {
1059 tcursor(CURSOR_SAVE);
1060 tclearregion(0, 0, term.col-1, term.row-1);
1066 tnew(int col, int row)
1068 term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
1076 Line *tmp = term.line;
1078 term.line = term.alt;
1080 term.mode ^= MODE_ALTSCREEN;
1085 newterm(const Arg* a)
1089 die("fork failed: %s\n", strerror(errno));
1092 chdir(getcwd_by_pid(pid));
1093 execlp("st", "./st", NULL);
1098 static char *getcwd_by_pid(pid_t pid) {
1100 snprintf(buf, sizeof buf, "/proc/%d/cwd", pid);
1101 return realpath(buf, NULL);
1105 tscrolldown(int orig, int n)
1107 if (historyBufferScroll(-n)) return;
1111 LIMIT(n, 0, term.bot-orig+1);
1113 tsetdirt(orig, term.bot-n);
1114 tclearregion(0, term.bot-n+1, term.col-1, term.bot);
1116 for (i = term.bot; i >= orig+n; i--) {
1117 temp = term.line[i];
1118 term.line[i] = term.line[i-n];
1119 term.line[i-n] = temp;
1126 tscrollup(int orig, int n)
1128 if (historyBufferScroll(n)) return;
1132 LIMIT(n, 0, term.bot-orig+1);
1134 tclearregion(0, orig, term.col-1, orig+n-1);
1135 tsetdirt(orig+n, term.bot);
1137 for (i = orig; i <= term.bot-n; i++) {
1138 temp = term.line[i];
1139 term.line[i] = term.line[i+n];
1140 term.line[i+n] = temp;
1143 selscroll(orig, -n);
1147 selscroll(int orig, int n)
1152 if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) {
1154 } else if (BETWEEN(sel.nb.y, orig, term.bot)) {
1157 if (sel.ob.y < term.top || sel.ob.y > term.bot ||
1158 sel.oe.y < term.top || sel.oe.y > term.bot) {
1167 tnewline(int first_col)
1171 if (y == term.bot) {
1172 tscrollup(term.top, 1);
1176 tmoveto(first_col ? 0 : term.c.x, y);
1182 char *p = csiescseq.buf, *np;
1191 csiescseq.buf[csiescseq.len] = '\0';
1192 while (p < csiescseq.buf+csiescseq.len) {
1194 v = strtol(p, &np, 10);
1197 if (v == LONG_MAX || v == LONG_MIN)
1199 csiescseq.arg[csiescseq.narg++] = v;
1201 if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ)
1205 csiescseq.mode[0] = *p++;
1206 csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0';
1209 /* for absolute user moves, when decom is set */
1211 tmoveato(int x, int y)
1213 tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0));
1217 tmoveto(int x, int y)
1221 if (term.c.state & CURSOR_ORIGIN) {
1226 maxy = term.row - 1;
1228 term.c.state &= ~CURSOR_WRAPNEXT;
1229 term.c.x = LIMIT(x, 0, term.col-1);
1230 term.c.y = LIMIT(y, miny, maxy);
1234 tsetchar(Rune u, Glyph *attr, int x, int y)
1236 static char *vt100_0[62] = { /* 0x41 - 0x7e */
1237 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1238 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1239 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1240 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1241 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1242 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1243 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1244 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1248 * The table is proudly stolen from rxvt.
1250 if (term.trantbl[term.charset] == CS_GRAPHIC0 &&
1251 BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
1252 utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ);
1254 if (term.line[y][x].mode & ATTR_WIDE) {
1255 if (x+1 < term.col) {
1256 term.line[y][x+1].u = ' ';
1257 term.line[y][x+1].mode &= ~ATTR_WDUMMY;
1259 } else if (term.line[y][x].mode & ATTR_WDUMMY) {
1260 term.line[y][x-1].u = ' ';
1261 term.line[y][x-1].mode &= ~ATTR_WIDE;
1265 term.line[y][x] = *attr;
1266 term.line[y][x].u = u;
1270 tclearregion(int x1, int y1, int x2, int y2)
1276 temp = x1, x1 = x2, x2 = temp;
1278 temp = y1, y1 = y2, y2 = temp;
1280 LIMIT(x1, 0, buffCols-1);
1281 LIMIT(x2, 0, buffCols-1);
1282 LIMIT(y1, 0, term.row-1);
1283 LIMIT(y2, 0, term.row-1);
1285 for (y = y1; y <= y2; y++) {
1287 for (x = x1; x <= x2; x++) {
1288 gp = &term.line[y][x];
1289 gp->fg = term.c.attr.fg;
1290 gp->bg = term.c.attr.bg;
1303 LIMIT(n, 0, term.col - term.c.x);
1307 size = term.col - src;
1308 line = term.line[term.c.y];
1310 memmove(&line[dst], &line[src], size * sizeof(Glyph));
1311 tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
1320 LIMIT(n, 0, term.col - term.c.x);
1324 size = term.col - dst;
1325 line = term.line[term.c.y];
1327 memmove(&line[dst], &line[src], size * sizeof(Glyph));
1328 tclearregion(src, term.c.y, dst - 1, term.c.y);
1332 tinsertblankline(int n)
1334 if (BETWEEN(term.c.y, term.top, term.bot))
1335 tscrolldown(term.c.y, n);
1341 if (BETWEEN(term.c.y, term.top, term.bot))
1342 tscrollup(term.c.y, n);
1346 tdefcolor(int *attr, int *npar, int l)
1351 switch (attr[*npar + 1]) {
1352 case 2: /* direct color in RGB space */
1353 if (*npar + 4 >= l) {
1355 "erresc(38): Incorrect number of parameters (%d)\n",
1359 r = attr[*npar + 2];
1360 g = attr[*npar + 3];
1361 b = attr[*npar + 4];
1363 if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255))
1364 fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n",
1367 idx = TRUECOLOR(r, g, b);
1369 case 5: /* indexed color */
1370 if (*npar + 2 >= l) {
1372 "erresc(38): Incorrect number of parameters (%d)\n",
1377 if (!BETWEEN(attr[*npar], 0, 255))
1378 fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]);
1382 case 0: /* implemented defined (only foreground) */
1383 case 1: /* transparent */
1384 case 3: /* direct color in CMY space */
1385 case 4: /* direct color in CMYK space */
1388 "erresc(38): gfx attr %d unknown\n", attr[*npar]);
1396 tsetattr(int *attr, int l)
1401 for (i = 0; i < l; i++) {
1404 term.c.attr.mode &= ~(
1413 term.c.attr.fg = defaultfg;
1414 term.c.attr.bg = defaultbg;
1417 term.c.attr.mode |= ATTR_BOLD;
1420 term.c.attr.mode |= ATTR_FAINT;
1423 term.c.attr.mode |= ATTR_ITALIC;
1426 term.c.attr.mode |= ATTR_UNDERLINE;
1428 case 5: /* slow blink */
1430 case 6: /* rapid blink */
1431 term.c.attr.mode |= ATTR_BLINK;
1434 term.c.attr.mode |= ATTR_REVERSE;
1437 term.c.attr.mode |= ATTR_INVISIBLE;
1440 term.c.attr.mode |= ATTR_STRUCK;
1443 term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT);
1446 term.c.attr.mode &= ~ATTR_ITALIC;
1449 term.c.attr.mode &= ~ATTR_UNDERLINE;
1452 term.c.attr.mode &= ~ATTR_BLINK;
1455 term.c.attr.mode &= ~ATTR_REVERSE;
1458 term.c.attr.mode &= ~ATTR_INVISIBLE;
1461 term.c.attr.mode &= ~ATTR_STRUCK;
1464 if ((idx = tdefcolor(attr, &i, l)) >= 0)
1465 term.c.attr.fg = idx;
1468 term.c.attr.fg = defaultfg;
1471 if ((idx = tdefcolor(attr, &i, l)) >= 0)
1472 term.c.attr.bg = idx;
1475 term.c.attr.bg = defaultbg;
1478 if (BETWEEN(attr[i], 30, 37)) {
1479 term.c.attr.fg = attr[i] - 30;
1480 } else if (BETWEEN(attr[i], 40, 47)) {
1481 term.c.attr.bg = attr[i] - 40;
1482 } else if (BETWEEN(attr[i], 90, 97)) {
1483 term.c.attr.fg = attr[i] - 90 + 8;
1484 } else if (BETWEEN(attr[i], 100, 107)) {
1485 term.c.attr.bg = attr[i] - 100 + 8;
1488 "erresc(default): gfx attr %d unknown\n",
1498 tsetscroll(int t, int b)
1502 LIMIT(t, 0, term.row-1);
1503 LIMIT(b, 0, term.row-1);
1514 tsetmode(int priv, int set, int *args, int narg)
1518 for (lim = args + narg; args < lim; ++args) {
1521 case 1: /* DECCKM -- Cursor key */
1522 xsetmode(set, MODE_APPCURSOR);
1524 case 5: /* DECSCNM -- Reverse video */
1525 xsetmode(set, MODE_REVERSE);
1527 case 6: /* DECOM -- Origin */
1528 MODBIT(term.c.state, set, CURSOR_ORIGIN);
1531 case 7: /* DECAWM -- Auto wrap */
1532 MODBIT(term.mode, set, MODE_WRAP);
1534 case 0: /* Error (IGNORED) */
1535 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1536 case 3: /* DECCOLM -- Column (IGNORED) */
1537 case 4: /* DECSCLM -- Scroll (IGNORED) */
1538 case 8: /* DECARM -- Auto repeat (IGNORED) */
1539 case 18: /* DECPFF -- Printer feed (IGNORED) */
1540 case 19: /* DECPEX -- Printer extent (IGNORED) */
1541 case 42: /* DECNRCM -- National characters (IGNORED) */
1542 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1544 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1545 xsetmode(!set, MODE_HIDE);
1547 case 9: /* X10 mouse compatibility mode */
1548 xsetpointermotion(0);
1549 xsetmode(0, MODE_MOUSE);
1550 xsetmode(set, MODE_MOUSEX10);
1552 case 1000: /* 1000: report button press */
1553 xsetpointermotion(0);
1554 xsetmode(0, MODE_MOUSE);
1555 xsetmode(set, MODE_MOUSEBTN);
1557 case 1002: /* 1002: report motion on button press */
1558 xsetpointermotion(0);
1559 xsetmode(0, MODE_MOUSE);
1560 xsetmode(set, MODE_MOUSEMOTION);
1562 case 1003: /* 1003: enable all mouse motions */
1563 xsetpointermotion(set);
1564 xsetmode(0, MODE_MOUSE);
1565 xsetmode(set, MODE_MOUSEMANY);
1567 case 1004: /* 1004: send focus events to tty */
1568 xsetmode(set, MODE_FOCUS);
1570 case 1006: /* 1006: extended reporting mode */
1571 xsetmode(set, MODE_MOUSESGR);
1574 xsetmode(set, MODE_8BIT);
1576 case 1049: /* swap screen & set/restore cursor as xterm */
1577 if (!allowaltscreen)
1579 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1581 case 47: /* swap screen */
1583 if (!allowaltscreen)
1585 alt = IS_SET(MODE_ALTSCREEN);
1587 tclearregion(0, 0, term.col-1,
1590 if (set ^ alt) /* set is always 1 or 0 */
1596 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1598 case 2004: /* 2004: bracketed paste mode */
1599 xsetmode(set, MODE_BRCKTPASTE);
1601 /* Not implemented mouse modes. See comments there. */
1602 case 1001: /* mouse highlight mode; can hang the
1603 terminal by design when implemented. */
1604 case 1005: /* UTF-8 mouse mode; will confuse
1605 applications not supporting UTF-8
1607 case 1015: /* urxvt mangled mouse mode; incompatible
1608 and can be mistaken for other control
1613 "erresc: unknown private set/reset mode %d\n",
1619 case 0: /* Error (IGNORED) */
1622 xsetmode(set, MODE_KBDLOCK);
1624 case 4: /* IRM -- Insertion-replacement */
1625 MODBIT(term.mode, set, MODE_INSERT);
1627 case 12: /* SRM -- Send/Receive */
1628 MODBIT(term.mode, !set, MODE_ECHO);
1630 case 20: /* LNM -- Linefeed/new line */
1631 MODBIT(term.mode, set, MODE_CRLF);
1635 "erresc: unknown set/reset mode %d\n",
1649 switch (csiescseq.mode[0]) {
1652 fprintf(stderr, "erresc: unknown csi ");
1656 case '@': /* ICH -- Insert <n> blank char */
1657 DEFAULT(csiescseq.arg[0], 1);
1658 tinsertblank(csiescseq.arg[0]);
1660 case 'A': /* CUU -- Cursor <n> Up */
1661 DEFAULT(csiescseq.arg[0], 1);
1662 tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
1664 case 'B': /* CUD -- Cursor <n> Down */
1665 case 'e': /* VPR --Cursor <n> Down */
1666 DEFAULT(csiescseq.arg[0], 1);
1667 tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
1669 case 'i': /* MC -- Media Copy */
1670 switch (csiescseq.arg[0]) {
1675 tdumpline(term.c.y);
1681 term.mode &= ~MODE_PRINT;
1684 term.mode |= MODE_PRINT;
1688 case 'c': /* DA -- Device Attributes */
1689 if (csiescseq.arg[0] == 0)
1690 ttywrite(vtiden, strlen(vtiden), 0);
1692 case 'b': /* REP -- if last char is printable print it <n> more times */
1693 DEFAULT(csiescseq.arg[0], 1);
1695 while (csiescseq.arg[0]-- > 0)
1698 case 'C': /* CUF -- Cursor <n> Forward */
1699 case 'a': /* HPR -- Cursor <n> Forward */
1700 DEFAULT(csiescseq.arg[0], 1);
1701 tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
1703 case 'D': /* CUB -- Cursor <n> Backward */
1704 DEFAULT(csiescseq.arg[0], 1);
1705 tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
1707 case 'E': /* CNL -- Cursor <n> Down and first col */
1708 DEFAULT(csiescseq.arg[0], 1);
1709 tmoveto(0, term.c.y+csiescseq.arg[0]);
1711 case 'F': /* CPL -- Cursor <n> Up and first col */
1712 DEFAULT(csiescseq.arg[0], 1);
1713 tmoveto(0, term.c.y-csiescseq.arg[0]);
1715 case 'g': /* TBC -- Tabulation clear */
1716 switch (csiescseq.arg[0]) {
1717 case 0: /* clear current tab stop */
1718 term.tabs[term.c.x] = 0;
1720 case 3: /* clear all the tabs */
1721 memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1727 case 'G': /* CHA -- Move to <col> */
1729 DEFAULT(csiescseq.arg[0], 1);
1730 tmoveto(csiescseq.arg[0]-1, term.c.y);
1732 case 'H': /* CUP -- Move to <row> <col> */
1734 DEFAULT(csiescseq.arg[0], 1);
1735 DEFAULT(csiescseq.arg[1], 1);
1736 tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
1738 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1739 DEFAULT(csiescseq.arg[0], 1);
1740 tputtab(csiescseq.arg[0]);
1742 case 'J': /* ED -- Clear screen */
1743 switch (csiescseq.arg[0]) {
1745 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
1746 if (term.c.y < term.row-1) {
1747 tclearregion(0, term.c.y+1, term.col-1,
1753 tclearregion(0, 0, term.col-1, term.c.y-1);
1754 tclearregion(0, term.c.y, term.c.x, term.c.y);
1757 tclearregion(0, 0, term.col-1, term.row-1);
1763 case 'K': /* EL -- Clear line */
1764 switch (csiescseq.arg[0]) {
1766 tclearregion(term.c.x, term.c.y, term.col-1,
1770 tclearregion(0, term.c.y, term.c.x, term.c.y);
1773 tclearregion(0, term.c.y, term.col-1, term.c.y);
1777 case 'S': /* SU -- Scroll <n> line up */
1778 DEFAULT(csiescseq.arg[0], 1);
1779 tscrollup(term.top, csiescseq.arg[0]);
1781 case 'T': /* SD -- Scroll <n> line down */
1782 DEFAULT(csiescseq.arg[0], 1);
1783 tscrolldown(term.top, csiescseq.arg[0]);
1785 case 'L': /* IL -- Insert <n> blank lines */
1786 DEFAULT(csiescseq.arg[0], 1);
1787 tinsertblankline(csiescseq.arg[0]);
1789 case 'l': /* RM -- Reset Mode */
1790 tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
1792 case 'M': /* DL -- Delete <n> lines */
1793 DEFAULT(csiescseq.arg[0], 1);
1794 tdeleteline(csiescseq.arg[0]);
1796 case 'X': /* ECH -- Erase <n> char */
1797 DEFAULT(csiescseq.arg[0], 1);
1798 tclearregion(term.c.x, term.c.y,
1799 term.c.x + csiescseq.arg[0] - 1, term.c.y);
1801 case 'P': /* DCH -- Delete <n> char */
1802 DEFAULT(csiescseq.arg[0], 1);
1803 tdeletechar(csiescseq.arg[0]);
1805 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1806 DEFAULT(csiescseq.arg[0], 1);
1807 tputtab(-csiescseq.arg[0]);
1809 case 'd': /* VPA -- Move to <row> */
1810 DEFAULT(csiescseq.arg[0], 1);
1811 tmoveato(term.c.x, csiescseq.arg[0]-1);
1813 case 'h': /* SM -- Set terminal mode */
1814 tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
1816 case 'm': /* SGR -- Terminal attribute (color) */
1817 tsetattr(csiescseq.arg, csiescseq.narg);
1819 case 'n': /* DSR – Device Status Report (cursor position) */
1820 if (csiescseq.arg[0] == 6) {
1821 len = snprintf(buf, sizeof(buf), "\033[%i;%iR",
1822 term.c.y+1, term.c.x+1);
1823 ttywrite(buf, len, 0);
1826 case 'r': /* DECSTBM -- Set Scrolling Region */
1827 if (csiescseq.priv) {
1830 DEFAULT(csiescseq.arg[0], 1);
1831 DEFAULT(csiescseq.arg[1], term.row);
1832 tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
1836 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1837 tcursor(CURSOR_SAVE);
1839 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1840 tcursor(CURSOR_LOAD);
1843 switch (csiescseq.mode[1]) {
1844 case 'q': /* DECSCUSR -- Set Cursor Style */
1845 if (xsetcursor(csiescseq.arg[0]))
1861 fprintf(stderr, "ESC[");
1862 for (i = 0; i < csiescseq.len; i++) {
1863 c = csiescseq.buf[i] & 0xff;
1866 } else if (c == '\n') {
1867 fprintf(stderr, "(\\n)");
1868 } else if (c == '\r') {
1869 fprintf(stderr, "(\\r)");
1870 } else if (c == 0x1b) {
1871 fprintf(stderr, "(\\e)");
1873 fprintf(stderr, "(%02x)", c);
1882 memset(&csiescseq, 0, sizeof(csiescseq));
1888 char *p = NULL, *dec;
1891 term.esc &= ~(ESC_STR_END|ESC_STR);
1893 par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
1895 switch (strescseq.type) {
1896 case ']': /* OSC -- Operating System Command */
1902 xsettitle(strescseq.args[1]);
1905 if (narg > 2 && allowwindowops) {
1906 dec = base64dec(strescseq.args[2]);
1911 fprintf(stderr, "erresc: invalid base64\n");
1915 case 4: /* color set */
1918 p = strescseq.args[2];
1920 case 104: /* color reset, here p = NULL */
1921 j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
1922 if (xsetcolorname(j, p)) {
1923 if (par == 104 && narg <= 1)
1924 return; /* color reset without parameter */
1925 fprintf(stderr, "erresc: invalid color j=%d, p=%s\n",
1926 j, p ? p : "(null)");
1929 * TODO if defaultbg color is changed, borders
1937 case 'k': /* old title set compatibility */
1938 xsettitle(strescseq.args[0]);
1940 case 'P': /* DCS -- Device Control String */
1941 case '_': /* APC -- Application Program Command */
1942 case '^': /* PM -- Privacy Message */
1946 fprintf(stderr, "erresc: unknown str ");
1954 char *p = strescseq.buf;
1957 strescseq.buf[strescseq.len] = '\0';
1962 while (strescseq.narg < STR_ARG_SIZ) {
1963 strescseq.args[strescseq.narg++] = p;
1964 while ((c = *p) != ';' && c != '\0')
1978 fprintf(stderr, "ESC%c", strescseq.type);
1979 for (i = 0; i < strescseq.len; i++) {
1980 c = strescseq.buf[i] & 0xff;
1984 } else if (isprint(c)) {
1986 } else if (c == '\n') {
1987 fprintf(stderr, "(\\n)");
1988 } else if (c == '\r') {
1989 fprintf(stderr, "(\\r)");
1990 } else if (c == 0x1b) {
1991 fprintf(stderr, "(\\e)");
1993 fprintf(stderr, "(%02x)", c);
1996 fprintf(stderr, "ESC\\\n");
2002 strescseq = (STREscape){
2003 .buf = xrealloc(strescseq.buf, STR_BUF_SIZ),
2009 sendbreak(const Arg *arg)
2011 if (tcsendbreak(cmdfd, 0))
2012 perror("Error sending break");
2016 tprinter(char *s, size_t len)
2018 if (iofd != -1 && xwrite(iofd, s, len) < 0) {
2019 perror("Error writing to output file");
2026 toggleprinter(const Arg *arg)
2028 term.mode ^= MODE_PRINT;
2032 printscreen(const Arg *arg)
2038 printsel(const Arg *arg)
2048 if ((ptr = getsel())) {
2049 tprinter(ptr, strlen(ptr));
2060 bp = &term.line[n][0];
2061 end = &bp[MIN(tlinelen(n), term.col) - 1];
2062 if (bp != end || bp->u != ' ') {
2063 for ( ; bp <= end; ++bp)
2064 tprinter(buf, utf8encode(bp->u, buf));
2074 for (i = 0; i < term.row; ++i)
2084 while (x < term.col && n--)
2085 for (++x; x < term.col && !term.tabs[x]; ++x)
2088 while (x > 0 && n++)
2089 for (--x; x > 0 && !term.tabs[x]; --x)
2092 term.c.x = LIMIT(x, 0, term.col-1);
2096 tdefutf8(char ascii)
2099 term.mode |= MODE_UTF8;
2100 else if (ascii == '@')
2101 term.mode &= ~MODE_UTF8;
2105 tdeftran(char ascii)
2107 static char cs[] = "0B";
2108 static int vcs[] = {CS_GRAPHIC0, CS_USA};
2111 if ((p = strchr(cs, ascii)) == NULL) {
2112 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
2114 term.trantbl[term.icharset] = vcs[p - cs];
2123 if (c == '8') { /* DEC screen alignment test. */
2124 for (x = 0; x < term.col; ++x) {
2125 for (y = 0; y < term.row; ++y)
2126 tsetchar('E', &term.c.attr, x, y);
2132 tstrsequence(uchar c)
2135 case 0x90: /* DCS -- Device Control String */
2138 case 0x9f: /* APC -- Application Program Command */
2141 case 0x9e: /* PM -- Privacy Message */
2144 case 0x9d: /* OSC -- Operating System Command */
2150 term.esc |= ESC_STR;
2154 tcontrolcode(uchar ascii)
2161 tmoveto(term.c.x-1, term.c.y);
2164 tmoveto(0, term.c.y);
2169 /* go to first col if the mode is set */
2170 tnewline(IS_SET(MODE_CRLF));
2172 case '\a': /* BEL */
2173 if (term.esc & ESC_STR_END) {
2174 /* backwards compatibility to xterm */
2180 case '\033': /* ESC */
2182 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
2183 term.esc |= ESC_START;
2185 case '\016': /* SO (LS1 -- Locking shift 1) */
2186 case '\017': /* SI (LS0 -- Locking shift 0) */
2187 term.charset = 1 - (ascii - '\016');
2189 case '\032': /* SUB */
2190 tsetchar('?', &term.c.attr, term.c.x, term.c.y);
2192 case '\030': /* CAN */
2195 case '\005': /* ENQ (IGNORED) */
2196 case '\000': /* NUL (IGNORED) */
2197 case '\021': /* XON (IGNORED) */
2198 case '\023': /* XOFF (IGNORED) */
2199 case 0177: /* DEL (IGNORED) */
2201 case 0x80: /* TODO: PAD */
2202 case 0x81: /* TODO: HOP */
2203 case 0x82: /* TODO: BPH */
2204 case 0x83: /* TODO: NBH */
2205 case 0x84: /* TODO: IND */
2207 case 0x85: /* NEL -- Next line */
2208 tnewline(1); /* always go to first col */
2210 case 0x86: /* TODO: SSA */
2211 case 0x87: /* TODO: ESA */
2213 case 0x88: /* HTS -- Horizontal tab stop */
2214 term.tabs[term.c.x] = 1;
2216 case 0x89: /* TODO: HTJ */
2217 case 0x8a: /* TODO: VTS */
2218 case 0x8b: /* TODO: PLD */
2219 case 0x8c: /* TODO: PLU */
2220 case 0x8d: /* TODO: RI */
2221 case 0x8e: /* TODO: SS2 */
2222 case 0x8f: /* TODO: SS3 */
2223 case 0x91: /* TODO: PU1 */
2224 case 0x92: /* TODO: PU2 */
2225 case 0x93: /* TODO: STS */
2226 case 0x94: /* TODO: CCH */
2227 case 0x95: /* TODO: MW */
2228 case 0x96: /* TODO: SPA */
2229 case 0x97: /* TODO: EPA */
2230 case 0x98: /* TODO: SOS */
2231 case 0x99: /* TODO: SGCI */
2233 case 0x9a: /* DECID -- Identify Terminal */
2234 ttywrite(vtiden, strlen(vtiden), 0);
2236 case 0x9b: /* TODO: CSI */
2237 case 0x9c: /* TODO: ST */
2239 case 0x90: /* DCS -- Device Control String */
2240 case 0x9d: /* OSC -- Operating System Command */
2241 case 0x9e: /* PM -- Privacy Message */
2242 case 0x9f: /* APC -- Application Program Command */
2243 tstrsequence(ascii);
2246 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2247 term.esc &= ~(ESC_STR_END|ESC_STR);
2251 * returns 1 when the sequence is finished and it hasn't to read
2252 * more characters for this sequence, otherwise 0
2255 eschandle(uchar ascii)
2259 term.esc |= ESC_CSI;
2262 term.esc |= ESC_TEST;
2265 term.esc |= ESC_UTF8;
2267 case 'P': /* DCS -- Device Control String */
2268 case '_': /* APC -- Application Program Command */
2269 case '^': /* PM -- Privacy Message */
2270 case ']': /* OSC -- Operating System Command */
2271 case 'k': /* old title set compatibility */
2272 tstrsequence(ascii);
2274 case 'n': /* LS2 -- Locking shift 2 */
2275 case 'o': /* LS3 -- Locking shift 3 */
2276 term.charset = 2 + (ascii - 'n');
2278 case '(': /* GZD4 -- set primary charset G0 */
2279 case ')': /* G1D4 -- set secondary charset G1 */
2280 case '*': /* G2D4 -- set tertiary charset G2 */
2281 case '+': /* G3D4 -- set quaternary charset G3 */
2282 term.icharset = ascii - '(';
2283 term.esc |= ESC_ALTCHARSET;
2285 case 'D': /* IND -- Linefeed */
2286 if (term.c.y == term.bot) {
2287 tscrollup(term.top, 1);
2289 tmoveto(term.c.x, term.c.y+1);
2292 case 'E': /* NEL -- Next line */
2293 tnewline(1); /* always go to first col */
2295 case 'H': /* HTS -- Horizontal tab stop */
2296 term.tabs[term.c.x] = 1;
2298 case 'M': /* RI -- Reverse index */
2299 if (term.c.y == term.top) {
2300 tscrolldown(term.top, 1);
2302 tmoveto(term.c.x, term.c.y-1);
2305 case 'Z': /* DECID -- Identify Terminal */
2306 ttywrite(vtiden, strlen(vtiden), 0);
2308 case 'c': /* RIS -- Reset to initial state */
2313 case '=': /* DECPAM -- Application keypad */
2314 xsetmode(1, MODE_APPKEYPAD);
2316 case '>': /* DECPNM -- Normal keypad */
2317 xsetmode(0, MODE_APPKEYPAD);
2319 case '7': /* DECSC -- Save Cursor */
2320 tcursor(CURSOR_SAVE);
2322 case '8': /* DECRC -- Restore Cursor */
2323 tcursor(CURSOR_LOAD);
2325 case '\\': /* ST -- String Terminator */
2326 if (term.esc & ESC_STR_END)
2330 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2331 (uchar) ascii, isprint(ascii)? ascii:'.');
2345 control = ISCONTROL(u);
2346 if (u < 127 || !IS_SET(MODE_UTF8)) {
2350 len = utf8encode(u, c);
2351 if (!control && (width = wcwidth(u)) == -1)
2355 if (IS_SET(MODE_PRINT))
2359 * STR sequence must be checked before anything else
2360 * because it uses all following characters until it
2361 * receives a ESC, a SUB, a ST or any other C1 control
2364 if (term.esc & ESC_STR) {
2365 if (u == '\a' || u == 030 || u == 032 || u == 033 ||
2367 term.esc &= ~(ESC_START|ESC_STR);
2368 term.esc |= ESC_STR_END;
2369 goto check_control_code;
2372 if (strescseq.len+len >= strescseq.siz) {
2374 * Here is a bug in terminals. If the user never sends
2375 * some code to stop the str or esc command, then st
2376 * will stop responding. But this is better than
2377 * silently failing with unknown characters. At least
2378 * then users will report back.
2380 * In the case users ever get fixed, here is the code:
2386 if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2)
2389 strescseq.buf = xrealloc(strescseq.buf, strescseq.siz);
2392 memmove(&strescseq.buf[strescseq.len], c, len);
2393 strescseq.len += len;
2399 * Actions of control codes must be performed as soon they arrive
2400 * because they can be embedded inside a control sequence, and
2401 * they must not cause conflicts with sequences.
2406 * control codes are not shown ever
2411 } else if (term.esc & ESC_START) {
2412 if (term.esc & ESC_CSI) {
2413 csiescseq.buf[csiescseq.len++] = u;
2414 if (BETWEEN(u, 0x40, 0x7E)
2415 || csiescseq.len >= \
2416 sizeof(csiescseq.buf)-1) {
2422 } else if (term.esc & ESC_UTF8) {
2424 } else if (term.esc & ESC_ALTCHARSET) {
2426 } else if (term.esc & ESC_TEST) {
2431 /* sequence already finished */
2435 * All characters which form part of a sequence are not
2440 //if (selected(term.c.x, term.c.y))
2443 gp = &term.line[term.c.y][term.c.x];
2444 if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
2445 gp->mode |= ATTR_WRAP;
2447 gp = &term.line[term.c.y][term.c.x];
2450 if (IS_SET(MODE_INSERT) && term.c.x+width < term.col)
2451 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
2453 if (term.c.x+width > term.col) {
2455 gp = &term.line[term.c.y][term.c.x];
2458 tsetchar(u, &term.c.attr, term.c.x, term.c.y);
2462 gp->mode |= ATTR_WIDE;
2463 if (term.c.x+1 < term.col) {
2465 gp[1].mode = ATTR_WDUMMY;
2468 if (term.c.x+width < term.col) {
2469 tmoveto(term.c.x+width, term.c.y);
2471 term.c.state |= CURSOR_WRAPNEXT;
2476 twrite(const char *buf, int buflen, int show_ctrl)
2482 for (n = 0; n < buflen; n += charsize) {
2483 if (IS_SET(MODE_UTF8)) {
2484 /* process a complete utf8 char */
2485 charsize = utf8decode(buf + n, &u, buflen - n);
2492 if (show_ctrl && ISCONTROL(u)) {
2497 } else if (u != '\n' && u != '\r' && u != '\t') {
2508 tresize(int col, int row)
2511 int const colSet = col, alt = IS_SET(MODE_ALTSCREEN), ini = buf == NULL;
2512 col = MAX(col, buffCols);
2513 row = MIN(row, buffSize);
2514 int const minrow = MIN(row, term.row), mincol = MIN(col, buffCols);
2518 if (col < 1 || row < 1) {
2520 "tresize: error resizing to %dx%d\n", col, row);
2523 if (alt) tswapscreen();
2526 * slide screen to keep cursor where we expect it -
2527 * tscrollup would work here, but we can optimize to
2528 * memmove because we're freeing the earlier lines
2530 for (i = 0; i <= term.c.y - row; i++) {
2533 /* ensure that both src and dst are not NULL */
2535 memmove(term.alt, term.alt + i, row * sizeof(Line));
2537 for (i += row; i < term.row; i++) {
2541 /* resize to new height */
2542 buf = xrealloc(buf, (buffSize + row) * sizeof(Line));
2543 term.alt = xrealloc(term.alt, row * sizeof(Line));
2544 term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
2545 mark = xrealloc(mark, col * row * sizeof(*mark));
2546 term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
2548 /* resize each row to new width, zero-pad if needed */
2549 for (i = 0; i < minrow; i++) {
2550 term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph));
2553 /* allocate any new rows */
2554 for (/* i = minrow */; i < row; i++) {
2555 term.alt[i] = xmalloc(col * sizeof(Glyph));
2557 if (col > buffCols) {
2558 bp = term.tabs + buffCols;
2560 memset(bp, 0, sizeof(*term.tabs) * (col - buffCols));
2561 while (--bp > term.tabs && !*bp)
2563 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
2566 Glyph g=(Glyph){.bg=term.c.attr.bg, .fg=term.c.attr.fg, .u=' ', .mode=0};
2567 for (i = 0; i < buffSize; ++i) {
2568 buf[i] = xrealloc(ini ? NULL : buf[i], col*sizeof(Glyph));
2569 for (int j = ini ? 0 : buffCols; j < col; ++j) buf[i][j] = g;
2571 for (i = 0; i < row; ++i) buf[buffSize + i] = buf[i];
2572 term.line = &buf[*(histOp?&histOff:&insertOff) +=MAX(term.c.y-row+1,0)];
2573 memset(mark, 0, col * row * sizeof(*mark));
2574 /* update terminal size */
2578 if (alt) tswapscreen();
2579 /* reset scrolling region */
2580 tsetscroll(0, row-1);
2581 /* make use of the LIMIT in tmoveto */
2582 tmoveto(term.c.x, term.c.y);
2583 /* Clearing both screens (it makes dirty all lines) */
2585 for (i = 0; i < 2; i++) {
2586 if (mincol < col && 0 < minrow) {
2587 tclearregion(mincol, 0, col - 1, minrow - 1);
2589 if (0 < col && minrow < row) {
2590 tclearregion(0, minrow, col - 1, row - 1);
2593 tcursor(CURSOR_LOAD);
2605 drawregion(int x1, int y1, int x2, int y2)
2607 if (altToggle && histMode && !histOp)
2608 memset(term.dirty, 0, sizeof(*term.dirty) * term.row);
2609 int const o = !IS_SET(MODE_ALTSCREEN) && histMode && !histOp, h =rows();
2612 for (y = y1; y < y2; y++) {
2613 int const oy = o ? (y + insertOff - histOff + h) % h : y;
2614 if (!BETWEEN(oy, 0, term.row-1) || !term.dirty[y]) continue;
2615 xdrawline(term.line[y], x1, oy, x2);
2617 memset(&term.dirty[y1], 0, sizeof(*term.dirty) * (y2 - y1));
2623 int cx = term.c.x, ocx = term.ocx, ocy = term.ocy;
2628 /* adjust cursor position */
2629 LIMIT(term.ocx, 0, term.col-1);
2630 LIMIT(term.ocy, 0, term.row-1);
2631 if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY)
2633 if (term.line[term.c.y][cx].mode & ATTR_WDUMMY)
2636 if (histMode) historyPreDraw();
2637 drawregion(0, 0, term.col, term.row);
2639 xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
2640 term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
2642 term.ocy = term.c.y;
2644 if (ocx != term.ocx || ocy != term.ocy)
2645 xximspot(term.ocx, term.ocy);