Add support for scroll(1)
[st.git] / st.c
blob5f2352af2f10fc7e97d6d127f5d2e8244cb02722
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) == '\177')
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)) (*src)++;
370 return **src ? *((*src)++) : '='; /* emulate padding if string ends */
373 char *
374 base64dec(const char *src)
376 size_t in_len = strlen(src);
377 char *result, *dst;
379 if (in_len % 4)
380 in_len += 4 - (in_len % 4);
381 result = dst = xmalloc(in_len / 4 * 3 + 1);
382 while (*src) {
383 int a = base64_digits[(unsigned char) base64dec_getc(&src)];
384 int b = base64_digits[(unsigned char) base64dec_getc(&src)];
385 int c = base64_digits[(unsigned char) base64dec_getc(&src)];
386 int d = base64_digits[(unsigned char) base64dec_getc(&src)];
388 /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */
389 if (a == -1 || b == -1)
390 break;
392 *dst++ = (a << 2) | ((b & 0x30) >> 4);
393 if (c == -1)
394 break;
395 *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2);
396 if (d == -1)
397 break;
398 *dst++ = ((c & 0x03) << 6) | d;
400 *dst = '\0';
401 return result;
404 void
405 selinit(void)
407 sel.mode = SEL_IDLE;
408 sel.snap = 0;
409 sel.ob.x = -1;
413 tlinelen(int y)
415 int i = term.col;
417 if (term.line[y][i - 1].mode & ATTR_WRAP)
418 return i;
420 while (i > 0 && term.line[y][i - 1].u == ' ')
421 --i;
423 return i;
426 void
427 selstart(int col, int row, int snap)
429 selclear();
430 sel.mode = SEL_EMPTY;
431 sel.type = SEL_REGULAR;
432 sel.alt = IS_SET(MODE_ALTSCREEN);
433 sel.snap = snap;
434 sel.oe.x = sel.ob.x = col;
435 sel.oe.y = sel.ob.y = row;
436 selnormalize();
438 if (sel.snap != 0)
439 sel.mode = SEL_READY;
440 tsetdirt(sel.nb.y, sel.ne.y);
443 void
444 selextend(int col, int row, int type, int done)
446 int oldey, oldex, oldsby, oldsey, oldtype;
448 if (sel.mode == SEL_IDLE)
449 return;
450 if (done && sel.mode == SEL_EMPTY) {
451 selclear();
452 return;
455 oldey = sel.oe.y;
456 oldex = sel.oe.x;
457 oldsby = sel.nb.y;
458 oldsey = sel.ne.y;
459 oldtype = sel.type;
461 sel.oe.x = col;
462 sel.oe.y = row;
463 selnormalize();
464 sel.type = type;
466 if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY)
467 tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey));
469 sel.mode = done ? SEL_IDLE : SEL_READY;
472 void
473 selnormalize(void)
475 int i;
477 if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) {
478 sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x;
479 sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x;
480 } else {
481 sel.nb.x = MIN(sel.ob.x, sel.oe.x);
482 sel.ne.x = MAX(sel.ob.x, sel.oe.x);
484 sel.nb.y = MIN(sel.ob.y, sel.oe.y);
485 sel.ne.y = MAX(sel.ob.y, sel.oe.y);
487 selsnap(&sel.nb.x, &sel.nb.y, -1);
488 selsnap(&sel.ne.x, &sel.ne.y, +1);
490 /* expand selection over line breaks */
491 if (sel.type == SEL_RECTANGULAR)
492 return;
493 i = tlinelen(sel.nb.y);
494 if (i < sel.nb.x)
495 sel.nb.x = i;
496 if (tlinelen(sel.ne.y) <= sel.ne.x)
497 sel.ne.x = term.col - 1;
501 selected(int x, int y)
503 if (sel.mode == SEL_EMPTY || sel.ob.x == -1 ||
504 sel.alt != IS_SET(MODE_ALTSCREEN))
505 return 0;
507 if (sel.type == SEL_RECTANGULAR)
508 return BETWEEN(y, sel.nb.y, sel.ne.y)
509 && BETWEEN(x, sel.nb.x, sel.ne.x);
511 return BETWEEN(y, sel.nb.y, sel.ne.y)
512 && (y != sel.nb.y || x >= sel.nb.x)
513 && (y != sel.ne.y || x <= sel.ne.x);
516 void
517 selsnap(int *x, int *y, int direction)
519 int newx, newy, xt, yt;
520 int delim, prevdelim;
521 Glyph *gp, *prevgp;
523 switch (sel.snap) {
524 case SNAP_WORD:
526 * Snap around if the word wraps around at the end or
527 * beginning of a line.
529 prevgp = &term.line[*y][*x];
530 prevdelim = ISDELIM(prevgp->u);
531 for (;;) {
532 newx = *x + direction;
533 newy = *y;
534 if (!BETWEEN(newx, 0, term.col - 1)) {
535 newy += direction;
536 newx = (newx + term.col) % term.col;
537 if (!BETWEEN(newy, 0, term.row - 1))
538 break;
540 if (direction > 0)
541 yt = *y, xt = *x;
542 else
543 yt = newy, xt = newx;
544 if (!(term.line[yt][xt].mode & ATTR_WRAP))
545 break;
548 if (newx >= tlinelen(newy))
549 break;
551 gp = &term.line[newy][newx];
552 delim = ISDELIM(gp->u);
553 if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
554 || (delim && gp->u != prevgp->u)))
555 break;
557 *x = newx;
558 *y = newy;
559 prevgp = gp;
560 prevdelim = delim;
562 break;
563 case SNAP_LINE:
565 * Snap around if the the previous line or the current one
566 * has set ATTR_WRAP at its end. Then the whole next or
567 * previous line will be selected.
569 *x = (direction < 0) ? 0 : term.col - 1;
570 if (direction < 0) {
571 for (; *y > 0; *y += direction) {
572 if (!(term.line[*y-1][term.col-1].mode
573 & ATTR_WRAP)) {
574 break;
577 } else if (direction > 0) {
578 for (; *y < term.row-1; *y += direction) {
579 if (!(term.line[*y][term.col-1].mode
580 & ATTR_WRAP)) {
581 break;
585 break;
589 char *
590 getsel(void)
592 char *str, *ptr;
593 int y, bufsize, lastx, linelen;
594 Glyph *gp, *last;
596 if (sel.ob.x == -1)
597 return NULL;
599 bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ;
600 ptr = str = xmalloc(bufsize);
602 /* append every set & selected glyph to the selection */
603 for (y = sel.nb.y; y <= sel.ne.y; y++) {
604 if ((linelen = tlinelen(y)) == 0) {
605 *ptr++ = '\n';
606 continue;
609 if (sel.type == SEL_RECTANGULAR) {
610 gp = &term.line[y][sel.nb.x];
611 lastx = sel.ne.x;
612 } else {
613 gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0];
614 lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
616 last = &term.line[y][MIN(lastx, linelen-1)];
617 while (last >= gp && last->u == ' ')
618 --last;
620 for ( ; gp <= last; ++gp) {
621 if (gp->mode & ATTR_WDUMMY)
622 continue;
624 ptr += utf8encode(gp->u, ptr);
628 * Copy and pasting of line endings is inconsistent
629 * in the inconsistent terminal and GUI world.
630 * The best solution seems like to produce '\n' when
631 * something is copied from st and convert '\n' to
632 * '\r', when something to be pasted is received by
633 * st.
634 * FIXME: Fix the computer world.
636 if ((y < sel.ne.y || lastx >= linelen) && !(last->mode & ATTR_WRAP))
637 *ptr++ = '\n';
639 *ptr = 0;
640 return str;
643 void
644 selclear(void)
646 if (sel.ob.x == -1)
647 return;
648 sel.mode = SEL_IDLE;
649 sel.ob.x = -1;
650 tsetdirt(sel.nb.y, sel.ne.y);
653 void
654 die(const char *errstr, ...)
656 va_list ap;
658 va_start(ap, errstr);
659 vfprintf(stderr, errstr, ap);
660 va_end(ap);
661 exit(1);
664 void
665 execsh(char *cmd, char **args)
667 char *sh, *prog, *arg;
668 const struct passwd *pw;
670 errno = 0;
671 if ((pw = getpwuid(getuid())) == NULL) {
672 if (errno)
673 die("getpwuid: %s\n", strerror(errno));
674 else
675 die("who are you?\n");
678 if ((sh = getenv("SHELL")) == NULL)
679 sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd;
681 if (args) {
682 prog = args[0];
683 arg = NULL;
684 } else if (scroll || utmp) {
685 prog = scroll ? scroll : utmp;
686 arg = scroll ? utmp : NULL;
687 } else {
688 prog = sh;
689 arg = NULL;
691 DEFAULT(args, ((char *[]) {prog, arg, NULL}));
693 unsetenv("COLUMNS");
694 unsetenv("LINES");
695 unsetenv("TERMCAP");
696 setenv("LOGNAME", pw->pw_name, 1);
697 setenv("USER", pw->pw_name, 1);
698 setenv("SHELL", sh, 1);
699 setenv("HOME", pw->pw_dir, 1);
700 setenv("TERM", termname, 1);
702 signal(SIGCHLD, SIG_DFL);
703 signal(SIGHUP, SIG_DFL);
704 signal(SIGINT, SIG_DFL);
705 signal(SIGQUIT, SIG_DFL);
706 signal(SIGTERM, SIG_DFL);
707 signal(SIGALRM, SIG_DFL);
709 execvp(prog, args);
710 _exit(1);
713 void
714 sigchld(int a)
716 int stat;
717 pid_t p;
719 if ((p = waitpid(pid, &stat, WNOHANG)) < 0)
720 die("waiting for pid %hd failed: %s\n", pid, strerror(errno));
722 if (pid != p)
723 return;
725 if (WIFEXITED(stat) && WEXITSTATUS(stat))
726 die("child exited with status %d\n", WEXITSTATUS(stat));
727 else if (WIFSIGNALED(stat))
728 die("child terminated due to signal %d\n", WTERMSIG(stat));
729 exit(0);
732 void
733 stty(char **args)
735 char cmd[_POSIX_ARG_MAX], **p, *q, *s;
736 size_t n, siz;
738 if ((n = strlen(stty_args)) > sizeof(cmd)-1)
739 die("incorrect stty parameters\n");
740 memcpy(cmd, stty_args, n);
741 q = cmd + n;
742 siz = sizeof(cmd) - n;
743 for (p = args; p && (s = *p); ++p) {
744 if ((n = strlen(s)) > siz-1)
745 die("stty parameter length too long\n");
746 *q++ = ' ';
747 memcpy(q, s, n);
748 q += n;
749 siz -= n + 1;
751 *q = '\0';
752 if (system(cmd) != 0)
753 perror("Couldn't call stty");
757 ttynew(char *line, char *cmd, char *out, char **args)
759 int m, s;
761 if (out) {
762 term.mode |= MODE_PRINT;
763 iofd = (!strcmp(out, "-")) ?
764 1 : open(out, O_WRONLY | O_CREAT, 0666);
765 if (iofd < 0) {
766 fprintf(stderr, "Error opening %s:%s\n",
767 out, strerror(errno));
771 if (line) {
772 if ((cmdfd = open(line, O_RDWR)) < 0)
773 die("open line '%s' failed: %s\n",
774 line, strerror(errno));
775 dup2(cmdfd, 0);
776 stty(args);
777 return cmdfd;
780 /* seems to work fine on linux, openbsd and freebsd */
781 if (openpty(&m, &s, NULL, NULL, NULL) < 0)
782 die("openpty failed: %s\n", strerror(errno));
784 switch (pid = fork()) {
785 case -1:
786 die("fork failed: %s\n", strerror(errno));
787 break;
788 case 0:
789 close(iofd);
790 setsid(); /* create a new process group */
791 dup2(s, 0);
792 dup2(s, 1);
793 dup2(s, 2);
794 if (ioctl(s, TIOCSCTTY, NULL) < 0)
795 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno));
796 close(s);
797 close(m);
798 #ifdef __OpenBSD__
799 if (pledge("stdio getpw proc exec", NULL) == -1)
800 die("pledge\n");
801 #endif
802 execsh(cmd, args);
803 break;
804 default:
805 #ifdef __OpenBSD__
806 if (pledge("stdio rpath tty proc", NULL) == -1)
807 die("pledge\n");
808 #endif
809 close(s);
810 cmdfd = m;
811 signal(SIGCHLD, sigchld);
812 break;
814 return cmdfd;
817 size_t
818 ttyread(void)
820 static char buf[BUFSIZ];
821 static int buflen = 0;
822 int written;
823 int ret;
825 /* append read bytes to unprocessed bytes */
826 if ((ret = read(cmdfd, buf+buflen, LEN(buf)-buflen)) < 0)
827 die("couldn't read from shell: %s\n", strerror(errno));
828 buflen += ret;
830 written = twrite(buf, buflen, 0);
831 buflen -= written;
832 /* keep any uncomplete utf8 char for the next call */
833 if (buflen > 0)
834 memmove(buf, buf + written, buflen);
836 return ret;
839 void
840 ttywrite(const char *s, size_t n, int may_echo)
842 const char *next;
844 if (may_echo && IS_SET(MODE_ECHO))
845 twrite(s, n, 1);
847 if (!IS_SET(MODE_CRLF)) {
848 ttywriteraw(s, n);
849 return;
852 /* This is similar to how the kernel handles ONLCR for ttys */
853 while (n > 0) {
854 if (*s == '\r') {
855 next = s + 1;
856 ttywriteraw("\r\n", 2);
857 } else {
858 next = memchr(s, '\r', n);
859 DEFAULT(next, s + n);
860 ttywriteraw(s, next - s);
862 n -= next - s;
863 s = next;
867 void
868 ttywriteraw(const char *s, size_t n)
870 fd_set wfd, rfd;
871 ssize_t r;
872 size_t lim = 256;
875 * Remember that we are using a pty, which might be a modem line.
876 * Writing too much will clog the line. That's why we are doing this
877 * dance.
878 * FIXME: Migrate the world to Plan 9.
880 while (n > 0) {
881 FD_ZERO(&wfd);
882 FD_ZERO(&rfd);
883 FD_SET(cmdfd, &wfd);
884 FD_SET(cmdfd, &rfd);
886 /* Check if we can write. */
887 if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) {
888 if (errno == EINTR)
889 continue;
890 die("select failed: %s\n", strerror(errno));
892 if (FD_ISSET(cmdfd, &wfd)) {
894 * Only write the bytes written by ttywrite() or the
895 * default of 256. This seems to be a reasonable value
896 * for a serial line. Bigger values might clog the I/O.
898 if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0)
899 goto write_error;
900 if (r < n) {
902 * We weren't able to write out everything.
903 * This means the buffer is getting full
904 * again. Empty it.
906 if (n < lim)
907 lim = ttyread();
908 n -= r;
909 s += r;
910 } else {
911 /* All bytes have been written. */
912 break;
915 if (FD_ISSET(cmdfd, &rfd))
916 lim = ttyread();
918 return;
920 write_error:
921 die("write error on tty: %s\n", strerror(errno));
924 void
925 ttyresize(int tw, int th)
927 struct winsize w;
929 w.ws_row = term.row;
930 w.ws_col = term.col;
931 w.ws_xpixel = tw;
932 w.ws_ypixel = th;
933 if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
934 fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
937 void
938 ttyhangup()
940 /* Send SIGHUP to shell */
941 kill(pid, SIGHUP);
945 tattrset(int attr)
947 int i, j;
949 for (i = 0; i < term.row-1; i++) {
950 for (j = 0; j < term.col-1; j++) {
951 if (term.line[i][j].mode & attr)
952 return 1;
956 return 0;
959 void
960 tsetdirt(int top, int bot)
962 int i;
964 LIMIT(top, 0, term.row-1);
965 LIMIT(bot, 0, term.row-1);
967 for (i = top; i <= bot; i++)
968 term.dirty[i] = 1;
971 void
972 tsetdirtattr(int attr)
974 int i, j;
976 for (i = 0; i < term.row-1; i++) {
977 for (j = 0; j < term.col-1; j++) {
978 if (term.line[i][j].mode & attr) {
979 tsetdirt(i, i);
980 break;
986 void
987 tfulldirt(void)
989 tsetdirt(0, term.row-1);
992 void
993 tcursor(int mode)
995 static TCursor c[2];
996 int alt = IS_SET(MODE_ALTSCREEN);
998 if (mode == CURSOR_SAVE) {
999 c[alt] = term.c;
1000 } else if (mode == CURSOR_LOAD) {
1001 term.c = c[alt];
1002 tmoveto(c[alt].x, c[alt].y);
1006 void
1007 treset(void)
1009 uint i;
1011 term.c = (TCursor){{
1012 .mode = ATTR_NULL,
1013 .fg = defaultfg,
1014 .bg = defaultbg
1015 }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
1017 memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1018 for (i = tabspaces; i < term.col; i += tabspaces)
1019 term.tabs[i] = 1;
1020 term.top = 0;
1021 term.bot = term.row - 1;
1022 term.mode = MODE_WRAP|MODE_UTF8;
1023 memset(term.trantbl, CS_USA, sizeof(term.trantbl));
1024 term.charset = 0;
1026 for (i = 0; i < 2; i++) {
1027 tmoveto(0, 0);
1028 tcursor(CURSOR_SAVE);
1029 tclearregion(0, 0, term.col-1, term.row-1);
1030 tswapscreen();
1034 void
1035 tnew(int col, int row)
1037 term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
1038 tresize(col, row);
1039 treset();
1042 void
1043 tswapscreen(void)
1045 Line *tmp = term.line;
1047 term.line = term.alt;
1048 term.alt = tmp;
1049 term.mode ^= MODE_ALTSCREEN;
1050 tfulldirt();
1053 void
1054 tscrolldown(int orig, int n)
1056 int i;
1057 Line temp;
1059 LIMIT(n, 0, term.bot-orig+1);
1061 tsetdirt(orig, term.bot-n);
1062 tclearregion(0, term.bot-n+1, term.col-1, term.bot);
1064 for (i = term.bot; i >= orig+n; i--) {
1065 temp = term.line[i];
1066 term.line[i] = term.line[i-n];
1067 term.line[i-n] = temp;
1070 selscroll(orig, n);
1073 void
1074 tscrollup(int orig, int n)
1076 int i;
1077 Line temp;
1079 LIMIT(n, 0, term.bot-orig+1);
1081 tclearregion(0, orig, term.col-1, orig+n-1);
1082 tsetdirt(orig+n, term.bot);
1084 for (i = orig; i <= term.bot-n; i++) {
1085 temp = term.line[i];
1086 term.line[i] = term.line[i+n];
1087 term.line[i+n] = temp;
1090 selscroll(orig, -n);
1093 void
1094 selscroll(int orig, int n)
1096 if (sel.ob.x == -1)
1097 return;
1099 if (BETWEEN(sel.ob.y, orig, term.bot) || BETWEEN(sel.oe.y, orig, term.bot)) {
1100 if ((sel.ob.y += n) > term.bot || (sel.oe.y += n) < term.top) {
1101 selclear();
1102 return;
1104 if (sel.type == SEL_RECTANGULAR) {
1105 if (sel.ob.y < term.top)
1106 sel.ob.y = term.top;
1107 if (sel.oe.y > term.bot)
1108 sel.oe.y = term.bot;
1109 } else {
1110 if (sel.ob.y < term.top) {
1111 sel.ob.y = term.top;
1112 sel.ob.x = 0;
1114 if (sel.oe.y > term.bot) {
1115 sel.oe.y = term.bot;
1116 sel.oe.x = term.col;
1119 selnormalize();
1123 void
1124 tnewline(int first_col)
1126 int y = term.c.y;
1128 if (y == term.bot) {
1129 tscrollup(term.top, 1);
1130 } else {
1131 y++;
1133 tmoveto(first_col ? 0 : term.c.x, y);
1136 void
1137 csiparse(void)
1139 char *p = csiescseq.buf, *np;
1140 long int v;
1142 csiescseq.narg = 0;
1143 if (*p == '?') {
1144 csiescseq.priv = 1;
1145 p++;
1148 csiescseq.buf[csiescseq.len] = '\0';
1149 while (p < csiescseq.buf+csiescseq.len) {
1150 np = NULL;
1151 v = strtol(p, &np, 10);
1152 if (np == p)
1153 v = 0;
1154 if (v == LONG_MAX || v == LONG_MIN)
1155 v = -1;
1156 csiescseq.arg[csiescseq.narg++] = v;
1157 p = np;
1158 if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ)
1159 break;
1160 p++;
1162 csiescseq.mode[0] = *p++;
1163 csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0';
1166 /* for absolute user moves, when decom is set */
1167 void
1168 tmoveato(int x, int y)
1170 tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0));
1173 void
1174 tmoveto(int x, int y)
1176 int miny, maxy;
1178 if (term.c.state & CURSOR_ORIGIN) {
1179 miny = term.top;
1180 maxy = term.bot;
1181 } else {
1182 miny = 0;
1183 maxy = term.row - 1;
1185 term.c.state &= ~CURSOR_WRAPNEXT;
1186 term.c.x = LIMIT(x, 0, term.col-1);
1187 term.c.y = LIMIT(y, miny, maxy);
1190 void
1191 tsetchar(Rune u, Glyph *attr, int x, int y)
1193 static char *vt100_0[62] = { /* 0x41 - 0x7e */
1194 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1195 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1196 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1197 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1198 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1199 "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1200 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1201 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1205 * The table is proudly stolen from rxvt.
1207 if (term.trantbl[term.charset] == CS_GRAPHIC0 &&
1208 BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
1209 utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ);
1211 if (term.line[y][x].mode & ATTR_WIDE) {
1212 if (x+1 < term.col) {
1213 term.line[y][x+1].u = ' ';
1214 term.line[y][x+1].mode &= ~ATTR_WDUMMY;
1216 } else if (term.line[y][x].mode & ATTR_WDUMMY) {
1217 term.line[y][x-1].u = ' ';
1218 term.line[y][x-1].mode &= ~ATTR_WIDE;
1221 term.dirty[y] = 1;
1222 term.line[y][x] = *attr;
1223 term.line[y][x].u = u;
1226 void
1227 tclearregion(int x1, int y1, int x2, int y2)
1229 int x, y, temp;
1230 Glyph *gp;
1232 if (x1 > x2)
1233 temp = x1, x1 = x2, x2 = temp;
1234 if (y1 > y2)
1235 temp = y1, y1 = y2, y2 = temp;
1237 LIMIT(x1, 0, term.col-1);
1238 LIMIT(x2, 0, term.col-1);
1239 LIMIT(y1, 0, term.row-1);
1240 LIMIT(y2, 0, term.row-1);
1242 for (y = y1; y <= y2; y++) {
1243 term.dirty[y] = 1;
1244 for (x = x1; x <= x2; x++) {
1245 gp = &term.line[y][x];
1246 if (selected(x, y))
1247 selclear();
1248 gp->fg = term.c.attr.fg;
1249 gp->bg = term.c.attr.bg;
1250 gp->mode = 0;
1251 gp->u = ' ';
1256 void
1257 tdeletechar(int n)
1259 int dst, src, size;
1260 Glyph *line;
1262 LIMIT(n, 0, term.col - term.c.x);
1264 dst = term.c.x;
1265 src = term.c.x + n;
1266 size = term.col - src;
1267 line = term.line[term.c.y];
1269 memmove(&line[dst], &line[src], size * sizeof(Glyph));
1270 tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
1273 void
1274 tinsertblank(int n)
1276 int dst, src, size;
1277 Glyph *line;
1279 LIMIT(n, 0, term.col - term.c.x);
1281 dst = term.c.x + n;
1282 src = term.c.x;
1283 size = term.col - dst;
1284 line = term.line[term.c.y];
1286 memmove(&line[dst], &line[src], size * sizeof(Glyph));
1287 tclearregion(src, term.c.y, dst - 1, term.c.y);
1290 void
1291 tinsertblankline(int n)
1293 if (BETWEEN(term.c.y, term.top, term.bot))
1294 tscrolldown(term.c.y, n);
1297 void
1298 tdeleteline(int n)
1300 if (BETWEEN(term.c.y, term.top, term.bot))
1301 tscrollup(term.c.y, n);
1304 int32_t
1305 tdefcolor(int *attr, int *npar, int l)
1307 int32_t idx = -1;
1308 uint r, g, b;
1310 switch (attr[*npar + 1]) {
1311 case 2: /* direct color in RGB space */
1312 if (*npar + 4 >= l) {
1313 fprintf(stderr,
1314 "erresc(38): Incorrect number of parameters (%d)\n",
1315 *npar);
1316 break;
1318 r = attr[*npar + 2];
1319 g = attr[*npar + 3];
1320 b = attr[*npar + 4];
1321 *npar += 4;
1322 if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255))
1323 fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n",
1324 r, g, b);
1325 else
1326 idx = TRUECOLOR(r, g, b);
1327 break;
1328 case 5: /* indexed color */
1329 if (*npar + 2 >= l) {
1330 fprintf(stderr,
1331 "erresc(38): Incorrect number of parameters (%d)\n",
1332 *npar);
1333 break;
1335 *npar += 2;
1336 if (!BETWEEN(attr[*npar], 0, 255))
1337 fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]);
1338 else
1339 idx = attr[*npar];
1340 break;
1341 case 0: /* implemented defined (only foreground) */
1342 case 1: /* transparent */
1343 case 3: /* direct color in CMY space */
1344 case 4: /* direct color in CMYK space */
1345 default:
1346 fprintf(stderr,
1347 "erresc(38): gfx attr %d unknown\n", attr[*npar]);
1348 break;
1351 return idx;
1354 void
1355 tsetattr(int *attr, int l)
1357 int i;
1358 int32_t idx;
1360 for (i = 0; i < l; i++) {
1361 switch (attr[i]) {
1362 case 0:
1363 term.c.attr.mode &= ~(
1364 ATTR_BOLD |
1365 ATTR_FAINT |
1366 ATTR_ITALIC |
1367 ATTR_UNDERLINE |
1368 ATTR_BLINK |
1369 ATTR_REVERSE |
1370 ATTR_INVISIBLE |
1371 ATTR_STRUCK );
1372 term.c.attr.fg = defaultfg;
1373 term.c.attr.bg = defaultbg;
1374 break;
1375 case 1:
1376 term.c.attr.mode |= ATTR_BOLD;
1377 break;
1378 case 2:
1379 term.c.attr.mode |= ATTR_FAINT;
1380 break;
1381 case 3:
1382 term.c.attr.mode |= ATTR_ITALIC;
1383 break;
1384 case 4:
1385 term.c.attr.mode |= ATTR_UNDERLINE;
1386 break;
1387 case 5: /* slow blink */
1388 /* FALLTHROUGH */
1389 case 6: /* rapid blink */
1390 term.c.attr.mode |= ATTR_BLINK;
1391 break;
1392 case 7:
1393 term.c.attr.mode |= ATTR_REVERSE;
1394 break;
1395 case 8:
1396 term.c.attr.mode |= ATTR_INVISIBLE;
1397 break;
1398 case 9:
1399 term.c.attr.mode |= ATTR_STRUCK;
1400 break;
1401 case 22:
1402 term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT);
1403 break;
1404 case 23:
1405 term.c.attr.mode &= ~ATTR_ITALIC;
1406 break;
1407 case 24:
1408 term.c.attr.mode &= ~ATTR_UNDERLINE;
1409 break;
1410 case 25:
1411 term.c.attr.mode &= ~ATTR_BLINK;
1412 break;
1413 case 27:
1414 term.c.attr.mode &= ~ATTR_REVERSE;
1415 break;
1416 case 28:
1417 term.c.attr.mode &= ~ATTR_INVISIBLE;
1418 break;
1419 case 29:
1420 term.c.attr.mode &= ~ATTR_STRUCK;
1421 break;
1422 case 38:
1423 if ((idx = tdefcolor(attr, &i, l)) >= 0)
1424 term.c.attr.fg = idx;
1425 break;
1426 case 39:
1427 term.c.attr.fg = defaultfg;
1428 break;
1429 case 48:
1430 if ((idx = tdefcolor(attr, &i, l)) >= 0)
1431 term.c.attr.bg = idx;
1432 break;
1433 case 49:
1434 term.c.attr.bg = defaultbg;
1435 break;
1436 default:
1437 if (BETWEEN(attr[i], 30, 37)) {
1438 term.c.attr.fg = attr[i] - 30;
1439 } else if (BETWEEN(attr[i], 40, 47)) {
1440 term.c.attr.bg = attr[i] - 40;
1441 } else if (BETWEEN(attr[i], 90, 97)) {
1442 term.c.attr.fg = attr[i] - 90 + 8;
1443 } else if (BETWEEN(attr[i], 100, 107)) {
1444 term.c.attr.bg = attr[i] - 100 + 8;
1445 } else {
1446 fprintf(stderr,
1447 "erresc(default): gfx attr %d unknown\n",
1448 attr[i]);
1449 csidump();
1451 break;
1456 void
1457 tsetscroll(int t, int b)
1459 int temp;
1461 LIMIT(t, 0, term.row-1);
1462 LIMIT(b, 0, term.row-1);
1463 if (t > b) {
1464 temp = t;
1465 t = b;
1466 b = temp;
1468 term.top = t;
1469 term.bot = b;
1472 void
1473 tsetmode(int priv, int set, int *args, int narg)
1475 int alt, *lim;
1477 for (lim = args + narg; args < lim; ++args) {
1478 if (priv) {
1479 switch (*args) {
1480 case 1: /* DECCKM -- Cursor key */
1481 xsetmode(set, MODE_APPCURSOR);
1482 break;
1483 case 5: /* DECSCNM -- Reverse video */
1484 xsetmode(set, MODE_REVERSE);
1485 break;
1486 case 6: /* DECOM -- Origin */
1487 MODBIT(term.c.state, set, CURSOR_ORIGIN);
1488 tmoveato(0, 0);
1489 break;
1490 case 7: /* DECAWM -- Auto wrap */
1491 MODBIT(term.mode, set, MODE_WRAP);
1492 break;
1493 case 0: /* Error (IGNORED) */
1494 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1495 case 3: /* DECCOLM -- Column (IGNORED) */
1496 case 4: /* DECSCLM -- Scroll (IGNORED) */
1497 case 8: /* DECARM -- Auto repeat (IGNORED) */
1498 case 18: /* DECPFF -- Printer feed (IGNORED) */
1499 case 19: /* DECPEX -- Printer extent (IGNORED) */
1500 case 42: /* DECNRCM -- National characters (IGNORED) */
1501 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1502 break;
1503 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1504 xsetmode(!set, MODE_HIDE);
1505 break;
1506 case 9: /* X10 mouse compatibility mode */
1507 xsetpointermotion(0);
1508 xsetmode(0, MODE_MOUSE);
1509 xsetmode(set, MODE_MOUSEX10);
1510 break;
1511 case 1000: /* 1000: report button press */
1512 xsetpointermotion(0);
1513 xsetmode(0, MODE_MOUSE);
1514 xsetmode(set, MODE_MOUSEBTN);
1515 break;
1516 case 1002: /* 1002: report motion on button press */
1517 xsetpointermotion(0);
1518 xsetmode(0, MODE_MOUSE);
1519 xsetmode(set, MODE_MOUSEMOTION);
1520 break;
1521 case 1003: /* 1003: enable all mouse motions */
1522 xsetpointermotion(set);
1523 xsetmode(0, MODE_MOUSE);
1524 xsetmode(set, MODE_MOUSEMANY);
1525 break;
1526 case 1004: /* 1004: send focus events to tty */
1527 xsetmode(set, MODE_FOCUS);
1528 break;
1529 case 1006: /* 1006: extended reporting mode */
1530 xsetmode(set, MODE_MOUSESGR);
1531 break;
1532 case 1034:
1533 xsetmode(set, MODE_8BIT);
1534 break;
1535 case 1049: /* swap screen & set/restore cursor as xterm */
1536 if (!allowaltscreen)
1537 break;
1538 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1539 /* FALLTHROUGH */
1540 case 47: /* swap screen */
1541 case 1047:
1542 if (!allowaltscreen)
1543 break;
1544 alt = IS_SET(MODE_ALTSCREEN);
1545 if (alt) {
1546 tclearregion(0, 0, term.col-1,
1547 term.row-1);
1549 if (set ^ alt) /* set is always 1 or 0 */
1550 tswapscreen();
1551 if (*args != 1049)
1552 break;
1553 /* FALLTHROUGH */
1554 case 1048:
1555 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1556 break;
1557 case 2004: /* 2004: bracketed paste mode */
1558 xsetmode(set, MODE_BRCKTPASTE);
1559 break;
1560 /* Not implemented mouse modes. See comments there. */
1561 case 1001: /* mouse highlight mode; can hang the
1562 terminal by design when implemented. */
1563 case 1005: /* UTF-8 mouse mode; will confuse
1564 applications not supporting UTF-8
1565 and luit. */
1566 case 1015: /* urxvt mangled mouse mode; incompatible
1567 and can be mistaken for other control
1568 codes. */
1569 break;
1570 default:
1571 fprintf(stderr,
1572 "erresc: unknown private set/reset mode %d\n",
1573 *args);
1574 break;
1576 } else {
1577 switch (*args) {
1578 case 0: /* Error (IGNORED) */
1579 break;
1580 case 2:
1581 xsetmode(set, MODE_KBDLOCK);
1582 break;
1583 case 4: /* IRM -- Insertion-replacement */
1584 MODBIT(term.mode, set, MODE_INSERT);
1585 break;
1586 case 12: /* SRM -- Send/Receive */
1587 MODBIT(term.mode, !set, MODE_ECHO);
1588 break;
1589 case 20: /* LNM -- Linefeed/new line */
1590 MODBIT(term.mode, set, MODE_CRLF);
1591 break;
1592 default:
1593 fprintf(stderr,
1594 "erresc: unknown set/reset mode %d\n",
1595 *args);
1596 break;
1602 void
1603 csihandle(void)
1605 char buf[40];
1606 int len;
1608 switch (csiescseq.mode[0]) {
1609 default:
1610 unknown:
1611 fprintf(stderr, "erresc: unknown csi ");
1612 csidump();
1613 /* die(""); */
1614 break;
1615 case '@': /* ICH -- Insert <n> blank char */
1616 DEFAULT(csiescseq.arg[0], 1);
1617 tinsertblank(csiescseq.arg[0]);
1618 break;
1619 case 'A': /* CUU -- Cursor <n> Up */
1620 DEFAULT(csiescseq.arg[0], 1);
1621 tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
1622 break;
1623 case 'B': /* CUD -- Cursor <n> Down */
1624 case 'e': /* VPR --Cursor <n> Down */
1625 DEFAULT(csiescseq.arg[0], 1);
1626 tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
1627 break;
1628 case 'i': /* MC -- Media Copy */
1629 switch (csiescseq.arg[0]) {
1630 case 0:
1631 tdump();
1632 break;
1633 case 1:
1634 tdumpline(term.c.y);
1635 break;
1636 case 2:
1637 tdumpsel();
1638 break;
1639 case 4:
1640 term.mode &= ~MODE_PRINT;
1641 break;
1642 case 5:
1643 term.mode |= MODE_PRINT;
1644 break;
1646 break;
1647 case 'c': /* DA -- Device Attributes */
1648 if (csiescseq.arg[0] == 0)
1649 ttywrite(vtiden, strlen(vtiden), 0);
1650 break;
1651 case 'C': /* CUF -- Cursor <n> Forward */
1652 case 'a': /* HPR -- Cursor <n> Forward */
1653 DEFAULT(csiescseq.arg[0], 1);
1654 tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
1655 break;
1656 case 'D': /* CUB -- Cursor <n> Backward */
1657 DEFAULT(csiescseq.arg[0], 1);
1658 tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
1659 break;
1660 case 'E': /* CNL -- Cursor <n> Down and first col */
1661 DEFAULT(csiescseq.arg[0], 1);
1662 tmoveto(0, term.c.y+csiescseq.arg[0]);
1663 break;
1664 case 'F': /* CPL -- Cursor <n> Up and first col */
1665 DEFAULT(csiescseq.arg[0], 1);
1666 tmoveto(0, term.c.y-csiescseq.arg[0]);
1667 break;
1668 case 'g': /* TBC -- Tabulation clear */
1669 switch (csiescseq.arg[0]) {
1670 case 0: /* clear current tab stop */
1671 term.tabs[term.c.x] = 0;
1672 break;
1673 case 3: /* clear all the tabs */
1674 memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1675 break;
1676 default:
1677 goto unknown;
1679 break;
1680 case 'G': /* CHA -- Move to <col> */
1681 case '`': /* HPA */
1682 DEFAULT(csiescseq.arg[0], 1);
1683 tmoveto(csiescseq.arg[0]-1, term.c.y);
1684 break;
1685 case 'H': /* CUP -- Move to <row> <col> */
1686 case 'f': /* HVP */
1687 DEFAULT(csiescseq.arg[0], 1);
1688 DEFAULT(csiescseq.arg[1], 1);
1689 tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
1690 break;
1691 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1692 DEFAULT(csiescseq.arg[0], 1);
1693 tputtab(csiescseq.arg[0]);
1694 break;
1695 case 'J': /* ED -- Clear screen */
1696 switch (csiescseq.arg[0]) {
1697 case 0: /* below */
1698 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
1699 if (term.c.y < term.row-1) {
1700 tclearregion(0, term.c.y+1, term.col-1,
1701 term.row-1);
1703 break;
1704 case 1: /* above */
1705 if (term.c.y > 1)
1706 tclearregion(0, 0, term.col-1, term.c.y-1);
1707 tclearregion(0, term.c.y, term.c.x, term.c.y);
1708 break;
1709 case 2: /* all */
1710 tclearregion(0, 0, term.col-1, term.row-1);
1711 break;
1712 default:
1713 goto unknown;
1715 break;
1716 case 'K': /* EL -- Clear line */
1717 switch (csiescseq.arg[0]) {
1718 case 0: /* right */
1719 tclearregion(term.c.x, term.c.y, term.col-1,
1720 term.c.y);
1721 break;
1722 case 1: /* left */
1723 tclearregion(0, term.c.y, term.c.x, term.c.y);
1724 break;
1725 case 2: /* all */
1726 tclearregion(0, term.c.y, term.col-1, term.c.y);
1727 break;
1729 break;
1730 case 'S': /* SU -- Scroll <n> line up */
1731 DEFAULT(csiescseq.arg[0], 1);
1732 tscrollup(term.top, csiescseq.arg[0]);
1733 break;
1734 case 'T': /* SD -- Scroll <n> line down */
1735 DEFAULT(csiescseq.arg[0], 1);
1736 tscrolldown(term.top, csiescseq.arg[0]);
1737 break;
1738 case 'L': /* IL -- Insert <n> blank lines */
1739 DEFAULT(csiescseq.arg[0], 1);
1740 tinsertblankline(csiescseq.arg[0]);
1741 break;
1742 case 'l': /* RM -- Reset Mode */
1743 tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
1744 break;
1745 case 'M': /* DL -- Delete <n> lines */
1746 DEFAULT(csiescseq.arg[0], 1);
1747 tdeleteline(csiescseq.arg[0]);
1748 break;
1749 case 'X': /* ECH -- Erase <n> char */
1750 DEFAULT(csiescseq.arg[0], 1);
1751 tclearregion(term.c.x, term.c.y,
1752 term.c.x + csiescseq.arg[0] - 1, term.c.y);
1753 break;
1754 case 'P': /* DCH -- Delete <n> char */
1755 DEFAULT(csiescseq.arg[0], 1);
1756 tdeletechar(csiescseq.arg[0]);
1757 break;
1758 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1759 DEFAULT(csiescseq.arg[0], 1);
1760 tputtab(-csiescseq.arg[0]);
1761 break;
1762 case 'd': /* VPA -- Move to <row> */
1763 DEFAULT(csiescseq.arg[0], 1);
1764 tmoveato(term.c.x, csiescseq.arg[0]-1);
1765 break;
1766 case 'h': /* SM -- Set terminal mode */
1767 tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
1768 break;
1769 case 'm': /* SGR -- Terminal attribute (color) */
1770 tsetattr(csiescseq.arg, csiescseq.narg);
1771 break;
1772 case 'n': /* DSR – Device Status Report (cursor position) */
1773 if (csiescseq.arg[0] == 6) {
1774 len = snprintf(buf, sizeof(buf),"\033[%i;%iR",
1775 term.c.y+1, term.c.x+1);
1776 ttywrite(buf, len, 0);
1778 break;
1779 case 'r': /* DECSTBM -- Set Scrolling Region */
1780 if (csiescseq.priv) {
1781 goto unknown;
1782 } else {
1783 DEFAULT(csiescseq.arg[0], 1);
1784 DEFAULT(csiescseq.arg[1], term.row);
1785 tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
1786 tmoveato(0, 0);
1788 break;
1789 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1790 tcursor(CURSOR_SAVE);
1791 break;
1792 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1793 tcursor(CURSOR_LOAD);
1794 break;
1795 case ' ':
1796 switch (csiescseq.mode[1]) {
1797 case 'q': /* DECSCUSR -- Set Cursor Style */
1798 if (xsetcursor(csiescseq.arg[0]))
1799 goto unknown;
1800 break;
1801 default:
1802 goto unknown;
1804 break;
1808 void
1809 csidump(void)
1811 size_t i;
1812 uint c;
1814 fprintf(stderr, "ESC[");
1815 for (i = 0; i < csiescseq.len; i++) {
1816 c = csiescseq.buf[i] & 0xff;
1817 if (isprint(c)) {
1818 putc(c, stderr);
1819 } else if (c == '\n') {
1820 fprintf(stderr, "(\\n)");
1821 } else if (c == '\r') {
1822 fprintf(stderr, "(\\r)");
1823 } else if (c == 0x1b) {
1824 fprintf(stderr, "(\\e)");
1825 } else {
1826 fprintf(stderr, "(%02x)", c);
1829 putc('\n', stderr);
1832 void
1833 csireset(void)
1835 memset(&csiescseq, 0, sizeof(csiescseq));
1838 void
1839 strhandle(void)
1841 char *p = NULL, *dec;
1842 int j, narg, par;
1844 term.esc &= ~(ESC_STR_END|ESC_STR);
1845 strparse();
1846 par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
1848 switch (strescseq.type) {
1849 case ']': /* OSC -- Operating System Command */
1850 switch (par) {
1851 case 0:
1852 case 1:
1853 case 2:
1854 if (narg > 1)
1855 xsettitle(strescseq.args[1]);
1856 return;
1857 case 52:
1858 if (narg > 2) {
1859 dec = base64dec(strescseq.args[2]);
1860 if (dec) {
1861 xsetsel(dec);
1862 xclipcopy();
1863 } else {
1864 fprintf(stderr, "erresc: invalid base64\n");
1867 return;
1868 case 4: /* color set */
1869 if (narg < 3)
1870 break;
1871 p = strescseq.args[2];
1872 /* FALLTHROUGH */
1873 case 104: /* color reset, here p = NULL */
1874 j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
1875 if (xsetcolorname(j, p)) {
1876 if (par == 104 && narg <= 1)
1877 return; /* color reset without parameter */
1878 fprintf(stderr, "erresc: invalid color j=%d, p=%s\n",
1879 j, p ? p : "(null)");
1880 } else {
1882 * TODO if defaultbg color is changed, borders
1883 * are dirty
1885 redraw();
1887 return;
1889 break;
1890 case 'k': /* old title set compatibility */
1891 xsettitle(strescseq.args[0]);
1892 return;
1893 case 'P': /* DCS -- Device Control String */
1894 term.mode |= ESC_DCS;
1895 case '_': /* APC -- Application Program Command */
1896 case '^': /* PM -- Privacy Message */
1897 return;
1900 fprintf(stderr, "erresc: unknown str ");
1901 strdump();
1904 void
1905 strparse(void)
1907 int c;
1908 char *p = strescseq.buf;
1910 strescseq.narg = 0;
1911 strescseq.buf[strescseq.len] = '\0';
1913 if (*p == '\0')
1914 return;
1916 while (strescseq.narg < STR_ARG_SIZ) {
1917 strescseq.args[strescseq.narg++] = p;
1918 while ((c = *p) != ';' && c != '\0')
1919 ++p;
1920 if (c == '\0')
1921 return;
1922 *p++ = '\0';
1926 void
1927 strdump(void)
1929 size_t i;
1930 uint c;
1932 fprintf(stderr, "ESC%c", strescseq.type);
1933 for (i = 0; i < strescseq.len; i++) {
1934 c = strescseq.buf[i] & 0xff;
1935 if (c == '\0') {
1936 putc('\n', stderr);
1937 return;
1938 } else if (isprint(c)) {
1939 putc(c, stderr);
1940 } else if (c == '\n') {
1941 fprintf(stderr, "(\\n)");
1942 } else if (c == '\r') {
1943 fprintf(stderr, "(\\r)");
1944 } else if (c == 0x1b) {
1945 fprintf(stderr, "(\\e)");
1946 } else {
1947 fprintf(stderr, "(%02x)", c);
1950 fprintf(stderr, "ESC\\\n");
1953 void
1954 strreset(void)
1956 strescseq = (STREscape){
1957 .buf = xrealloc(strescseq.buf, STR_BUF_SIZ),
1958 .siz = STR_BUF_SIZ,
1962 void
1963 sendbreak(const Arg *arg)
1965 if (tcsendbreak(cmdfd, 0))
1966 perror("Error sending break");
1969 void
1970 tprinter(char *s, size_t len)
1972 if (iofd != -1 && xwrite(iofd, s, len) < 0) {
1973 perror("Error writing to output file");
1974 close(iofd);
1975 iofd = -1;
1979 void
1980 toggleprinter(const Arg *arg)
1982 term.mode ^= MODE_PRINT;
1985 void
1986 printscreen(const Arg *arg)
1988 tdump();
1991 void
1992 printsel(const Arg *arg)
1994 tdumpsel();
1997 void
1998 tdumpsel(void)
2000 char *ptr;
2002 if ((ptr = getsel())) {
2003 tprinter(ptr, strlen(ptr));
2004 free(ptr);
2008 void
2009 tdumpline(int n)
2011 char buf[UTF_SIZ];
2012 Glyph *bp, *end;
2014 bp = &term.line[n][0];
2015 end = &bp[MIN(tlinelen(n), term.col) - 1];
2016 if (bp != end || bp->u != ' ') {
2017 for ( ;bp <= end; ++bp)
2018 tprinter(buf, utf8encode(bp->u, buf));
2020 tprinter("\n", 1);
2023 void
2024 tdump(void)
2026 int i;
2028 for (i = 0; i < term.row; ++i)
2029 tdumpline(i);
2032 void
2033 tputtab(int n)
2035 uint x = term.c.x;
2037 if (n > 0) {
2038 while (x < term.col && n--)
2039 for (++x; x < term.col && !term.tabs[x]; ++x)
2040 /* nothing */ ;
2041 } else if (n < 0) {
2042 while (x > 0 && n++)
2043 for (--x; x > 0 && !term.tabs[x]; --x)
2044 /* nothing */ ;
2046 term.c.x = LIMIT(x, 0, term.col-1);
2049 void
2050 tdefutf8(char ascii)
2052 if (ascii == 'G')
2053 term.mode |= MODE_UTF8;
2054 else if (ascii == '@')
2055 term.mode &= ~MODE_UTF8;
2058 void
2059 tdeftran(char ascii)
2061 static char cs[] = "0B";
2062 static int vcs[] = {CS_GRAPHIC0, CS_USA};
2063 char *p;
2065 if ((p = strchr(cs, ascii)) == NULL) {
2066 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
2067 } else {
2068 term.trantbl[term.icharset] = vcs[p - cs];
2072 void
2073 tdectest(char c)
2075 int x, y;
2077 if (c == '8') { /* DEC screen alignment test. */
2078 for (x = 0; x < term.col; ++x) {
2079 for (y = 0; y < term.row; ++y)
2080 tsetchar('E', &term.c.attr, x, y);
2085 void
2086 tstrsequence(uchar c)
2088 strreset();
2090 switch (c) {
2091 case 0x90: /* DCS -- Device Control String */
2092 c = 'P';
2093 term.esc |= ESC_DCS;
2094 break;
2095 case 0x9f: /* APC -- Application Program Command */
2096 c = '_';
2097 break;
2098 case 0x9e: /* PM -- Privacy Message */
2099 c = '^';
2100 break;
2101 case 0x9d: /* OSC -- Operating System Command */
2102 c = ']';
2103 break;
2105 strescseq.type = c;
2106 term.esc |= ESC_STR;
2109 void
2110 tcontrolcode(uchar ascii)
2112 switch (ascii) {
2113 case '\t': /* HT */
2114 tputtab(1);
2115 return;
2116 case '\b': /* BS */
2117 tmoveto(term.c.x-1, term.c.y);
2118 return;
2119 case '\r': /* CR */
2120 tmoveto(0, term.c.y);
2121 return;
2122 case '\f': /* LF */
2123 case '\v': /* VT */
2124 case '\n': /* LF */
2125 /* go to first col if the mode is set */
2126 tnewline(IS_SET(MODE_CRLF));
2127 return;
2128 case '\a': /* BEL */
2129 if (term.esc & ESC_STR_END) {
2130 /* backwards compatibility to xterm */
2131 strhandle();
2132 } else {
2133 xbell();
2135 break;
2136 case '\033': /* ESC */
2137 csireset();
2138 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
2139 term.esc |= ESC_START;
2140 return;
2141 case '\016': /* SO (LS1 -- Locking shift 1) */
2142 case '\017': /* SI (LS0 -- Locking shift 0) */
2143 term.charset = 1 - (ascii - '\016');
2144 return;
2145 case '\032': /* SUB */
2146 tsetchar('?', &term.c.attr, term.c.x, term.c.y);
2147 case '\030': /* CAN */
2148 csireset();
2149 break;
2150 case '\005': /* ENQ (IGNORED) */
2151 case '\000': /* NUL (IGNORED) */
2152 case '\021': /* XON (IGNORED) */
2153 case '\023': /* XOFF (IGNORED) */
2154 case 0177: /* DEL (IGNORED) */
2155 return;
2156 case 0x80: /* TODO: PAD */
2157 case 0x81: /* TODO: HOP */
2158 case 0x82: /* TODO: BPH */
2159 case 0x83: /* TODO: NBH */
2160 case 0x84: /* TODO: IND */
2161 break;
2162 case 0x85: /* NEL -- Next line */
2163 tnewline(1); /* always go to first col */
2164 break;
2165 case 0x86: /* TODO: SSA */
2166 case 0x87: /* TODO: ESA */
2167 break;
2168 case 0x88: /* HTS -- Horizontal tab stop */
2169 term.tabs[term.c.x] = 1;
2170 break;
2171 case 0x89: /* TODO: HTJ */
2172 case 0x8a: /* TODO: VTS */
2173 case 0x8b: /* TODO: PLD */
2174 case 0x8c: /* TODO: PLU */
2175 case 0x8d: /* TODO: RI */
2176 case 0x8e: /* TODO: SS2 */
2177 case 0x8f: /* TODO: SS3 */
2178 case 0x91: /* TODO: PU1 */
2179 case 0x92: /* TODO: PU2 */
2180 case 0x93: /* TODO: STS */
2181 case 0x94: /* TODO: CCH */
2182 case 0x95: /* TODO: MW */
2183 case 0x96: /* TODO: SPA */
2184 case 0x97: /* TODO: EPA */
2185 case 0x98: /* TODO: SOS */
2186 case 0x99: /* TODO: SGCI */
2187 break;
2188 case 0x9a: /* DECID -- Identify Terminal */
2189 ttywrite(vtiden, strlen(vtiden), 0);
2190 break;
2191 case 0x9b: /* TODO: CSI */
2192 case 0x9c: /* TODO: ST */
2193 break;
2194 case 0x90: /* DCS -- Device Control String */
2195 case 0x9d: /* OSC -- Operating System Command */
2196 case 0x9e: /* PM -- Privacy Message */
2197 case 0x9f: /* APC -- Application Program Command */
2198 tstrsequence(ascii);
2199 return;
2201 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2202 term.esc &= ~(ESC_STR_END|ESC_STR);
2206 * returns 1 when the sequence is finished and it hasn't to read
2207 * more characters for this sequence, otherwise 0
2210 eschandle(uchar ascii)
2212 switch (ascii) {
2213 case '[':
2214 term.esc |= ESC_CSI;
2215 return 0;
2216 case '#':
2217 term.esc |= ESC_TEST;
2218 return 0;
2219 case '%':
2220 term.esc |= ESC_UTF8;
2221 return 0;
2222 case 'P': /* DCS -- Device Control String */
2223 case '_': /* APC -- Application Program Command */
2224 case '^': /* PM -- Privacy Message */
2225 case ']': /* OSC -- Operating System Command */
2226 case 'k': /* old title set compatibility */
2227 tstrsequence(ascii);
2228 return 0;
2229 case 'n': /* LS2 -- Locking shift 2 */
2230 case 'o': /* LS3 -- Locking shift 3 */
2231 term.charset = 2 + (ascii - 'n');
2232 break;
2233 case '(': /* GZD4 -- set primary charset G0 */
2234 case ')': /* G1D4 -- set secondary charset G1 */
2235 case '*': /* G2D4 -- set tertiary charset G2 */
2236 case '+': /* G3D4 -- set quaternary charset G3 */
2237 term.icharset = ascii - '(';
2238 term.esc |= ESC_ALTCHARSET;
2239 return 0;
2240 case 'D': /* IND -- Linefeed */
2241 if (term.c.y == term.bot) {
2242 tscrollup(term.top, 1);
2243 } else {
2244 tmoveto(term.c.x, term.c.y+1);
2246 break;
2247 case 'E': /* NEL -- Next line */
2248 tnewline(1); /* always go to first col */
2249 break;
2250 case 'H': /* HTS -- Horizontal tab stop */
2251 term.tabs[term.c.x] = 1;
2252 break;
2253 case 'M': /* RI -- Reverse index */
2254 if (term.c.y == term.top) {
2255 tscrolldown(term.top, 1);
2256 } else {
2257 tmoveto(term.c.x, term.c.y-1);
2259 break;
2260 case 'Z': /* DECID -- Identify Terminal */
2261 ttywrite(vtiden, strlen(vtiden), 0);
2262 break;
2263 case 'c': /* RIS -- Reset to initial state */
2264 treset();
2265 resettitle();
2266 xloadcols();
2267 break;
2268 case '=': /* DECPAM -- Application keypad */
2269 xsetmode(1, MODE_APPKEYPAD);
2270 break;
2271 case '>': /* DECPNM -- Normal keypad */
2272 xsetmode(0, MODE_APPKEYPAD);
2273 break;
2274 case '7': /* DECSC -- Save Cursor */
2275 tcursor(CURSOR_SAVE);
2276 break;
2277 case '8': /* DECRC -- Restore Cursor */
2278 tcursor(CURSOR_LOAD);
2279 break;
2280 case '\\': /* ST -- String Terminator */
2281 if (term.esc & ESC_STR_END)
2282 strhandle();
2283 break;
2284 default:
2285 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2286 (uchar) ascii, isprint(ascii)? ascii:'.');
2287 break;
2289 return 1;
2292 void
2293 tputc(Rune u)
2295 char c[UTF_SIZ];
2296 int control;
2297 int width, len;
2298 Glyph *gp;
2300 control = ISCONTROL(u);
2301 if (!IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
2302 c[0] = u;
2303 width = len = 1;
2304 } else {
2305 len = utf8encode(u, c);
2306 if (!control && (width = wcwidth(u)) == -1) {
2307 memcpy(c, "\357\277\275", 4); /* UTF_INVALID */
2308 width = 1;
2312 if (IS_SET(MODE_PRINT))
2313 tprinter(c, len);
2316 * STR sequence must be checked before anything else
2317 * because it uses all following characters until it
2318 * receives a ESC, a SUB, a ST or any other C1 control
2319 * character.
2321 if (term.esc & ESC_STR) {
2322 if (u == '\a' || u == 030 || u == 032 || u == 033 ||
2323 ISCONTROLC1(u)) {
2324 term.esc &= ~(ESC_START|ESC_STR|ESC_DCS);
2325 if (IS_SET(MODE_SIXEL)) {
2326 /* TODO: render sixel */;
2327 term.mode &= ~MODE_SIXEL;
2328 return;
2330 term.esc |= ESC_STR_END;
2331 goto check_control_code;
2334 if (IS_SET(MODE_SIXEL)) {
2335 /* TODO: implement sixel mode */
2336 return;
2338 if (term.esc&ESC_DCS && strescseq.len == 0 && u == 'q')
2339 term.mode |= MODE_SIXEL;
2341 if (strescseq.len+len >= strescseq.siz) {
2343 * Here is a bug in terminals. If the user never sends
2344 * some code to stop the str or esc command, then st
2345 * will stop responding. But this is better than
2346 * silently failing with unknown characters. At least
2347 * then users will report back.
2349 * In the case users ever get fixed, here is the code:
2352 * term.esc = 0;
2353 * strhandle();
2355 if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2)
2356 return;
2357 strescseq.siz *= 2;
2358 strescseq.buf = xrealloc(strescseq.buf, strescseq.siz);
2361 memmove(&strescseq.buf[strescseq.len], c, len);
2362 strescseq.len += len;
2363 return;
2366 check_control_code:
2368 * Actions of control codes must be performed as soon they arrive
2369 * because they can be embedded inside a control sequence, and
2370 * they must not cause conflicts with sequences.
2372 if (control) {
2373 tcontrolcode(u);
2375 * control codes are not shown ever
2377 return;
2378 } else if (term.esc & ESC_START) {
2379 if (term.esc & ESC_CSI) {
2380 csiescseq.buf[csiescseq.len++] = u;
2381 if (BETWEEN(u, 0x40, 0x7E)
2382 || csiescseq.len >= \
2383 sizeof(csiescseq.buf)-1) {
2384 term.esc = 0;
2385 csiparse();
2386 csihandle();
2388 return;
2389 } else if (term.esc & ESC_UTF8) {
2390 tdefutf8(u);
2391 } else if (term.esc & ESC_ALTCHARSET) {
2392 tdeftran(u);
2393 } else if (term.esc & ESC_TEST) {
2394 tdectest(u);
2395 } else {
2396 if (!eschandle(u))
2397 return;
2398 /* sequence already finished */
2400 term.esc = 0;
2402 * All characters which form part of a sequence are not
2403 * printed
2405 return;
2407 if (sel.ob.x != -1 && BETWEEN(term.c.y, sel.ob.y, sel.oe.y))
2408 selclear();
2410 gp = &term.line[term.c.y][term.c.x];
2411 if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
2412 gp->mode |= ATTR_WRAP;
2413 tnewline(1);
2414 gp = &term.line[term.c.y][term.c.x];
2417 if (IS_SET(MODE_INSERT) && term.c.x+width < term.col)
2418 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
2420 if (term.c.x+width > term.col) {
2421 tnewline(1);
2422 gp = &term.line[term.c.y][term.c.x];
2425 tsetchar(u, &term.c.attr, term.c.x, term.c.y);
2427 if (width == 2) {
2428 gp->mode |= ATTR_WIDE;
2429 if (term.c.x+1 < term.col) {
2430 gp[1].u = '\0';
2431 gp[1].mode = ATTR_WDUMMY;
2434 if (term.c.x+width < term.col) {
2435 tmoveto(term.c.x+width, term.c.y);
2436 } else {
2437 term.c.state |= CURSOR_WRAPNEXT;
2442 twrite(const char *buf, int buflen, int show_ctrl)
2444 int charsize;
2445 Rune u;
2446 int n;
2448 for (n = 0; n < buflen; n += charsize) {
2449 if (IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
2450 /* process a complete utf8 char */
2451 charsize = utf8decode(buf + n, &u, buflen - n);
2452 if (charsize == 0)
2453 break;
2454 } else {
2455 u = buf[n] & 0xFF;
2456 charsize = 1;
2458 if (show_ctrl && ISCONTROL(u)) {
2459 if (u & 0x80) {
2460 u &= 0x7f;
2461 tputc('^');
2462 tputc('[');
2463 } else if (u != '\n' && u != '\r' && u != '\t') {
2464 u ^= 0x40;
2465 tputc('^');
2468 tputc(u);
2470 return n;
2473 void
2474 tresize(int col, int row)
2476 int i;
2477 int minrow = MIN(row, term.row);
2478 int mincol = MIN(col, term.col);
2479 int *bp;
2480 TCursor c;
2482 if (col < 1 || row < 1) {
2483 fprintf(stderr,
2484 "tresize: error resizing to %dx%d\n", col, row);
2485 return;
2489 * slide screen to keep cursor where we expect it -
2490 * tscrollup would work here, but we can optimize to
2491 * memmove because we're freeing the earlier lines
2493 for (i = 0; i <= term.c.y - row; i++) {
2494 free(term.line[i]);
2495 free(term.alt[i]);
2497 /* ensure that both src and dst are not NULL */
2498 if (i > 0) {
2499 memmove(term.line, term.line + i, row * sizeof(Line));
2500 memmove(term.alt, term.alt + i, row * sizeof(Line));
2502 for (i += row; i < term.row; i++) {
2503 free(term.line[i]);
2504 free(term.alt[i]);
2507 /* resize to new height */
2508 term.line = xrealloc(term.line, row * sizeof(Line));
2509 term.alt = xrealloc(term.alt, row * sizeof(Line));
2510 term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
2511 term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
2513 /* resize each row to new width, zero-pad if needed */
2514 for (i = 0; i < minrow; i++) {
2515 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
2516 term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph));
2519 /* allocate any new rows */
2520 for (/* i = minrow */; i < row; i++) {
2521 term.line[i] = xmalloc(col * sizeof(Glyph));
2522 term.alt[i] = xmalloc(col * sizeof(Glyph));
2524 if (col > term.col) {
2525 bp = term.tabs + term.col;
2527 memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
2528 while (--bp > term.tabs && !*bp)
2529 /* nothing */ ;
2530 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
2531 *bp = 1;
2533 /* update terminal size */
2534 term.col = col;
2535 term.row = row;
2536 /* reset scrolling region */
2537 tsetscroll(0, row-1);
2538 /* make use of the LIMIT in tmoveto */
2539 tmoveto(term.c.x, term.c.y);
2540 /* Clearing both screens (it makes dirty all lines) */
2541 c = term.c;
2542 for (i = 0; i < 2; i++) {
2543 if (mincol < col && 0 < minrow) {
2544 tclearregion(mincol, 0, col - 1, minrow - 1);
2546 if (0 < col && minrow < row) {
2547 tclearregion(0, minrow, col - 1, row - 1);
2549 tswapscreen();
2550 tcursor(CURSOR_LOAD);
2552 term.c = c;
2555 void
2556 resettitle(void)
2558 xsettitle(NULL);
2561 void
2562 drawregion(int x1, int y1, int x2, int y2)
2564 int y;
2565 for (y = y1; y < y2; y++) {
2566 if (!term.dirty[y])
2567 continue;
2569 term.dirty[y] = 0;
2570 xdrawline(term.line[y], x1, y, x2);
2574 void
2575 draw(void)
2577 int cx = term.c.x;
2579 if (!xstartdraw())
2580 return;
2582 /* adjust cursor position */
2583 LIMIT(term.ocx, 0, term.col-1);
2584 LIMIT(term.ocy, 0, term.row-1);
2585 if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY)
2586 term.ocx--;
2587 if (term.line[term.c.y][cx].mode & ATTR_WDUMMY)
2588 cx--;
2590 drawregion(0, 0, term.col, term.row);
2591 xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
2592 term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
2593 term.ocx = cx, term.ocy = term.c.y;
2594 xfinishdraw();
2595 xximspot(term.ocx, term.ocy);
2598 void
2599 redraw(void)
2601 tfulldirt();
2602 draw();