tiny code-style and typo-fix in comment
[st.git] / st.c
blobc161497638ca2c6ba919b556f77f77ad3638f8b3
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 } Term;
134 /* CSI Escape sequence structs */
135 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
136 typedef struct {
137 char buf[ESC_BUF_SIZ]; /* raw string */
138 size_t len; /* raw string length */
139 char priv;
140 int arg[ESC_ARG_SIZ];
141 int narg; /* nb of args */
142 char mode[2];
143 } CSIEscape;
145 /* STR Escape sequence structs */
146 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
147 typedef struct {
148 char type; /* ESC type ... */
149 char *buf; /* allocated raw string */
150 size_t siz; /* allocation size */
151 size_t len; /* raw string length */
152 char *args[STR_ARG_SIZ];
153 int narg; /* nb of args */
154 } STREscape;
156 static void execsh(char *, char **);
157 static void stty(char **);
158 static void sigchld(int);
159 static void ttywriteraw(const char *, size_t);
161 static void csidump(void);
162 static void csihandle(void);
163 static void csiparse(void);
164 static void csireset(void);
165 static int eschandle(uchar);
166 static void strdump(void);
167 static void strhandle(void);
168 static void strparse(void);
169 static void strreset(void);
171 static void tprinter(char *, size_t);
172 static void tdumpsel(void);
173 static void tdumpline(int);
174 static void tdump(void);
175 static void tclearregion(int, int, int, int);
176 static void tcursor(int);
177 static void tdeletechar(int);
178 static void tdeleteline(int);
179 static void tinsertblank(int);
180 static void tinsertblankline(int);
181 static int tlinelen(int);
182 static void tmoveto(int, int);
183 static void tmoveato(int, int);
184 static void tnewline(int);
185 static void tputtab(int);
186 static void tputc(Rune);
187 static void treset(void);
188 static void tscrollup(int, int);
189 static void tscrolldown(int, int);
190 static void tsetattr(int *, int);
191 static void tsetchar(Rune, Glyph *, int, int);
192 static void tsetdirt(int, int);
193 static void tsetscroll(int, int);
194 static void tswapscreen(void);
195 static void tsetmode(int, int, int *, int);
196 static int twrite(const char *, int, int);
197 static void tfulldirt(void);
198 static void tcontrolcode(uchar );
199 static void tdectest(char );
200 static void tdefutf8(char);
201 static int32_t tdefcolor(int *, int *, int);
202 static void tdeftran(char);
203 static void tstrsequence(uchar);
205 static void drawregion(int, int, int, int);
207 static void selnormalize(void);
208 static void selscroll(int, int);
209 static void selsnap(int *, int *, int);
211 static size_t utf8decode(const char *, Rune *, size_t);
212 static Rune utf8decodebyte(char, size_t *);
213 static char utf8encodebyte(Rune, size_t);
214 static size_t utf8validate(Rune *, size_t);
216 static char *base64dec(const char *);
217 static char base64dec_getc(const char **);
219 static ssize_t xwrite(int, const char *, size_t);
221 /* Globals */
222 static Term term;
223 static Selection sel;
224 static CSIEscape csiescseq;
225 static STREscape strescseq;
226 static int iofd = 1;
227 static int cmdfd;
228 static pid_t pid;
230 static uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
231 static uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
232 static Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000};
233 static Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
235 ssize_t
236 xwrite(int fd, const char *s, size_t len)
238 size_t aux = len;
239 ssize_t r;
241 while (len > 0) {
242 r = write(fd, s, len);
243 if (r < 0)
244 return r;
245 len -= r;
246 s += r;
249 return aux;
252 void *
253 xmalloc(size_t len)
255 void *p;
257 if (!(p = malloc(len)))
258 die("malloc: %s\n", strerror(errno));
260 return p;
263 void *
264 xrealloc(void *p, size_t len)
266 if ((p = realloc(p, len)) == NULL)
267 die("realloc: %s\n", strerror(errno));
269 return p;
272 char *
273 xstrdup(char *s)
275 if ((s = strdup(s)) == NULL)
276 die("strdup: %s\n", strerror(errno));
278 return s;
281 size_t
282 utf8decode(const char *c, Rune *u, size_t clen)
284 size_t i, j, len, type;
285 Rune udecoded;
287 *u = UTF_INVALID;
288 if (!clen)
289 return 0;
290 udecoded = utf8decodebyte(c[0], &len);
291 if (!BETWEEN(len, 1, UTF_SIZ))
292 return 1;
293 for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
294 udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
295 if (type != 0)
296 return j;
298 if (j < len)
299 return 0;
300 *u = udecoded;
301 utf8validate(u, len);
303 return len;
306 Rune
307 utf8decodebyte(char c, size_t *i)
309 for (*i = 0; *i < LEN(utfmask); ++(*i))
310 if (((uchar)c & utfmask[*i]) == utfbyte[*i])
311 return (uchar)c & ~utfmask[*i];
313 return 0;
316 size_t
317 utf8encode(Rune u, char *c)
319 size_t len, i;
321 len = utf8validate(&u, 0);
322 if (len > UTF_SIZ)
323 return 0;
325 for (i = len - 1; i != 0; --i) {
326 c[i] = utf8encodebyte(u, 0);
327 u >>= 6;
329 c[0] = utf8encodebyte(u, len);
331 return len;
334 char
335 utf8encodebyte(Rune u, size_t i)
337 return utfbyte[i] | (u & ~utfmask[i]);
340 size_t
341 utf8validate(Rune *u, size_t i)
343 if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF))
344 *u = UTF_INVALID;
345 for (i = 1; *u > utfmax[i]; ++i)
348 return i;
351 static const char base64_digits[] = {
352 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
353 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0,
354 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
355 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
356 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
357 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0,
358 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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
366 char
367 base64dec_getc(const char **src)
369 while (**src && !isprint(**src))
370 (*src)++;
371 return **src ? *((*src)++) : '='; /* emulate padding if string ends */
374 char *
375 base64dec(const char *src)
377 size_t in_len = strlen(src);
378 char *result, *dst;
380 if (in_len % 4)
381 in_len += 4 - (in_len % 4);
382 result = dst = xmalloc(in_len / 4 * 3 + 1);
383 while (*src) {
384 int a = base64_digits[(unsigned char) base64dec_getc(&src)];
385 int b = base64_digits[(unsigned char) base64dec_getc(&src)];
386 int c = base64_digits[(unsigned char) base64dec_getc(&src)];
387 int d = base64_digits[(unsigned char) base64dec_getc(&src)];
389 /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */
390 if (a == -1 || b == -1)
391 break;
393 *dst++ = (a << 2) | ((b & 0x30) >> 4);
394 if (c == -1)
395 break;
396 *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2);
397 if (d == -1)
398 break;
399 *dst++ = ((c & 0x03) << 6) | d;
401 *dst = '\0';
402 return result;
405 void
406 selinit(void)
408 sel.mode = SEL_IDLE;
409 sel.snap = 0;
410 sel.ob.x = -1;
414 tlinelen(int y)
416 int i = term.col;
418 if (term.line[y][i - 1].mode & ATTR_WRAP)
419 return i;
421 while (i > 0 && term.line[y][i - 1].u == ' ')
422 --i;
424 return i;
427 void
428 selstart(int col, int row, int snap)
430 selclear();
431 sel.mode = SEL_EMPTY;
432 sel.type = SEL_REGULAR;
433 sel.alt = IS_SET(MODE_ALTSCREEN);
434 sel.snap = snap;
435 sel.oe.x = sel.ob.x = col;
436 sel.oe.y = sel.ob.y = row;
437 selnormalize();
439 if (sel.snap != 0)
440 sel.mode = SEL_READY;
441 tsetdirt(sel.nb.y, sel.ne.y);
444 void
445 selextend(int col, int row, int type, int done)
447 int oldey, oldex, oldsby, oldsey, oldtype;
449 if (sel.mode == SEL_IDLE)
450 return;
451 if (done && sel.mode == SEL_EMPTY) {
452 selclear();
453 return;
456 oldey = sel.oe.y;
457 oldex = sel.oe.x;
458 oldsby = sel.nb.y;
459 oldsey = sel.ne.y;
460 oldtype = sel.type;
462 sel.oe.x = col;
463 sel.oe.y = row;
464 selnormalize();
465 sel.type = type;
467 if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY)
468 tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey));
470 sel.mode = done ? SEL_IDLE : SEL_READY;
473 void
474 selnormalize(void)
476 int i;
478 if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) {
479 sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x;
480 sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x;
481 } else {
482 sel.nb.x = MIN(sel.ob.x, sel.oe.x);
483 sel.ne.x = MAX(sel.ob.x, sel.oe.x);
485 sel.nb.y = MIN(sel.ob.y, sel.oe.y);
486 sel.ne.y = MAX(sel.ob.y, sel.oe.y);
488 selsnap(&sel.nb.x, &sel.nb.y, -1);
489 selsnap(&sel.ne.x, &sel.ne.y, +1);
491 /* expand selection over line breaks */
492 if (sel.type == SEL_RECTANGULAR)
493 return;
494 i = tlinelen(sel.nb.y);
495 if (i < sel.nb.x)
496 sel.nb.x = i;
497 if (tlinelen(sel.ne.y) <= sel.ne.x)
498 sel.ne.x = term.col - 1;
502 selected(int x, int y)
504 if (sel.mode == SEL_EMPTY || sel.ob.x == -1 ||
505 sel.alt != IS_SET(MODE_ALTSCREEN))
506 return 0;
508 if (sel.type == SEL_RECTANGULAR)
509 return BETWEEN(y, sel.nb.y, sel.ne.y)
510 && BETWEEN(x, sel.nb.x, sel.ne.x);
512 return BETWEEN(y, sel.nb.y, sel.ne.y)
513 && (y != sel.nb.y || x >= sel.nb.x)
514 && (y != sel.ne.y || x <= sel.ne.x);
517 void
518 selsnap(int *x, int *y, int direction)
520 int newx, newy, xt, yt;
521 int delim, prevdelim;
522 Glyph *gp, *prevgp;
524 switch (sel.snap) {
525 case SNAP_WORD:
527 * Snap around if the word wraps around at the end or
528 * beginning of a line.
530 prevgp = &term.line[*y][*x];
531 prevdelim = ISDELIM(prevgp->u);
532 for (;;) {
533 newx = *x + direction;
534 newy = *y;
535 if (!BETWEEN(newx, 0, term.col - 1)) {
536 newy += direction;
537 newx = (newx + term.col) % term.col;
538 if (!BETWEEN(newy, 0, term.row - 1))
539 break;
541 if (direction > 0)
542 yt = *y, xt = *x;
543 else
544 yt = newy, xt = newx;
545 if (!(term.line[yt][xt].mode & ATTR_WRAP))
546 break;
549 if (newx >= tlinelen(newy))
550 break;
552 gp = &term.line[newy][newx];
553 delim = ISDELIM(gp->u);
554 if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
555 || (delim && gp->u != prevgp->u)))
556 break;
558 *x = newx;
559 *y = newy;
560 prevgp = gp;
561 prevdelim = delim;
563 break;
564 case SNAP_LINE:
566 * Snap around if the the previous line or the current one
567 * has set ATTR_WRAP at its end. Then the whole next or
568 * previous line will be selected.
570 *x = (direction < 0) ? 0 : term.col - 1;
571 if (direction < 0) {
572 for (; *y > 0; *y += direction) {
573 if (!(term.line[*y-1][term.col-1].mode
574 & ATTR_WRAP)) {
575 break;
578 } else if (direction > 0) {
579 for (; *y < term.row-1; *y += direction) {
580 if (!(term.line[*y][term.col-1].mode
581 & ATTR_WRAP)) {
582 break;
586 break;
590 char *
591 getsel(void)
593 char *str, *ptr;
594 int y, bufsize, lastx, linelen;
595 Glyph *gp, *last;
597 if (sel.ob.x == -1)
598 return NULL;
600 bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ;
601 ptr = str = xmalloc(bufsize);
603 /* append every set & selected glyph to the selection */
604 for (y = sel.nb.y; y <= sel.ne.y; y++) {
605 if ((linelen = tlinelen(y)) == 0) {
606 *ptr++ = '\n';
607 continue;
610 if (sel.type == SEL_RECTANGULAR) {
611 gp = &term.line[y][sel.nb.x];
612 lastx = sel.ne.x;
613 } else {
614 gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0];
615 lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
617 last = &term.line[y][MIN(lastx, linelen-1)];
618 while (last >= gp && last->u == ' ')
619 --last;
621 for ( ; gp <= last; ++gp) {
622 if (gp->mode & ATTR_WDUMMY)
623 continue;
625 ptr += utf8encode(gp->u, ptr);
629 * Copy and pasting of line endings is inconsistent
630 * in the inconsistent terminal and GUI world.
631 * The best solution seems like to produce '\n' when
632 * something is copied from st and convert '\n' to
633 * '\r', when something to be pasted is received by
634 * st.
635 * FIXME: Fix the computer world.
637 if ((y < sel.ne.y || lastx >= linelen) && !(last->mode & ATTR_WRAP))
638 *ptr++ = '\n';
640 *ptr = 0;
641 return str;
644 void
645 selclear(void)
647 if (sel.ob.x == -1)
648 return;
649 sel.mode = SEL_IDLE;
650 sel.ob.x = -1;
651 tsetdirt(sel.nb.y, sel.ne.y);
654 void
655 die(const char *errstr, ...)
657 va_list ap;
659 va_start(ap, errstr);
660 vfprintf(stderr, errstr, ap);
661 va_end(ap);
662 exit(1);
665 void
666 execsh(char *cmd, char **args)
668 char *sh, *prog, *arg;
669 const struct passwd *pw;
671 errno = 0;
672 if ((pw = getpwuid(getuid())) == NULL) {
673 if (errno)
674 die("getpwuid: %s\n", strerror(errno));
675 else
676 die("who are you?\n");
679 if ((sh = getenv("SHELL")) == NULL)
680 sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd;
682 if (args) {
683 prog = args[0];
684 arg = NULL;
685 } else if (scroll) {
686 prog = scroll;
687 arg = utmp ? utmp : sh;
688 } else if (utmp) {
689 prog = utmp;
690 arg = NULL;
691 } else {
692 prog = sh;
693 arg = NULL;
695 DEFAULT(args, ((char *[]) {prog, arg, NULL}));
697 unsetenv("COLUMNS");
698 unsetenv("LINES");
699 unsetenv("TERMCAP");
700 setenv("LOGNAME", pw->pw_name, 1);
701 setenv("USER", pw->pw_name, 1);
702 setenv("SHELL", sh, 1);
703 setenv("HOME", pw->pw_dir, 1);
704 setenv("TERM", termname, 1);
706 signal(SIGCHLD, SIG_DFL);
707 signal(SIGHUP, SIG_DFL);
708 signal(SIGINT, SIG_DFL);
709 signal(SIGQUIT, SIG_DFL);
710 signal(SIGTERM, SIG_DFL);
711 signal(SIGALRM, SIG_DFL);
713 execvp(prog, args);
714 _exit(1);
717 void
718 sigchld(int a)
720 int stat;
721 pid_t p;
723 if ((p = waitpid(pid, &stat, WNOHANG)) < 0)
724 die("waiting for pid %hd failed: %s\n", pid, strerror(errno));
726 if (pid != p)
727 return;
729 if (WIFEXITED(stat) && WEXITSTATUS(stat))
730 die("child exited with status %d\n", WEXITSTATUS(stat));
731 else if (WIFSIGNALED(stat))
732 die("child terminated due to signal %d\n", WTERMSIG(stat));
733 _exit(0);
736 void
737 stty(char **args)
739 char cmd[_POSIX_ARG_MAX], **p, *q, *s;
740 size_t n, siz;
742 if ((n = strlen(stty_args)) > sizeof(cmd)-1)
743 die("incorrect stty parameters\n");
744 memcpy(cmd, stty_args, n);
745 q = cmd + n;
746 siz = sizeof(cmd) - n;
747 for (p = args; p && (s = *p); ++p) {
748 if ((n = strlen(s)) > siz-1)
749 die("stty parameter length too long\n");
750 *q++ = ' ';
751 memcpy(q, s, n);
752 q += n;
753 siz -= n + 1;
755 *q = '\0';
756 if (system(cmd) != 0)
757 perror("Couldn't call stty");
761 ttynew(char *line, char *cmd, char *out, char **args)
763 int m, s;
765 if (out) {
766 term.mode |= MODE_PRINT;
767 iofd = (!strcmp(out, "-")) ?
768 1 : open(out, O_WRONLY | O_CREAT, 0666);
769 if (iofd < 0) {
770 fprintf(stderr, "Error opening %s:%s\n",
771 out, strerror(errno));
775 if (line) {
776 if ((cmdfd = open(line, O_RDWR)) < 0)
777 die("open line '%s' failed: %s\n",
778 line, strerror(errno));
779 dup2(cmdfd, 0);
780 stty(args);
781 return cmdfd;
784 /* seems to work fine on linux, openbsd and freebsd */
785 if (openpty(&m, &s, NULL, NULL, NULL) < 0)
786 die("openpty failed: %s\n", strerror(errno));
788 switch (pid = fork()) {
789 case -1:
790 die("fork failed: %s\n", strerror(errno));
791 break;
792 case 0:
793 close(iofd);
794 setsid(); /* create a new process group */
795 dup2(s, 0);
796 dup2(s, 1);
797 dup2(s, 2);
798 if (ioctl(s, TIOCSCTTY, NULL) < 0)
799 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno));
800 close(s);
801 close(m);
802 #ifdef __OpenBSD__
803 if (pledge("stdio getpw proc exec", NULL) == -1)
804 die("pledge\n");
805 #endif
806 execsh(cmd, args);
807 break;
808 default:
809 #ifdef __OpenBSD__
810 if (pledge("stdio rpath tty proc", NULL) == -1)
811 die("pledge\n");
812 #endif
813 close(s);
814 cmdfd = m;
815 signal(SIGCHLD, sigchld);
816 break;
818 return cmdfd;
821 size_t
822 ttyread(void)
824 static char buf[BUFSIZ];
825 static int buflen = 0;
826 int ret, written;
828 /* append read bytes to unprocessed bytes */
829 ret = read(cmdfd, buf+buflen, LEN(buf)-buflen);
831 switch (ret) {
832 case 0:
833 exit(0);
834 case -1:
835 die("couldn't read from shell: %s\n", strerror(errno));
836 default:
837 buflen += ret;
838 written = twrite(buf, buflen, 0);
839 buflen -= written;
840 /* keep any incomplete UTF-8 byte sequence for the next call */
841 if (buflen > 0)
842 memmove(buf, buf + written, buflen);
843 return ret;
848 void
849 ttywrite(const char *s, size_t n, int may_echo)
851 const char *next;
853 if (may_echo && IS_SET(MODE_ECHO))
854 twrite(s, n, 1);
856 if (!IS_SET(MODE_CRLF)) {
857 ttywriteraw(s, n);
858 return;
861 /* This is similar to how the kernel handles ONLCR for ttys */
862 while (n > 0) {
863 if (*s == '\r') {
864 next = s + 1;
865 ttywriteraw("\r\n", 2);
866 } else {
867 next = memchr(s, '\r', n);
868 DEFAULT(next, s + n);
869 ttywriteraw(s, next - s);
871 n -= next - s;
872 s = next;
876 void
877 ttywriteraw(const char *s, size_t n)
879 fd_set wfd, rfd;
880 ssize_t r;
881 size_t lim = 256;
884 * Remember that we are using a pty, which might be a modem line.
885 * Writing too much will clog the line. That's why we are doing this
886 * dance.
887 * FIXME: Migrate the world to Plan 9.
889 while (n > 0) {
890 FD_ZERO(&wfd);
891 FD_ZERO(&rfd);
892 FD_SET(cmdfd, &wfd);
893 FD_SET(cmdfd, &rfd);
895 /* Check if we can write. */
896 if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) {
897 if (errno == EINTR)
898 continue;
899 die("select failed: %s\n", strerror(errno));
901 if (FD_ISSET(cmdfd, &wfd)) {
903 * Only write the bytes written by ttywrite() or the
904 * default of 256. This seems to be a reasonable value
905 * for a serial line. Bigger values might clog the I/O.
907 if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0)
908 goto write_error;
909 if (r < n) {
911 * We weren't able to write out everything.
912 * This means the buffer is getting full
913 * again. Empty it.
915 if (n < lim)
916 lim = ttyread();
917 n -= r;
918 s += r;
919 } else {
920 /* All bytes have been written. */
921 break;
924 if (FD_ISSET(cmdfd, &rfd))
925 lim = ttyread();
927 return;
929 write_error:
930 die("write error on tty: %s\n", strerror(errno));
933 void
934 ttyresize(int tw, int th)
936 struct winsize w;
938 w.ws_row = term.row;
939 w.ws_col = term.col;
940 w.ws_xpixel = tw;
941 w.ws_ypixel = th;
942 if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
943 fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
946 void
947 ttyhangup()
949 /* Send SIGHUP to shell */
950 kill(pid, SIGHUP);
954 tattrset(int attr)
956 int i, j;
958 for (i = 0; i < term.row-1; i++) {
959 for (j = 0; j < term.col-1; j++) {
960 if (term.line[i][j].mode & attr)
961 return 1;
965 return 0;
968 void
969 tsetdirt(int top, int bot)
971 int i;
973 LIMIT(top, 0, term.row-1);
974 LIMIT(bot, 0, term.row-1);
976 for (i = top; i <= bot; i++)
977 term.dirty[i] = 1;
980 void
981 tsetdirtattr(int attr)
983 int i, j;
985 for (i = 0; i < term.row-1; i++) {
986 for (j = 0; j < term.col-1; j++) {
987 if (term.line[i][j].mode & attr) {
988 tsetdirt(i, i);
989 break;
995 void
996 tfulldirt(void)
998 tsetdirt(0, term.row-1);
1001 void
1002 tcursor(int mode)
1004 static TCursor c[2];
1005 int alt = IS_SET(MODE_ALTSCREEN);
1007 if (mode == CURSOR_SAVE) {
1008 c[alt] = term.c;
1009 } else if (mode == CURSOR_LOAD) {
1010 term.c = c[alt];
1011 tmoveto(c[alt].x, c[alt].y);
1015 void
1016 treset(void)
1018 uint i;
1020 term.c = (TCursor){{
1021 .mode = ATTR_NULL,
1022 .fg = defaultfg,
1023 .bg = defaultbg
1024 }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
1026 memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1027 for (i = tabspaces; i < term.col; i += tabspaces)
1028 term.tabs[i] = 1;
1029 term.top = 0;
1030 term.bot = term.row - 1;
1031 term.mode = MODE_WRAP|MODE_UTF8;
1032 memset(term.trantbl, CS_USA, sizeof(term.trantbl));
1033 term.charset = 0;
1035 for (i = 0; i < 2; i++) {
1036 tmoveto(0, 0);
1037 tcursor(CURSOR_SAVE);
1038 tclearregion(0, 0, term.col-1, term.row-1);
1039 tswapscreen();
1043 void
1044 tnew(int col, int row)
1046 term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
1047 tresize(col, row);
1048 treset();
1051 void
1052 tswapscreen(void)
1054 Line *tmp = term.line;
1056 term.line = term.alt;
1057 term.alt = tmp;
1058 term.mode ^= MODE_ALTSCREEN;
1059 tfulldirt();
1062 void
1063 tscrolldown(int orig, int n)
1065 int i;
1066 Line temp;
1068 LIMIT(n, 0, term.bot-orig+1);
1070 tsetdirt(orig, term.bot-n);
1071 tclearregion(0, term.bot-n+1, term.col-1, term.bot);
1073 for (i = term.bot; i >= orig+n; i--) {
1074 temp = term.line[i];
1075 term.line[i] = term.line[i-n];
1076 term.line[i-n] = temp;
1079 selscroll(orig, n);
1082 void
1083 tscrollup(int orig, int n)
1085 int i;
1086 Line temp;
1088 LIMIT(n, 0, term.bot-orig+1);
1090 tclearregion(0, orig, term.col-1, orig+n-1);
1091 tsetdirt(orig+n, term.bot);
1093 for (i = orig; i <= term.bot-n; i++) {
1094 temp = term.line[i];
1095 term.line[i] = term.line[i+n];
1096 term.line[i+n] = temp;
1099 selscroll(orig, -n);
1102 void
1103 selscroll(int orig, int n)
1105 if (sel.ob.x == -1)
1106 return;
1108 if (BETWEEN(sel.ob.y, orig, term.bot) || BETWEEN(sel.oe.y, orig, term.bot)) {
1109 if ((sel.ob.y += n) > term.bot || (sel.oe.y += n) < term.top) {
1110 selclear();
1111 return;
1113 if (sel.type == SEL_RECTANGULAR) {
1114 if (sel.ob.y < term.top)
1115 sel.ob.y = term.top;
1116 if (sel.oe.y > term.bot)
1117 sel.oe.y = term.bot;
1118 } else {
1119 if (sel.ob.y < term.top) {
1120 sel.ob.y = term.top;
1121 sel.ob.x = 0;
1123 if (sel.oe.y > term.bot) {
1124 sel.oe.y = term.bot;
1125 sel.oe.x = term.col;
1128 selnormalize();
1132 void
1133 tnewline(int first_col)
1135 int y = term.c.y;
1137 if (y == term.bot) {
1138 tscrollup(term.top, 1);
1139 } else {
1140 y++;
1142 tmoveto(first_col ? 0 : term.c.x, y);
1145 void
1146 csiparse(void)
1148 char *p = csiescseq.buf, *np;
1149 long int v;
1151 csiescseq.narg = 0;
1152 if (*p == '?') {
1153 csiescseq.priv = 1;
1154 p++;
1157 csiescseq.buf[csiescseq.len] = '\0';
1158 while (p < csiescseq.buf+csiescseq.len) {
1159 np = NULL;
1160 v = strtol(p, &np, 10);
1161 if (np == p)
1162 v = 0;
1163 if (v == LONG_MAX || v == LONG_MIN)
1164 v = -1;
1165 csiescseq.arg[csiescseq.narg++] = v;
1166 p = np;
1167 if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ)
1168 break;
1169 p++;
1171 csiescseq.mode[0] = *p++;
1172 csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0';
1175 /* for absolute user moves, when decom is set */
1176 void
1177 tmoveato(int x, int y)
1179 tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0));
1182 void
1183 tmoveto(int x, int y)
1185 int miny, maxy;
1187 if (term.c.state & CURSOR_ORIGIN) {
1188 miny = term.top;
1189 maxy = term.bot;
1190 } else {
1191 miny = 0;
1192 maxy = term.row - 1;
1194 term.c.state &= ~CURSOR_WRAPNEXT;
1195 term.c.x = LIMIT(x, 0, term.col-1);
1196 term.c.y = LIMIT(y, miny, maxy);
1199 void
1200 tsetchar(Rune u, Glyph *attr, int x, int y)
1202 static char *vt100_0[62] = { /* 0x41 - 0x7e */
1203 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1204 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1205 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1206 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1207 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1208 "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1209 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1210 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1214 * The table is proudly stolen from rxvt.
1216 if (term.trantbl[term.charset] == CS_GRAPHIC0 &&
1217 BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
1218 utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ);
1220 if (term.line[y][x].mode & ATTR_WIDE) {
1221 if (x+1 < term.col) {
1222 term.line[y][x+1].u = ' ';
1223 term.line[y][x+1].mode &= ~ATTR_WDUMMY;
1225 } else if (term.line[y][x].mode & ATTR_WDUMMY) {
1226 term.line[y][x-1].u = ' ';
1227 term.line[y][x-1].mode &= ~ATTR_WIDE;
1230 term.dirty[y] = 1;
1231 term.line[y][x] = *attr;
1232 term.line[y][x].u = u;
1235 void
1236 tclearregion(int x1, int y1, int x2, int y2)
1238 int x, y, temp;
1239 Glyph *gp;
1241 if (x1 > x2)
1242 temp = x1, x1 = x2, x2 = temp;
1243 if (y1 > y2)
1244 temp = y1, y1 = y2, y2 = temp;
1246 LIMIT(x1, 0, term.col-1);
1247 LIMIT(x2, 0, term.col-1);
1248 LIMIT(y1, 0, term.row-1);
1249 LIMIT(y2, 0, term.row-1);
1251 for (y = y1; y <= y2; y++) {
1252 term.dirty[y] = 1;
1253 for (x = x1; x <= x2; x++) {
1254 gp = &term.line[y][x];
1255 if (selected(x, y))
1256 selclear();
1257 gp->fg = term.c.attr.fg;
1258 gp->bg = term.c.attr.bg;
1259 gp->mode = 0;
1260 gp->u = ' ';
1265 void
1266 tdeletechar(int n)
1268 int dst, src, size;
1269 Glyph *line;
1271 LIMIT(n, 0, term.col - term.c.x);
1273 dst = term.c.x;
1274 src = term.c.x + n;
1275 size = term.col - src;
1276 line = term.line[term.c.y];
1278 memmove(&line[dst], &line[src], size * sizeof(Glyph));
1279 tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
1282 void
1283 tinsertblank(int n)
1285 int dst, src, size;
1286 Glyph *line;
1288 LIMIT(n, 0, term.col - term.c.x);
1290 dst = term.c.x + n;
1291 src = term.c.x;
1292 size = term.col - dst;
1293 line = term.line[term.c.y];
1295 memmove(&line[dst], &line[src], size * sizeof(Glyph));
1296 tclearregion(src, term.c.y, dst - 1, term.c.y);
1299 void
1300 tinsertblankline(int n)
1302 if (BETWEEN(term.c.y, term.top, term.bot))
1303 tscrolldown(term.c.y, n);
1306 void
1307 tdeleteline(int n)
1309 if (BETWEEN(term.c.y, term.top, term.bot))
1310 tscrollup(term.c.y, n);
1313 int32_t
1314 tdefcolor(int *attr, int *npar, int l)
1316 int32_t idx = -1;
1317 uint r, g, b;
1319 switch (attr[*npar + 1]) {
1320 case 2: /* direct color in RGB space */
1321 if (*npar + 4 >= l) {
1322 fprintf(stderr,
1323 "erresc(38): Incorrect number of parameters (%d)\n",
1324 *npar);
1325 break;
1327 r = attr[*npar + 2];
1328 g = attr[*npar + 3];
1329 b = attr[*npar + 4];
1330 *npar += 4;
1331 if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255))
1332 fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n",
1333 r, g, b);
1334 else
1335 idx = TRUECOLOR(r, g, b);
1336 break;
1337 case 5: /* indexed color */
1338 if (*npar + 2 >= l) {
1339 fprintf(stderr,
1340 "erresc(38): Incorrect number of parameters (%d)\n",
1341 *npar);
1342 break;
1344 *npar += 2;
1345 if (!BETWEEN(attr[*npar], 0, 255))
1346 fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]);
1347 else
1348 idx = attr[*npar];
1349 break;
1350 case 0: /* implemented defined (only foreground) */
1351 case 1: /* transparent */
1352 case 3: /* direct color in CMY space */
1353 case 4: /* direct color in CMYK space */
1354 default:
1355 fprintf(stderr,
1356 "erresc(38): gfx attr %d unknown\n", attr[*npar]);
1357 break;
1360 return idx;
1363 void
1364 tsetattr(int *attr, int l)
1366 int i;
1367 int32_t idx;
1369 for (i = 0; i < l; i++) {
1370 switch (attr[i]) {
1371 case 0:
1372 term.c.attr.mode &= ~(
1373 ATTR_BOLD |
1374 ATTR_FAINT |
1375 ATTR_ITALIC |
1376 ATTR_UNDERLINE |
1377 ATTR_BLINK |
1378 ATTR_REVERSE |
1379 ATTR_INVISIBLE |
1380 ATTR_STRUCK );
1381 term.c.attr.fg = defaultfg;
1382 term.c.attr.bg = defaultbg;
1383 break;
1384 case 1:
1385 term.c.attr.mode |= ATTR_BOLD;
1386 break;
1387 case 2:
1388 term.c.attr.mode |= ATTR_FAINT;
1389 break;
1390 case 3:
1391 term.c.attr.mode |= ATTR_ITALIC;
1392 break;
1393 case 4:
1394 term.c.attr.mode |= ATTR_UNDERLINE;
1395 break;
1396 case 5: /* slow blink */
1397 /* FALLTHROUGH */
1398 case 6: /* rapid blink */
1399 term.c.attr.mode |= ATTR_BLINK;
1400 break;
1401 case 7:
1402 term.c.attr.mode |= ATTR_REVERSE;
1403 break;
1404 case 8:
1405 term.c.attr.mode |= ATTR_INVISIBLE;
1406 break;
1407 case 9:
1408 term.c.attr.mode |= ATTR_STRUCK;
1409 break;
1410 case 22:
1411 term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT);
1412 break;
1413 case 23:
1414 term.c.attr.mode &= ~ATTR_ITALIC;
1415 break;
1416 case 24:
1417 term.c.attr.mode &= ~ATTR_UNDERLINE;
1418 break;
1419 case 25:
1420 term.c.attr.mode &= ~ATTR_BLINK;
1421 break;
1422 case 27:
1423 term.c.attr.mode &= ~ATTR_REVERSE;
1424 break;
1425 case 28:
1426 term.c.attr.mode &= ~ATTR_INVISIBLE;
1427 break;
1428 case 29:
1429 term.c.attr.mode &= ~ATTR_STRUCK;
1430 break;
1431 case 38:
1432 if ((idx = tdefcolor(attr, &i, l)) >= 0)
1433 term.c.attr.fg = idx;
1434 break;
1435 case 39:
1436 term.c.attr.fg = defaultfg;
1437 break;
1438 case 48:
1439 if ((idx = tdefcolor(attr, &i, l)) >= 0)
1440 term.c.attr.bg = idx;
1441 break;
1442 case 49:
1443 term.c.attr.bg = defaultbg;
1444 break;
1445 default:
1446 if (BETWEEN(attr[i], 30, 37)) {
1447 term.c.attr.fg = attr[i] - 30;
1448 } else if (BETWEEN(attr[i], 40, 47)) {
1449 term.c.attr.bg = attr[i] - 40;
1450 } else if (BETWEEN(attr[i], 90, 97)) {
1451 term.c.attr.fg = attr[i] - 90 + 8;
1452 } else if (BETWEEN(attr[i], 100, 107)) {
1453 term.c.attr.bg = attr[i] - 100 + 8;
1454 } else {
1455 fprintf(stderr,
1456 "erresc(default): gfx attr %d unknown\n",
1457 attr[i]);
1458 csidump();
1460 break;
1465 void
1466 tsetscroll(int t, int b)
1468 int temp;
1470 LIMIT(t, 0, term.row-1);
1471 LIMIT(b, 0, term.row-1);
1472 if (t > b) {
1473 temp = t;
1474 t = b;
1475 b = temp;
1477 term.top = t;
1478 term.bot = b;
1481 void
1482 tsetmode(int priv, int set, int *args, int narg)
1484 int alt, *lim;
1486 for (lim = args + narg; args < lim; ++args) {
1487 if (priv) {
1488 switch (*args) {
1489 case 1: /* DECCKM -- Cursor key */
1490 xsetmode(set, MODE_APPCURSOR);
1491 break;
1492 case 5: /* DECSCNM -- Reverse video */
1493 xsetmode(set, MODE_REVERSE);
1494 break;
1495 case 6: /* DECOM -- Origin */
1496 MODBIT(term.c.state, set, CURSOR_ORIGIN);
1497 tmoveato(0, 0);
1498 break;
1499 case 7: /* DECAWM -- Auto wrap */
1500 MODBIT(term.mode, set, MODE_WRAP);
1501 break;
1502 case 0: /* Error (IGNORED) */
1503 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1504 case 3: /* DECCOLM -- Column (IGNORED) */
1505 case 4: /* DECSCLM -- Scroll (IGNORED) */
1506 case 8: /* DECARM -- Auto repeat (IGNORED) */
1507 case 18: /* DECPFF -- Printer feed (IGNORED) */
1508 case 19: /* DECPEX -- Printer extent (IGNORED) */
1509 case 42: /* DECNRCM -- National characters (IGNORED) */
1510 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1511 break;
1512 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1513 xsetmode(!set, MODE_HIDE);
1514 break;
1515 case 9: /* X10 mouse compatibility mode */
1516 xsetpointermotion(0);
1517 xsetmode(0, MODE_MOUSE);
1518 xsetmode(set, MODE_MOUSEX10);
1519 break;
1520 case 1000: /* 1000: report button press */
1521 xsetpointermotion(0);
1522 xsetmode(0, MODE_MOUSE);
1523 xsetmode(set, MODE_MOUSEBTN);
1524 break;
1525 case 1002: /* 1002: report motion on button press */
1526 xsetpointermotion(0);
1527 xsetmode(0, MODE_MOUSE);
1528 xsetmode(set, MODE_MOUSEMOTION);
1529 break;
1530 case 1003: /* 1003: enable all mouse motions */
1531 xsetpointermotion(set);
1532 xsetmode(0, MODE_MOUSE);
1533 xsetmode(set, MODE_MOUSEMANY);
1534 break;
1535 case 1004: /* 1004: send focus events to tty */
1536 xsetmode(set, MODE_FOCUS);
1537 break;
1538 case 1006: /* 1006: extended reporting mode */
1539 xsetmode(set, MODE_MOUSESGR);
1540 break;
1541 case 1034:
1542 xsetmode(set, MODE_8BIT);
1543 break;
1544 case 1049: /* swap screen & set/restore cursor as xterm */
1545 if (!allowaltscreen)
1546 break;
1547 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1548 /* FALLTHROUGH */
1549 case 47: /* swap screen */
1550 case 1047:
1551 if (!allowaltscreen)
1552 break;
1553 alt = IS_SET(MODE_ALTSCREEN);
1554 if (alt) {
1555 tclearregion(0, 0, term.col-1,
1556 term.row-1);
1558 if (set ^ alt) /* set is always 1 or 0 */
1559 tswapscreen();
1560 if (*args != 1049)
1561 break;
1562 /* FALLTHROUGH */
1563 case 1048:
1564 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1565 break;
1566 case 2004: /* 2004: bracketed paste mode */
1567 xsetmode(set, MODE_BRCKTPASTE);
1568 break;
1569 /* Not implemented mouse modes. See comments there. */
1570 case 1001: /* mouse highlight mode; can hang the
1571 terminal by design when implemented. */
1572 case 1005: /* UTF-8 mouse mode; will confuse
1573 applications not supporting UTF-8
1574 and luit. */
1575 case 1015: /* urxvt mangled mouse mode; incompatible
1576 and can be mistaken for other control
1577 codes. */
1578 break;
1579 default:
1580 fprintf(stderr,
1581 "erresc: unknown private set/reset mode %d\n",
1582 *args);
1583 break;
1585 } else {
1586 switch (*args) {
1587 case 0: /* Error (IGNORED) */
1588 break;
1589 case 2:
1590 xsetmode(set, MODE_KBDLOCK);
1591 break;
1592 case 4: /* IRM -- Insertion-replacement */
1593 MODBIT(term.mode, set, MODE_INSERT);
1594 break;
1595 case 12: /* SRM -- Send/Receive */
1596 MODBIT(term.mode, !set, MODE_ECHO);
1597 break;
1598 case 20: /* LNM -- Linefeed/new line */
1599 MODBIT(term.mode, set, MODE_CRLF);
1600 break;
1601 default:
1602 fprintf(stderr,
1603 "erresc: unknown set/reset mode %d\n",
1604 *args);
1605 break;
1611 void
1612 csihandle(void)
1614 char buf[40];
1615 int len;
1617 switch (csiescseq.mode[0]) {
1618 default:
1619 unknown:
1620 fprintf(stderr, "erresc: unknown csi ");
1621 csidump();
1622 /* die(""); */
1623 break;
1624 case '@': /* ICH -- Insert <n> blank char */
1625 DEFAULT(csiescseq.arg[0], 1);
1626 tinsertblank(csiescseq.arg[0]);
1627 break;
1628 case 'A': /* CUU -- Cursor <n> Up */
1629 DEFAULT(csiescseq.arg[0], 1);
1630 tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
1631 break;
1632 case 'B': /* CUD -- Cursor <n> Down */
1633 case 'e': /* VPR --Cursor <n> Down */
1634 DEFAULT(csiescseq.arg[0], 1);
1635 tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
1636 break;
1637 case 'i': /* MC -- Media Copy */
1638 switch (csiescseq.arg[0]) {
1639 case 0:
1640 tdump();
1641 break;
1642 case 1:
1643 tdumpline(term.c.y);
1644 break;
1645 case 2:
1646 tdumpsel();
1647 break;
1648 case 4:
1649 term.mode &= ~MODE_PRINT;
1650 break;
1651 case 5:
1652 term.mode |= MODE_PRINT;
1653 break;
1655 break;
1656 case 'c': /* DA -- Device Attributes */
1657 if (csiescseq.arg[0] == 0)
1658 ttywrite(vtiden, strlen(vtiden), 0);
1659 break;
1660 case 'C': /* CUF -- Cursor <n> Forward */
1661 case 'a': /* HPR -- Cursor <n> Forward */
1662 DEFAULT(csiescseq.arg[0], 1);
1663 tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
1664 break;
1665 case 'D': /* CUB -- Cursor <n> Backward */
1666 DEFAULT(csiescseq.arg[0], 1);
1667 tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
1668 break;
1669 case 'E': /* CNL -- Cursor <n> Down and first col */
1670 DEFAULT(csiescseq.arg[0], 1);
1671 tmoveto(0, term.c.y+csiescseq.arg[0]);
1672 break;
1673 case 'F': /* CPL -- Cursor <n> Up and first col */
1674 DEFAULT(csiescseq.arg[0], 1);
1675 tmoveto(0, term.c.y-csiescseq.arg[0]);
1676 break;
1677 case 'g': /* TBC -- Tabulation clear */
1678 switch (csiescseq.arg[0]) {
1679 case 0: /* clear current tab stop */
1680 term.tabs[term.c.x] = 0;
1681 break;
1682 case 3: /* clear all the tabs */
1683 memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1684 break;
1685 default:
1686 goto unknown;
1688 break;
1689 case 'G': /* CHA -- Move to <col> */
1690 case '`': /* HPA */
1691 DEFAULT(csiescseq.arg[0], 1);
1692 tmoveto(csiescseq.arg[0]-1, term.c.y);
1693 break;
1694 case 'H': /* CUP -- Move to <row> <col> */
1695 case 'f': /* HVP */
1696 DEFAULT(csiescseq.arg[0], 1);
1697 DEFAULT(csiescseq.arg[1], 1);
1698 tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
1699 break;
1700 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1701 DEFAULT(csiescseq.arg[0], 1);
1702 tputtab(csiescseq.arg[0]);
1703 break;
1704 case 'J': /* ED -- Clear screen */
1705 switch (csiescseq.arg[0]) {
1706 case 0: /* below */
1707 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
1708 if (term.c.y < term.row-1) {
1709 tclearregion(0, term.c.y+1, term.col-1,
1710 term.row-1);
1712 break;
1713 case 1: /* above */
1714 if (term.c.y > 1)
1715 tclearregion(0, 0, term.col-1, term.c.y-1);
1716 tclearregion(0, term.c.y, term.c.x, term.c.y);
1717 break;
1718 case 2: /* all */
1719 tclearregion(0, 0, term.col-1, term.row-1);
1720 break;
1721 default:
1722 goto unknown;
1724 break;
1725 case 'K': /* EL -- Clear line */
1726 switch (csiescseq.arg[0]) {
1727 case 0: /* right */
1728 tclearregion(term.c.x, term.c.y, term.col-1,
1729 term.c.y);
1730 break;
1731 case 1: /* left */
1732 tclearregion(0, term.c.y, term.c.x, term.c.y);
1733 break;
1734 case 2: /* all */
1735 tclearregion(0, term.c.y, term.col-1, term.c.y);
1736 break;
1738 break;
1739 case 'S': /* SU -- Scroll <n> line up */
1740 DEFAULT(csiescseq.arg[0], 1);
1741 tscrollup(term.top, csiescseq.arg[0]);
1742 break;
1743 case 'T': /* SD -- Scroll <n> line down */
1744 DEFAULT(csiescseq.arg[0], 1);
1745 tscrolldown(term.top, csiescseq.arg[0]);
1746 break;
1747 case 'L': /* IL -- Insert <n> blank lines */
1748 DEFAULT(csiescseq.arg[0], 1);
1749 tinsertblankline(csiescseq.arg[0]);
1750 break;
1751 case 'l': /* RM -- Reset Mode */
1752 tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
1753 break;
1754 case 'M': /* DL -- Delete <n> lines */
1755 DEFAULT(csiescseq.arg[0], 1);
1756 tdeleteline(csiescseq.arg[0]);
1757 break;
1758 case 'X': /* ECH -- Erase <n> char */
1759 DEFAULT(csiescseq.arg[0], 1);
1760 tclearregion(term.c.x, term.c.y,
1761 term.c.x + csiescseq.arg[0] - 1, term.c.y);
1762 break;
1763 case 'P': /* DCH -- Delete <n> char */
1764 DEFAULT(csiescseq.arg[0], 1);
1765 tdeletechar(csiescseq.arg[0]);
1766 break;
1767 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1768 DEFAULT(csiescseq.arg[0], 1);
1769 tputtab(-csiescseq.arg[0]);
1770 break;
1771 case 'd': /* VPA -- Move to <row> */
1772 DEFAULT(csiescseq.arg[0], 1);
1773 tmoveato(term.c.x, csiescseq.arg[0]-1);
1774 break;
1775 case 'h': /* SM -- Set terminal mode */
1776 tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
1777 break;
1778 case 'm': /* SGR -- Terminal attribute (color) */
1779 tsetattr(csiescseq.arg, csiescseq.narg);
1780 break;
1781 case 'n': /* DSR – Device Status Report (cursor position) */
1782 if (csiescseq.arg[0] == 6) {
1783 len = snprintf(buf, sizeof(buf),"\033[%i;%iR",
1784 term.c.y+1, term.c.x+1);
1785 ttywrite(buf, len, 0);
1787 break;
1788 case 'r': /* DECSTBM -- Set Scrolling Region */
1789 if (csiescseq.priv) {
1790 goto unknown;
1791 } else {
1792 DEFAULT(csiescseq.arg[0], 1);
1793 DEFAULT(csiescseq.arg[1], term.row);
1794 tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
1795 tmoveato(0, 0);
1797 break;
1798 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1799 tcursor(CURSOR_SAVE);
1800 break;
1801 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1802 tcursor(CURSOR_LOAD);
1803 break;
1804 case ' ':
1805 switch (csiescseq.mode[1]) {
1806 case 'q': /* DECSCUSR -- Set Cursor Style */
1807 if (xsetcursor(csiescseq.arg[0]))
1808 goto unknown;
1809 break;
1810 default:
1811 goto unknown;
1813 break;
1817 void
1818 csidump(void)
1820 size_t i;
1821 uint c;
1823 fprintf(stderr, "ESC[");
1824 for (i = 0; i < csiescseq.len; i++) {
1825 c = csiescseq.buf[i] & 0xff;
1826 if (isprint(c)) {
1827 putc(c, stderr);
1828 } else if (c == '\n') {
1829 fprintf(stderr, "(\\n)");
1830 } else if (c == '\r') {
1831 fprintf(stderr, "(\\r)");
1832 } else if (c == 0x1b) {
1833 fprintf(stderr, "(\\e)");
1834 } else {
1835 fprintf(stderr, "(%02x)", c);
1838 putc('\n', stderr);
1841 void
1842 csireset(void)
1844 memset(&csiescseq, 0, sizeof(csiescseq));
1847 void
1848 strhandle(void)
1850 char *p = NULL, *dec;
1851 int j, narg, par;
1853 term.esc &= ~(ESC_STR_END|ESC_STR);
1854 strparse();
1855 par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
1857 switch (strescseq.type) {
1858 case ']': /* OSC -- Operating System Command */
1859 switch (par) {
1860 case 0:
1861 case 1:
1862 case 2:
1863 if (narg > 1)
1864 xsettitle(strescseq.args[1]);
1865 return;
1866 case 52:
1867 if (narg > 2) {
1868 dec = base64dec(strescseq.args[2]);
1869 if (dec) {
1870 xsetsel(dec);
1871 xclipcopy();
1872 } else {
1873 fprintf(stderr, "erresc: invalid base64\n");
1876 return;
1877 case 4: /* color set */
1878 if (narg < 3)
1879 break;
1880 p = strescseq.args[2];
1881 /* FALLTHROUGH */
1882 case 104: /* color reset, here p = NULL */
1883 j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
1884 if (xsetcolorname(j, p)) {
1885 if (par == 104 && narg <= 1)
1886 return; /* color reset without parameter */
1887 fprintf(stderr, "erresc: invalid color j=%d, p=%s\n",
1888 j, p ? p : "(null)");
1889 } else {
1891 * TODO if defaultbg color is changed, borders
1892 * are dirty
1894 redraw();
1896 return;
1898 break;
1899 case 'k': /* old title set compatibility */
1900 xsettitle(strescseq.args[0]);
1901 return;
1902 case 'P': /* DCS -- Device Control String */
1903 term.mode |= ESC_DCS;
1904 case '_': /* APC -- Application Program Command */
1905 case '^': /* PM -- Privacy Message */
1906 return;
1909 fprintf(stderr, "erresc: unknown str ");
1910 strdump();
1913 void
1914 strparse(void)
1916 int c;
1917 char *p = strescseq.buf;
1919 strescseq.narg = 0;
1920 strescseq.buf[strescseq.len] = '\0';
1922 if (*p == '\0')
1923 return;
1925 while (strescseq.narg < STR_ARG_SIZ) {
1926 strescseq.args[strescseq.narg++] = p;
1927 while ((c = *p) != ';' && c != '\0')
1928 ++p;
1929 if (c == '\0')
1930 return;
1931 *p++ = '\0';
1935 void
1936 strdump(void)
1938 size_t i;
1939 uint c;
1941 fprintf(stderr, "ESC%c", strescseq.type);
1942 for (i = 0; i < strescseq.len; i++) {
1943 c = strescseq.buf[i] & 0xff;
1944 if (c == '\0') {
1945 putc('\n', stderr);
1946 return;
1947 } else if (isprint(c)) {
1948 putc(c, stderr);
1949 } else if (c == '\n') {
1950 fprintf(stderr, "(\\n)");
1951 } else if (c == '\r') {
1952 fprintf(stderr, "(\\r)");
1953 } else if (c == 0x1b) {
1954 fprintf(stderr, "(\\e)");
1955 } else {
1956 fprintf(stderr, "(%02x)", c);
1959 fprintf(stderr, "ESC\\\n");
1962 void
1963 strreset(void)
1965 strescseq = (STREscape){
1966 .buf = xrealloc(strescseq.buf, STR_BUF_SIZ),
1967 .siz = STR_BUF_SIZ,
1971 void
1972 sendbreak(const Arg *arg)
1974 if (tcsendbreak(cmdfd, 0))
1975 perror("Error sending break");
1978 void
1979 tprinter(char *s, size_t len)
1981 if (iofd != -1 && xwrite(iofd, s, len) < 0) {
1982 perror("Error writing to output file");
1983 close(iofd);
1984 iofd = -1;
1988 void
1989 toggleprinter(const Arg *arg)
1991 term.mode ^= MODE_PRINT;
1994 void
1995 printscreen(const Arg *arg)
1997 tdump();
2000 void
2001 printsel(const Arg *arg)
2003 tdumpsel();
2006 void
2007 tdumpsel(void)
2009 char *ptr;
2011 if ((ptr = getsel())) {
2012 tprinter(ptr, strlen(ptr));
2013 free(ptr);
2017 void
2018 tdumpline(int n)
2020 char buf[UTF_SIZ];
2021 Glyph *bp, *end;
2023 bp = &term.line[n][0];
2024 end = &bp[MIN(tlinelen(n), term.col) - 1];
2025 if (bp != end || bp->u != ' ') {
2026 for ( ; bp <= end; ++bp)
2027 tprinter(buf, utf8encode(bp->u, buf));
2029 tprinter("\n", 1);
2032 void
2033 tdump(void)
2035 int i;
2037 for (i = 0; i < term.row; ++i)
2038 tdumpline(i);
2041 void
2042 tputtab(int n)
2044 uint x = term.c.x;
2046 if (n > 0) {
2047 while (x < term.col && n--)
2048 for (++x; x < term.col && !term.tabs[x]; ++x)
2049 /* nothing */ ;
2050 } else if (n < 0) {
2051 while (x > 0 && n++)
2052 for (--x; x > 0 && !term.tabs[x]; --x)
2053 /* nothing */ ;
2055 term.c.x = LIMIT(x, 0, term.col-1);
2058 void
2059 tdefutf8(char ascii)
2061 if (ascii == 'G')
2062 term.mode |= MODE_UTF8;
2063 else if (ascii == '@')
2064 term.mode &= ~MODE_UTF8;
2067 void
2068 tdeftran(char ascii)
2070 static char cs[] = "0B";
2071 static int vcs[] = {CS_GRAPHIC0, CS_USA};
2072 char *p;
2074 if ((p = strchr(cs, ascii)) == NULL) {
2075 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
2076 } else {
2077 term.trantbl[term.icharset] = vcs[p - cs];
2081 void
2082 tdectest(char c)
2084 int x, y;
2086 if (c == '8') { /* DEC screen alignment test. */
2087 for (x = 0; x < term.col; ++x) {
2088 for (y = 0; y < term.row; ++y)
2089 tsetchar('E', &term.c.attr, x, y);
2094 void
2095 tstrsequence(uchar c)
2097 strreset();
2099 switch (c) {
2100 case 0x90: /* DCS -- Device Control String */
2101 c = 'P';
2102 term.esc |= ESC_DCS;
2103 break;
2104 case 0x9f: /* APC -- Application Program Command */
2105 c = '_';
2106 break;
2107 case 0x9e: /* PM -- Privacy Message */
2108 c = '^';
2109 break;
2110 case 0x9d: /* OSC -- Operating System Command */
2111 c = ']';
2112 break;
2114 strescseq.type = c;
2115 term.esc |= ESC_STR;
2118 void
2119 tcontrolcode(uchar ascii)
2121 switch (ascii) {
2122 case '\t': /* HT */
2123 tputtab(1);
2124 return;
2125 case '\b': /* BS */
2126 tmoveto(term.c.x-1, term.c.y);
2127 return;
2128 case '\r': /* CR */
2129 tmoveto(0, term.c.y);
2130 return;
2131 case '\f': /* LF */
2132 case '\v': /* VT */
2133 case '\n': /* LF */
2134 /* go to first col if the mode is set */
2135 tnewline(IS_SET(MODE_CRLF));
2136 return;
2137 case '\a': /* BEL */
2138 if (term.esc & ESC_STR_END) {
2139 /* backwards compatibility to xterm */
2140 strhandle();
2141 } else {
2142 xbell();
2144 break;
2145 case '\033': /* ESC */
2146 csireset();
2147 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
2148 term.esc |= ESC_START;
2149 return;
2150 case '\016': /* SO (LS1 -- Locking shift 1) */
2151 case '\017': /* SI (LS0 -- Locking shift 0) */
2152 term.charset = 1 - (ascii - '\016');
2153 return;
2154 case '\032': /* SUB */
2155 tsetchar('?', &term.c.attr, term.c.x, term.c.y);
2156 case '\030': /* CAN */
2157 csireset();
2158 break;
2159 case '\005': /* ENQ (IGNORED) */
2160 case '\000': /* NUL (IGNORED) */
2161 case '\021': /* XON (IGNORED) */
2162 case '\023': /* XOFF (IGNORED) */
2163 case 0177: /* DEL (IGNORED) */
2164 return;
2165 case 0x80: /* TODO: PAD */
2166 case 0x81: /* TODO: HOP */
2167 case 0x82: /* TODO: BPH */
2168 case 0x83: /* TODO: NBH */
2169 case 0x84: /* TODO: IND */
2170 break;
2171 case 0x85: /* NEL -- Next line */
2172 tnewline(1); /* always go to first col */
2173 break;
2174 case 0x86: /* TODO: SSA */
2175 case 0x87: /* TODO: ESA */
2176 break;
2177 case 0x88: /* HTS -- Horizontal tab stop */
2178 term.tabs[term.c.x] = 1;
2179 break;
2180 case 0x89: /* TODO: HTJ */
2181 case 0x8a: /* TODO: VTS */
2182 case 0x8b: /* TODO: PLD */
2183 case 0x8c: /* TODO: PLU */
2184 case 0x8d: /* TODO: RI */
2185 case 0x8e: /* TODO: SS2 */
2186 case 0x8f: /* TODO: SS3 */
2187 case 0x91: /* TODO: PU1 */
2188 case 0x92: /* TODO: PU2 */
2189 case 0x93: /* TODO: STS */
2190 case 0x94: /* TODO: CCH */
2191 case 0x95: /* TODO: MW */
2192 case 0x96: /* TODO: SPA */
2193 case 0x97: /* TODO: EPA */
2194 case 0x98: /* TODO: SOS */
2195 case 0x99: /* TODO: SGCI */
2196 break;
2197 case 0x9a: /* DECID -- Identify Terminal */
2198 ttywrite(vtiden, strlen(vtiden), 0);
2199 break;
2200 case 0x9b: /* TODO: CSI */
2201 case 0x9c: /* TODO: ST */
2202 break;
2203 case 0x90: /* DCS -- Device Control String */
2204 case 0x9d: /* OSC -- Operating System Command */
2205 case 0x9e: /* PM -- Privacy Message */
2206 case 0x9f: /* APC -- Application Program Command */
2207 tstrsequence(ascii);
2208 return;
2210 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2211 term.esc &= ~(ESC_STR_END|ESC_STR);
2215 * returns 1 when the sequence is finished and it hasn't to read
2216 * more characters for this sequence, otherwise 0
2219 eschandle(uchar ascii)
2221 switch (ascii) {
2222 case '[':
2223 term.esc |= ESC_CSI;
2224 return 0;
2225 case '#':
2226 term.esc |= ESC_TEST;
2227 return 0;
2228 case '%':
2229 term.esc |= ESC_UTF8;
2230 return 0;
2231 case 'P': /* DCS -- Device Control String */
2232 case '_': /* APC -- Application Program Command */
2233 case '^': /* PM -- Privacy Message */
2234 case ']': /* OSC -- Operating System Command */
2235 case 'k': /* old title set compatibility */
2236 tstrsequence(ascii);
2237 return 0;
2238 case 'n': /* LS2 -- Locking shift 2 */
2239 case 'o': /* LS3 -- Locking shift 3 */
2240 term.charset = 2 + (ascii - 'n');
2241 break;
2242 case '(': /* GZD4 -- set primary charset G0 */
2243 case ')': /* G1D4 -- set secondary charset G1 */
2244 case '*': /* G2D4 -- set tertiary charset G2 */
2245 case '+': /* G3D4 -- set quaternary charset G3 */
2246 term.icharset = ascii - '(';
2247 term.esc |= ESC_ALTCHARSET;
2248 return 0;
2249 case 'D': /* IND -- Linefeed */
2250 if (term.c.y == term.bot) {
2251 tscrollup(term.top, 1);
2252 } else {
2253 tmoveto(term.c.x, term.c.y+1);
2255 break;
2256 case 'E': /* NEL -- Next line */
2257 tnewline(1); /* always go to first col */
2258 break;
2259 case 'H': /* HTS -- Horizontal tab stop */
2260 term.tabs[term.c.x] = 1;
2261 break;
2262 case 'M': /* RI -- Reverse index */
2263 if (term.c.y == term.top) {
2264 tscrolldown(term.top, 1);
2265 } else {
2266 tmoveto(term.c.x, term.c.y-1);
2268 break;
2269 case 'Z': /* DECID -- Identify Terminal */
2270 ttywrite(vtiden, strlen(vtiden), 0);
2271 break;
2272 case 'c': /* RIS -- Reset to initial state */
2273 treset();
2274 resettitle();
2275 xloadcols();
2276 break;
2277 case '=': /* DECPAM -- Application keypad */
2278 xsetmode(1, MODE_APPKEYPAD);
2279 break;
2280 case '>': /* DECPNM -- Normal keypad */
2281 xsetmode(0, MODE_APPKEYPAD);
2282 break;
2283 case '7': /* DECSC -- Save Cursor */
2284 tcursor(CURSOR_SAVE);
2285 break;
2286 case '8': /* DECRC -- Restore Cursor */
2287 tcursor(CURSOR_LOAD);
2288 break;
2289 case '\\': /* ST -- String Terminator */
2290 if (term.esc & ESC_STR_END)
2291 strhandle();
2292 break;
2293 default:
2294 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2295 (uchar) ascii, isprint(ascii)? ascii:'.');
2296 break;
2298 return 1;
2301 void
2302 tputc(Rune u)
2304 char c[UTF_SIZ];
2305 int control;
2306 int width, len;
2307 Glyph *gp;
2309 control = ISCONTROL(u);
2310 if (!IS_SET(MODE_UTF8 | MODE_SIXEL)) {
2311 c[0] = u;
2312 width = len = 1;
2313 } else {
2314 len = utf8encode(u, c);
2315 if (!control && (width = wcwidth(u)) == -1) {
2316 memcpy(c, "\357\277\275", 4); /* UTF_INVALID */
2317 width = 1;
2321 if (IS_SET(MODE_PRINT))
2322 tprinter(c, len);
2325 * STR sequence must be checked before anything else
2326 * because it uses all following characters until it
2327 * receives a ESC, a SUB, a ST or any other C1 control
2328 * character.
2330 if (term.esc & ESC_STR) {
2331 if (u == '\a' || u == 030 || u == 032 || u == 033 ||
2332 ISCONTROLC1(u)) {
2333 term.esc &= ~(ESC_START|ESC_STR|ESC_DCS);
2334 if (IS_SET(MODE_SIXEL)) {
2335 /* TODO: render sixel */;
2336 term.mode &= ~MODE_SIXEL;
2337 return;
2339 term.esc |= ESC_STR_END;
2340 goto check_control_code;
2343 if (IS_SET(MODE_SIXEL)) {
2344 /* TODO: implement sixel mode */
2345 return;
2347 if (term.esc&ESC_DCS && strescseq.len == 0 && u == 'q')
2348 term.mode |= MODE_SIXEL;
2350 if (strescseq.len+len >= strescseq.siz) {
2352 * Here is a bug in terminals. If the user never sends
2353 * some code to stop the str or esc command, then st
2354 * will stop responding. But this is better than
2355 * silently failing with unknown characters. At least
2356 * then users will report back.
2358 * In the case users ever get fixed, here is the code:
2361 * term.esc = 0;
2362 * strhandle();
2364 if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2)
2365 return;
2366 strescseq.siz *= 2;
2367 strescseq.buf = xrealloc(strescseq.buf, strescseq.siz);
2370 memmove(&strescseq.buf[strescseq.len], c, len);
2371 strescseq.len += len;
2372 return;
2375 check_control_code:
2377 * Actions of control codes must be performed as soon they arrive
2378 * because they can be embedded inside a control sequence, and
2379 * they must not cause conflicts with sequences.
2381 if (control) {
2382 tcontrolcode(u);
2384 * control codes are not shown ever
2386 return;
2387 } else if (term.esc & ESC_START) {
2388 if (term.esc & ESC_CSI) {
2389 csiescseq.buf[csiescseq.len++] = u;
2390 if (BETWEEN(u, 0x40, 0x7E)
2391 || csiescseq.len >= \
2392 sizeof(csiescseq.buf)-1) {
2393 term.esc = 0;
2394 csiparse();
2395 csihandle();
2397 return;
2398 } else if (term.esc & ESC_UTF8) {
2399 tdefutf8(u);
2400 } else if (term.esc & ESC_ALTCHARSET) {
2401 tdeftran(u);
2402 } else if (term.esc & ESC_TEST) {
2403 tdectest(u);
2404 } else {
2405 if (!eschandle(u))
2406 return;
2407 /* sequence already finished */
2409 term.esc = 0;
2411 * All characters which form part of a sequence are not
2412 * printed
2414 return;
2416 if (sel.ob.x != -1 && BETWEEN(term.c.y, sel.ob.y, sel.oe.y))
2417 selclear();
2419 gp = &term.line[term.c.y][term.c.x];
2420 if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
2421 gp->mode |= ATTR_WRAP;
2422 tnewline(1);
2423 gp = &term.line[term.c.y][term.c.x];
2426 if (IS_SET(MODE_INSERT) && term.c.x+width < term.col)
2427 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
2429 if (term.c.x+width > term.col) {
2430 tnewline(1);
2431 gp = &term.line[term.c.y][term.c.x];
2434 tsetchar(u, &term.c.attr, term.c.x, term.c.y);
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();