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 void stty(char **);
161 static void sigchld(int);
162 static void ttywriteraw(const char *, size_t);
164 static void csidump(void);
165 static void csihandle(void);
166 static void csiparse(void);
167 static void csireset(void);
168 static int eschandle(uchar);
169 static void strdump(void);
170 static void strhandle(void);
171 static void strparse(void);
172 static void strreset(void);
174 static void tprinter(char *, size_t);
175 static void tdumpsel(void);
176 static void tdumpline(int);
177 static void tdump(void);
178 static void tclearregion(int, int, int, int);
179 static void tcursor(int);
180 static void tdeletechar(int);
181 static void tdeleteline(int);
182 static void tinsertblank(int);
183 static void tinsertblankline(int);
184 static int tlinelen(int);
185 static void tmoveto(int, int);
186 static void tmoveato(int, int);
187 static void tnewline(int);
188 static void tputtab(int);
189 static void tputc(Rune);
190 static void treset(void);
191 static void tscrollup(int, int);
192 static void tscrolldown(int, int);
193 static void tsetattr(int *, int);
194 static void tsetchar(Rune, Glyph *, int, int);
195 static void tsetdirt(int, int);
196 static void tsetscroll(int, int);
197 static void tswapscreen(void);
198 static void tsetmode(int, int, int *, int);
199 static int twrite(const char *, int, int);
200 static void tfulldirt(void);
201 static void tcontrolcode(uchar );
202 static void tdectest(char );
203 static void tdefutf8(char);
204 static int32_t tdefcolor(int *, int *, int);
205 static void tdeftran(char);
206 static void tstrsequence(uchar);
208 static void drawregion(int, int, int, int);
210 static void selscroll(int, int);
211 static void selnormalize(void);
213 static size_t utf8decode(const char *, Rune *, size_t);
214 static Rune utf8decodebyte(char, size_t *);
215 static char utf8encodebyte(Rune, size_t);
216 static size_t utf8validate(Rune *, size_t);
218 static char *base64dec(const char *);
219 static char base64dec_getc(const char **);
221 static ssize_t xwrite(int, const char *, size_t);
225 static Selection sel;
226 static CSIEscape csiescseq;
227 static STREscape strescseq;
232 static uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
233 static uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
234 static Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000};
235 static Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
238 extern int const buffSize;
239 int histOp, histMode, histOff, histOffX, insertOff, altToggle, *mark;
242 static inline int rows() { return IS_SET(MODE_ALTSCREEN) ? term.row : buffSize;}
243 static inline int rangeY(int i) { while (i < 0) i += rows(); return i % rows();}
246 xwrite(int fd, const char *s, size_t len)
252 r = write(fd, s, len);
267 if (!(p = malloc(len)))
268 die("malloc: %s\n", strerror(errno));
274 xrealloc(void *p, size_t len)
276 if ((p = realloc(p, len)) == NULL)
277 die("realloc: %s\n", strerror(errno));
285 if ((s = strdup(s)) == NULL)
286 die("strdup: %s\n", strerror(errno));
292 utf8decode(const char *c, Rune *u, size_t clen)
294 size_t i, j, len, type;
300 udecoded = utf8decodebyte(c[0], &len);
301 if (!BETWEEN(len, 1, UTF_SIZ))
303 for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
304 udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
311 utf8validate(u, len);
317 utf8decodebyte(char c, size_t *i)
319 for (*i = 0; *i < LEN(utfmask); ++(*i))
320 if (((uchar)c & utfmask[*i]) == utfbyte[*i])
321 return (uchar)c & ~utfmask[*i];
327 utf8encode(Rune u, char *c)
331 len = utf8validate(&u, 0);
335 for (i = len - 1; i != 0; --i) {
336 c[i] = utf8encodebyte(u, 0);
339 c[0] = utf8encodebyte(u, len);
345 utf8encodebyte(Rune u, size_t i)
347 return utfbyte[i] | (u & ~utfmask[i]);
351 utf8validate(Rune *u, size_t i)
353 if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF))
355 for (i = 1; *u > utfmax[i]; ++i)
361 static const char base64_digits[] = {
362 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
363 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0,
364 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
365 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
366 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
367 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0,
368 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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
377 base64dec_getc(const char **src)
379 while (**src && !isprint(**src))
381 return **src ? *((*src)++) : '='; /* emulate padding if string ends */
385 base64dec(const char *src)
387 size_t in_len = strlen(src);
391 in_len += 4 - (in_len % 4);
392 result = dst = xmalloc(in_len / 4 * 3 + 1);
394 int a = base64_digits[(unsigned char) base64dec_getc(&src)];
395 int b = base64_digits[(unsigned char) base64dec_getc(&src)];
396 int c = base64_digits[(unsigned char) base64dec_getc(&src)];
397 int d = base64_digits[(unsigned char) base64dec_getc(&src)];
399 /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */
400 if (a == -1 || b == -1)
403 *dst++ = (a << 2) | ((b & 0x30) >> 4);
406 *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2);
409 *dst++ = ((c & 0x03) << 6) | d;
428 if (term.line[y][i - 1].mode & ATTR_WRAP)
431 while (i > 0 && term.line[y][i - 1].u == ' ')
437 void historyOpToggle(int start, int paint) {
438 if (!histOp == !(histOp + start)) if ((histOp += start) || 1) return;
439 if (histMode && paint && (!IS_SET(MODE_ALTSCREEN) || altToggle)) draw();
440 tcursor(CURSOR_SAVE);
442 if (histMode && altToggle) {
444 memset(term.dirty,0,sizeof(*term.dirty)*term.row);
446 tcursor(CURSOR_LOAD);
447 *(!IS_SET(MODE_ALTSCREEN)?&term.line:&term.alt)=&buf[histOp?histOff:insertOff];
450 void historyModeToggle(int start) {
451 if (!(histMode = (histOp = !!start))) {
455 tcursor(CURSOR_SAVE);
461 int historyBufferScroll(int n) {
462 if (IS_SET(MODE_ALTSCREEN) || !n) return histOp;
463 int p=abs(n=(n<0) ? max(n,-term.row) : min(n,term.row)), r=term.row-p,
464 s=sizeof(*term.dirty), *ptr=histOp?&histOff:&insertOff;
465 if (!histMode || histOp) tfulldirt(); else {
466 memmove(&term.dirty[-min(n,0)], &term.dirty[max(n,0)], s*r);
467 memset(&term.dirty[n>0 ? r : 0], 0, s * p);
469 int const prevOffBuf = sel.alt ? 0 : insertOff + term.row;
470 term.line = &buf[*ptr = (buffSize+*ptr+n) % buffSize];
471 // Cut part of selection removed from buffer, and update sel.ne/b.
472 if (sel.ob.x != -1 && !histOp && n) {
473 int const offBuf = sel.alt ? 0 : insertOff + term.row,
474 pb = rangeY(sel.ob.y - prevOffBuf),
475 pe = rangeY(sel.oe.y - prevOffBuf);
476 int const b = rangeY(sel.ob.y - offBuf), nln = n < 0,
477 e = rangeY(sel.oe.y - offBuf), last = offBuf - nln;
478 if (pb != b && ((pb < b) != nln)) sel.ob.y = last;
479 if (pe != e && ((pe < e) != nln)) sel.oe.y = last;
480 if (sel.oe.y == last && sel.ob.y == last) selclear();
483 // Clear the new region exposed by the shift.
484 if (!histOp) tclearregion(0, n>0?r+1:0, buffCols-1, n>0?term.row:p-1);
488 int historyMove(int x, int y, int ly) {
489 historyOpToggle(1, 1);
490 y += ((term.c.x += x) < 0 ?term.c.x-term.col :term.c.x) / term.col;//< x
491 if ((term.c.x %= term.col) < 0) term.c.x += term.col;
492 if ((term.c.y += y) >= term.row) ly += term.c.y - term.row + 1; //< y
493 else if (term.c.y < 0) ly += term.c.y;
494 term.c.y = MIN(MAX(term.c.y, 0), term.row - 1);
495 int off=insertOff-histOff, bot=rangeY(off), top=-rangeY(-term.row-off),
496 pTop = (-ly>-top), pBot = (ly > bot), fin=histMode&&(pTop||pBot);
497 if (fin && (x||y)) term.c.x = pBot ? term.col-1 : 0;
498 historyBufferScroll(fin ? (pBot ? bot : top) : ly);
499 historyOpToggle(-1, 1);
503 #include "normalMode.c"
505 void selnormalize(void) {
506 historyOpToggle(1, 1);
508 int const oldb = sel.nb.y, olde = sel.ne.y;
509 if (sel.ob.x == -1) {
510 sel.ne.y = sel.nb.y = -1;
512 int const offsetBuffer = sel.alt ? 0 : insertOff + term.row;
513 int const off = sel.alt ? 0 : (histMode ? histOff : insertOff);
514 int const nby = rangeY(sel.ob.y - off),
515 ney = rangeY(sel.oe.y - off);
516 sel.swap = rangeY(sel.ob.y - offsetBuffer)
517 > rangeY(sel.oe.y - offsetBuffer);
518 sel.nb.y = sel.swap ? ney : nby;
519 sel.ne.y = !sel.swap ? ney : nby;
520 int const cnb = sel.nb.y < term.row, cne = sel.ne.y < term.row;
521 if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) {
522 if (cnb) sel.nb.x = (!sel.swap) ? sel.ob.x : sel.oe.x;
523 if (cne) sel.ne.x = (!sel.swap) ? sel.oe.x : sel.ob.x;
525 if (cnb) sel.nb.x = MIN(sel.ob.x, sel.oe.x);
526 if (cne) sel.ne.x = MAX(sel.ob.x, sel.oe.x);
529 int const nBet=sel.nb.y<=sel.ne.y, oBet=oldb<=olde;
530 for (int i = 0; i < term.row; ++i) {
531 int const n = nBet ? BETWEEN(i, sel.nb.y, sel.ne.y)
532 : OUT(i, sel.nb.y, sel.ne.y);
533 term.dirty[i] |= (sel.type == SEL_RECTANGULAR && n) ||
534 (n != (oBet ? BETWEEN(i,oldb,olde) : OUT(i,oldb,olde)));
537 if (BETWEEN(oldb, 0, term.row - 1)) term.dirty[oldb] = 1;
538 if (BETWEEN(olde, 0, term.row - 1)) term.dirty[olde] = 1;
539 if (BETWEEN(sel.nb.y, 0, term.row - 1)) term.dirty[sel.nb.y] = 1;
540 if (BETWEEN(sel.ne.y, 0, term.row - 1)) term.dirty[sel.ne.y] = 1;
542 historyOpToggle(-1, 1);
546 selstart(int col, int row, int snap)
549 sel.mode = SEL_EMPTY;
550 sel.type = SEL_REGULAR;
551 sel.alt = IS_SET(MODE_ALTSCREEN);
553 sel.oe.x = sel.ob.x = col;
554 sel.oe.y = sel.ob.y = row + !sel.alt * (histMode ? histOff : insertOff);
555 if (sel.snap != 0) sel.mode = SEL_READY;
560 selextend(int col, int row, int type, int done)
562 if (sel.mode == SEL_IDLE)
564 if (done && sel.mode == SEL_EMPTY) {
570 sel.oe.y = row + (sel.alt ? 0 : (histMode ? histOff : insertOff));
573 sel.mode = done ? SEL_IDLE : SEL_READY;
577 selected(int x, int y)
579 if (sel.mode == SEL_EMPTY || sel.ob.x == -1 ||
580 sel.alt != IS_SET(MODE_ALTSCREEN))
583 if (sel.type == SEL_RECTANGULAR)
584 return BETWEEN(y, sel.nb.y, sel.ne.y)
585 && BETWEEN(x, sel.nb.x, sel.ne.x);
587 return ((sel.nb.y > sel.ne.y) ? OUT(y, sel.nb.y, sel.ne.y)
588 : BETWEEN(y, sel.nb.y, sel.ne.y)) &&
589 (y != sel.nb.y || x >= sel.nb.x) &&
590 (y != sel.ne.y || x <= sel.ne.x);
597 int y, yy, bufsize, lastx;
603 int const start = sel.swap ? sel.oe.y : sel.ob.y, h = rows();
604 int endy = (sel.swap ? sel.ob.y : sel.oe.y);
605 for (; endy < start; endy += h);
606 Line * const cbuf = IS_SET(MODE_ALTSCREEN) ? term.line : buf;
607 bufsize = (term.col+1) * (endy-start+1 ) * UTF_SIZ;
609 ptr = str = xmalloc(bufsize);
611 /* append every set & selected glyph to the selection */
612 for (y = start; y <= endy; y++) {
615 if (sel.type == SEL_RECTANGULAR) {
616 gp = &cbuf[yy][sel.nb.x];
619 gp = &cbuf[yy][start == y ? sel.nb.x : 0];
620 lastx = (endy == y) ? sel.ne.x : term.col-1;
622 last = &cbuf[yy][lastx];
623 if (!(cbuf[yy][term.col - 1].mode & ATTR_WRAP))
624 while (last > gp && last->u == ' ') --last;
626 for ( ; gp <= last; ++gp) {
627 if (gp->mode & ATTR_WDUMMY) continue;
628 ptr += utf8encode(gp->u, ptr);
632 * Copy and pasting of line endings is inconsistent
633 * in the inconsistent terminal and GUI world.
634 * The best solution seems like to produce '\n' when
635 * something is copied from st and convert '\n' to
636 * '\r', when something to be pasted is received by
638 * FIXME: Fix the computer world.
640 if ((y < endy || lastx >= term.col - 1) &&
641 (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR))
659 die(const char *errstr, ...)
663 va_start(ap, errstr);
664 vfprintf(stderr, errstr, ap);
670 execsh(char *cmd, char **args)
672 char *sh, *prog, *arg;
673 const struct passwd *pw;
676 if ((pw = getpwuid(getuid())) == NULL) {
678 die("getpwuid: %s\n", strerror(errno));
680 die("who are you?\n");
683 if ((sh = getenv("SHELL")) == NULL)
684 sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd;
691 arg = utmp ? utmp : sh;
699 DEFAULT(args, ((char *[]) {prog, arg, NULL}));
704 setenv("LOGNAME", pw->pw_name, 1);
705 setenv("USER", pw->pw_name, 1);
706 setenv("SHELL", sh, 1);
707 setenv("HOME", pw->pw_dir, 1);
708 setenv("TERM", termname, 1);
710 signal(SIGCHLD, SIG_DFL);
711 signal(SIGHUP, SIG_DFL);
712 signal(SIGINT, SIG_DFL);
713 signal(SIGQUIT, SIG_DFL);
714 signal(SIGTERM, SIG_DFL);
715 signal(SIGALRM, SIG_DFL);
727 if ((p = waitpid(pid, &stat, WNOHANG)) < 0)
728 die("waiting for pid %hd failed: %s\n", pid, strerror(errno));
733 if (WIFEXITED(stat) && WEXITSTATUS(stat))
734 die("child exited with status %d\n", WEXITSTATUS(stat));
735 else if (WIFSIGNALED(stat))
736 die("child terminated due to signal %d\n", WTERMSIG(stat));
743 char cmd[_POSIX_ARG_MAX], **p, *q, *s;
746 if ((n = strlen(stty_args)) > sizeof(cmd)-1)
747 die("incorrect stty parameters\n");
748 memcpy(cmd, stty_args, n);
750 siz = sizeof(cmd) - n;
751 for (p = args; p && (s = *p); ++p) {
752 if ((n = strlen(s)) > siz-1)
753 die("stty parameter length too long\n");
760 if (system(cmd) != 0)
761 perror("Couldn't call stty");
765 ttynew(char *line, char *cmd, char *out, char **args)
770 term.mode |= MODE_PRINT;
771 iofd = (!strcmp(out, "-")) ?
772 1 : open(out, O_WRONLY | O_CREAT, 0666);
774 fprintf(stderr, "Error opening %s:%s\n",
775 out, strerror(errno));
780 if ((cmdfd = open(line, O_RDWR)) < 0)
781 die("open line '%s' failed: %s\n",
782 line, strerror(errno));
788 /* seems to work fine on linux, openbsd and freebsd */
789 if (openpty(&m, &s, NULL, NULL, NULL) < 0)
790 die("openpty failed: %s\n", strerror(errno));
792 switch (pid = fork()) {
794 die("fork failed: %s\n", strerror(errno));
798 setsid(); /* create a new process group */
802 if (ioctl(s, TIOCSCTTY, NULL) < 0)
803 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno));
807 if (pledge("stdio getpw proc exec", NULL) == -1)
814 if (pledge("stdio rpath tty proc", NULL) == -1)
819 signal(SIGCHLD, sigchld);
828 static char buf[BUFSIZ];
829 static int buflen = 0;
832 /* append read bytes to unprocessed bytes */
833 ret = read(cmdfd, buf+buflen, LEN(buf)-buflen);
839 die("couldn't read from shell: %s\n", strerror(errno));
842 written = twrite(buf, buflen, 0);
844 /* keep any incomplete UTF-8 byte sequence for the next call */
846 memmove(buf, buf + written, buflen);
852 ttywrite(const char *s, size_t n, int may_echo)
856 if (may_echo && IS_SET(MODE_ECHO))
859 if (!IS_SET(MODE_CRLF)) {
864 /* This is similar to how the kernel handles ONLCR for ttys */
868 ttywriteraw("\r\n", 2);
870 next = memchr(s, '\r', n);
871 DEFAULT(next, s + n);
872 ttywriteraw(s, next - s);
880 ttywriteraw(const char *s, size_t n)
887 * Remember that we are using a pty, which might be a modem line.
888 * Writing too much will clog the line. That's why we are doing this
890 * FIXME: Migrate the world to Plan 9.
898 /* Check if we can write. */
899 if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) {
902 die("select failed: %s\n", strerror(errno));
904 if (FD_ISSET(cmdfd, &wfd)) {
906 * Only write the bytes written by ttywrite() or the
907 * default of 256. This seems to be a reasonable value
908 * for a serial line. Bigger values might clog the I/O.
910 if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0)
914 * We weren't able to write out everything.
915 * This means the buffer is getting full
923 /* All bytes have been written. */
927 if (FD_ISSET(cmdfd, &rfd))
933 die("write error on tty: %s\n", strerror(errno));
937 ttyresize(int tw, int th)
945 if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
946 fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
952 /* Send SIGHUP to shell */
961 for (i = 0; i < term.row-1; i++) {
962 for (j = 0; j < term.col-1; j++) {
963 if (term.line[i][j].mode & attr)
972 tsetdirt(int top, int bot)
976 LIMIT(top, 0, term.row-1);
977 LIMIT(bot, 0, term.row-1);
979 for (i = top; i <= bot; i++)
984 tsetdirtattr(int attr)
988 for (i = 0; i < term.row-1; i++) {
989 for (j = 0; j < term.col-1; j++) {
990 if (term.line[i][j].mode & attr) {
1001 tsetdirt(0, term.row-1);
1007 int alt = (histOp) ? 0 : (IS_SET(MODE_ALTSCREEN) + 1);
1009 if (mode == CURSOR_SAVE) {
1011 } else if (mode == CURSOR_LOAD) {
1013 tmoveto(c[alt].x, c[alt].y);
1022 term.c = (TCursor){{
1026 }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
1028 memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1029 for (i = tabspaces; i < term.col; i += tabspaces)
1032 term.bot = term.row - 1;
1033 term.mode = MODE_WRAP|MODE_UTF8;
1034 memset(term.trantbl, CS_USA, sizeof(term.trantbl));
1037 for (i = 0; i < 2; i++) {
1039 tcursor(CURSOR_SAVE);
1040 tclearregion(0, 0, term.col-1, term.row-1);
1046 tnew(int col, int row)
1048 term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
1056 Line *tmp = term.line;
1058 term.line = term.alt;
1060 term.mode ^= MODE_ALTSCREEN;
1065 tscrolldown(int orig, int n)
1067 if (historyBufferScroll(-n)) return;
1071 LIMIT(n, 0, term.bot-orig+1);
1073 tsetdirt(orig, term.bot-n);
1074 tclearregion(0, term.bot-n+1, term.col-1, term.bot);
1076 for (i = term.bot; i >= orig+n; i--) {
1077 temp = term.line[i];
1078 term.line[i] = term.line[i-n];
1079 term.line[i-n] = temp;
1086 tscrollup(int orig, int n)
1088 if (historyBufferScroll(n)) return;
1092 LIMIT(n, 0, term.bot-orig+1);
1094 tclearregion(0, orig, term.col-1, orig+n-1);
1095 tsetdirt(orig+n, term.bot);
1097 for (i = orig; i <= term.bot-n; i++) {
1098 temp = term.line[i];
1099 term.line[i] = term.line[i+n];
1100 term.line[i+n] = temp;
1103 selscroll(orig, -n);
1107 selscroll(int orig, int n)
1112 if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) {
1114 } else if (BETWEEN(sel.nb.y, orig, term.bot)) {
1117 if (sel.ob.y < term.top || sel.ob.y > term.bot ||
1118 sel.oe.y < term.top || sel.oe.y > term.bot) {
1127 tnewline(int first_col)
1131 if (y == term.bot) {
1132 tscrollup(term.top, 1);
1136 tmoveto(first_col ? 0 : term.c.x, y);
1142 char *p = csiescseq.buf, *np;
1151 csiescseq.buf[csiescseq.len] = '\0';
1152 while (p < csiescseq.buf+csiescseq.len) {
1154 v = strtol(p, &np, 10);
1157 if (v == LONG_MAX || v == LONG_MIN)
1159 csiescseq.arg[csiescseq.narg++] = v;
1161 if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ)
1165 csiescseq.mode[0] = *p++;
1166 csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0';
1169 /* for absolute user moves, when decom is set */
1171 tmoveato(int x, int y)
1173 tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0));
1177 tmoveto(int x, int y)
1181 if (term.c.state & CURSOR_ORIGIN) {
1186 maxy = term.row - 1;
1188 term.c.state &= ~CURSOR_WRAPNEXT;
1189 term.c.x = LIMIT(x, 0, term.col-1);
1190 term.c.y = LIMIT(y, miny, maxy);
1194 tsetchar(Rune u, Glyph *attr, int x, int y)
1196 static char *vt100_0[62] = { /* 0x41 - 0x7e */
1197 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1198 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1199 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1200 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1201 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1202 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1203 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1204 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1208 * The table is proudly stolen from rxvt.
1210 if (term.trantbl[term.charset] == CS_GRAPHIC0 &&
1211 BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
1212 utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ);
1214 if (term.line[y][x].mode & ATTR_WIDE) {
1215 if (x+1 < term.col) {
1216 term.line[y][x+1].u = ' ';
1217 term.line[y][x+1].mode &= ~ATTR_WDUMMY;
1219 } else if (term.line[y][x].mode & ATTR_WDUMMY) {
1220 term.line[y][x-1].u = ' ';
1221 term.line[y][x-1].mode &= ~ATTR_WIDE;
1225 term.line[y][x] = *attr;
1226 term.line[y][x].u = u;
1230 tclearregion(int x1, int y1, int x2, int y2)
1236 temp = x1, x1 = x2, x2 = temp;
1238 temp = y1, y1 = y2, y2 = temp;
1240 LIMIT(x1, 0, buffCols-1);
1241 LIMIT(x2, 0, buffCols-1);
1242 LIMIT(y1, 0, term.row-1);
1243 LIMIT(y2, 0, term.row-1);
1245 for (y = y1; y <= y2; y++) {
1247 for (x = x1; x <= x2; x++) {
1248 gp = &term.line[y][x];
1249 gp->fg = term.c.attr.fg;
1250 gp->bg = term.c.attr.bg;
1263 LIMIT(n, 0, term.col - term.c.x);
1267 size = term.col - src;
1268 line = term.line[term.c.y];
1270 memmove(&line[dst], &line[src], size * sizeof(Glyph));
1271 tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
1280 LIMIT(n, 0, term.col - term.c.x);
1284 size = term.col - dst;
1285 line = term.line[term.c.y];
1287 memmove(&line[dst], &line[src], size * sizeof(Glyph));
1288 tclearregion(src, term.c.y, dst - 1, term.c.y);
1292 tinsertblankline(int n)
1294 if (BETWEEN(term.c.y, term.top, term.bot))
1295 tscrolldown(term.c.y, n);
1301 if (BETWEEN(term.c.y, term.top, term.bot))
1302 tscrollup(term.c.y, n);
1306 tdefcolor(int *attr, int *npar, int l)
1311 switch (attr[*npar + 1]) {
1312 case 2: /* direct color in RGB space */
1313 if (*npar + 4 >= l) {
1315 "erresc(38): Incorrect number of parameters (%d)\n",
1319 r = attr[*npar + 2];
1320 g = attr[*npar + 3];
1321 b = attr[*npar + 4];
1323 if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255))
1324 fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n",
1327 idx = TRUECOLOR(r, g, b);
1329 case 5: /* indexed color */
1330 if (*npar + 2 >= l) {
1332 "erresc(38): Incorrect number of parameters (%d)\n",
1337 if (!BETWEEN(attr[*npar], 0, 255))
1338 fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]);
1342 case 0: /* implemented defined (only foreground) */
1343 case 1: /* transparent */
1344 case 3: /* direct color in CMY space */
1345 case 4: /* direct color in CMYK space */
1348 "erresc(38): gfx attr %d unknown\n", attr[*npar]);
1356 tsetattr(int *attr, int l)
1361 for (i = 0; i < l; i++) {
1364 term.c.attr.mode &= ~(
1373 term.c.attr.fg = defaultfg;
1374 term.c.attr.bg = defaultbg;
1377 term.c.attr.mode |= ATTR_BOLD;
1380 term.c.attr.mode |= ATTR_FAINT;
1383 term.c.attr.mode |= ATTR_ITALIC;
1386 term.c.attr.mode |= ATTR_UNDERLINE;
1388 case 5: /* slow blink */
1390 case 6: /* rapid blink */
1391 term.c.attr.mode |= ATTR_BLINK;
1394 term.c.attr.mode |= ATTR_REVERSE;
1397 term.c.attr.mode |= ATTR_INVISIBLE;
1400 term.c.attr.mode |= ATTR_STRUCK;
1403 term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT);
1406 term.c.attr.mode &= ~ATTR_ITALIC;
1409 term.c.attr.mode &= ~ATTR_UNDERLINE;
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 if ((idx = tdefcolor(attr, &i, l)) >= 0)
1425 term.c.attr.fg = idx;
1428 term.c.attr.fg = defaultfg;
1431 if ((idx = tdefcolor(attr, &i, l)) >= 0)
1432 term.c.attr.bg = idx;
1435 term.c.attr.bg = defaultbg;
1438 if (BETWEEN(attr[i], 30, 37)) {
1439 term.c.attr.fg = attr[i] - 30;
1440 } else if (BETWEEN(attr[i], 40, 47)) {
1441 term.c.attr.bg = attr[i] - 40;
1442 } else if (BETWEEN(attr[i], 90, 97)) {
1443 term.c.attr.fg = attr[i] - 90 + 8;
1444 } else if (BETWEEN(attr[i], 100, 107)) {
1445 term.c.attr.bg = attr[i] - 100 + 8;
1448 "erresc(default): gfx attr %d unknown\n",
1458 tsetscroll(int t, int b)
1462 LIMIT(t, 0, term.row-1);
1463 LIMIT(b, 0, term.row-1);
1474 tsetmode(int priv, int set, int *args, int narg)
1478 for (lim = args + narg; args < lim; ++args) {
1481 case 1: /* DECCKM -- Cursor key */
1482 xsetmode(set, MODE_APPCURSOR);
1484 case 5: /* DECSCNM -- Reverse video */
1485 xsetmode(set, MODE_REVERSE);
1487 case 6: /* DECOM -- Origin */
1488 MODBIT(term.c.state, set, CURSOR_ORIGIN);
1491 case 7: /* DECAWM -- Auto wrap */
1492 MODBIT(term.mode, set, MODE_WRAP);
1494 case 0: /* Error (IGNORED) */
1495 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1496 case 3: /* DECCOLM -- Column (IGNORED) */
1497 case 4: /* DECSCLM -- Scroll (IGNORED) */
1498 case 8: /* DECARM -- Auto repeat (IGNORED) */
1499 case 18: /* DECPFF -- Printer feed (IGNORED) */
1500 case 19: /* DECPEX -- Printer extent (IGNORED) */
1501 case 42: /* DECNRCM -- National characters (IGNORED) */
1502 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1504 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1505 xsetmode(!set, MODE_HIDE);
1507 case 9: /* X10 mouse compatibility mode */
1508 xsetpointermotion(0);
1509 xsetmode(0, MODE_MOUSE);
1510 xsetmode(set, MODE_MOUSEX10);
1512 case 1000: /* 1000: report button press */
1513 xsetpointermotion(0);
1514 xsetmode(0, MODE_MOUSE);
1515 xsetmode(set, MODE_MOUSEBTN);
1517 case 1002: /* 1002: report motion on button press */
1518 xsetpointermotion(0);
1519 xsetmode(0, MODE_MOUSE);
1520 xsetmode(set, MODE_MOUSEMOTION);
1522 case 1003: /* 1003: enable all mouse motions */
1523 xsetpointermotion(set);
1524 xsetmode(0, MODE_MOUSE);
1525 xsetmode(set, MODE_MOUSEMANY);
1527 case 1004: /* 1004: send focus events to tty */
1528 xsetmode(set, MODE_FOCUS);
1530 case 1006: /* 1006: extended reporting mode */
1531 xsetmode(set, MODE_MOUSESGR);
1534 xsetmode(set, MODE_8BIT);
1536 case 1049: /* swap screen & set/restore cursor as xterm */
1537 if (!allowaltscreen)
1539 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1541 case 47: /* swap screen */
1543 if (!allowaltscreen)
1545 alt = IS_SET(MODE_ALTSCREEN);
1547 tclearregion(0, 0, term.col-1,
1550 if (set ^ alt) /* set is always 1 or 0 */
1556 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1558 case 2004: /* 2004: bracketed paste mode */
1559 xsetmode(set, MODE_BRCKTPASTE);
1561 /* Not implemented mouse modes. See comments there. */
1562 case 1001: /* mouse highlight mode; can hang the
1563 terminal by design when implemented. */
1564 case 1005: /* UTF-8 mouse mode; will confuse
1565 applications not supporting UTF-8
1567 case 1015: /* urxvt mangled mouse mode; incompatible
1568 and can be mistaken for other control
1573 "erresc: unknown private set/reset mode %d\n",
1579 case 0: /* Error (IGNORED) */
1582 xsetmode(set, MODE_KBDLOCK);
1584 case 4: /* IRM -- Insertion-replacement */
1585 MODBIT(term.mode, set, MODE_INSERT);
1587 case 12: /* SRM -- Send/Receive */
1588 MODBIT(term.mode, !set, MODE_ECHO);
1590 case 20: /* LNM -- Linefeed/new line */
1591 MODBIT(term.mode, set, MODE_CRLF);
1595 "erresc: unknown set/reset mode %d\n",
1609 switch (csiescseq.mode[0]) {
1612 fprintf(stderr, "erresc: unknown csi ");
1616 case '@': /* ICH -- Insert <n> blank char */
1617 DEFAULT(csiescseq.arg[0], 1);
1618 tinsertblank(csiescseq.arg[0]);
1620 case 'A': /* CUU -- Cursor <n> Up */
1621 DEFAULT(csiescseq.arg[0], 1);
1622 tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
1624 case 'B': /* CUD -- Cursor <n> Down */
1625 case 'e': /* VPR --Cursor <n> Down */
1626 DEFAULT(csiescseq.arg[0], 1);
1627 tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
1629 case 'i': /* MC -- Media Copy */
1630 switch (csiescseq.arg[0]) {
1635 tdumpline(term.c.y);
1641 term.mode &= ~MODE_PRINT;
1644 term.mode |= MODE_PRINT;
1648 case 'c': /* DA -- Device Attributes */
1649 if (csiescseq.arg[0] == 0)
1650 ttywrite(vtiden, strlen(vtiden), 0);
1652 case 'b': /* REP -- if last char is printable print it <n> more times */
1653 DEFAULT(csiescseq.arg[0], 1);
1655 while (csiescseq.arg[0]-- > 0)
1658 case 'C': /* CUF -- Cursor <n> Forward */
1659 case 'a': /* HPR -- Cursor <n> Forward */
1660 DEFAULT(csiescseq.arg[0], 1);
1661 tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
1663 case 'D': /* CUB -- Cursor <n> Backward */
1664 DEFAULT(csiescseq.arg[0], 1);
1665 tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
1667 case 'E': /* CNL -- Cursor <n> Down and first col */
1668 DEFAULT(csiescseq.arg[0], 1);
1669 tmoveto(0, term.c.y+csiescseq.arg[0]);
1671 case 'F': /* CPL -- Cursor <n> Up and first col */
1672 DEFAULT(csiescseq.arg[0], 1);
1673 tmoveto(0, term.c.y-csiescseq.arg[0]);
1675 case 'g': /* TBC -- Tabulation clear */
1676 switch (csiescseq.arg[0]) {
1677 case 0: /* clear current tab stop */
1678 term.tabs[term.c.x] = 0;
1680 case 3: /* clear all the tabs */
1681 memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1687 case 'G': /* CHA -- Move to <col> */
1689 DEFAULT(csiescseq.arg[0], 1);
1690 tmoveto(csiescseq.arg[0]-1, term.c.y);
1692 case 'H': /* CUP -- Move to <row> <col> */
1694 DEFAULT(csiescseq.arg[0], 1);
1695 DEFAULT(csiescseq.arg[1], 1);
1696 tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
1698 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1699 DEFAULT(csiescseq.arg[0], 1);
1700 tputtab(csiescseq.arg[0]);
1702 case 'J': /* ED -- Clear screen */
1703 switch (csiescseq.arg[0]) {
1705 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
1706 if (term.c.y < term.row-1) {
1707 tclearregion(0, term.c.y+1, term.col-1,
1713 tclearregion(0, 0, term.col-1, term.c.y-1);
1714 tclearregion(0, term.c.y, term.c.x, term.c.y);
1717 tclearregion(0, 0, term.col-1, term.row-1);
1723 case 'K': /* EL -- Clear line */
1724 switch (csiescseq.arg[0]) {
1726 tclearregion(term.c.x, term.c.y, term.col-1,
1730 tclearregion(0, term.c.y, term.c.x, term.c.y);
1733 tclearregion(0, term.c.y, term.col-1, term.c.y);
1737 case 'S': /* SU -- Scroll <n> line up */
1738 DEFAULT(csiescseq.arg[0], 1);
1739 tscrollup(term.top, csiescseq.arg[0]);
1741 case 'T': /* SD -- Scroll <n> line down */
1742 DEFAULT(csiescseq.arg[0], 1);
1743 tscrolldown(term.top, csiescseq.arg[0]);
1745 case 'L': /* IL -- Insert <n> blank lines */
1746 DEFAULT(csiescseq.arg[0], 1);
1747 tinsertblankline(csiescseq.arg[0]);
1749 case 'l': /* RM -- Reset Mode */
1750 tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
1752 case 'M': /* DL -- Delete <n> lines */
1753 DEFAULT(csiescseq.arg[0], 1);
1754 tdeleteline(csiescseq.arg[0]);
1756 case 'X': /* ECH -- Erase <n> char */
1757 DEFAULT(csiescseq.arg[0], 1);
1758 tclearregion(term.c.x, term.c.y,
1759 term.c.x + csiescseq.arg[0] - 1, term.c.y);
1761 case 'P': /* DCH -- Delete <n> char */
1762 DEFAULT(csiescseq.arg[0], 1);
1763 tdeletechar(csiescseq.arg[0]);
1765 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1766 DEFAULT(csiescseq.arg[0], 1);
1767 tputtab(-csiescseq.arg[0]);
1769 case 'd': /* VPA -- Move to <row> */
1770 DEFAULT(csiescseq.arg[0], 1);
1771 tmoveato(term.c.x, csiescseq.arg[0]-1);
1773 case 'h': /* SM -- Set terminal mode */
1774 tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
1776 case 'm': /* SGR -- Terminal attribute (color) */
1777 tsetattr(csiescseq.arg, csiescseq.narg);
1779 case 'n': /* DSR – Device Status Report (cursor position) */
1780 if (csiescseq.arg[0] == 6) {
1781 len = snprintf(buf, sizeof(buf), "\033[%i;%iR",
1782 term.c.y+1, term.c.x+1);
1783 ttywrite(buf, len, 0);
1786 case 'r': /* DECSTBM -- Set Scrolling Region */
1787 if (csiescseq.priv) {
1790 DEFAULT(csiescseq.arg[0], 1);
1791 DEFAULT(csiescseq.arg[1], term.row);
1792 tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
1796 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1797 tcursor(CURSOR_SAVE);
1799 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1800 tcursor(CURSOR_LOAD);
1803 switch (csiescseq.mode[1]) {
1804 case 'q': /* DECSCUSR -- Set Cursor Style */
1805 if (xsetcursor(csiescseq.arg[0]))
1821 fprintf(stderr, "ESC[");
1822 for (i = 0; i < csiescseq.len; i++) {
1823 c = csiescseq.buf[i] & 0xff;
1826 } else if (c == '\n') {
1827 fprintf(stderr, "(\\n)");
1828 } else if (c == '\r') {
1829 fprintf(stderr, "(\\r)");
1830 } else if (c == 0x1b) {
1831 fprintf(stderr, "(\\e)");
1833 fprintf(stderr, "(%02x)", c);
1842 memset(&csiescseq, 0, sizeof(csiescseq));
1848 char *p = NULL, *dec;
1851 term.esc &= ~(ESC_STR_END|ESC_STR);
1853 par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
1855 switch (strescseq.type) {
1856 case ']': /* OSC -- Operating System Command */
1862 xsettitle(strescseq.args[1]);
1865 if (narg > 2 && allowwindowops) {
1866 dec = base64dec(strescseq.args[2]);
1871 fprintf(stderr, "erresc: invalid base64\n");
1875 case 4: /* color set */
1878 p = strescseq.args[2];
1880 case 104: /* color reset, here p = NULL */
1881 j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
1882 if (xsetcolorname(j, p)) {
1883 if (par == 104 && narg <= 1)
1884 return; /* color reset without parameter */
1885 fprintf(stderr, "erresc: invalid color j=%d, p=%s\n",
1886 j, p ? p : "(null)");
1889 * TODO if defaultbg color is changed, borders
1897 case 'k': /* old title set compatibility */
1898 xsettitle(strescseq.args[0]);
1900 case 'P': /* DCS -- Device Control String */
1901 case '_': /* APC -- Application Program Command */
1902 case '^': /* PM -- Privacy Message */
1906 fprintf(stderr, "erresc: unknown str ");
1914 char *p = strescseq.buf;
1917 strescseq.buf[strescseq.len] = '\0';
1922 while (strescseq.narg < STR_ARG_SIZ) {
1923 strescseq.args[strescseq.narg++] = p;
1924 while ((c = *p) != ';' && c != '\0')
1938 fprintf(stderr, "ESC%c", strescseq.type);
1939 for (i = 0; i < strescseq.len; i++) {
1940 c = strescseq.buf[i] & 0xff;
1944 } else if (isprint(c)) {
1946 } else if (c == '\n') {
1947 fprintf(stderr, "(\\n)");
1948 } else if (c == '\r') {
1949 fprintf(stderr, "(\\r)");
1950 } else if (c == 0x1b) {
1951 fprintf(stderr, "(\\e)");
1953 fprintf(stderr, "(%02x)", c);
1956 fprintf(stderr, "ESC\\\n");
1962 strescseq = (STREscape){
1963 .buf = xrealloc(strescseq.buf, STR_BUF_SIZ),
1969 sendbreak(const Arg *arg)
1971 if (tcsendbreak(cmdfd, 0))
1972 perror("Error sending break");
1976 tprinter(char *s, size_t len)
1978 if (iofd != -1 && xwrite(iofd, s, len) < 0) {
1979 perror("Error writing to output file");
1986 toggleprinter(const Arg *arg)
1988 term.mode ^= MODE_PRINT;
1992 printscreen(const Arg *arg)
1998 printsel(const Arg *arg)
2008 if ((ptr = getsel())) {
2009 tprinter(ptr, strlen(ptr));
2020 bp = &term.line[n][0];
2021 end = &bp[MIN(tlinelen(n), term.col) - 1];
2022 if (bp != end || bp->u != ' ') {
2023 for ( ; bp <= end; ++bp)
2024 tprinter(buf, utf8encode(bp->u, buf));
2034 for (i = 0; i < term.row; ++i)
2044 while (x < term.col && n--)
2045 for (++x; x < term.col && !term.tabs[x]; ++x)
2048 while (x > 0 && n++)
2049 for (--x; x > 0 && !term.tabs[x]; --x)
2052 term.c.x = LIMIT(x, 0, term.col-1);
2056 tdefutf8(char ascii)
2059 term.mode |= MODE_UTF8;
2060 else if (ascii == '@')
2061 term.mode &= ~MODE_UTF8;
2065 tdeftran(char ascii)
2067 static char cs[] = "0B";
2068 static int vcs[] = {CS_GRAPHIC0, CS_USA};
2071 if ((p = strchr(cs, ascii)) == NULL) {
2072 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
2074 term.trantbl[term.icharset] = vcs[p - cs];
2083 if (c == '8') { /* DEC screen alignment test. */
2084 for (x = 0; x < term.col; ++x) {
2085 for (y = 0; y < term.row; ++y)
2086 tsetchar('E', &term.c.attr, x, y);
2092 tstrsequence(uchar c)
2095 case 0x90: /* DCS -- Device Control String */
2098 case 0x9f: /* APC -- Application Program Command */
2101 case 0x9e: /* PM -- Privacy Message */
2104 case 0x9d: /* OSC -- Operating System Command */
2110 term.esc |= ESC_STR;
2114 tcontrolcode(uchar ascii)
2121 tmoveto(term.c.x-1, term.c.y);
2124 tmoveto(0, term.c.y);
2129 /* go to first col if the mode is set */
2130 tnewline(IS_SET(MODE_CRLF));
2132 case '\a': /* BEL */
2133 if (term.esc & ESC_STR_END) {
2134 /* backwards compatibility to xterm */
2140 case '\033': /* ESC */
2142 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
2143 term.esc |= ESC_START;
2145 case '\016': /* SO (LS1 -- Locking shift 1) */
2146 case '\017': /* SI (LS0 -- Locking shift 0) */
2147 term.charset = 1 - (ascii - '\016');
2149 case '\032': /* SUB */
2150 tsetchar('?', &term.c.attr, term.c.x, term.c.y);
2152 case '\030': /* CAN */
2155 case '\005': /* ENQ (IGNORED) */
2156 case '\000': /* NUL (IGNORED) */
2157 case '\021': /* XON (IGNORED) */
2158 case '\023': /* XOFF (IGNORED) */
2159 case 0177: /* DEL (IGNORED) */
2161 case 0x80: /* TODO: PAD */
2162 case 0x81: /* TODO: HOP */
2163 case 0x82: /* TODO: BPH */
2164 case 0x83: /* TODO: NBH */
2165 case 0x84: /* TODO: IND */
2167 case 0x85: /* NEL -- Next line */
2168 tnewline(1); /* always go to first col */
2170 case 0x86: /* TODO: SSA */
2171 case 0x87: /* TODO: ESA */
2173 case 0x88: /* HTS -- Horizontal tab stop */
2174 term.tabs[term.c.x] = 1;
2176 case 0x89: /* TODO: HTJ */
2177 case 0x8a: /* TODO: VTS */
2178 case 0x8b: /* TODO: PLD */
2179 case 0x8c: /* TODO: PLU */
2180 case 0x8d: /* TODO: RI */
2181 case 0x8e: /* TODO: SS2 */
2182 case 0x8f: /* TODO: SS3 */
2183 case 0x91: /* TODO: PU1 */
2184 case 0x92: /* TODO: PU2 */
2185 case 0x93: /* TODO: STS */
2186 case 0x94: /* TODO: CCH */
2187 case 0x95: /* TODO: MW */
2188 case 0x96: /* TODO: SPA */
2189 case 0x97: /* TODO: EPA */
2190 case 0x98: /* TODO: SOS */
2191 case 0x99: /* TODO: SGCI */
2193 case 0x9a: /* DECID -- Identify Terminal */
2194 ttywrite(vtiden, strlen(vtiden), 0);
2196 case 0x9b: /* TODO: CSI */
2197 case 0x9c: /* TODO: ST */
2199 case 0x90: /* DCS -- Device Control String */
2200 case 0x9d: /* OSC -- Operating System Command */
2201 case 0x9e: /* PM -- Privacy Message */
2202 case 0x9f: /* APC -- Application Program Command */
2203 tstrsequence(ascii);
2206 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2207 term.esc &= ~(ESC_STR_END|ESC_STR);
2211 * returns 1 when the sequence is finished and it hasn't to read
2212 * more characters for this sequence, otherwise 0
2215 eschandle(uchar ascii)
2219 term.esc |= ESC_CSI;
2222 term.esc |= ESC_TEST;
2225 term.esc |= ESC_UTF8;
2227 case 'P': /* DCS -- Device Control String */
2228 case '_': /* APC -- Application Program Command */
2229 case '^': /* PM -- Privacy Message */
2230 case ']': /* OSC -- Operating System Command */
2231 case 'k': /* old title set compatibility */
2232 tstrsequence(ascii);
2234 case 'n': /* LS2 -- Locking shift 2 */
2235 case 'o': /* LS3 -- Locking shift 3 */
2236 term.charset = 2 + (ascii - 'n');
2238 case '(': /* GZD4 -- set primary charset G0 */
2239 case ')': /* G1D4 -- set secondary charset G1 */
2240 case '*': /* G2D4 -- set tertiary charset G2 */
2241 case '+': /* G3D4 -- set quaternary charset G3 */
2242 term.icharset = ascii - '(';
2243 term.esc |= ESC_ALTCHARSET;
2245 case 'D': /* IND -- Linefeed */
2246 if (term.c.y == term.bot) {
2247 tscrollup(term.top, 1);
2249 tmoveto(term.c.x, term.c.y+1);
2252 case 'E': /* NEL -- Next line */
2253 tnewline(1); /* always go to first col */
2255 case 'H': /* HTS -- Horizontal tab stop */
2256 term.tabs[term.c.x] = 1;
2258 case 'M': /* RI -- Reverse index */
2259 if (term.c.y == term.top) {
2260 tscrolldown(term.top, 1);
2262 tmoveto(term.c.x, term.c.y-1);
2265 case 'Z': /* DECID -- Identify Terminal */
2266 ttywrite(vtiden, strlen(vtiden), 0);
2268 case 'c': /* RIS -- Reset to initial state */
2273 case '=': /* DECPAM -- Application keypad */
2274 xsetmode(1, MODE_APPKEYPAD);
2276 case '>': /* DECPNM -- Normal keypad */
2277 xsetmode(0, MODE_APPKEYPAD);
2279 case '7': /* DECSC -- Save Cursor */
2280 tcursor(CURSOR_SAVE);
2282 case '8': /* DECRC -- Restore Cursor */
2283 tcursor(CURSOR_LOAD);
2285 case '\\': /* ST -- String Terminator */
2286 if (term.esc & ESC_STR_END)
2290 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2291 (uchar) ascii, isprint(ascii)? ascii:'.');
2305 control = ISCONTROL(u);
2306 if (u < 127 || !IS_SET(MODE_UTF8)) {
2310 len = utf8encode(u, c);
2311 if (!control && (width = wcwidth(u)) == -1)
2315 if (IS_SET(MODE_PRINT))
2319 * STR sequence must be checked before anything else
2320 * because it uses all following characters until it
2321 * receives a ESC, a SUB, a ST or any other C1 control
2324 if (term.esc & ESC_STR) {
2325 if (u == '\a' || u == 030 || u == 032 || u == 033 ||
2327 term.esc &= ~(ESC_START|ESC_STR);
2328 term.esc |= ESC_STR_END;
2329 goto check_control_code;
2332 if (strescseq.len+len >= strescseq.siz) {
2334 * Here is a bug in terminals. If the user never sends
2335 * some code to stop the str or esc command, then st
2336 * will stop responding. But this is better than
2337 * silently failing with unknown characters. At least
2338 * then users will report back.
2340 * In the case users ever get fixed, here is the code:
2346 if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2)
2349 strescseq.buf = xrealloc(strescseq.buf, strescseq.siz);
2352 memmove(&strescseq.buf[strescseq.len], c, len);
2353 strescseq.len += len;
2359 * Actions of control codes must be performed as soon they arrive
2360 * because they can be embedded inside a control sequence, and
2361 * they must not cause conflicts with sequences.
2366 * control codes are not shown ever
2371 } else if (term.esc & ESC_START) {
2372 if (term.esc & ESC_CSI) {
2373 csiescseq.buf[csiescseq.len++] = u;
2374 if (BETWEEN(u, 0x40, 0x7E)
2375 || csiescseq.len >= \
2376 sizeof(csiescseq.buf)-1) {
2382 } else if (term.esc & ESC_UTF8) {
2384 } else if (term.esc & ESC_ALTCHARSET) {
2386 } else if (term.esc & ESC_TEST) {
2391 /* sequence already finished */
2395 * All characters which form part of a sequence are not
2400 //if (selected(term.c.x, term.c.y))
2403 gp = &term.line[term.c.y][term.c.x];
2404 if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
2405 gp->mode |= ATTR_WRAP;
2407 gp = &term.line[term.c.y][term.c.x];
2410 if (IS_SET(MODE_INSERT) && term.c.x+width < term.col)
2411 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
2413 if (term.c.x+width > term.col) {
2415 gp = &term.line[term.c.y][term.c.x];
2418 tsetchar(u, &term.c.attr, term.c.x, term.c.y);
2422 gp->mode |= ATTR_WIDE;
2423 if (term.c.x+1 < term.col) {
2425 gp[1].mode = ATTR_WDUMMY;
2428 if (term.c.x+width < term.col) {
2429 tmoveto(term.c.x+width, term.c.y);
2431 term.c.state |= CURSOR_WRAPNEXT;
2436 twrite(const char *buf, int buflen, int show_ctrl)
2442 for (n = 0; n < buflen; n += charsize) {
2443 if (IS_SET(MODE_UTF8)) {
2444 /* process a complete utf8 char */
2445 charsize = utf8decode(buf + n, &u, buflen - n);
2452 if (show_ctrl && ISCONTROL(u)) {
2457 } else if (u != '\n' && u != '\r' && u != '\t') {
2468 tresize(int col, int row)
2471 int const colSet = col, alt = IS_SET(MODE_ALTSCREEN), ini = buf == NULL;
2472 col = MAX(col, buffCols);
2473 row = MIN(row, buffSize);
2474 int const minrow = MIN(row, term.row), mincol = MIN(col, buffCols);
2478 if (col < 1 || row < 1) {
2480 "tresize: error resizing to %dx%d\n", col, row);
2483 if (alt) tswapscreen();
2486 * slide screen to keep cursor where we expect it -
2487 * tscrollup would work here, but we can optimize to
2488 * memmove because we're freeing the earlier lines
2490 for (i = 0; i <= term.c.y - row; i++) {
2493 /* ensure that both src and dst are not NULL */
2495 memmove(term.alt, term.alt + i, row * sizeof(Line));
2497 for (i += row; i < term.row; i++) {
2501 /* resize to new height */
2502 buf = xrealloc(buf, (buffSize + row) * sizeof(Line));
2503 term.alt = xrealloc(term.alt, row * sizeof(Line));
2504 term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
2505 mark = xrealloc(mark, col * row * sizeof(*mark));
2506 term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
2508 /* resize each row to new width, zero-pad if needed */
2509 for (i = 0; i < minrow; i++) {
2510 term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph));
2513 /* allocate any new rows */
2514 for (/* i = minrow */; i < row; i++) {
2515 term.alt[i] = xmalloc(col * sizeof(Glyph));
2517 if (col > buffCols) {
2518 bp = term.tabs + buffCols;
2520 memset(bp, 0, sizeof(*term.tabs) * (col - buffCols));
2521 while (--bp > term.tabs && !*bp)
2523 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
2526 Glyph g=(Glyph){.bg=term.c.attr.bg, .fg=term.c.attr.fg, .u=' ', .mode=0};
2527 for (i = 0; i < buffSize; ++i) {
2528 buf[i] = xrealloc(ini ? NULL : buf[i], col*sizeof(Glyph));
2529 for (int j = ini ? 0 : buffCols; j < col; ++j) buf[i][j] = g;
2531 for (i = 0; i < row; ++i) buf[buffSize + i] = buf[i];
2532 term.line = &buf[*(histOp?&histOff:&insertOff) +=MAX(term.c.y-row+1,0)];
2533 memset(mark, 0, col * row * sizeof(*mark));
2534 /* update terminal size */
2538 if (alt) tswapscreen();
2539 /* reset scrolling region */
2540 tsetscroll(0, row-1);
2541 /* make use of the LIMIT in tmoveto */
2542 tmoveto(term.c.x, term.c.y);
2543 /* Clearing both screens (it makes dirty all lines) */
2545 for (i = 0; i < 2; i++) {
2546 if (mincol < col && 0 < minrow) {
2547 tclearregion(mincol, 0, col - 1, minrow - 1);
2549 if (0 < col && minrow < row) {
2550 tclearregion(0, minrow, col - 1, row - 1);
2553 tcursor(CURSOR_LOAD);
2565 drawregion(int x1, int y1, int x2, int y2)
2567 if (altToggle && histMode && !histOp)
2568 memset(term.dirty, 0, sizeof(*term.dirty) * term.row);
2569 int const o = !IS_SET(MODE_ALTSCREEN) && histMode && !histOp, h =rows();
2572 for (y = y1; y < y2; y++) {
2573 int const oy = o ? (y + insertOff - histOff + h) % h : y;
2574 if (!BETWEEN(oy, 0, term.row-1) || !term.dirty[y]) continue;
2575 xdrawline(term.line[y], x1, oy, x2);
2577 memset(&term.dirty[y1], 0, sizeof(*term.dirty) * (y2 - y1));
2583 int cx = term.c.x, ocx = term.ocx, ocy = term.ocy;
2588 /* adjust cursor position */
2589 LIMIT(term.ocx, 0, term.col-1);
2590 LIMIT(term.ocy, 0, term.row-1);
2591 if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY)
2593 if (term.line[term.c.y][cx].mode & ATTR_WDUMMY)
2596 if (histMode) historyPreDraw();
2597 drawregion(0, 0, term.col, term.row);
2599 xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
2600 term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
2602 term.ocy = term.c.y;
2604 if (ocx != term.ocx || ocy != term.ocy)
2605 xximspot(term.ocx, term.ocy);