support REP (repeat) escape sequence
[st.git] / st.c
blob54af09826c7389b4eac288273fb1aa121f471d76
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;
850 void
851 ttywrite(const char *s, size_t n, int may_echo)
853 const char *next;
855 if (may_echo && IS_SET(MODE_ECHO))
856 twrite(s, n, 1);
858 if (!IS_SET(MODE_CRLF)) {
859 ttywriteraw(s, n);
860 return;
863 /* This is similar to how the kernel handles ONLCR for ttys */
864 while (n > 0) {
865 if (*s == '\r') {
866 next = s + 1;
867 ttywriteraw("\r\n", 2);
868 } else {
869 next = memchr(s, '\r', n);
870 DEFAULT(next, s + n);
871 ttywriteraw(s, next - s);
873 n -= next - s;
874 s = next;
878 void
879 ttywriteraw(const char *s, size_t n)
881 fd_set wfd, rfd;
882 ssize_t r;
883 size_t lim = 256;
886 * Remember that we are using a pty, which might be a modem line.
887 * Writing too much will clog the line. That's why we are doing this
888 * dance.
889 * FIXME: Migrate the world to Plan 9.
891 while (n > 0) {
892 FD_ZERO(&wfd);
893 FD_ZERO(&rfd);
894 FD_SET(cmdfd, &wfd);
895 FD_SET(cmdfd, &rfd);
897 /* Check if we can write. */
898 if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) {
899 if (errno == EINTR)
900 continue;
901 die("select failed: %s\n", strerror(errno));
903 if (FD_ISSET(cmdfd, &wfd)) {
905 * Only write the bytes written by ttywrite() or the
906 * default of 256. This seems to be a reasonable value
907 * for a serial line. Bigger values might clog the I/O.
909 if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0)
910 goto write_error;
911 if (r < n) {
913 * We weren't able to write out everything.
914 * This means the buffer is getting full
915 * again. Empty it.
917 if (n < lim)
918 lim = ttyread();
919 n -= r;
920 s += r;
921 } else {
922 /* All bytes have been written. */
923 break;
926 if (FD_ISSET(cmdfd, &rfd))
927 lim = ttyread();
929 return;
931 write_error:
932 die("write error on tty: %s\n", strerror(errno));
935 void
936 ttyresize(int tw, int th)
938 struct winsize w;
940 w.ws_row = term.row;
941 w.ws_col = term.col;
942 w.ws_xpixel = tw;
943 w.ws_ypixel = th;
944 if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
945 fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
948 void
949 ttyhangup()
951 /* Send SIGHUP to shell */
952 kill(pid, SIGHUP);
956 tattrset(int attr)
958 int i, j;
960 for (i = 0; i < term.row-1; i++) {
961 for (j = 0; j < term.col-1; j++) {
962 if (term.line[i][j].mode & attr)
963 return 1;
967 return 0;
970 void
971 tsetdirt(int top, int bot)
973 int i;
975 LIMIT(top, 0, term.row-1);
976 LIMIT(bot, 0, term.row-1);
978 for (i = top; i <= bot; i++)
979 term.dirty[i] = 1;
982 void
983 tsetdirtattr(int attr)
985 int i, j;
987 for (i = 0; i < term.row-1; i++) {
988 for (j = 0; j < term.col-1; j++) {
989 if (term.line[i][j].mode & attr) {
990 tsetdirt(i, i);
991 break;
997 void
998 tfulldirt(void)
1000 tsetdirt(0, term.row-1);
1003 void
1004 tcursor(int mode)
1006 static TCursor c[2];
1007 int alt = IS_SET(MODE_ALTSCREEN);
1009 if (mode == CURSOR_SAVE) {
1010 c[alt] = term.c;
1011 } else if (mode == CURSOR_LOAD) {
1012 term.c = c[alt];
1013 tmoveto(c[alt].x, c[alt].y);
1017 void
1018 treset(void)
1020 uint i;
1022 term.c = (TCursor){{
1023 .mode = ATTR_NULL,
1024 .fg = defaultfg,
1025 .bg = defaultbg
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)
1030 term.tabs[i] = 1;
1031 term.top = 0;
1032 term.bot = term.row - 1;
1033 term.mode = MODE_WRAP|MODE_UTF8;
1034 memset(term.trantbl, CS_USA, sizeof(term.trantbl));
1035 term.charset = 0;
1037 for (i = 0; i < 2; i++) {
1038 tmoveto(0, 0);
1039 tcursor(CURSOR_SAVE);
1040 tclearregion(0, 0, term.col-1, term.row-1);
1041 tswapscreen();
1045 void
1046 tnew(int col, int row)
1048 term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
1049 tresize(col, row);
1050 treset();
1053 void
1054 tswapscreen(void)
1056 Line *tmp = term.line;
1058 term.line = term.alt;
1059 term.alt = tmp;
1060 term.mode ^= MODE_ALTSCREEN;
1061 tfulldirt();
1064 void
1065 tscrolldown(int orig, int n)
1067 int i;
1068 Line temp;
1070 LIMIT(n, 0, term.bot-orig+1);
1072 tsetdirt(orig, term.bot-n);
1073 tclearregion(0, term.bot-n+1, term.col-1, term.bot);
1075 for (i = term.bot; i >= orig+n; i--) {
1076 temp = term.line[i];
1077 term.line[i] = term.line[i-n];
1078 term.line[i-n] = temp;
1081 selscroll(orig, n);
1084 void
1085 tscrollup(int orig, int n)
1087 int i;
1088 Line temp;
1090 LIMIT(n, 0, term.bot-orig+1);
1092 tclearregion(0, orig, term.col-1, orig+n-1);
1093 tsetdirt(orig+n, term.bot);
1095 for (i = orig; i <= term.bot-n; i++) {
1096 temp = term.line[i];
1097 term.line[i] = term.line[i+n];
1098 term.line[i+n] = temp;
1101 selscroll(orig, -n);
1104 void
1105 selscroll(int orig, int n)
1107 if (sel.ob.x == -1)
1108 return;
1110 if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) {
1111 selclear();
1112 } else if (BETWEEN(sel.nb.y, orig, term.bot)) {
1113 sel.ob.y += n;
1114 sel.oe.y += n;
1115 if (sel.ob.y < term.top || sel.ob.y > term.bot ||
1116 sel.oe.y < term.top || sel.oe.y > term.bot) {
1117 selclear();
1118 } else {
1119 selnormalize();
1124 void
1125 tnewline(int first_col)
1127 int y = term.c.y;
1129 if (y == term.bot) {
1130 tscrollup(term.top, 1);
1131 } else {
1132 y++;
1134 tmoveto(first_col ? 0 : term.c.x, y);
1137 void
1138 csiparse(void)
1140 char *p = csiescseq.buf, *np;
1141 long int v;
1143 csiescseq.narg = 0;
1144 if (*p == '?') {
1145 csiescseq.priv = 1;
1146 p++;
1149 csiescseq.buf[csiescseq.len] = '\0';
1150 while (p < csiescseq.buf+csiescseq.len) {
1151 np = NULL;
1152 v = strtol(p, &np, 10);
1153 if (np == p)
1154 v = 0;
1155 if (v == LONG_MAX || v == LONG_MIN)
1156 v = -1;
1157 csiescseq.arg[csiescseq.narg++] = v;
1158 p = np;
1159 if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ)
1160 break;
1161 p++;
1163 csiescseq.mode[0] = *p++;
1164 csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0';
1167 /* for absolute user moves, when decom is set */
1168 void
1169 tmoveato(int x, int y)
1171 tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0));
1174 void
1175 tmoveto(int x, int y)
1177 int miny, maxy;
1179 if (term.c.state & CURSOR_ORIGIN) {
1180 miny = term.top;
1181 maxy = term.bot;
1182 } else {
1183 miny = 0;
1184 maxy = term.row - 1;
1186 term.c.state &= ~CURSOR_WRAPNEXT;
1187 term.c.x = LIMIT(x, 0, term.col-1);
1188 term.c.y = LIMIT(y, miny, maxy);
1191 void
1192 tsetchar(Rune u, Glyph *attr, int x, int y)
1194 static char *vt100_0[62] = { /* 0x41 - 0x7e */
1195 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1196 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1197 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1198 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1199 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1200 "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1201 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1202 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1206 * The table is proudly stolen from rxvt.
1208 if (term.trantbl[term.charset] == CS_GRAPHIC0 &&
1209 BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
1210 utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ);
1212 if (term.line[y][x].mode & ATTR_WIDE) {
1213 if (x+1 < term.col) {
1214 term.line[y][x+1].u = ' ';
1215 term.line[y][x+1].mode &= ~ATTR_WDUMMY;
1217 } else if (term.line[y][x].mode & ATTR_WDUMMY) {
1218 term.line[y][x-1].u = ' ';
1219 term.line[y][x-1].mode &= ~ATTR_WIDE;
1222 term.dirty[y] = 1;
1223 term.line[y][x] = *attr;
1224 term.line[y][x].u = u;
1227 void
1228 tclearregion(int x1, int y1, int x2, int y2)
1230 int x, y, temp;
1231 Glyph *gp;
1233 if (x1 > x2)
1234 temp = x1, x1 = x2, x2 = temp;
1235 if (y1 > y2)
1236 temp = y1, y1 = y2, y2 = temp;
1238 LIMIT(x1, 0, term.col-1);
1239 LIMIT(x2, 0, term.col-1);
1240 LIMIT(y1, 0, term.row-1);
1241 LIMIT(y2, 0, term.row-1);
1243 for (y = y1; y <= y2; y++) {
1244 term.dirty[y] = 1;
1245 for (x = x1; x <= x2; x++) {
1246 gp = &term.line[y][x];
1247 if (selected(x, y))
1248 selclear();
1249 gp->fg = term.c.attr.fg;
1250 gp->bg = term.c.attr.bg;
1251 gp->mode = 0;
1252 gp->u = ' ';
1257 void
1258 tdeletechar(int n)
1260 int dst, src, size;
1261 Glyph *line;
1263 LIMIT(n, 0, term.col - term.c.x);
1265 dst = term.c.x;
1266 src = term.c.x + n;
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);
1274 void
1275 tinsertblank(int n)
1277 int dst, src, size;
1278 Glyph *line;
1280 LIMIT(n, 0, term.col - term.c.x);
1282 dst = term.c.x + n;
1283 src = 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);
1291 void
1292 tinsertblankline(int n)
1294 if (BETWEEN(term.c.y, term.top, term.bot))
1295 tscrolldown(term.c.y, n);
1298 void
1299 tdeleteline(int n)
1301 if (BETWEEN(term.c.y, term.top, term.bot))
1302 tscrollup(term.c.y, n);
1305 int32_t
1306 tdefcolor(int *attr, int *npar, int l)
1308 int32_t idx = -1;
1309 uint r, g, b;
1311 switch (attr[*npar + 1]) {
1312 case 2: /* direct color in RGB space */
1313 if (*npar + 4 >= l) {
1314 fprintf(stderr,
1315 "erresc(38): Incorrect number of parameters (%d)\n",
1316 *npar);
1317 break;
1319 r = attr[*npar + 2];
1320 g = attr[*npar + 3];
1321 b = attr[*npar + 4];
1322 *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",
1325 r, g, b);
1326 else
1327 idx = TRUECOLOR(r, g, b);
1328 break;
1329 case 5: /* indexed color */
1330 if (*npar + 2 >= l) {
1331 fprintf(stderr,
1332 "erresc(38): Incorrect number of parameters (%d)\n",
1333 *npar);
1334 break;
1336 *npar += 2;
1337 if (!BETWEEN(attr[*npar], 0, 255))
1338 fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]);
1339 else
1340 idx = attr[*npar];
1341 break;
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 */
1346 default:
1347 fprintf(stderr,
1348 "erresc(38): gfx attr %d unknown\n", attr[*npar]);
1349 break;
1352 return idx;
1355 void
1356 tsetattr(int *attr, int l)
1358 int i;
1359 int32_t idx;
1361 for (i = 0; i < l; i++) {
1362 switch (attr[i]) {
1363 case 0:
1364 term.c.attr.mode &= ~(
1365 ATTR_BOLD |
1366 ATTR_FAINT |
1367 ATTR_ITALIC |
1368 ATTR_UNDERLINE |
1369 ATTR_BLINK |
1370 ATTR_REVERSE |
1371 ATTR_INVISIBLE |
1372 ATTR_STRUCK );
1373 term.c.attr.fg = defaultfg;
1374 term.c.attr.bg = defaultbg;
1375 break;
1376 case 1:
1377 term.c.attr.mode |= ATTR_BOLD;
1378 break;
1379 case 2:
1380 term.c.attr.mode |= ATTR_FAINT;
1381 break;
1382 case 3:
1383 term.c.attr.mode |= ATTR_ITALIC;
1384 break;
1385 case 4:
1386 term.c.attr.mode |= ATTR_UNDERLINE;
1387 break;
1388 case 5: /* slow blink */
1389 /* FALLTHROUGH */
1390 case 6: /* rapid blink */
1391 term.c.attr.mode |= ATTR_BLINK;
1392 break;
1393 case 7:
1394 term.c.attr.mode |= ATTR_REVERSE;
1395 break;
1396 case 8:
1397 term.c.attr.mode |= ATTR_INVISIBLE;
1398 break;
1399 case 9:
1400 term.c.attr.mode |= ATTR_STRUCK;
1401 break;
1402 case 22:
1403 term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT);
1404 break;
1405 case 23:
1406 term.c.attr.mode &= ~ATTR_ITALIC;
1407 break;
1408 case 24:
1409 term.c.attr.mode &= ~ATTR_UNDERLINE;
1410 break;
1411 case 25:
1412 term.c.attr.mode &= ~ATTR_BLINK;
1413 break;
1414 case 27:
1415 term.c.attr.mode &= ~ATTR_REVERSE;
1416 break;
1417 case 28:
1418 term.c.attr.mode &= ~ATTR_INVISIBLE;
1419 break;
1420 case 29:
1421 term.c.attr.mode &= ~ATTR_STRUCK;
1422 break;
1423 case 38:
1424 if ((idx = tdefcolor(attr, &i, l)) >= 0)
1425 term.c.attr.fg = idx;
1426 break;
1427 case 39:
1428 term.c.attr.fg = defaultfg;
1429 break;
1430 case 48:
1431 if ((idx = tdefcolor(attr, &i, l)) >= 0)
1432 term.c.attr.bg = idx;
1433 break;
1434 case 49:
1435 term.c.attr.bg = defaultbg;
1436 break;
1437 default:
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;
1446 } else {
1447 fprintf(stderr,
1448 "erresc(default): gfx attr %d unknown\n",
1449 attr[i]);
1450 csidump();
1452 break;
1457 void
1458 tsetscroll(int t, int b)
1460 int temp;
1462 LIMIT(t, 0, term.row-1);
1463 LIMIT(b, 0, term.row-1);
1464 if (t > b) {
1465 temp = t;
1466 t = b;
1467 b = temp;
1469 term.top = t;
1470 term.bot = b;
1473 void
1474 tsetmode(int priv, int set, int *args, int narg)
1476 int alt, *lim;
1478 for (lim = args + narg; args < lim; ++args) {
1479 if (priv) {
1480 switch (*args) {
1481 case 1: /* DECCKM -- Cursor key */
1482 xsetmode(set, MODE_APPCURSOR);
1483 break;
1484 case 5: /* DECSCNM -- Reverse video */
1485 xsetmode(set, MODE_REVERSE);
1486 break;
1487 case 6: /* DECOM -- Origin */
1488 MODBIT(term.c.state, set, CURSOR_ORIGIN);
1489 tmoveato(0, 0);
1490 break;
1491 case 7: /* DECAWM -- Auto wrap */
1492 MODBIT(term.mode, set, MODE_WRAP);
1493 break;
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) */
1503 break;
1504 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1505 xsetmode(!set, MODE_HIDE);
1506 break;
1507 case 9: /* X10 mouse compatibility mode */
1508 xsetpointermotion(0);
1509 xsetmode(0, MODE_MOUSE);
1510 xsetmode(set, MODE_MOUSEX10);
1511 break;
1512 case 1000: /* 1000: report button press */
1513 xsetpointermotion(0);
1514 xsetmode(0, MODE_MOUSE);
1515 xsetmode(set, MODE_MOUSEBTN);
1516 break;
1517 case 1002: /* 1002: report motion on button press */
1518 xsetpointermotion(0);
1519 xsetmode(0, MODE_MOUSE);
1520 xsetmode(set, MODE_MOUSEMOTION);
1521 break;
1522 case 1003: /* 1003: enable all mouse motions */
1523 xsetpointermotion(set);
1524 xsetmode(0, MODE_MOUSE);
1525 xsetmode(set, MODE_MOUSEMANY);
1526 break;
1527 case 1004: /* 1004: send focus events to tty */
1528 xsetmode(set, MODE_FOCUS);
1529 break;
1530 case 1006: /* 1006: extended reporting mode */
1531 xsetmode(set, MODE_MOUSESGR);
1532 break;
1533 case 1034:
1534 xsetmode(set, MODE_8BIT);
1535 break;
1536 case 1049: /* swap screen & set/restore cursor as xterm */
1537 if (!allowaltscreen)
1538 break;
1539 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1540 /* FALLTHROUGH */
1541 case 47: /* swap screen */
1542 case 1047:
1543 if (!allowaltscreen)
1544 break;
1545 alt = IS_SET(MODE_ALTSCREEN);
1546 if (alt) {
1547 tclearregion(0, 0, term.col-1,
1548 term.row-1);
1550 if (set ^ alt) /* set is always 1 or 0 */
1551 tswapscreen();
1552 if (*args != 1049)
1553 break;
1554 /* FALLTHROUGH */
1555 case 1048:
1556 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1557 break;
1558 case 2004: /* 2004: bracketed paste mode */
1559 xsetmode(set, MODE_BRCKTPASTE);
1560 break;
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
1566 and luit. */
1567 case 1015: /* urxvt mangled mouse mode; incompatible
1568 and can be mistaken for other control
1569 codes. */
1570 break;
1571 default:
1572 fprintf(stderr,
1573 "erresc: unknown private set/reset mode %d\n",
1574 *args);
1575 break;
1577 } else {
1578 switch (*args) {
1579 case 0: /* Error (IGNORED) */
1580 break;
1581 case 2:
1582 xsetmode(set, MODE_KBDLOCK);
1583 break;
1584 case 4: /* IRM -- Insertion-replacement */
1585 MODBIT(term.mode, set, MODE_INSERT);
1586 break;
1587 case 12: /* SRM -- Send/Receive */
1588 MODBIT(term.mode, !set, MODE_ECHO);
1589 break;
1590 case 20: /* LNM -- Linefeed/new line */
1591 MODBIT(term.mode, set, MODE_CRLF);
1592 break;
1593 default:
1594 fprintf(stderr,
1595 "erresc: unknown set/reset mode %d\n",
1596 *args);
1597 break;
1603 void
1604 csihandle(void)
1606 char buf[40];
1607 int len;
1609 switch (csiescseq.mode[0]) {
1610 default:
1611 unknown:
1612 fprintf(stderr, "erresc: unknown csi ");
1613 csidump();
1614 /* die(""); */
1615 break;
1616 case '@': /* ICH -- Insert <n> blank char */
1617 DEFAULT(csiescseq.arg[0], 1);
1618 tinsertblank(csiescseq.arg[0]);
1619 break;
1620 case 'A': /* CUU -- Cursor <n> Up */
1621 DEFAULT(csiescseq.arg[0], 1);
1622 tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
1623 break;
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]);
1628 break;
1629 case 'i': /* MC -- Media Copy */
1630 switch (csiescseq.arg[0]) {
1631 case 0:
1632 tdump();
1633 break;
1634 case 1:
1635 tdumpline(term.c.y);
1636 break;
1637 case 2:
1638 tdumpsel();
1639 break;
1640 case 4:
1641 term.mode &= ~MODE_PRINT;
1642 break;
1643 case 5:
1644 term.mode |= MODE_PRINT;
1645 break;
1647 break;
1648 case 'c': /* DA -- Device Attributes */
1649 if (csiescseq.arg[0] == 0)
1650 ttywrite(vtiden, strlen(vtiden), 0);
1651 break;
1652 case 'b': /* REP -- if last char is printable print it <n> more times */
1653 DEFAULT(csiescseq.arg[0], 1);
1654 if (term.lastc)
1655 while (csiescseq.arg[0]-- > 0)
1656 tputc(term.lastc);
1657 break;
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);
1662 break;
1663 case 'D': /* CUB -- Cursor <n> Backward */
1664 DEFAULT(csiescseq.arg[0], 1);
1665 tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
1666 break;
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]);
1670 break;
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]);
1674 break;
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;
1679 break;
1680 case 3: /* clear all the tabs */
1681 memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1682 break;
1683 default:
1684 goto unknown;
1686 break;
1687 case 'G': /* CHA -- Move to <col> */
1688 case '`': /* HPA */
1689 DEFAULT(csiescseq.arg[0], 1);
1690 tmoveto(csiescseq.arg[0]-1, term.c.y);
1691 break;
1692 case 'H': /* CUP -- Move to <row> <col> */
1693 case 'f': /* HVP */
1694 DEFAULT(csiescseq.arg[0], 1);
1695 DEFAULT(csiescseq.arg[1], 1);
1696 tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
1697 break;
1698 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1699 DEFAULT(csiescseq.arg[0], 1);
1700 tputtab(csiescseq.arg[0]);
1701 break;
1702 case 'J': /* ED -- Clear screen */
1703 switch (csiescseq.arg[0]) {
1704 case 0: /* below */
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,
1708 term.row-1);
1710 break;
1711 case 1: /* above */
1712 if (term.c.y > 1)
1713 tclearregion(0, 0, term.col-1, term.c.y-1);
1714 tclearregion(0, term.c.y, term.c.x, term.c.y);
1715 break;
1716 case 2: /* all */
1717 tclearregion(0, 0, term.col-1, term.row-1);
1718 break;
1719 default:
1720 goto unknown;
1722 break;
1723 case 'K': /* EL -- Clear line */
1724 switch (csiescseq.arg[0]) {
1725 case 0: /* right */
1726 tclearregion(term.c.x, term.c.y, term.col-1,
1727 term.c.y);
1728 break;
1729 case 1: /* left */
1730 tclearregion(0, term.c.y, term.c.x, term.c.y);
1731 break;
1732 case 2: /* all */
1733 tclearregion(0, term.c.y, term.col-1, term.c.y);
1734 break;
1736 break;
1737 case 'S': /* SU -- Scroll <n> line up */
1738 DEFAULT(csiescseq.arg[0], 1);
1739 tscrollup(term.top, csiescseq.arg[0]);
1740 break;
1741 case 'T': /* SD -- Scroll <n> line down */
1742 DEFAULT(csiescseq.arg[0], 1);
1743 tscrolldown(term.top, csiescseq.arg[0]);
1744 break;
1745 case 'L': /* IL -- Insert <n> blank lines */
1746 DEFAULT(csiescseq.arg[0], 1);
1747 tinsertblankline(csiescseq.arg[0]);
1748 break;
1749 case 'l': /* RM -- Reset Mode */
1750 tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
1751 break;
1752 case 'M': /* DL -- Delete <n> lines */
1753 DEFAULT(csiescseq.arg[0], 1);
1754 tdeleteline(csiescseq.arg[0]);
1755 break;
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);
1760 break;
1761 case 'P': /* DCH -- Delete <n> char */
1762 DEFAULT(csiescseq.arg[0], 1);
1763 tdeletechar(csiescseq.arg[0]);
1764 break;
1765 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1766 DEFAULT(csiescseq.arg[0], 1);
1767 tputtab(-csiescseq.arg[0]);
1768 break;
1769 case 'd': /* VPA -- Move to <row> */
1770 DEFAULT(csiescseq.arg[0], 1);
1771 tmoveato(term.c.x, csiescseq.arg[0]-1);
1772 break;
1773 case 'h': /* SM -- Set terminal mode */
1774 tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
1775 break;
1776 case 'm': /* SGR -- Terminal attribute (color) */
1777 tsetattr(csiescseq.arg, csiescseq.narg);
1778 break;
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);
1785 break;
1786 case 'r': /* DECSTBM -- Set Scrolling Region */
1787 if (csiescseq.priv) {
1788 goto unknown;
1789 } else {
1790 DEFAULT(csiescseq.arg[0], 1);
1791 DEFAULT(csiescseq.arg[1], term.row);
1792 tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
1793 tmoveato(0, 0);
1795 break;
1796 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1797 tcursor(CURSOR_SAVE);
1798 break;
1799 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1800 tcursor(CURSOR_LOAD);
1801 break;
1802 case ' ':
1803 switch (csiescseq.mode[1]) {
1804 case 'q': /* DECSCUSR -- Set Cursor Style */
1805 if (xsetcursor(csiescseq.arg[0]))
1806 goto unknown;
1807 break;
1808 default:
1809 goto unknown;
1811 break;
1815 void
1816 csidump(void)
1818 size_t i;
1819 uint c;
1821 fprintf(stderr, "ESC[");
1822 for (i = 0; i < csiescseq.len; i++) {
1823 c = csiescseq.buf[i] & 0xff;
1824 if (isprint(c)) {
1825 putc(c, stderr);
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)");
1832 } else {
1833 fprintf(stderr, "(%02x)", c);
1836 putc('\n', stderr);
1839 void
1840 csireset(void)
1842 memset(&csiescseq, 0, sizeof(csiescseq));
1845 void
1846 strhandle(void)
1848 char *p = NULL, *dec;
1849 int j, narg, par;
1851 term.esc &= ~(ESC_STR_END|ESC_STR);
1852 strparse();
1853 par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
1855 switch (strescseq.type) {
1856 case ']': /* OSC -- Operating System Command */
1857 switch (par) {
1858 case 0:
1859 case 1:
1860 case 2:
1861 if (narg > 1)
1862 xsettitle(strescseq.args[1]);
1863 return;
1864 case 52:
1865 if (narg > 2) {
1866 dec = base64dec(strescseq.args[2]);
1867 if (dec) {
1868 xsetsel(dec);
1869 xclipcopy();
1870 } else {
1871 fprintf(stderr, "erresc: invalid base64\n");
1874 return;
1875 case 4: /* color set */
1876 if (narg < 3)
1877 break;
1878 p = strescseq.args[2];
1879 /* FALLTHROUGH */
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)");
1887 } else {
1889 * TODO if defaultbg color is changed, borders
1890 * are dirty
1892 redraw();
1894 return;
1896 break;
1897 case 'k': /* old title set compatibility */
1898 xsettitle(strescseq.args[0]);
1899 return;
1900 case 'P': /* DCS -- Device Control String */
1901 term.mode |= ESC_DCS;
1902 case '_': /* APC -- Application Program Command */
1903 case '^': /* PM -- Privacy Message */
1904 return;
1907 fprintf(stderr, "erresc: unknown str ");
1908 strdump();
1911 void
1912 strparse(void)
1914 int c;
1915 char *p = strescseq.buf;
1917 strescseq.narg = 0;
1918 strescseq.buf[strescseq.len] = '\0';
1920 if (*p == '\0')
1921 return;
1923 while (strescseq.narg < STR_ARG_SIZ) {
1924 strescseq.args[strescseq.narg++] = p;
1925 while ((c = *p) != ';' && c != '\0')
1926 ++p;
1927 if (c == '\0')
1928 return;
1929 *p++ = '\0';
1933 void
1934 strdump(void)
1936 size_t i;
1937 uint c;
1939 fprintf(stderr, "ESC%c", strescseq.type);
1940 for (i = 0; i < strescseq.len; i++) {
1941 c = strescseq.buf[i] & 0xff;
1942 if (c == '\0') {
1943 putc('\n', stderr);
1944 return;
1945 } else if (isprint(c)) {
1946 putc(c, stderr);
1947 } else if (c == '\n') {
1948 fprintf(stderr, "(\\n)");
1949 } else if (c == '\r') {
1950 fprintf(stderr, "(\\r)");
1951 } else if (c == 0x1b) {
1952 fprintf(stderr, "(\\e)");
1953 } else {
1954 fprintf(stderr, "(%02x)", c);
1957 fprintf(stderr, "ESC\\\n");
1960 void
1961 strreset(void)
1963 strescseq = (STREscape){
1964 .buf = xrealloc(strescseq.buf, STR_BUF_SIZ),
1965 .siz = STR_BUF_SIZ,
1969 void
1970 sendbreak(const Arg *arg)
1972 if (tcsendbreak(cmdfd, 0))
1973 perror("Error sending break");
1976 void
1977 tprinter(char *s, size_t len)
1979 if (iofd != -1 && xwrite(iofd, s, len) < 0) {
1980 perror("Error writing to output file");
1981 close(iofd);
1982 iofd = -1;
1986 void
1987 toggleprinter(const Arg *arg)
1989 term.mode ^= MODE_PRINT;
1992 void
1993 printscreen(const Arg *arg)
1995 tdump();
1998 void
1999 printsel(const Arg *arg)
2001 tdumpsel();
2004 void
2005 tdumpsel(void)
2007 char *ptr;
2009 if ((ptr = getsel())) {
2010 tprinter(ptr, strlen(ptr));
2011 free(ptr);
2015 void
2016 tdumpline(int n)
2018 char buf[UTF_SIZ];
2019 Glyph *bp, *end;
2021 bp = &term.line[n][0];
2022 end = &bp[MIN(tlinelen(n), term.col) - 1];
2023 if (bp != end || bp->u != ' ') {
2024 for ( ; bp <= end; ++bp)
2025 tprinter(buf, utf8encode(bp->u, buf));
2027 tprinter("\n", 1);
2030 void
2031 tdump(void)
2033 int i;
2035 for (i = 0; i < term.row; ++i)
2036 tdumpline(i);
2039 void
2040 tputtab(int n)
2042 uint x = term.c.x;
2044 if (n > 0) {
2045 while (x < term.col && n--)
2046 for (++x; x < term.col && !term.tabs[x]; ++x)
2047 /* nothing */ ;
2048 } else if (n < 0) {
2049 while (x > 0 && n++)
2050 for (--x; x > 0 && !term.tabs[x]; --x)
2051 /* nothing */ ;
2053 term.c.x = LIMIT(x, 0, term.col-1);
2056 void
2057 tdefutf8(char ascii)
2059 if (ascii == 'G')
2060 term.mode |= MODE_UTF8;
2061 else if (ascii == '@')
2062 term.mode &= ~MODE_UTF8;
2065 void
2066 tdeftran(char ascii)
2068 static char cs[] = "0B";
2069 static int vcs[] = {CS_GRAPHIC0, CS_USA};
2070 char *p;
2072 if ((p = strchr(cs, ascii)) == NULL) {
2073 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
2074 } else {
2075 term.trantbl[term.icharset] = vcs[p - cs];
2079 void
2080 tdectest(char c)
2082 int x, y;
2084 if (c == '8') { /* DEC screen alignment test. */
2085 for (x = 0; x < term.col; ++x) {
2086 for (y = 0; y < term.row; ++y)
2087 tsetchar('E', &term.c.attr, x, y);
2092 void
2093 tstrsequence(uchar c)
2095 strreset();
2097 switch (c) {
2098 case 0x90: /* DCS -- Device Control String */
2099 c = 'P';
2100 term.esc |= ESC_DCS;
2101 break;
2102 case 0x9f: /* APC -- Application Program Command */
2103 c = '_';
2104 break;
2105 case 0x9e: /* PM -- Privacy Message */
2106 c = '^';
2107 break;
2108 case 0x9d: /* OSC -- Operating System Command */
2109 c = ']';
2110 break;
2112 strescseq.type = c;
2113 term.esc |= ESC_STR;
2116 void
2117 tcontrolcode(uchar ascii)
2119 switch (ascii) {
2120 case '\t': /* HT */
2121 tputtab(1);
2122 return;
2123 case '\b': /* BS */
2124 tmoveto(term.c.x-1, term.c.y);
2125 return;
2126 case '\r': /* CR */
2127 tmoveto(0, term.c.y);
2128 return;
2129 case '\f': /* LF */
2130 case '\v': /* VT */
2131 case '\n': /* LF */
2132 /* go to first col if the mode is set */
2133 tnewline(IS_SET(MODE_CRLF));
2134 return;
2135 case '\a': /* BEL */
2136 if (term.esc & ESC_STR_END) {
2137 /* backwards compatibility to xterm */
2138 strhandle();
2139 } else {
2140 xbell();
2142 break;
2143 case '\033': /* ESC */
2144 csireset();
2145 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
2146 term.esc |= ESC_START;
2147 return;
2148 case '\016': /* SO (LS1 -- Locking shift 1) */
2149 case '\017': /* SI (LS0 -- Locking shift 0) */
2150 term.charset = 1 - (ascii - '\016');
2151 return;
2152 case '\032': /* SUB */
2153 tsetchar('?', &term.c.attr, term.c.x, term.c.y);
2154 /* FALLTHROUGH */
2155 case '\030': /* CAN */
2156 csireset();
2157 break;
2158 case '\005': /* ENQ (IGNORED) */
2159 case '\000': /* NUL (IGNORED) */
2160 case '\021': /* XON (IGNORED) */
2161 case '\023': /* XOFF (IGNORED) */
2162 case 0177: /* DEL (IGNORED) */
2163 return;
2164 case 0x80: /* TODO: PAD */
2165 case 0x81: /* TODO: HOP */
2166 case 0x82: /* TODO: BPH */
2167 case 0x83: /* TODO: NBH */
2168 case 0x84: /* TODO: IND */
2169 break;
2170 case 0x85: /* NEL -- Next line */
2171 tnewline(1); /* always go to first col */
2172 break;
2173 case 0x86: /* TODO: SSA */
2174 case 0x87: /* TODO: ESA */
2175 break;
2176 case 0x88: /* HTS -- Horizontal tab stop */
2177 term.tabs[term.c.x] = 1;
2178 break;
2179 case 0x89: /* TODO: HTJ */
2180 case 0x8a: /* TODO: VTS */
2181 case 0x8b: /* TODO: PLD */
2182 case 0x8c: /* TODO: PLU */
2183 case 0x8d: /* TODO: RI */
2184 case 0x8e: /* TODO: SS2 */
2185 case 0x8f: /* TODO: SS3 */
2186 case 0x91: /* TODO: PU1 */
2187 case 0x92: /* TODO: PU2 */
2188 case 0x93: /* TODO: STS */
2189 case 0x94: /* TODO: CCH */
2190 case 0x95: /* TODO: MW */
2191 case 0x96: /* TODO: SPA */
2192 case 0x97: /* TODO: EPA */
2193 case 0x98: /* TODO: SOS */
2194 case 0x99: /* TODO: SGCI */
2195 break;
2196 case 0x9a: /* DECID -- Identify Terminal */
2197 ttywrite(vtiden, strlen(vtiden), 0);
2198 break;
2199 case 0x9b: /* TODO: CSI */
2200 case 0x9c: /* TODO: ST */
2201 break;
2202 case 0x90: /* DCS -- Device Control String */
2203 case 0x9d: /* OSC -- Operating System Command */
2204 case 0x9e: /* PM -- Privacy Message */
2205 case 0x9f: /* APC -- Application Program Command */
2206 tstrsequence(ascii);
2207 return;
2209 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2210 term.esc &= ~(ESC_STR_END|ESC_STR);
2214 * returns 1 when the sequence is finished and it hasn't to read
2215 * more characters for this sequence, otherwise 0
2218 eschandle(uchar ascii)
2220 switch (ascii) {
2221 case '[':
2222 term.esc |= ESC_CSI;
2223 return 0;
2224 case '#':
2225 term.esc |= ESC_TEST;
2226 return 0;
2227 case '%':
2228 term.esc |= ESC_UTF8;
2229 return 0;
2230 case 'P': /* DCS -- Device Control String */
2231 case '_': /* APC -- Application Program Command */
2232 case '^': /* PM -- Privacy Message */
2233 case ']': /* OSC -- Operating System Command */
2234 case 'k': /* old title set compatibility */
2235 tstrsequence(ascii);
2236 return 0;
2237 case 'n': /* LS2 -- Locking shift 2 */
2238 case 'o': /* LS3 -- Locking shift 3 */
2239 term.charset = 2 + (ascii - 'n');
2240 break;
2241 case '(': /* GZD4 -- set primary charset G0 */
2242 case ')': /* G1D4 -- set secondary charset G1 */
2243 case '*': /* G2D4 -- set tertiary charset G2 */
2244 case '+': /* G3D4 -- set quaternary charset G3 */
2245 term.icharset = ascii - '(';
2246 term.esc |= ESC_ALTCHARSET;
2247 return 0;
2248 case 'D': /* IND -- Linefeed */
2249 if (term.c.y == term.bot) {
2250 tscrollup(term.top, 1);
2251 } else {
2252 tmoveto(term.c.x, term.c.y+1);
2254 break;
2255 case 'E': /* NEL -- Next line */
2256 tnewline(1); /* always go to first col */
2257 break;
2258 case 'H': /* HTS -- Horizontal tab stop */
2259 term.tabs[term.c.x] = 1;
2260 break;
2261 case 'M': /* RI -- Reverse index */
2262 if (term.c.y == term.top) {
2263 tscrolldown(term.top, 1);
2264 } else {
2265 tmoveto(term.c.x, term.c.y-1);
2267 break;
2268 case 'Z': /* DECID -- Identify Terminal */
2269 ttywrite(vtiden, strlen(vtiden), 0);
2270 break;
2271 case 'c': /* RIS -- Reset to initial state */
2272 treset();
2273 resettitle();
2274 xloadcols();
2275 break;
2276 case '=': /* DECPAM -- Application keypad */
2277 xsetmode(1, MODE_APPKEYPAD);
2278 break;
2279 case '>': /* DECPNM -- Normal keypad */
2280 xsetmode(0, MODE_APPKEYPAD);
2281 break;
2282 case '7': /* DECSC -- Save Cursor */
2283 tcursor(CURSOR_SAVE);
2284 break;
2285 case '8': /* DECRC -- Restore Cursor */
2286 tcursor(CURSOR_LOAD);
2287 break;
2288 case '\\': /* ST -- String Terminator */
2289 if (term.esc & ESC_STR_END)
2290 strhandle();
2291 break;
2292 default:
2293 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2294 (uchar) ascii, isprint(ascii)? ascii:'.');
2295 break;
2297 return 1;
2300 void
2301 tputc(Rune u)
2303 char c[UTF_SIZ];
2304 int control;
2305 int width, len;
2306 Glyph *gp;
2308 control = ISCONTROL(u);
2309 if (u < 127 || !IS_SET(MODE_UTF8 | MODE_SIXEL)) {
2310 c[0] = u;
2311 width = len = 1;
2312 } else {
2313 len = utf8encode(u, c);
2314 if (!control && (width = wcwidth(u)) == -1)
2315 width = 1;
2318 if (IS_SET(MODE_PRINT))
2319 tprinter(c, len);
2322 * STR sequence must be checked before anything else
2323 * because it uses all following characters until it
2324 * receives a ESC, a SUB, a ST or any other C1 control
2325 * character.
2327 if (term.esc & ESC_STR) {
2328 if (u == '\a' || u == 030 || u == 032 || u == 033 ||
2329 ISCONTROLC1(u)) {
2330 term.esc &= ~(ESC_START|ESC_STR|ESC_DCS);
2331 if (IS_SET(MODE_SIXEL)) {
2332 /* TODO: render sixel */;
2333 term.mode &= ~MODE_SIXEL;
2334 return;
2336 term.esc |= ESC_STR_END;
2337 goto check_control_code;
2340 if (IS_SET(MODE_SIXEL)) {
2341 /* TODO: implement sixel mode */
2342 return;
2344 if (term.esc&ESC_DCS && strescseq.len == 0 && u == 'q')
2345 term.mode |= MODE_SIXEL;
2347 if (strescseq.len+len >= strescseq.siz) {
2349 * Here is a bug in terminals. If the user never sends
2350 * some code to stop the str or esc command, then st
2351 * will stop responding. But this is better than
2352 * silently failing with unknown characters. At least
2353 * then users will report back.
2355 * In the case users ever get fixed, here is the code:
2358 * term.esc = 0;
2359 * strhandle();
2361 if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2)
2362 return;
2363 strescseq.siz *= 2;
2364 strescseq.buf = xrealloc(strescseq.buf, strescseq.siz);
2367 memmove(&strescseq.buf[strescseq.len], c, len);
2368 strescseq.len += len;
2369 return;
2372 check_control_code:
2374 * Actions of control codes must be performed as soon they arrive
2375 * because they can be embedded inside a control sequence, and
2376 * they must not cause conflicts with sequences.
2378 if (control) {
2379 tcontrolcode(u);
2381 * control codes are not shown ever
2383 if (!term.esc)
2384 term.lastc = 0;
2385 return;
2386 } else if (term.esc & ESC_START) {
2387 if (term.esc & ESC_CSI) {
2388 csiescseq.buf[csiescseq.len++] = u;
2389 if (BETWEEN(u, 0x40, 0x7E)
2390 || csiescseq.len >= \
2391 sizeof(csiescseq.buf)-1) {
2392 term.esc = 0;
2393 csiparse();
2394 csihandle();
2396 return;
2397 } else if (term.esc & ESC_UTF8) {
2398 tdefutf8(u);
2399 } else if (term.esc & ESC_ALTCHARSET) {
2400 tdeftran(u);
2401 } else if (term.esc & ESC_TEST) {
2402 tdectest(u);
2403 } else {
2404 if (!eschandle(u))
2405 return;
2406 /* sequence already finished */
2408 term.esc = 0;
2410 * All characters which form part of a sequence are not
2411 * printed
2413 return;
2415 if (selected(term.c.x, term.c.y))
2416 selclear();
2418 gp = &term.line[term.c.y][term.c.x];
2419 if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
2420 gp->mode |= ATTR_WRAP;
2421 tnewline(1);
2422 gp = &term.line[term.c.y][term.c.x];
2425 if (IS_SET(MODE_INSERT) && term.c.x+width < term.col)
2426 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
2428 if (term.c.x+width > term.col) {
2429 tnewline(1);
2430 gp = &term.line[term.c.y][term.c.x];
2433 tsetchar(u, &term.c.attr, term.c.x, term.c.y);
2434 term.lastc = u;
2436 if (width == 2) {
2437 gp->mode |= ATTR_WIDE;
2438 if (term.c.x+1 < term.col) {
2439 gp[1].u = '\0';
2440 gp[1].mode = ATTR_WDUMMY;
2443 if (term.c.x+width < term.col) {
2444 tmoveto(term.c.x+width, term.c.y);
2445 } else {
2446 term.c.state |= CURSOR_WRAPNEXT;
2451 twrite(const char *buf, int buflen, int show_ctrl)
2453 int charsize;
2454 Rune u;
2455 int n;
2457 for (n = 0; n < buflen; n += charsize) {
2458 if (IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
2459 /* process a complete utf8 char */
2460 charsize = utf8decode(buf + n, &u, buflen - n);
2461 if (charsize == 0)
2462 break;
2463 } else {
2464 u = buf[n] & 0xFF;
2465 charsize = 1;
2467 if (show_ctrl && ISCONTROL(u)) {
2468 if (u & 0x80) {
2469 u &= 0x7f;
2470 tputc('^');
2471 tputc('[');
2472 } else if (u != '\n' && u != '\r' && u != '\t') {
2473 u ^= 0x40;
2474 tputc('^');
2477 tputc(u);
2479 return n;
2482 void
2483 tresize(int col, int row)
2485 int i;
2486 int minrow = MIN(row, term.row);
2487 int mincol = MIN(col, term.col);
2488 int *bp;
2489 TCursor c;
2491 if (col < 1 || row < 1) {
2492 fprintf(stderr,
2493 "tresize: error resizing to %dx%d\n", col, row);
2494 return;
2498 * slide screen to keep cursor where we expect it -
2499 * tscrollup would work here, but we can optimize to
2500 * memmove because we're freeing the earlier lines
2502 for (i = 0; i <= term.c.y - row; i++) {
2503 free(term.line[i]);
2504 free(term.alt[i]);
2506 /* ensure that both src and dst are not NULL */
2507 if (i > 0) {
2508 memmove(term.line, term.line + i, row * sizeof(Line));
2509 memmove(term.alt, term.alt + i, row * sizeof(Line));
2511 for (i += row; i < term.row; i++) {
2512 free(term.line[i]);
2513 free(term.alt[i]);
2516 /* resize to new height */
2517 term.line = xrealloc(term.line, row * sizeof(Line));
2518 term.alt = xrealloc(term.alt, row * sizeof(Line));
2519 term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
2520 term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
2522 /* resize each row to new width, zero-pad if needed */
2523 for (i = 0; i < minrow; i++) {
2524 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
2525 term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph));
2528 /* allocate any new rows */
2529 for (/* i = minrow */; i < row; i++) {
2530 term.line[i] = xmalloc(col * sizeof(Glyph));
2531 term.alt[i] = xmalloc(col * sizeof(Glyph));
2533 if (col > term.col) {
2534 bp = term.tabs + term.col;
2536 memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
2537 while (--bp > term.tabs && !*bp)
2538 /* nothing */ ;
2539 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
2540 *bp = 1;
2542 /* update terminal size */
2543 term.col = col;
2544 term.row = row;
2545 /* reset scrolling region */
2546 tsetscroll(0, row-1);
2547 /* make use of the LIMIT in tmoveto */
2548 tmoveto(term.c.x, term.c.y);
2549 /* Clearing both screens (it makes dirty all lines) */
2550 c = term.c;
2551 for (i = 0; i < 2; i++) {
2552 if (mincol < col && 0 < minrow) {
2553 tclearregion(mincol, 0, col - 1, minrow - 1);
2555 if (0 < col && minrow < row) {
2556 tclearregion(0, minrow, col - 1, row - 1);
2558 tswapscreen();
2559 tcursor(CURSOR_LOAD);
2561 term.c = c;
2564 void
2565 resettitle(void)
2567 xsettitle(NULL);
2570 void
2571 drawregion(int x1, int y1, int x2, int y2)
2573 int y;
2575 for (y = y1; y < y2; y++) {
2576 if (!term.dirty[y])
2577 continue;
2579 term.dirty[y] = 0;
2580 xdrawline(term.line[y], x1, y, x2);
2584 void
2585 draw(void)
2587 int cx = term.c.x, ocx = term.ocx, ocy = term.ocy;
2589 if (!xstartdraw())
2590 return;
2592 /* adjust cursor position */
2593 LIMIT(term.ocx, 0, term.col-1);
2594 LIMIT(term.ocy, 0, term.row-1);
2595 if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY)
2596 term.ocx--;
2597 if (term.line[term.c.y][cx].mode & ATTR_WDUMMY)
2598 cx--;
2600 drawregion(0, 0, term.col, term.row);
2601 xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
2602 term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
2603 term.ocx = cx;
2604 term.ocy = term.c.y;
2605 xfinishdraw();
2606 if (ocx != term.ocx || ocy != term.ocy)
2607 xximspot(term.ocx, term.ocy);
2610 void
2611 redraw(void)
2613 tfulldirt();
2614 draw();