tiny style fix
[st.git] / st.c
blob2d901ab66e2436c95906d5edeb77ee05e7ae5058
1 /* See LICENSE for license details. */
2 #include <ctype.h>
3 #include <errno.h>
4 #include <fcntl.h>
5 #include <limits.h>
6 #include <pwd.h>
7 #include <stdarg.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <signal.h>
12 #include <sys/ioctl.h>
13 #include <sys/select.h>
14 #include <sys/types.h>
15 #include <sys/wait.h>
16 #include <termios.h>
17 #include <unistd.h>
18 #include <wchar.h>
20 #include "st.h"
21 #include "win.h"
23 #if defined(__linux)
24 #include <pty.h>
25 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
26 #include <util.h>
27 #elif defined(__FreeBSD__) || defined(__DragonFly__)
28 #include <libutil.h>
29 #endif
31 /* Arbitrary sizes */
32 #define UTF_INVALID 0xFFFD
33 #define UTF_SIZ 4
34 #define ESC_BUF_SIZ (128*UTF_SIZ)
35 #define ESC_ARG_SIZ 16
36 #define STR_BUF_SIZ ESC_BUF_SIZ
37 #define STR_ARG_SIZ ESC_ARG_SIZ
39 /* macros */
40 #define IS_SET(flag) ((term.mode & (flag)) != 0)
41 #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f)
42 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
43 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
44 #define ISDELIM(u) (u && wcschr(worddelimiters, u))
46 enum term_mode {
47 MODE_WRAP = 1 << 0,
48 MODE_INSERT = 1 << 1,
49 MODE_ALTSCREEN = 1 << 2,
50 MODE_CRLF = 1 << 3,
51 MODE_ECHO = 1 << 4,
52 MODE_PRINT = 1 << 5,
53 MODE_UTF8 = 1 << 6,
54 MODE_SIXEL = 1 << 7,
57 enum cursor_movement {
58 CURSOR_SAVE,
59 CURSOR_LOAD
62 enum cursor_state {
63 CURSOR_DEFAULT = 0,
64 CURSOR_WRAPNEXT = 1,
65 CURSOR_ORIGIN = 2
68 enum charset {
69 CS_GRAPHIC0,
70 CS_GRAPHIC1,
71 CS_UK,
72 CS_USA,
73 CS_MULTI,
74 CS_GER,
75 CS_FIN
78 enum escape_state {
79 ESC_START = 1,
80 ESC_CSI = 2,
81 ESC_STR = 4, /* OSC, PM, APC */
82 ESC_ALTCHARSET = 8,
83 ESC_STR_END = 16, /* a final string was encountered */
84 ESC_TEST = 32, /* Enter in test mode */
85 ESC_UTF8 = 64,
86 ESC_DCS =128,
89 typedef struct {
90 Glyph attr; /* current char attributes */
91 int x;
92 int y;
93 char state;
94 } TCursor;
96 typedef struct {
97 int mode;
98 int type;
99 int snap;
101 * Selection variables:
102 * nb – normalized coordinates of the beginning of the selection
103 * ne – normalized coordinates of the end of the selection
104 * ob – original coordinates of the beginning of the selection
105 * oe – original coordinates of the end of the selection
107 struct {
108 int x, y;
109 } nb, ne, ob, oe;
111 int alt;
112 } Selection;
114 /* Internal representation of the screen */
115 typedef struct {
116 int row; /* nb row */
117 int col; /* nb col */
118 Line *line; /* screen */
119 Line *alt; /* alternate screen */
120 int *dirty; /* dirtyness of lines */
121 TCursor c; /* cursor */
122 int ocx; /* old cursor col */
123 int ocy; /* old cursor row */
124 int top; /* top scroll limit */
125 int bot; /* bottom scroll limit */
126 int mode; /* terminal mode flags */
127 int esc; /* escape state flags */
128 char trantbl[4]; /* charset table translation */
129 int charset; /* current charset */
130 int icharset; /* selected charset for sequence */
131 int *tabs;
132 Rune lastc; /* last printed char outside of sequence, 0 if control */
133 } Term;
135 /* CSI Escape sequence structs */
136 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
137 typedef struct {
138 char buf[ESC_BUF_SIZ]; /* raw string */
139 size_t len; /* raw string length */
140 char priv;
141 int arg[ESC_ARG_SIZ];
142 int narg; /* nb of args */
143 char mode[2];
144 } CSIEscape;
146 /* STR Escape sequence structs */
147 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
148 typedef struct {
149 char type; /* ESC type ... */
150 char *buf; /* allocated raw string */
151 size_t siz; /* allocation size */
152 size_t len; /* raw string length */
153 char *args[STR_ARG_SIZ];
154 int narg; /* nb of args */
155 } STREscape;
157 static void execsh(char *, char **);
158 static void stty(char **);
159 static void sigchld(int);
160 static void ttywriteraw(const char *, size_t);
162 static void csidump(void);
163 static void csihandle(void);
164 static void csiparse(void);
165 static void csireset(void);
166 static int eschandle(uchar);
167 static void strdump(void);
168 static void strhandle(void);
169 static void strparse(void);
170 static void strreset(void);
172 static void tprinter(char *, size_t);
173 static void tdumpsel(void);
174 static void tdumpline(int);
175 static void tdump(void);
176 static void tclearregion(int, int, int, int);
177 static void tcursor(int);
178 static void tdeletechar(int);
179 static void tdeleteline(int);
180 static void tinsertblank(int);
181 static void tinsertblankline(int);
182 static int tlinelen(int);
183 static void tmoveto(int, int);
184 static void tmoveato(int, int);
185 static void tnewline(int);
186 static void tputtab(int);
187 static void tputc(Rune);
188 static void treset(void);
189 static void tscrollup(int, int);
190 static void tscrolldown(int, int);
191 static void tsetattr(int *, int);
192 static void tsetchar(Rune, Glyph *, int, int);
193 static void tsetdirt(int, int);
194 static void tsetscroll(int, int);
195 static void tswapscreen(void);
196 static void tsetmode(int, int, int *, int);
197 static int twrite(const char *, int, int);
198 static void tfulldirt(void);
199 static void tcontrolcode(uchar );
200 static void tdectest(char );
201 static void tdefutf8(char);
202 static int32_t tdefcolor(int *, int *, int);
203 static void tdeftran(char);
204 static void tstrsequence(uchar);
206 static void drawregion(int, int, int, int);
208 static void selnormalize(void);
209 static void selscroll(int, int);
210 static void selsnap(int *, int *, int);
212 static size_t utf8decode(const char *, Rune *, size_t);
213 static Rune utf8decodebyte(char, size_t *);
214 static char utf8encodebyte(Rune, size_t);
215 static size_t utf8validate(Rune *, size_t);
217 static char *base64dec(const char *);
218 static char base64dec_getc(const char **);
220 static ssize_t xwrite(int, const char *, size_t);
222 /* Globals */
223 static Term term;
224 static Selection sel;
225 static CSIEscape csiescseq;
226 static STREscape strescseq;
227 static int iofd = 1;
228 static int cmdfd;
229 static pid_t pid;
231 static uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
232 static uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
233 static Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000};
234 static Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
236 ssize_t
237 xwrite(int fd, const char *s, size_t len)
239 size_t aux = len;
240 ssize_t r;
242 while (len > 0) {
243 r = write(fd, s, len);
244 if (r < 0)
245 return r;
246 len -= r;
247 s += r;
250 return aux;
253 void *
254 xmalloc(size_t len)
256 void *p;
258 if (!(p = malloc(len)))
259 die("malloc: %s\n", strerror(errno));
261 return p;
264 void *
265 xrealloc(void *p, size_t len)
267 if ((p = realloc(p, len)) == NULL)
268 die("realloc: %s\n", strerror(errno));
270 return p;
273 char *
274 xstrdup(char *s)
276 if ((s = strdup(s)) == NULL)
277 die("strdup: %s\n", strerror(errno));
279 return s;
282 size_t
283 utf8decode(const char *c, Rune *u, size_t clen)
285 size_t i, j, len, type;
286 Rune udecoded;
288 *u = UTF_INVALID;
289 if (!clen)
290 return 0;
291 udecoded = utf8decodebyte(c[0], &len);
292 if (!BETWEEN(len, 1, UTF_SIZ))
293 return 1;
294 for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
295 udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
296 if (type != 0)
297 return j;
299 if (j < len)
300 return 0;
301 *u = udecoded;
302 utf8validate(u, len);
304 return len;
307 Rune
308 utf8decodebyte(char c, size_t *i)
310 for (*i = 0; *i < LEN(utfmask); ++(*i))
311 if (((uchar)c & utfmask[*i]) == utfbyte[*i])
312 return (uchar)c & ~utfmask[*i];
314 return 0;
317 size_t
318 utf8encode(Rune u, char *c)
320 size_t len, i;
322 len = utf8validate(&u, 0);
323 if (len > UTF_SIZ)
324 return 0;
326 for (i = len - 1; i != 0; --i) {
327 c[i] = utf8encodebyte(u, 0);
328 u >>= 6;
330 c[0] = utf8encodebyte(u, len);
332 return len;
335 char
336 utf8encodebyte(Rune u, size_t i)
338 return utfbyte[i] | (u & ~utfmask[i]);
341 size_t
342 utf8validate(Rune *u, size_t i)
344 if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF))
345 *u = UTF_INVALID;
346 for (i = 1; *u > utfmax[i]; ++i)
349 return i;
352 static const char base64_digits[] = {
353 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
354 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0,
355 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
356 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
357 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
358 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0,
359 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
360 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
361 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
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, 0, 0, 0, 0, 0,
364 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
367 char
368 base64dec_getc(const char **src)
370 while (**src && !isprint(**src))
371 (*src)++;
372 return **src ? *((*src)++) : '='; /* emulate padding if string ends */
375 char *
376 base64dec(const char *src)
378 size_t in_len = strlen(src);
379 char *result, *dst;
381 if (in_len % 4)
382 in_len += 4 - (in_len % 4);
383 result = dst = xmalloc(in_len / 4 * 3 + 1);
384 while (*src) {
385 int a = base64_digits[(unsigned char) base64dec_getc(&src)];
386 int b = base64_digits[(unsigned char) base64dec_getc(&src)];
387 int c = base64_digits[(unsigned char) base64dec_getc(&src)];
388 int d = base64_digits[(unsigned char) base64dec_getc(&src)];
390 /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */
391 if (a == -1 || b == -1)
392 break;
394 *dst++ = (a << 2) | ((b & 0x30) >> 4);
395 if (c == -1)
396 break;
397 *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2);
398 if (d == -1)
399 break;
400 *dst++ = ((c & 0x03) << 6) | d;
402 *dst = '\0';
403 return result;
406 void
407 selinit(void)
409 sel.mode = SEL_IDLE;
410 sel.snap = 0;
411 sel.ob.x = -1;
415 tlinelen(int y)
417 int i = term.col;
419 if (term.line[y][i - 1].mode & ATTR_WRAP)
420 return i;
422 while (i > 0 && term.line[y][i - 1].u == ' ')
423 --i;
425 return i;
428 void
429 selstart(int col, int row, int snap)
431 selclear();
432 sel.mode = SEL_EMPTY;
433 sel.type = SEL_REGULAR;
434 sel.alt = IS_SET(MODE_ALTSCREEN);
435 sel.snap = snap;
436 sel.oe.x = sel.ob.x = col;
437 sel.oe.y = sel.ob.y = row;
438 selnormalize();
440 if (sel.snap != 0)
441 sel.mode = SEL_READY;
442 tsetdirt(sel.nb.y, sel.ne.y);
445 void
446 selextend(int col, int row, int type, int done)
448 int oldey, oldex, oldsby, oldsey, oldtype;
450 if (sel.mode == SEL_IDLE)
451 return;
452 if (done && sel.mode == SEL_EMPTY) {
453 selclear();
454 return;
457 oldey = sel.oe.y;
458 oldex = sel.oe.x;
459 oldsby = sel.nb.y;
460 oldsey = sel.ne.y;
461 oldtype = sel.type;
463 sel.oe.x = col;
464 sel.oe.y = row;
465 selnormalize();
466 sel.type = type;
468 if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY)
469 tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey));
471 sel.mode = done ? SEL_IDLE : SEL_READY;
474 void
475 selnormalize(void)
477 int i;
479 if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) {
480 sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x;
481 sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x;
482 } else {
483 sel.nb.x = MIN(sel.ob.x, sel.oe.x);
484 sel.ne.x = MAX(sel.ob.x, sel.oe.x);
486 sel.nb.y = MIN(sel.ob.y, sel.oe.y);
487 sel.ne.y = MAX(sel.ob.y, sel.oe.y);
489 selsnap(&sel.nb.x, &sel.nb.y, -1);
490 selsnap(&sel.ne.x, &sel.ne.y, +1);
492 /* expand selection over line breaks */
493 if (sel.type == SEL_RECTANGULAR)
494 return;
495 i = tlinelen(sel.nb.y);
496 if (i < sel.nb.x)
497 sel.nb.x = i;
498 if (tlinelen(sel.ne.y) <= sel.ne.x)
499 sel.ne.x = term.col - 1;
503 selected(int x, int y)
505 if (sel.mode == SEL_EMPTY || sel.ob.x == -1 ||
506 sel.alt != IS_SET(MODE_ALTSCREEN))
507 return 0;
509 if (sel.type == SEL_RECTANGULAR)
510 return BETWEEN(y, sel.nb.y, sel.ne.y)
511 && BETWEEN(x, sel.nb.x, sel.ne.x);
513 return BETWEEN(y, sel.nb.y, sel.ne.y)
514 && (y != sel.nb.y || x >= sel.nb.x)
515 && (y != sel.ne.y || x <= sel.ne.x);
518 void
519 selsnap(int *x, int *y, int direction)
521 int newx, newy, xt, yt;
522 int delim, prevdelim;
523 Glyph *gp, *prevgp;
525 switch (sel.snap) {
526 case SNAP_WORD:
528 * Snap around if the word wraps around at the end or
529 * beginning of a line.
531 prevgp = &term.line[*y][*x];
532 prevdelim = ISDELIM(prevgp->u);
533 for (;;) {
534 newx = *x + direction;
535 newy = *y;
536 if (!BETWEEN(newx, 0, term.col - 1)) {
537 newy += direction;
538 newx = (newx + term.col) % term.col;
539 if (!BETWEEN(newy, 0, term.row - 1))
540 break;
542 if (direction > 0)
543 yt = *y, xt = *x;
544 else
545 yt = newy, xt = newx;
546 if (!(term.line[yt][xt].mode & ATTR_WRAP))
547 break;
550 if (newx >= tlinelen(newy))
551 break;
553 gp = &term.line[newy][newx];
554 delim = ISDELIM(gp->u);
555 if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
556 || (delim && gp->u != prevgp->u)))
557 break;
559 *x = newx;
560 *y = newy;
561 prevgp = gp;
562 prevdelim = delim;
564 break;
565 case SNAP_LINE:
567 * Snap around if the the previous line or the current one
568 * has set ATTR_WRAP at its end. Then the whole next or
569 * previous line will be selected.
571 *x = (direction < 0) ? 0 : term.col - 1;
572 if (direction < 0) {
573 for (; *y > 0; *y += direction) {
574 if (!(term.line[*y-1][term.col-1].mode
575 & ATTR_WRAP)) {
576 break;
579 } else if (direction > 0) {
580 for (; *y < term.row-1; *y += direction) {
581 if (!(term.line[*y][term.col-1].mode
582 & ATTR_WRAP)) {
583 break;
587 break;
591 char *
592 getsel(void)
594 char *str, *ptr;
595 int y, bufsize, lastx, linelen;
596 Glyph *gp, *last;
598 if (sel.ob.x == -1)
599 return NULL;
601 bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ;
602 ptr = str = xmalloc(bufsize);
604 /* append every set & selected glyph to the selection */
605 for (y = sel.nb.y; y <= sel.ne.y; y++) {
606 if ((linelen = tlinelen(y)) == 0) {
607 *ptr++ = '\n';
608 continue;
611 if (sel.type == SEL_RECTANGULAR) {
612 gp = &term.line[y][sel.nb.x];
613 lastx = sel.ne.x;
614 } else {
615 gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0];
616 lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
618 last = &term.line[y][MIN(lastx, linelen-1)];
619 while (last >= gp && last->u == ' ')
620 --last;
622 for ( ; gp <= last; ++gp) {
623 if (gp->mode & ATTR_WDUMMY)
624 continue;
626 ptr += utf8encode(gp->u, ptr);
630 * Copy and pasting of line endings is inconsistent
631 * in the inconsistent terminal and GUI world.
632 * The best solution seems like to produce '\n' when
633 * something is copied from st and convert '\n' to
634 * '\r', when something to be pasted is received by
635 * st.
636 * FIXME: Fix the computer world.
638 if ((y < sel.ne.y || lastx >= linelen) &&
639 (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR))
640 *ptr++ = '\n';
642 *ptr = 0;
643 return str;
646 void
647 selclear(void)
649 if (sel.ob.x == -1)
650 return;
651 sel.mode = SEL_IDLE;
652 sel.ob.x = -1;
653 tsetdirt(sel.nb.y, sel.ne.y);
656 void
657 die(const char *errstr, ...)
659 va_list ap;
661 va_start(ap, errstr);
662 vfprintf(stderr, errstr, ap);
663 va_end(ap);
664 exit(1);
667 void
668 execsh(char *cmd, char **args)
670 char *sh, *prog, *arg;
671 const struct passwd *pw;
673 errno = 0;
674 if ((pw = getpwuid(getuid())) == NULL) {
675 if (errno)
676 die("getpwuid: %s\n", strerror(errno));
677 else
678 die("who are you?\n");
681 if ((sh = getenv("SHELL")) == NULL)
682 sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd;
684 if (args) {
685 prog = args[0];
686 arg = NULL;
687 } else if (scroll) {
688 prog = scroll;
689 arg = utmp ? utmp : sh;
690 } else if (utmp) {
691 prog = utmp;
692 arg = NULL;
693 } else {
694 prog = sh;
695 arg = NULL;
697 DEFAULT(args, ((char *[]) {prog, arg, NULL}));
699 unsetenv("COLUMNS");
700 unsetenv("LINES");
701 unsetenv("TERMCAP");
702 setenv("LOGNAME", pw->pw_name, 1);
703 setenv("USER", pw->pw_name, 1);
704 setenv("SHELL", sh, 1);
705 setenv("HOME", pw->pw_dir, 1);
706 setenv("TERM", termname, 1);
708 signal(SIGCHLD, SIG_DFL);
709 signal(SIGHUP, SIG_DFL);
710 signal(SIGINT, SIG_DFL);
711 signal(SIGQUIT, SIG_DFL);
712 signal(SIGTERM, SIG_DFL);
713 signal(SIGALRM, SIG_DFL);
715 execvp(prog, args);
716 _exit(1);
719 void
720 sigchld(int a)
722 int stat;
723 pid_t p;
725 if ((p = waitpid(pid, &stat, WNOHANG)) < 0)
726 die("waiting for pid %hd failed: %s\n", pid, strerror(errno));
728 if (pid != p)
729 return;
731 if (WIFEXITED(stat) && WEXITSTATUS(stat))
732 die("child exited with status %d\n", WEXITSTATUS(stat));
733 else if (WIFSIGNALED(stat))
734 die("child terminated due to signal %d\n", WTERMSIG(stat));
735 _exit(0);
738 void
739 stty(char **args)
741 char cmd[_POSIX_ARG_MAX], **p, *q, *s;
742 size_t n, siz;
744 if ((n = strlen(stty_args)) > sizeof(cmd)-1)
745 die("incorrect stty parameters\n");
746 memcpy(cmd, stty_args, n);
747 q = cmd + n;
748 siz = sizeof(cmd) - n;
749 for (p = args; p && (s = *p); ++p) {
750 if ((n = strlen(s)) > siz-1)
751 die("stty parameter length too long\n");
752 *q++ = ' ';
753 memcpy(q, s, n);
754 q += n;
755 siz -= n + 1;
757 *q = '\0';
758 if (system(cmd) != 0)
759 perror("Couldn't call stty");
763 ttynew(char *line, char *cmd, char *out, char **args)
765 int m, s;
767 if (out) {
768 term.mode |= MODE_PRINT;
769 iofd = (!strcmp(out, "-")) ?
770 1 : open(out, O_WRONLY | O_CREAT, 0666);
771 if (iofd < 0) {
772 fprintf(stderr, "Error opening %s:%s\n",
773 out, strerror(errno));
777 if (line) {
778 if ((cmdfd = open(line, O_RDWR)) < 0)
779 die("open line '%s' failed: %s\n",
780 line, strerror(errno));
781 dup2(cmdfd, 0);
782 stty(args);
783 return cmdfd;
786 /* seems to work fine on linux, openbsd and freebsd */
787 if (openpty(&m, &s, NULL, NULL, NULL) < 0)
788 die("openpty failed: %s\n", strerror(errno));
790 switch (pid = fork()) {
791 case -1:
792 die("fork failed: %s\n", strerror(errno));
793 break;
794 case 0:
795 close(iofd);
796 setsid(); /* create a new process group */
797 dup2(s, 0);
798 dup2(s, 1);
799 dup2(s, 2);
800 if (ioctl(s, TIOCSCTTY, NULL) < 0)
801 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno));
802 close(s);
803 close(m);
804 #ifdef __OpenBSD__
805 if (pledge("stdio getpw proc exec", NULL) == -1)
806 die("pledge\n");
807 #endif
808 execsh(cmd, args);
809 break;
810 default:
811 #ifdef __OpenBSD__
812 if (pledge("stdio rpath tty proc", NULL) == -1)
813 die("pledge\n");
814 #endif
815 close(s);
816 cmdfd = m;
817 signal(SIGCHLD, sigchld);
818 break;
820 return cmdfd;
823 size_t
824 ttyread(void)
826 static char buf[BUFSIZ];
827 static int buflen = 0;
828 int ret, written;
830 /* append read bytes to unprocessed bytes */
831 ret = read(cmdfd, buf+buflen, LEN(buf)-buflen);
833 switch (ret) {
834 case 0:
835 exit(0);
836 case -1:
837 die("couldn't read from shell: %s\n", strerror(errno));
838 default:
839 buflen += ret;
840 written = twrite(buf, buflen, 0);
841 buflen -= written;
842 /* keep any incomplete UTF-8 byte sequence for the next call */
843 if (buflen > 0)
844 memmove(buf, buf + written, buflen);
845 return ret;
849 void
850 ttywrite(const char *s, size_t n, int may_echo)
852 const char *next;
854 if (may_echo && IS_SET(MODE_ECHO))
855 twrite(s, n, 1);
857 if (!IS_SET(MODE_CRLF)) {
858 ttywriteraw(s, n);
859 return;
862 /* This is similar to how the kernel handles ONLCR for ttys */
863 while (n > 0) {
864 if (*s == '\r') {
865 next = s + 1;
866 ttywriteraw("\r\n", 2);
867 } else {
868 next = memchr(s, '\r', n);
869 DEFAULT(next, s + n);
870 ttywriteraw(s, next - s);
872 n -= next - s;
873 s = next;
877 void
878 ttywriteraw(const char *s, size_t n)
880 fd_set wfd, rfd;
881 ssize_t r;
882 size_t lim = 256;
885 * Remember that we are using a pty, which might be a modem line.
886 * Writing too much will clog the line. That's why we are doing this
887 * dance.
888 * FIXME: Migrate the world to Plan 9.
890 while (n > 0) {
891 FD_ZERO(&wfd);
892 FD_ZERO(&rfd);
893 FD_SET(cmdfd, &wfd);
894 FD_SET(cmdfd, &rfd);
896 /* Check if we can write. */
897 if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) {
898 if (errno == EINTR)
899 continue;
900 die("select failed: %s\n", strerror(errno));
902 if (FD_ISSET(cmdfd, &wfd)) {
904 * Only write the bytes written by ttywrite() or the
905 * default of 256. This seems to be a reasonable value
906 * for a serial line. Bigger values might clog the I/O.
908 if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0)
909 goto write_error;
910 if (r < n) {
912 * We weren't able to write out everything.
913 * This means the buffer is getting full
914 * again. Empty it.
916 if (n < lim)
917 lim = ttyread();
918 n -= r;
919 s += r;
920 } else {
921 /* All bytes have been written. */
922 break;
925 if (FD_ISSET(cmdfd, &rfd))
926 lim = ttyread();
928 return;
930 write_error:
931 die("write error on tty: %s\n", strerror(errno));
934 void
935 ttyresize(int tw, int th)
937 struct winsize w;
939 w.ws_row = term.row;
940 w.ws_col = term.col;
941 w.ws_xpixel = tw;
942 w.ws_ypixel = th;
943 if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
944 fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
947 void
948 ttyhangup()
950 /* Send SIGHUP to shell */
951 kill(pid, SIGHUP);
955 tattrset(int attr)
957 int i, j;
959 for (i = 0; i < term.row-1; i++) {
960 for (j = 0; j < term.col-1; j++) {
961 if (term.line[i][j].mode & attr)
962 return 1;
966 return 0;
969 void
970 tsetdirt(int top, int bot)
972 int i;
974 LIMIT(top, 0, term.row-1);
975 LIMIT(bot, 0, term.row-1);
977 for (i = top; i <= bot; i++)
978 term.dirty[i] = 1;
981 void
982 tsetdirtattr(int attr)
984 int i, j;
986 for (i = 0; i < term.row-1; i++) {
987 for (j = 0; j < term.col-1; j++) {
988 if (term.line[i][j].mode & attr) {
989 tsetdirt(i, i);
990 break;
996 void
997 tfulldirt(void)
999 tsetdirt(0, term.row-1);
1002 void
1003 tcursor(int mode)
1005 static TCursor c[2];
1006 int alt = IS_SET(MODE_ALTSCREEN);
1008 if (mode == CURSOR_SAVE) {
1009 c[alt] = term.c;
1010 } else if (mode == CURSOR_LOAD) {
1011 term.c = c[alt];
1012 tmoveto(c[alt].x, c[alt].y);
1016 void
1017 treset(void)
1019 uint i;
1021 term.c = (TCursor){{
1022 .mode = ATTR_NULL,
1023 .fg = defaultfg,
1024 .bg = defaultbg
1025 }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
1027 memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1028 for (i = tabspaces; i < term.col; i += tabspaces)
1029 term.tabs[i] = 1;
1030 term.top = 0;
1031 term.bot = term.row - 1;
1032 term.mode = MODE_WRAP|MODE_UTF8;
1033 memset(term.trantbl, CS_USA, sizeof(term.trantbl));
1034 term.charset = 0;
1036 for (i = 0; i < 2; i++) {
1037 tmoveto(0, 0);
1038 tcursor(CURSOR_SAVE);
1039 tclearregion(0, 0, term.col-1, term.row-1);
1040 tswapscreen();
1044 void
1045 tnew(int col, int row)
1047 term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
1048 tresize(col, row);
1049 treset();
1052 void
1053 tswapscreen(void)
1055 Line *tmp = term.line;
1057 term.line = term.alt;
1058 term.alt = tmp;
1059 term.mode ^= MODE_ALTSCREEN;
1060 tfulldirt();
1063 void
1064 tscrolldown(int orig, int n)
1066 int i;
1067 Line temp;
1069 LIMIT(n, 0, term.bot-orig+1);
1071 tsetdirt(orig, term.bot-n);
1072 tclearregion(0, term.bot-n+1, term.col-1, term.bot);
1074 for (i = term.bot; i >= orig+n; i--) {
1075 temp = term.line[i];
1076 term.line[i] = term.line[i-n];
1077 term.line[i-n] = temp;
1080 selscroll(orig, n);
1083 void
1084 tscrollup(int orig, int n)
1086 int i;
1087 Line temp;
1089 LIMIT(n, 0, term.bot-orig+1);
1091 tclearregion(0, orig, term.col-1, orig+n-1);
1092 tsetdirt(orig+n, term.bot);
1094 for (i = orig; i <= term.bot-n; i++) {
1095 temp = term.line[i];
1096 term.line[i] = term.line[i+n];
1097 term.line[i+n] = temp;
1100 selscroll(orig, -n);
1103 void
1104 selscroll(int orig, int n)
1106 if (sel.ob.x == -1)
1107 return;
1109 if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) {
1110 selclear();
1111 } else if (BETWEEN(sel.nb.y, orig, term.bot)) {
1112 sel.ob.y += n;
1113 sel.oe.y += n;
1114 if (sel.ob.y < term.top || sel.ob.y > term.bot ||
1115 sel.oe.y < term.top || sel.oe.y > term.bot) {
1116 selclear();
1117 } else {
1118 selnormalize();
1123 void
1124 tnewline(int first_col)
1126 int y = term.c.y;
1128 if (y == term.bot) {
1129 tscrollup(term.top, 1);
1130 } else {
1131 y++;
1133 tmoveto(first_col ? 0 : term.c.x, y);
1136 void
1137 csiparse(void)
1139 char *p = csiescseq.buf, *np;
1140 long int v;
1142 csiescseq.narg = 0;
1143 if (*p == '?') {
1144 csiescseq.priv = 1;
1145 p++;
1148 csiescseq.buf[csiescseq.len] = '\0';
1149 while (p < csiescseq.buf+csiescseq.len) {
1150 np = NULL;
1151 v = strtol(p, &np, 10);
1152 if (np == p)
1153 v = 0;
1154 if (v == LONG_MAX || v == LONG_MIN)
1155 v = -1;
1156 csiescseq.arg[csiescseq.narg++] = v;
1157 p = np;
1158 if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ)
1159 break;
1160 p++;
1162 csiescseq.mode[0] = *p++;
1163 csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0';
1166 /* for absolute user moves, when decom is set */
1167 void
1168 tmoveato(int x, int y)
1170 tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0));
1173 void
1174 tmoveto(int x, int y)
1176 int miny, maxy;
1178 if (term.c.state & CURSOR_ORIGIN) {
1179 miny = term.top;
1180 maxy = term.bot;
1181 } else {
1182 miny = 0;
1183 maxy = term.row - 1;
1185 term.c.state &= ~CURSOR_WRAPNEXT;
1186 term.c.x = LIMIT(x, 0, term.col-1);
1187 term.c.y = LIMIT(y, miny, maxy);
1190 void
1191 tsetchar(Rune u, Glyph *attr, int x, int y)
1193 static char *vt100_0[62] = { /* 0x41 - 0x7e */
1194 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1195 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1196 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1197 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1198 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1199 "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1200 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1201 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1205 * The table is proudly stolen from rxvt.
1207 if (term.trantbl[term.charset] == CS_GRAPHIC0 &&
1208 BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
1209 utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ);
1211 if (term.line[y][x].mode & ATTR_WIDE) {
1212 if (x+1 < term.col) {
1213 term.line[y][x+1].u = ' ';
1214 term.line[y][x+1].mode &= ~ATTR_WDUMMY;
1216 } else if (term.line[y][x].mode & ATTR_WDUMMY) {
1217 term.line[y][x-1].u = ' ';
1218 term.line[y][x-1].mode &= ~ATTR_WIDE;
1221 term.dirty[y] = 1;
1222 term.line[y][x] = *attr;
1223 term.line[y][x].u = u;
1226 void
1227 tclearregion(int x1, int y1, int x2, int y2)
1229 int x, y, temp;
1230 Glyph *gp;
1232 if (x1 > x2)
1233 temp = x1, x1 = x2, x2 = temp;
1234 if (y1 > y2)
1235 temp = y1, y1 = y2, y2 = temp;
1237 LIMIT(x1, 0, term.col-1);
1238 LIMIT(x2, 0, term.col-1);
1239 LIMIT(y1, 0, term.row-1);
1240 LIMIT(y2, 0, term.row-1);
1242 for (y = y1; y <= y2; y++) {
1243 term.dirty[y] = 1;
1244 for (x = x1; x <= x2; x++) {
1245 gp = &term.line[y][x];
1246 if (selected(x, y))
1247 selclear();
1248 gp->fg = term.c.attr.fg;
1249 gp->bg = term.c.attr.bg;
1250 gp->mode = 0;
1251 gp->u = ' ';
1256 void
1257 tdeletechar(int n)
1259 int dst, src, size;
1260 Glyph *line;
1262 LIMIT(n, 0, term.col - term.c.x);
1264 dst = term.c.x;
1265 src = term.c.x + n;
1266 size = term.col - src;
1267 line = term.line[term.c.y];
1269 memmove(&line[dst], &line[src], size * sizeof(Glyph));
1270 tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
1273 void
1274 tinsertblank(int n)
1276 int dst, src, size;
1277 Glyph *line;
1279 LIMIT(n, 0, term.col - term.c.x);
1281 dst = term.c.x + n;
1282 src = term.c.x;
1283 size = term.col - dst;
1284 line = term.line[term.c.y];
1286 memmove(&line[dst], &line[src], size * sizeof(Glyph));
1287 tclearregion(src, term.c.y, dst - 1, term.c.y);
1290 void
1291 tinsertblankline(int n)
1293 if (BETWEEN(term.c.y, term.top, term.bot))
1294 tscrolldown(term.c.y, n);
1297 void
1298 tdeleteline(int n)
1300 if (BETWEEN(term.c.y, term.top, term.bot))
1301 tscrollup(term.c.y, n);
1304 int32_t
1305 tdefcolor(int *attr, int *npar, int l)
1307 int32_t idx = -1;
1308 uint r, g, b;
1310 switch (attr[*npar + 1]) {
1311 case 2: /* direct color in RGB space */
1312 if (*npar + 4 >= l) {
1313 fprintf(stderr,
1314 "erresc(38): Incorrect number of parameters (%d)\n",
1315 *npar);
1316 break;
1318 r = attr[*npar + 2];
1319 g = attr[*npar + 3];
1320 b = attr[*npar + 4];
1321 *npar += 4;
1322 if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255))
1323 fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n",
1324 r, g, b);
1325 else
1326 idx = TRUECOLOR(r, g, b);
1327 break;
1328 case 5: /* indexed color */
1329 if (*npar + 2 >= l) {
1330 fprintf(stderr,
1331 "erresc(38): Incorrect number of parameters (%d)\n",
1332 *npar);
1333 break;
1335 *npar += 2;
1336 if (!BETWEEN(attr[*npar], 0, 255))
1337 fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]);
1338 else
1339 idx = attr[*npar];
1340 break;
1341 case 0: /* implemented defined (only foreground) */
1342 case 1: /* transparent */
1343 case 3: /* direct color in CMY space */
1344 case 4: /* direct color in CMYK space */
1345 default:
1346 fprintf(stderr,
1347 "erresc(38): gfx attr %d unknown\n", attr[*npar]);
1348 break;
1351 return idx;
1354 void
1355 tsetattr(int *attr, int l)
1357 int i;
1358 int32_t idx;
1360 for (i = 0; i < l; i++) {
1361 switch (attr[i]) {
1362 case 0:
1363 term.c.attr.mode &= ~(
1364 ATTR_BOLD |
1365 ATTR_FAINT |
1366 ATTR_ITALIC |
1367 ATTR_UNDERLINE |
1368 ATTR_BLINK |
1369 ATTR_REVERSE |
1370 ATTR_INVISIBLE |
1371 ATTR_STRUCK );
1372 term.c.attr.fg = defaultfg;
1373 term.c.attr.bg = defaultbg;
1374 break;
1375 case 1:
1376 term.c.attr.mode |= ATTR_BOLD;
1377 break;
1378 case 2:
1379 term.c.attr.mode |= ATTR_FAINT;
1380 break;
1381 case 3:
1382 term.c.attr.mode |= ATTR_ITALIC;
1383 break;
1384 case 4:
1385 term.c.attr.mode |= ATTR_UNDERLINE;
1386 break;
1387 case 5: /* slow blink */
1388 /* FALLTHROUGH */
1389 case 6: /* rapid blink */
1390 term.c.attr.mode |= ATTR_BLINK;
1391 break;
1392 case 7:
1393 term.c.attr.mode |= ATTR_REVERSE;
1394 break;
1395 case 8:
1396 term.c.attr.mode |= ATTR_INVISIBLE;
1397 break;
1398 case 9:
1399 term.c.attr.mode |= ATTR_STRUCK;
1400 break;
1401 case 22:
1402 term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT);
1403 break;
1404 case 23:
1405 term.c.attr.mode &= ~ATTR_ITALIC;
1406 break;
1407 case 24:
1408 term.c.attr.mode &= ~ATTR_UNDERLINE;
1409 break;
1410 case 25:
1411 term.c.attr.mode &= ~ATTR_BLINK;
1412 break;
1413 case 27:
1414 term.c.attr.mode &= ~ATTR_REVERSE;
1415 break;
1416 case 28:
1417 term.c.attr.mode &= ~ATTR_INVISIBLE;
1418 break;
1419 case 29:
1420 term.c.attr.mode &= ~ATTR_STRUCK;
1421 break;
1422 case 38:
1423 if ((idx = tdefcolor(attr, &i, l)) >= 0)
1424 term.c.attr.fg = idx;
1425 break;
1426 case 39:
1427 term.c.attr.fg = defaultfg;
1428 break;
1429 case 48:
1430 if ((idx = tdefcolor(attr, &i, l)) >= 0)
1431 term.c.attr.bg = idx;
1432 break;
1433 case 49:
1434 term.c.attr.bg = defaultbg;
1435 break;
1436 default:
1437 if (BETWEEN(attr[i], 30, 37)) {
1438 term.c.attr.fg = attr[i] - 30;
1439 } else if (BETWEEN(attr[i], 40, 47)) {
1440 term.c.attr.bg = attr[i] - 40;
1441 } else if (BETWEEN(attr[i], 90, 97)) {
1442 term.c.attr.fg = attr[i] - 90 + 8;
1443 } else if (BETWEEN(attr[i], 100, 107)) {
1444 term.c.attr.bg = attr[i] - 100 + 8;
1445 } else {
1446 fprintf(stderr,
1447 "erresc(default): gfx attr %d unknown\n",
1448 attr[i]);
1449 csidump();
1451 break;
1456 void
1457 tsetscroll(int t, int b)
1459 int temp;
1461 LIMIT(t, 0, term.row-1);
1462 LIMIT(b, 0, term.row-1);
1463 if (t > b) {
1464 temp = t;
1465 t = b;
1466 b = temp;
1468 term.top = t;
1469 term.bot = b;
1472 void
1473 tsetmode(int priv, int set, int *args, int narg)
1475 int alt, *lim;
1477 for (lim = args + narg; args < lim; ++args) {
1478 if (priv) {
1479 switch (*args) {
1480 case 1: /* DECCKM -- Cursor key */
1481 xsetmode(set, MODE_APPCURSOR);
1482 break;
1483 case 5: /* DECSCNM -- Reverse video */
1484 xsetmode(set, MODE_REVERSE);
1485 break;
1486 case 6: /* DECOM -- Origin */
1487 MODBIT(term.c.state, set, CURSOR_ORIGIN);
1488 tmoveato(0, 0);
1489 break;
1490 case 7: /* DECAWM -- Auto wrap */
1491 MODBIT(term.mode, set, MODE_WRAP);
1492 break;
1493 case 0: /* Error (IGNORED) */
1494 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1495 case 3: /* DECCOLM -- Column (IGNORED) */
1496 case 4: /* DECSCLM -- Scroll (IGNORED) */
1497 case 8: /* DECARM -- Auto repeat (IGNORED) */
1498 case 18: /* DECPFF -- Printer feed (IGNORED) */
1499 case 19: /* DECPEX -- Printer extent (IGNORED) */
1500 case 42: /* DECNRCM -- National characters (IGNORED) */
1501 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1502 break;
1503 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1504 xsetmode(!set, MODE_HIDE);
1505 break;
1506 case 9: /* X10 mouse compatibility mode */
1507 xsetpointermotion(0);
1508 xsetmode(0, MODE_MOUSE);
1509 xsetmode(set, MODE_MOUSEX10);
1510 break;
1511 case 1000: /* 1000: report button press */
1512 xsetpointermotion(0);
1513 xsetmode(0, MODE_MOUSE);
1514 xsetmode(set, MODE_MOUSEBTN);
1515 break;
1516 case 1002: /* 1002: report motion on button press */
1517 xsetpointermotion(0);
1518 xsetmode(0, MODE_MOUSE);
1519 xsetmode(set, MODE_MOUSEMOTION);
1520 break;
1521 case 1003: /* 1003: enable all mouse motions */
1522 xsetpointermotion(set);
1523 xsetmode(0, MODE_MOUSE);
1524 xsetmode(set, MODE_MOUSEMANY);
1525 break;
1526 case 1004: /* 1004: send focus events to tty */
1527 xsetmode(set, MODE_FOCUS);
1528 break;
1529 case 1006: /* 1006: extended reporting mode */
1530 xsetmode(set, MODE_MOUSESGR);
1531 break;
1532 case 1034:
1533 xsetmode(set, MODE_8BIT);
1534 break;
1535 case 1049: /* swap screen & set/restore cursor as xterm */
1536 if (!allowaltscreen)
1537 break;
1538 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1539 /* FALLTHROUGH */
1540 case 47: /* swap screen */
1541 case 1047:
1542 if (!allowaltscreen)
1543 break;
1544 alt = IS_SET(MODE_ALTSCREEN);
1545 if (alt) {
1546 tclearregion(0, 0, term.col-1,
1547 term.row-1);
1549 if (set ^ alt) /* set is always 1 or 0 */
1550 tswapscreen();
1551 if (*args != 1049)
1552 break;
1553 /* FALLTHROUGH */
1554 case 1048:
1555 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1556 break;
1557 case 2004: /* 2004: bracketed paste mode */
1558 xsetmode(set, MODE_BRCKTPASTE);
1559 break;
1560 /* Not implemented mouse modes. See comments there. */
1561 case 1001: /* mouse highlight mode; can hang the
1562 terminal by design when implemented. */
1563 case 1005: /* UTF-8 mouse mode; will confuse
1564 applications not supporting UTF-8
1565 and luit. */
1566 case 1015: /* urxvt mangled mouse mode; incompatible
1567 and can be mistaken for other control
1568 codes. */
1569 break;
1570 default:
1571 fprintf(stderr,
1572 "erresc: unknown private set/reset mode %d\n",
1573 *args);
1574 break;
1576 } else {
1577 switch (*args) {
1578 case 0: /* Error (IGNORED) */
1579 break;
1580 case 2:
1581 xsetmode(set, MODE_KBDLOCK);
1582 break;
1583 case 4: /* IRM -- Insertion-replacement */
1584 MODBIT(term.mode, set, MODE_INSERT);
1585 break;
1586 case 12: /* SRM -- Send/Receive */
1587 MODBIT(term.mode, !set, MODE_ECHO);
1588 break;
1589 case 20: /* LNM -- Linefeed/new line */
1590 MODBIT(term.mode, set, MODE_CRLF);
1591 break;
1592 default:
1593 fprintf(stderr,
1594 "erresc: unknown set/reset mode %d\n",
1595 *args);
1596 break;
1602 void
1603 csihandle(void)
1605 char buf[40];
1606 int len;
1608 switch (csiescseq.mode[0]) {
1609 default:
1610 unknown:
1611 fprintf(stderr, "erresc: unknown csi ");
1612 csidump();
1613 /* die(""); */
1614 break;
1615 case '@': /* ICH -- Insert <n> blank char */
1616 DEFAULT(csiescseq.arg[0], 1);
1617 tinsertblank(csiescseq.arg[0]);
1618 break;
1619 case 'A': /* CUU -- Cursor <n> Up */
1620 DEFAULT(csiescseq.arg[0], 1);
1621 tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
1622 break;
1623 case 'B': /* CUD -- Cursor <n> Down */
1624 case 'e': /* VPR --Cursor <n> Down */
1625 DEFAULT(csiescseq.arg[0], 1);
1626 tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
1627 break;
1628 case 'i': /* MC -- Media Copy */
1629 switch (csiescseq.arg[0]) {
1630 case 0:
1631 tdump();
1632 break;
1633 case 1:
1634 tdumpline(term.c.y);
1635 break;
1636 case 2:
1637 tdumpsel();
1638 break;
1639 case 4:
1640 term.mode &= ~MODE_PRINT;
1641 break;
1642 case 5:
1643 term.mode |= MODE_PRINT;
1644 break;
1646 break;
1647 case 'c': /* DA -- Device Attributes */
1648 if (csiescseq.arg[0] == 0)
1649 ttywrite(vtiden, strlen(vtiden), 0);
1650 break;
1651 case 'b': /* REP -- if last char is printable print it <n> more times */
1652 DEFAULT(csiescseq.arg[0], 1);
1653 if (term.lastc)
1654 while (csiescseq.arg[0]-- > 0)
1655 tputc(term.lastc);
1656 break;
1657 case 'C': /* CUF -- Cursor <n> Forward */
1658 case 'a': /* HPR -- Cursor <n> Forward */
1659 DEFAULT(csiescseq.arg[0], 1);
1660 tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
1661 break;
1662 case 'D': /* CUB -- Cursor <n> Backward */
1663 DEFAULT(csiescseq.arg[0], 1);
1664 tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
1665 break;
1666 case 'E': /* CNL -- Cursor <n> Down and first col */
1667 DEFAULT(csiescseq.arg[0], 1);
1668 tmoveto(0, term.c.y+csiescseq.arg[0]);
1669 break;
1670 case 'F': /* CPL -- Cursor <n> Up and first col */
1671 DEFAULT(csiescseq.arg[0], 1);
1672 tmoveto(0, term.c.y-csiescseq.arg[0]);
1673 break;
1674 case 'g': /* TBC -- Tabulation clear */
1675 switch (csiescseq.arg[0]) {
1676 case 0: /* clear current tab stop */
1677 term.tabs[term.c.x] = 0;
1678 break;
1679 case 3: /* clear all the tabs */
1680 memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1681 break;
1682 default:
1683 goto unknown;
1685 break;
1686 case 'G': /* CHA -- Move to <col> */
1687 case '`': /* HPA */
1688 DEFAULT(csiescseq.arg[0], 1);
1689 tmoveto(csiescseq.arg[0]-1, term.c.y);
1690 break;
1691 case 'H': /* CUP -- Move to <row> <col> */
1692 case 'f': /* HVP */
1693 DEFAULT(csiescseq.arg[0], 1);
1694 DEFAULT(csiescseq.arg[1], 1);
1695 tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
1696 break;
1697 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1698 DEFAULT(csiescseq.arg[0], 1);
1699 tputtab(csiescseq.arg[0]);
1700 break;
1701 case 'J': /* ED -- Clear screen */
1702 switch (csiescseq.arg[0]) {
1703 case 0: /* below */
1704 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
1705 if (term.c.y < term.row-1) {
1706 tclearregion(0, term.c.y+1, term.col-1,
1707 term.row-1);
1709 break;
1710 case 1: /* above */
1711 if (term.c.y > 1)
1712 tclearregion(0, 0, term.col-1, term.c.y-1);
1713 tclearregion(0, term.c.y, term.c.x, term.c.y);
1714 break;
1715 case 2: /* all */
1716 tclearregion(0, 0, term.col-1, term.row-1);
1717 break;
1718 default:
1719 goto unknown;
1721 break;
1722 case 'K': /* EL -- Clear line */
1723 switch (csiescseq.arg[0]) {
1724 case 0: /* right */
1725 tclearregion(term.c.x, term.c.y, term.col-1,
1726 term.c.y);
1727 break;
1728 case 1: /* left */
1729 tclearregion(0, term.c.y, term.c.x, term.c.y);
1730 break;
1731 case 2: /* all */
1732 tclearregion(0, term.c.y, term.col-1, term.c.y);
1733 break;
1735 break;
1736 case 'S': /* SU -- Scroll <n> line up */
1737 DEFAULT(csiescseq.arg[0], 1);
1738 tscrollup(term.top, csiescseq.arg[0]);
1739 break;
1740 case 'T': /* SD -- Scroll <n> line down */
1741 DEFAULT(csiescseq.arg[0], 1);
1742 tscrolldown(term.top, csiescseq.arg[0]);
1743 break;
1744 case 'L': /* IL -- Insert <n> blank lines */
1745 DEFAULT(csiescseq.arg[0], 1);
1746 tinsertblankline(csiescseq.arg[0]);
1747 break;
1748 case 'l': /* RM -- Reset Mode */
1749 tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
1750 break;
1751 case 'M': /* DL -- Delete <n> lines */
1752 DEFAULT(csiescseq.arg[0], 1);
1753 tdeleteline(csiescseq.arg[0]);
1754 break;
1755 case 'X': /* ECH -- Erase <n> char */
1756 DEFAULT(csiescseq.arg[0], 1);
1757 tclearregion(term.c.x, term.c.y,
1758 term.c.x + csiescseq.arg[0] - 1, term.c.y);
1759 break;
1760 case 'P': /* DCH -- Delete <n> char */
1761 DEFAULT(csiescseq.arg[0], 1);
1762 tdeletechar(csiescseq.arg[0]);
1763 break;
1764 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1765 DEFAULT(csiescseq.arg[0], 1);
1766 tputtab(-csiescseq.arg[0]);
1767 break;
1768 case 'd': /* VPA -- Move to <row> */
1769 DEFAULT(csiescseq.arg[0], 1);
1770 tmoveato(term.c.x, csiescseq.arg[0]-1);
1771 break;
1772 case 'h': /* SM -- Set terminal mode */
1773 tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
1774 break;
1775 case 'm': /* SGR -- Terminal attribute (color) */
1776 tsetattr(csiescseq.arg, csiescseq.narg);
1777 break;
1778 case 'n': /* DSR – Device Status Report (cursor position) */
1779 if (csiescseq.arg[0] == 6) {
1780 len = snprintf(buf, sizeof(buf), "\033[%i;%iR",
1781 term.c.y+1, term.c.x+1);
1782 ttywrite(buf, len, 0);
1784 break;
1785 case 'r': /* DECSTBM -- Set Scrolling Region */
1786 if (csiescseq.priv) {
1787 goto unknown;
1788 } else {
1789 DEFAULT(csiescseq.arg[0], 1);
1790 DEFAULT(csiescseq.arg[1], term.row);
1791 tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
1792 tmoveato(0, 0);
1794 break;
1795 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1796 tcursor(CURSOR_SAVE);
1797 break;
1798 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1799 tcursor(CURSOR_LOAD);
1800 break;
1801 case ' ':
1802 switch (csiescseq.mode[1]) {
1803 case 'q': /* DECSCUSR -- Set Cursor Style */
1804 if (xsetcursor(csiescseq.arg[0]))
1805 goto unknown;
1806 break;
1807 default:
1808 goto unknown;
1810 break;
1814 void
1815 csidump(void)
1817 size_t i;
1818 uint c;
1820 fprintf(stderr, "ESC[");
1821 for (i = 0; i < csiescseq.len; i++) {
1822 c = csiescseq.buf[i] & 0xff;
1823 if (isprint(c)) {
1824 putc(c, stderr);
1825 } else if (c == '\n') {
1826 fprintf(stderr, "(\\n)");
1827 } else if (c == '\r') {
1828 fprintf(stderr, "(\\r)");
1829 } else if (c == 0x1b) {
1830 fprintf(stderr, "(\\e)");
1831 } else {
1832 fprintf(stderr, "(%02x)", c);
1835 putc('\n', stderr);
1838 void
1839 csireset(void)
1841 memset(&csiescseq, 0, sizeof(csiescseq));
1844 void
1845 strhandle(void)
1847 char *p = NULL, *dec;
1848 int j, narg, par;
1850 term.esc &= ~(ESC_STR_END|ESC_STR);
1851 strparse();
1852 par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
1854 switch (strescseq.type) {
1855 case ']': /* OSC -- Operating System Command */
1856 switch (par) {
1857 case 0:
1858 case 1:
1859 case 2:
1860 if (narg > 1)
1861 xsettitle(strescseq.args[1]);
1862 return;
1863 case 52:
1864 if (narg > 2) {
1865 dec = base64dec(strescseq.args[2]);
1866 if (dec) {
1867 xsetsel(dec);
1868 xclipcopy();
1869 } else {
1870 fprintf(stderr, "erresc: invalid base64\n");
1873 return;
1874 case 4: /* color set */
1875 if (narg < 3)
1876 break;
1877 p = strescseq.args[2];
1878 /* FALLTHROUGH */
1879 case 104: /* color reset, here p = NULL */
1880 j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
1881 if (xsetcolorname(j, p)) {
1882 if (par == 104 && narg <= 1)
1883 return; /* color reset without parameter */
1884 fprintf(stderr, "erresc: invalid color j=%d, p=%s\n",
1885 j, p ? p : "(null)");
1886 } else {
1888 * TODO if defaultbg color is changed, borders
1889 * are dirty
1891 redraw();
1893 return;
1895 break;
1896 case 'k': /* old title set compatibility */
1897 xsettitle(strescseq.args[0]);
1898 return;
1899 case 'P': /* DCS -- Device Control String */
1900 term.mode |= ESC_DCS;
1901 case '_': /* APC -- Application Program Command */
1902 case '^': /* PM -- Privacy Message */
1903 return;
1906 fprintf(stderr, "erresc: unknown str ");
1907 strdump();
1910 void
1911 strparse(void)
1913 int c;
1914 char *p = strescseq.buf;
1916 strescseq.narg = 0;
1917 strescseq.buf[strescseq.len] = '\0';
1919 if (*p == '\0')
1920 return;
1922 while (strescseq.narg < STR_ARG_SIZ) {
1923 strescseq.args[strescseq.narg++] = p;
1924 while ((c = *p) != ';' && c != '\0')
1925 ++p;
1926 if (c == '\0')
1927 return;
1928 *p++ = '\0';
1932 void
1933 strdump(void)
1935 size_t i;
1936 uint c;
1938 fprintf(stderr, "ESC%c", strescseq.type);
1939 for (i = 0; i < strescseq.len; i++) {
1940 c = strescseq.buf[i] & 0xff;
1941 if (c == '\0') {
1942 putc('\n', stderr);
1943 return;
1944 } else if (isprint(c)) {
1945 putc(c, stderr);
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)");
1952 } else {
1953 fprintf(stderr, "(%02x)", c);
1956 fprintf(stderr, "ESC\\\n");
1959 void
1960 strreset(void)
1962 strescseq = (STREscape){
1963 .buf = xrealloc(strescseq.buf, STR_BUF_SIZ),
1964 .siz = STR_BUF_SIZ,
1968 void
1969 sendbreak(const Arg *arg)
1971 if (tcsendbreak(cmdfd, 0))
1972 perror("Error sending break");
1975 void
1976 tprinter(char *s, size_t len)
1978 if (iofd != -1 && xwrite(iofd, s, len) < 0) {
1979 perror("Error writing to output file");
1980 close(iofd);
1981 iofd = -1;
1985 void
1986 toggleprinter(const Arg *arg)
1988 term.mode ^= MODE_PRINT;
1991 void
1992 printscreen(const Arg *arg)
1994 tdump();
1997 void
1998 printsel(const Arg *arg)
2000 tdumpsel();
2003 void
2004 tdumpsel(void)
2006 char *ptr;
2008 if ((ptr = getsel())) {
2009 tprinter(ptr, strlen(ptr));
2010 free(ptr);
2014 void
2015 tdumpline(int n)
2017 char buf[UTF_SIZ];
2018 Glyph *bp, *end;
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));
2026 tprinter("\n", 1);
2029 void
2030 tdump(void)
2032 int i;
2034 for (i = 0; i < term.row; ++i)
2035 tdumpline(i);
2038 void
2039 tputtab(int n)
2041 uint x = term.c.x;
2043 if (n > 0) {
2044 while (x < term.col && n--)
2045 for (++x; x < term.col && !term.tabs[x]; ++x)
2046 /* nothing */ ;
2047 } else if (n < 0) {
2048 while (x > 0 && n++)
2049 for (--x; x > 0 && !term.tabs[x]; --x)
2050 /* nothing */ ;
2052 term.c.x = LIMIT(x, 0, term.col-1);
2055 void
2056 tdefutf8(char ascii)
2058 if (ascii == 'G')
2059 term.mode |= MODE_UTF8;
2060 else if (ascii == '@')
2061 term.mode &= ~MODE_UTF8;
2064 void
2065 tdeftran(char ascii)
2067 static char cs[] = "0B";
2068 static int vcs[] = {CS_GRAPHIC0, CS_USA};
2069 char *p;
2071 if ((p = strchr(cs, ascii)) == NULL) {
2072 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
2073 } else {
2074 term.trantbl[term.icharset] = vcs[p - cs];
2078 void
2079 tdectest(char c)
2081 int x, y;
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);
2091 void
2092 tstrsequence(uchar c)
2094 strreset();
2096 switch (c) {
2097 case 0x90: /* DCS -- Device Control String */
2098 c = 'P';
2099 term.esc |= ESC_DCS;
2100 break;
2101 case 0x9f: /* APC -- Application Program Command */
2102 c = '_';
2103 break;
2104 case 0x9e: /* PM -- Privacy Message */
2105 c = '^';
2106 break;
2107 case 0x9d: /* OSC -- Operating System Command */
2108 c = ']';
2109 break;
2111 strescseq.type = c;
2112 term.esc |= ESC_STR;
2115 void
2116 tcontrolcode(uchar ascii)
2118 switch (ascii) {
2119 case '\t': /* HT */
2120 tputtab(1);
2121 return;
2122 case '\b': /* BS */
2123 tmoveto(term.c.x-1, term.c.y);
2124 return;
2125 case '\r': /* CR */
2126 tmoveto(0, term.c.y);
2127 return;
2128 case '\f': /* LF */
2129 case '\v': /* VT */
2130 case '\n': /* LF */
2131 /* go to first col if the mode is set */
2132 tnewline(IS_SET(MODE_CRLF));
2133 return;
2134 case '\a': /* BEL */
2135 if (term.esc & ESC_STR_END) {
2136 /* backwards compatibility to xterm */
2137 strhandle();
2138 } else {
2139 xbell();
2141 break;
2142 case '\033': /* ESC */
2143 csireset();
2144 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
2145 term.esc |= ESC_START;
2146 return;
2147 case '\016': /* SO (LS1 -- Locking shift 1) */
2148 case '\017': /* SI (LS0 -- Locking shift 0) */
2149 term.charset = 1 - (ascii - '\016');
2150 return;
2151 case '\032': /* SUB */
2152 tsetchar('?', &term.c.attr, term.c.x, term.c.y);
2153 /* FALLTHROUGH */
2154 case '\030': /* CAN */
2155 csireset();
2156 break;
2157 case '\005': /* ENQ (IGNORED) */
2158 case '\000': /* NUL (IGNORED) */
2159 case '\021': /* XON (IGNORED) */
2160 case '\023': /* XOFF (IGNORED) */
2161 case 0177: /* DEL (IGNORED) */
2162 return;
2163 case 0x80: /* TODO: PAD */
2164 case 0x81: /* TODO: HOP */
2165 case 0x82: /* TODO: BPH */
2166 case 0x83: /* TODO: NBH */
2167 case 0x84: /* TODO: IND */
2168 break;
2169 case 0x85: /* NEL -- Next line */
2170 tnewline(1); /* always go to first col */
2171 break;
2172 case 0x86: /* TODO: SSA */
2173 case 0x87: /* TODO: ESA */
2174 break;
2175 case 0x88: /* HTS -- Horizontal tab stop */
2176 term.tabs[term.c.x] = 1;
2177 break;
2178 case 0x89: /* TODO: HTJ */
2179 case 0x8a: /* TODO: VTS */
2180 case 0x8b: /* TODO: PLD */
2181 case 0x8c: /* TODO: PLU */
2182 case 0x8d: /* TODO: RI */
2183 case 0x8e: /* TODO: SS2 */
2184 case 0x8f: /* TODO: SS3 */
2185 case 0x91: /* TODO: PU1 */
2186 case 0x92: /* TODO: PU2 */
2187 case 0x93: /* TODO: STS */
2188 case 0x94: /* TODO: CCH */
2189 case 0x95: /* TODO: MW */
2190 case 0x96: /* TODO: SPA */
2191 case 0x97: /* TODO: EPA */
2192 case 0x98: /* TODO: SOS */
2193 case 0x99: /* TODO: SGCI */
2194 break;
2195 case 0x9a: /* DECID -- Identify Terminal */
2196 ttywrite(vtiden, strlen(vtiden), 0);
2197 break;
2198 case 0x9b: /* TODO: CSI */
2199 case 0x9c: /* TODO: ST */
2200 break;
2201 case 0x90: /* DCS -- Device Control String */
2202 case 0x9d: /* OSC -- Operating System Command */
2203 case 0x9e: /* PM -- Privacy Message */
2204 case 0x9f: /* APC -- Application Program Command */
2205 tstrsequence(ascii);
2206 return;
2208 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2209 term.esc &= ~(ESC_STR_END|ESC_STR);
2213 * returns 1 when the sequence is finished and it hasn't to read
2214 * more characters for this sequence, otherwise 0
2217 eschandle(uchar ascii)
2219 switch (ascii) {
2220 case '[':
2221 term.esc |= ESC_CSI;
2222 return 0;
2223 case '#':
2224 term.esc |= ESC_TEST;
2225 return 0;
2226 case '%':
2227 term.esc |= ESC_UTF8;
2228 return 0;
2229 case 'P': /* DCS -- Device Control String */
2230 case '_': /* APC -- Application Program Command */
2231 case '^': /* PM -- Privacy Message */
2232 case ']': /* OSC -- Operating System Command */
2233 case 'k': /* old title set compatibility */
2234 tstrsequence(ascii);
2235 return 0;
2236 case 'n': /* LS2 -- Locking shift 2 */
2237 case 'o': /* LS3 -- Locking shift 3 */
2238 term.charset = 2 + (ascii - 'n');
2239 break;
2240 case '(': /* GZD4 -- set primary charset G0 */
2241 case ')': /* G1D4 -- set secondary charset G1 */
2242 case '*': /* G2D4 -- set tertiary charset G2 */
2243 case '+': /* G3D4 -- set quaternary charset G3 */
2244 term.icharset = ascii - '(';
2245 term.esc |= ESC_ALTCHARSET;
2246 return 0;
2247 case 'D': /* IND -- Linefeed */
2248 if (term.c.y == term.bot) {
2249 tscrollup(term.top, 1);
2250 } else {
2251 tmoveto(term.c.x, term.c.y+1);
2253 break;
2254 case 'E': /* NEL -- Next line */
2255 tnewline(1); /* always go to first col */
2256 break;
2257 case 'H': /* HTS -- Horizontal tab stop */
2258 term.tabs[term.c.x] = 1;
2259 break;
2260 case 'M': /* RI -- Reverse index */
2261 if (term.c.y == term.top) {
2262 tscrolldown(term.top, 1);
2263 } else {
2264 tmoveto(term.c.x, term.c.y-1);
2266 break;
2267 case 'Z': /* DECID -- Identify Terminal */
2268 ttywrite(vtiden, strlen(vtiden), 0);
2269 break;
2270 case 'c': /* RIS -- Reset to initial state */
2271 treset();
2272 resettitle();
2273 xloadcols();
2274 break;
2275 case '=': /* DECPAM -- Application keypad */
2276 xsetmode(1, MODE_APPKEYPAD);
2277 break;
2278 case '>': /* DECPNM -- Normal keypad */
2279 xsetmode(0, MODE_APPKEYPAD);
2280 break;
2281 case '7': /* DECSC -- Save Cursor */
2282 tcursor(CURSOR_SAVE);
2283 break;
2284 case '8': /* DECRC -- Restore Cursor */
2285 tcursor(CURSOR_LOAD);
2286 break;
2287 case '\\': /* ST -- String Terminator */
2288 if (term.esc & ESC_STR_END)
2289 strhandle();
2290 break;
2291 default:
2292 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2293 (uchar) ascii, isprint(ascii)? ascii:'.');
2294 break;
2296 return 1;
2299 void
2300 tputc(Rune u)
2302 char c[UTF_SIZ];
2303 int control;
2304 int width, len;
2305 Glyph *gp;
2307 control = ISCONTROL(u);
2308 if (u < 127 || !IS_SET(MODE_UTF8 | MODE_SIXEL)) {
2309 c[0] = u;
2310 width = len = 1;
2311 } else {
2312 len = utf8encode(u, c);
2313 if (!control && (width = wcwidth(u)) == -1)
2314 width = 1;
2317 if (IS_SET(MODE_PRINT))
2318 tprinter(c, len);
2321 * STR sequence must be checked before anything else
2322 * because it uses all following characters until it
2323 * receives a ESC, a SUB, a ST or any other C1 control
2324 * character.
2326 if (term.esc & ESC_STR) {
2327 if (u == '\a' || u == 030 || u == 032 || u == 033 ||
2328 ISCONTROLC1(u)) {
2329 term.esc &= ~(ESC_START|ESC_STR|ESC_DCS);
2330 if (IS_SET(MODE_SIXEL)) {
2331 /* TODO: render sixel */;
2332 term.mode &= ~MODE_SIXEL;
2333 return;
2335 term.esc |= ESC_STR_END;
2336 goto check_control_code;
2339 if (IS_SET(MODE_SIXEL)) {
2340 /* TODO: implement sixel mode */
2341 return;
2343 if (term.esc&ESC_DCS && strescseq.len == 0 && u == 'q')
2344 term.mode |= MODE_SIXEL;
2346 if (strescseq.len+len >= strescseq.siz) {
2348 * Here is a bug in terminals. If the user never sends
2349 * some code to stop the str or esc command, then st
2350 * will stop responding. But this is better than
2351 * silently failing with unknown characters. At least
2352 * then users will report back.
2354 * In the case users ever get fixed, here is the code:
2357 * term.esc = 0;
2358 * strhandle();
2360 if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2)
2361 return;
2362 strescseq.siz *= 2;
2363 strescseq.buf = xrealloc(strescseq.buf, strescseq.siz);
2366 memmove(&strescseq.buf[strescseq.len], c, len);
2367 strescseq.len += len;
2368 return;
2371 check_control_code:
2373 * Actions of control codes must be performed as soon they arrive
2374 * because they can be embedded inside a control sequence, and
2375 * they must not cause conflicts with sequences.
2377 if (control) {
2378 tcontrolcode(u);
2380 * control codes are not shown ever
2382 if (!term.esc)
2383 term.lastc = 0;
2384 return;
2385 } else if (term.esc & ESC_START) {
2386 if (term.esc & ESC_CSI) {
2387 csiescseq.buf[csiescseq.len++] = u;
2388 if (BETWEEN(u, 0x40, 0x7E)
2389 || csiescseq.len >= \
2390 sizeof(csiescseq.buf)-1) {
2391 term.esc = 0;
2392 csiparse();
2393 csihandle();
2395 return;
2396 } else if (term.esc & ESC_UTF8) {
2397 tdefutf8(u);
2398 } else if (term.esc & ESC_ALTCHARSET) {
2399 tdeftran(u);
2400 } else if (term.esc & ESC_TEST) {
2401 tdectest(u);
2402 } else {
2403 if (!eschandle(u))
2404 return;
2405 /* sequence already finished */
2407 term.esc = 0;
2409 * All characters which form part of a sequence are not
2410 * printed
2412 return;
2414 if (selected(term.c.x, term.c.y))
2415 selclear();
2417 gp = &term.line[term.c.y][term.c.x];
2418 if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
2419 gp->mode |= ATTR_WRAP;
2420 tnewline(1);
2421 gp = &term.line[term.c.y][term.c.x];
2424 if (IS_SET(MODE_INSERT) && term.c.x+width < term.col)
2425 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
2427 if (term.c.x+width > term.col) {
2428 tnewline(1);
2429 gp = &term.line[term.c.y][term.c.x];
2432 tsetchar(u, &term.c.attr, term.c.x, term.c.y);
2433 term.lastc = u;
2435 if (width == 2) {
2436 gp->mode |= ATTR_WIDE;
2437 if (term.c.x+1 < term.col) {
2438 gp[1].u = '\0';
2439 gp[1].mode = ATTR_WDUMMY;
2442 if (term.c.x+width < term.col) {
2443 tmoveto(term.c.x+width, term.c.y);
2444 } else {
2445 term.c.state |= CURSOR_WRAPNEXT;
2450 twrite(const char *buf, int buflen, int show_ctrl)
2452 int charsize;
2453 Rune u;
2454 int n;
2456 for (n = 0; n < buflen; n += charsize) {
2457 if (IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
2458 /* process a complete utf8 char */
2459 charsize = utf8decode(buf + n, &u, buflen - n);
2460 if (charsize == 0)
2461 break;
2462 } else {
2463 u = buf[n] & 0xFF;
2464 charsize = 1;
2466 if (show_ctrl && ISCONTROL(u)) {
2467 if (u & 0x80) {
2468 u &= 0x7f;
2469 tputc('^');
2470 tputc('[');
2471 } else if (u != '\n' && u != '\r' && u != '\t') {
2472 u ^= 0x40;
2473 tputc('^');
2476 tputc(u);
2478 return n;
2481 void
2482 tresize(int col, int row)
2484 int i;
2485 int minrow = MIN(row, term.row);
2486 int mincol = MIN(col, term.col);
2487 int *bp;
2488 TCursor c;
2490 if (col < 1 || row < 1) {
2491 fprintf(stderr,
2492 "tresize: error resizing to %dx%d\n", col, row);
2493 return;
2497 * slide screen to keep cursor where we expect it -
2498 * tscrollup would work here, but we can optimize to
2499 * memmove because we're freeing the earlier lines
2501 for (i = 0; i <= term.c.y - row; i++) {
2502 free(term.line[i]);
2503 free(term.alt[i]);
2505 /* ensure that both src and dst are not NULL */
2506 if (i > 0) {
2507 memmove(term.line, term.line + i, row * sizeof(Line));
2508 memmove(term.alt, term.alt + i, row * sizeof(Line));
2510 for (i += row; i < term.row; i++) {
2511 free(term.line[i]);
2512 free(term.alt[i]);
2515 /* resize to new height */
2516 term.line = xrealloc(term.line, row * sizeof(Line));
2517 term.alt = xrealloc(term.alt, row * sizeof(Line));
2518 term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
2519 term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
2521 /* resize each row to new width, zero-pad if needed */
2522 for (i = 0; i < minrow; i++) {
2523 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
2524 term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph));
2527 /* allocate any new rows */
2528 for (/* i = minrow */; i < row; i++) {
2529 term.line[i] = xmalloc(col * sizeof(Glyph));
2530 term.alt[i] = xmalloc(col * sizeof(Glyph));
2532 if (col > term.col) {
2533 bp = term.tabs + term.col;
2535 memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
2536 while (--bp > term.tabs && !*bp)
2537 /* nothing */ ;
2538 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
2539 *bp = 1;
2541 /* update terminal size */
2542 term.col = col;
2543 term.row = row;
2544 /* reset scrolling region */
2545 tsetscroll(0, row-1);
2546 /* make use of the LIMIT in tmoveto */
2547 tmoveto(term.c.x, term.c.y);
2548 /* Clearing both screens (it makes dirty all lines) */
2549 c = term.c;
2550 for (i = 0; i < 2; i++) {
2551 if (mincol < col && 0 < minrow) {
2552 tclearregion(mincol, 0, col - 1, minrow - 1);
2554 if (0 < col && minrow < row) {
2555 tclearregion(0, minrow, col - 1, row - 1);
2557 tswapscreen();
2558 tcursor(CURSOR_LOAD);
2560 term.c = c;
2563 void
2564 resettitle(void)
2566 xsettitle(NULL);
2569 void
2570 drawregion(int x1, int y1, int x2, int y2)
2572 int y;
2574 for (y = y1; y < y2; y++) {
2575 if (!term.dirty[y])
2576 continue;
2578 term.dirty[y] = 0;
2579 xdrawline(term.line[y], x1, y, x2);
2583 void
2584 draw(void)
2586 int cx = term.c.x, ocx = term.ocx, ocy = term.ocy;
2588 if (!xstartdraw())
2589 return;
2591 /* adjust cursor position */
2592 LIMIT(term.ocx, 0, term.col-1);
2593 LIMIT(term.ocy, 0, term.row-1);
2594 if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY)
2595 term.ocx--;
2596 if (term.line[term.c.y][cx].mode & ATTR_WDUMMY)
2597 cx--;
2599 drawregion(0, 0, term.col, term.row);
2600 xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
2601 term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
2602 term.ocx = cx;
2603 term.ocy = term.c.y;
2604 xfinishdraw();
2605 if (ocx != term.ocx || ocy != term.ocy)
2606 xximspot(term.ocx, term.ocy);
2609 void
2610 redraw(void)
2612 tfulldirt();
2613 draw();