revert part of commit add0211522737b79dad990ccd65c8af63b5cc1dd
[st.git] / st.c
blob8e6ccb521a421bfda2642dde596eb0c0645e023c
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 int 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[STR_BUF_SIZ]; /* raw string */
150 int len; /* raw string length */
151 char *args[STR_ARG_SIZ];
152 int narg; /* nb of args */
153 } STREscape;
155 static void execsh(char *, char **);
156 static void stty(char **);
157 static void sigchld(int);
158 static void ttywriteraw(const char *, size_t);
160 static void csidump(void);
161 static void csihandle(void);
162 static void csiparse(void);
163 static void csireset(void);
164 static int eschandle(uchar);
165 static void strdump(void);
166 static void strhandle(void);
167 static void strparse(void);
168 static void strreset(void);
170 static void tprinter(char *, size_t);
171 static void tdumpsel(void);
172 static void tdumpline(int);
173 static void tdump(void);
174 static void tclearregion(int, int, int, int);
175 static void tcursor(int);
176 static void tdeletechar(int);
177 static void tdeleteline(int);
178 static void tinsertblank(int);
179 static void tinsertblankline(int);
180 static int tlinelen(int);
181 static void tmoveto(int, int);
182 static void tmoveato(int, int);
183 static void tnewline(int);
184 static void tputtab(int);
185 static void tputc(Rune);
186 static void treset(void);
187 static void tscrollup(int, int);
188 static void tscrolldown(int, int);
189 static void tsetattr(int *, int);
190 static void tsetchar(Rune, Glyph *, int, int);
191 static void tsetdirt(int, int);
192 static void tsetscroll(int, int);
193 static void tswapscreen(void);
194 static void tsetmode(int, int, int *, int);
195 static int twrite(const char *, int, int);
196 static void tfulldirt(void);
197 static void tcontrolcode(uchar );
198 static void tdectest(char );
199 static void tdefutf8(char);
200 static int32_t tdefcolor(int *, int *, int);
201 static void tdeftran(char);
202 static void tstrsequence(uchar);
204 static void drawregion(int, int, int, int);
206 static void selnormalize(void);
207 static void selscroll(int, int);
208 static void selsnap(int *, int *, int);
210 static size_t utf8decode(const char *, Rune *, size_t);
211 static Rune utf8decodebyte(char, size_t *);
212 static char utf8encodebyte(Rune, size_t);
213 static size_t utf8validate(Rune *, size_t);
215 static char *base64dec(const char *);
216 static char base64dec_getc(const char **);
218 static ssize_t xwrite(int, const char *, size_t);
220 /* Globals */
221 static Term term;
222 static Selection sel;
223 static CSIEscape csiescseq;
224 static STREscape strescseq;
225 static int iofd = 1;
226 static int cmdfd;
227 static pid_t pid;
229 static uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
230 static uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
231 static Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000};
232 static Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
234 ssize_t
235 xwrite(int fd, const char *s, size_t len)
237 size_t aux = len;
238 ssize_t r;
240 while (len > 0) {
241 r = write(fd, s, len);
242 if (r < 0)
243 return r;
244 len -= r;
245 s += r;
248 return aux;
251 void *
252 xmalloc(size_t len)
254 void *p;
256 if (!(p = malloc(len)))
257 die("malloc: %s\n", strerror(errno));
259 return p;
262 void *
263 xrealloc(void *p, size_t len)
265 if ((p = realloc(p, len)) == NULL)
266 die("realloc: %s\n", strerror(errno));
268 return p;
271 char *
272 xstrdup(char *s)
274 if ((s = strdup(s)) == NULL)
275 die("strdup: %s\n", strerror(errno));
277 return s;
280 size_t
281 utf8decode(const char *c, Rune *u, size_t clen)
283 size_t i, j, len, type;
284 Rune udecoded;
286 *u = UTF_INVALID;
287 if (!clen)
288 return 0;
289 udecoded = utf8decodebyte(c[0], &len);
290 if (!BETWEEN(len, 1, UTF_SIZ))
291 return 1;
292 for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
293 udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
294 if (type != 0)
295 return j;
297 if (j < len)
298 return 0;
299 *u = udecoded;
300 utf8validate(u, len);
302 return len;
305 Rune
306 utf8decodebyte(char c, size_t *i)
308 for (*i = 0; *i < LEN(utfmask); ++(*i))
309 if (((uchar)c & utfmask[*i]) == utfbyte[*i])
310 return (uchar)c & ~utfmask[*i];
312 return 0;
315 size_t
316 utf8encode(Rune u, char *c)
318 size_t len, i;
320 len = utf8validate(&u, 0);
321 if (len > UTF_SIZ)
322 return 0;
324 for (i = len - 1; i != 0; --i) {
325 c[i] = utf8encodebyte(u, 0);
326 u >>= 6;
328 c[0] = utf8encodebyte(u, len);
330 return len;
333 char
334 utf8encodebyte(Rune u, size_t i)
336 return utfbyte[i] | (u & ~utfmask[i]);
339 size_t
340 utf8validate(Rune *u, size_t i)
342 if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF))
343 *u = UTF_INVALID;
344 for (i = 1; *u > utfmax[i]; ++i)
347 return i;
350 static const char base64_digits[] = {
351 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
352 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0,
353 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
354 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
355 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
356 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0,
357 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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
365 char
366 base64dec_getc(const char **src)
368 while (**src && !isprint(**src)) (*src)++;
369 return *((*src)++);
372 char *
373 base64dec(const char *src)
375 size_t in_len = strlen(src);
376 char *result, *dst;
378 if (in_len % 4)
379 in_len += 4 - (in_len % 4);
380 result = dst = xmalloc(in_len / 4 * 3 + 1);
381 while (*src) {
382 int a = base64_digits[(unsigned char) base64dec_getc(&src)];
383 int b = base64_digits[(unsigned char) base64dec_getc(&src)];
384 int c = base64_digits[(unsigned char) base64dec_getc(&src)];
385 int d = base64_digits[(unsigned char) base64dec_getc(&src)];
387 *dst++ = (a << 2) | ((b & 0x30) >> 4);
388 if (c == -1)
389 break;
390 *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2);
391 if (d == -1)
392 break;
393 *dst++ = ((c & 0x03) << 6) | d;
395 *dst = '\0';
396 return result;
399 void
400 selinit(void)
402 sel.mode = SEL_IDLE;
403 sel.snap = 0;
404 sel.ob.x = -1;
408 tlinelen(int y)
410 int i = term.col;
412 if (term.line[y][i - 1].mode & ATTR_WRAP)
413 return i;
415 while (i > 0 && term.line[y][i - 1].u == ' ')
416 --i;
418 return i;
421 void
422 selstart(int col, int row, int snap)
424 selclear();
425 sel.mode = SEL_EMPTY;
426 sel.type = SEL_REGULAR;
427 sel.alt = IS_SET(MODE_ALTSCREEN);
428 sel.snap = snap;
429 sel.oe.x = sel.ob.x = col;
430 sel.oe.y = sel.ob.y = row;
431 selnormalize();
433 if (sel.snap != 0)
434 sel.mode = SEL_READY;
435 tsetdirt(sel.nb.y, sel.ne.y);
438 void
439 selextend(int col, int row, int type, int done)
441 int oldey, oldex, oldsby, oldsey, oldtype;
443 if (sel.mode == SEL_IDLE)
444 return;
445 if (done && sel.mode == SEL_EMPTY) {
446 selclear();
447 return;
450 oldey = sel.oe.y;
451 oldex = sel.oe.x;
452 oldsby = sel.nb.y;
453 oldsey = sel.ne.y;
454 oldtype = sel.type;
456 sel.oe.x = col;
457 sel.oe.y = row;
458 selnormalize();
459 sel.type = type;
461 if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type)
462 tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey));
464 sel.mode = done ? SEL_IDLE : SEL_READY;
467 void
468 selnormalize(void)
470 int i;
472 if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) {
473 sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x;
474 sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x;
475 } else {
476 sel.nb.x = MIN(sel.ob.x, sel.oe.x);
477 sel.ne.x = MAX(sel.ob.x, sel.oe.x);
479 sel.nb.y = MIN(sel.ob.y, sel.oe.y);
480 sel.ne.y = MAX(sel.ob.y, sel.oe.y);
482 selsnap(&sel.nb.x, &sel.nb.y, -1);
483 selsnap(&sel.ne.x, &sel.ne.y, +1);
485 /* expand selection over line breaks */
486 if (sel.type == SEL_RECTANGULAR)
487 return;
488 i = tlinelen(sel.nb.y);
489 if (i < sel.nb.x)
490 sel.nb.x = i;
491 if (tlinelen(sel.ne.y) <= sel.ne.x)
492 sel.ne.x = term.col - 1;
496 selected(int x, int y)
498 if (sel.mode == SEL_EMPTY || sel.ob.x == -1 ||
499 sel.alt != IS_SET(MODE_ALTSCREEN))
500 return 0;
502 if (sel.type == SEL_RECTANGULAR)
503 return BETWEEN(y, sel.nb.y, sel.ne.y)
504 && BETWEEN(x, sel.nb.x, sel.ne.x);
506 return BETWEEN(y, sel.nb.y, sel.ne.y)
507 && (y != sel.nb.y || x >= sel.nb.x)
508 && (y != sel.ne.y || x <= sel.ne.x);
511 void
512 selsnap(int *x, int *y, int direction)
514 int newx, newy, xt, yt;
515 int delim, prevdelim;
516 Glyph *gp, *prevgp;
518 switch (sel.snap) {
519 case SNAP_WORD:
521 * Snap around if the word wraps around at the end or
522 * beginning of a line.
524 prevgp = &term.line[*y][*x];
525 prevdelim = ISDELIM(prevgp->u);
526 for (;;) {
527 newx = *x + direction;
528 newy = *y;
529 if (!BETWEEN(newx, 0, term.col - 1)) {
530 newy += direction;
531 newx = (newx + term.col) % term.col;
532 if (!BETWEEN(newy, 0, term.row - 1))
533 break;
535 if (direction > 0)
536 yt = *y, xt = *x;
537 else
538 yt = newy, xt = newx;
539 if (!(term.line[yt][xt].mode & ATTR_WRAP))
540 break;
543 if (newx >= tlinelen(newy))
544 break;
546 gp = &term.line[newy][newx];
547 delim = ISDELIM(gp->u);
548 if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
549 || (delim && gp->u != prevgp->u)))
550 break;
552 *x = newx;
553 *y = newy;
554 prevgp = gp;
555 prevdelim = delim;
557 break;
558 case SNAP_LINE:
560 * Snap around if the the previous line or the current one
561 * has set ATTR_WRAP at its end. Then the whole next or
562 * previous line will be selected.
564 *x = (direction < 0) ? 0 : term.col - 1;
565 if (direction < 0) {
566 for (; *y > 0; *y += direction) {
567 if (!(term.line[*y-1][term.col-1].mode
568 & ATTR_WRAP)) {
569 break;
572 } else if (direction > 0) {
573 for (; *y < term.row-1; *y += direction) {
574 if (!(term.line[*y][term.col-1].mode
575 & ATTR_WRAP)) {
576 break;
580 break;
584 char *
585 getsel(void)
587 char *str, *ptr;
588 int y, bufsize, lastx, linelen;
589 Glyph *gp, *last;
591 if (sel.ob.x == -1)
592 return NULL;
594 bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ;
595 ptr = str = xmalloc(bufsize);
597 /* append every set & selected glyph to the selection */
598 for (y = sel.nb.y; y <= sel.ne.y; y++) {
599 if ((linelen = tlinelen(y)) == 0) {
600 *ptr++ = '\n';
601 continue;
604 if (sel.type == SEL_RECTANGULAR) {
605 gp = &term.line[y][sel.nb.x];
606 lastx = sel.ne.x;
607 } else {
608 gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0];
609 lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
611 last = &term.line[y][MIN(lastx, linelen-1)];
612 while (last >= gp && last->u == ' ')
613 --last;
615 for ( ; gp <= last; ++gp) {
616 if (gp->mode & ATTR_WDUMMY)
617 continue;
619 ptr += utf8encode(gp->u, ptr);
623 * Copy and pasting of line endings is inconsistent
624 * in the inconsistent terminal and GUI world.
625 * The best solution seems like to produce '\n' when
626 * something is copied from st and convert '\n' to
627 * '\r', when something to be pasted is received by
628 * st.
629 * FIXME: Fix the computer world.
631 if ((y < sel.ne.y || lastx >= linelen) && !(last->mode & ATTR_WRAP))
632 *ptr++ = '\n';
634 *ptr = 0;
635 return str;
638 void
639 selclear(void)
641 if (sel.ob.x == -1)
642 return;
643 sel.mode = SEL_IDLE;
644 sel.ob.x = -1;
645 tsetdirt(sel.nb.y, sel.ne.y);
648 void
649 die(const char *errstr, ...)
651 va_list ap;
653 va_start(ap, errstr);
654 vfprintf(stderr, errstr, ap);
655 va_end(ap);
656 exit(1);
659 void
660 execsh(char *cmd, char **args)
662 char *sh, *prog;
663 const struct passwd *pw;
665 errno = 0;
666 if ((pw = getpwuid(getuid())) == NULL) {
667 if (errno)
668 die("getpwuid: %s\n", strerror(errno));
669 else
670 die("who are you?\n");
673 if ((sh = getenv("SHELL")) == NULL)
674 sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd;
676 if (args)
677 prog = args[0];
678 else if (utmp)
679 prog = utmp;
680 else
681 prog = sh;
682 DEFAULT(args, ((char *[]) {prog, NULL}));
684 unsetenv("COLUMNS");
685 unsetenv("LINES");
686 unsetenv("TERMCAP");
687 setenv("LOGNAME", pw->pw_name, 1);
688 setenv("USER", pw->pw_name, 1);
689 setenv("SHELL", sh, 1);
690 setenv("HOME", pw->pw_dir, 1);
691 setenv("TERM", termname, 1);
693 signal(SIGCHLD, SIG_DFL);
694 signal(SIGHUP, SIG_DFL);
695 signal(SIGINT, SIG_DFL);
696 signal(SIGQUIT, SIG_DFL);
697 signal(SIGTERM, SIG_DFL);
698 signal(SIGALRM, SIG_DFL);
700 execvp(prog, args);
701 _exit(1);
704 void
705 sigchld(int a)
707 int stat;
708 pid_t p;
710 if ((p = waitpid(pid, &stat, WNOHANG)) < 0)
711 die("waiting for pid %hd failed: %s\n", pid, strerror(errno));
713 if (pid != p)
714 return;
716 if (WIFEXITED(stat) && WEXITSTATUS(stat))
717 die("child exited with status %d\n", WEXITSTATUS(stat));
718 else if (WIFSIGNALED(stat))
719 die("child terminated due to signal %d\n", WTERMSIG(stat));
720 exit(0);
723 void
724 stty(char **args)
726 char cmd[_POSIX_ARG_MAX], **p, *q, *s;
727 size_t n, siz;
729 if ((n = strlen(stty_args)) > sizeof(cmd)-1)
730 die("incorrect stty parameters\n");
731 memcpy(cmd, stty_args, n);
732 q = cmd + n;
733 siz = sizeof(cmd) - n;
734 for (p = args; p && (s = *p); ++p) {
735 if ((n = strlen(s)) > siz-1)
736 die("stty parameter length too long\n");
737 *q++ = ' ';
738 memcpy(q, s, n);
739 q += n;
740 siz -= n + 1;
742 *q = '\0';
743 if (system(cmd) != 0)
744 perror("Couldn't call stty");
748 ttynew(char *line, char *cmd, char *out, char **args)
750 int m, s;
752 if (out) {
753 term.mode |= MODE_PRINT;
754 iofd = (!strcmp(out, "-")) ?
755 1 : open(out, O_WRONLY | O_CREAT, 0666);
756 if (iofd < 0) {
757 fprintf(stderr, "Error opening %s:%s\n",
758 out, strerror(errno));
762 if (line) {
763 if ((cmdfd = open(line, O_RDWR)) < 0)
764 die("open line '%s' failed: %s\n",
765 line, strerror(errno));
766 dup2(cmdfd, 0);
767 stty(args);
768 return cmdfd;
771 /* seems to work fine on linux, openbsd and freebsd */
772 if (openpty(&m, &s, NULL, NULL, NULL) < 0)
773 die("openpty failed: %s\n", strerror(errno));
775 switch (pid = fork()) {
776 case -1:
777 die("fork failed: %s\n", strerror(errno));
778 break;
779 case 0:
780 close(iofd);
781 setsid(); /* create a new process group */
782 dup2(s, 0);
783 dup2(s, 1);
784 dup2(s, 2);
785 if (ioctl(s, TIOCSCTTY, NULL) < 0)
786 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno));
787 close(s);
788 close(m);
789 #ifdef __OpenBSD__
790 if (pledge("stdio getpw proc exec", NULL) == -1)
791 die("pledge\n");
792 #endif
793 execsh(cmd, args);
794 break;
795 default:
796 #ifdef __OpenBSD__
797 if (pledge("stdio rpath tty proc", NULL) == -1)
798 die("pledge\n");
799 #endif
800 close(s);
801 cmdfd = m;
802 signal(SIGCHLD, sigchld);
803 break;
805 return cmdfd;
808 size_t
809 ttyread(void)
811 static char buf[BUFSIZ];
812 static int buflen = 0;
813 int written;
814 int ret;
816 /* append read bytes to unprocessed bytes */
817 if ((ret = read(cmdfd, buf+buflen, LEN(buf)-buflen)) < 0)
818 die("couldn't read from shell: %s\n", strerror(errno));
819 buflen += ret;
821 written = twrite(buf, buflen, 0);
822 buflen -= written;
823 /* keep any uncomplete utf8 char for the next call */
824 if (buflen > 0)
825 memmove(buf, buf + written, buflen);
827 return ret;
830 void
831 ttywrite(const char *s, size_t n, int may_echo)
833 const char *next;
835 if (may_echo && IS_SET(MODE_ECHO))
836 twrite(s, n, 1);
838 if (!IS_SET(MODE_CRLF)) {
839 ttywriteraw(s, n);
840 return;
843 /* This is similar to how the kernel handles ONLCR for ttys */
844 while (n > 0) {
845 if (*s == '\r') {
846 next = s + 1;
847 ttywriteraw("\r\n", 2);
848 } else {
849 next = memchr(s, '\r', n);
850 DEFAULT(next, s + n);
851 ttywriteraw(s, next - s);
853 n -= next - s;
854 s = next;
858 void
859 ttywriteraw(const char *s, size_t n)
861 fd_set wfd, rfd;
862 ssize_t r;
863 size_t lim = 256;
866 * Remember that we are using a pty, which might be a modem line.
867 * Writing too much will clog the line. That's why we are doing this
868 * dance.
869 * FIXME: Migrate the world to Plan 9.
871 while (n > 0) {
872 FD_ZERO(&wfd);
873 FD_ZERO(&rfd);
874 FD_SET(cmdfd, &wfd);
875 FD_SET(cmdfd, &rfd);
877 /* Check if we can write. */
878 if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) {
879 if (errno == EINTR)
880 continue;
881 die("select failed: %s\n", strerror(errno));
883 if (FD_ISSET(cmdfd, &wfd)) {
885 * Only write the bytes written by ttywrite() or the
886 * default of 256. This seems to be a reasonable value
887 * for a serial line. Bigger values might clog the I/O.
889 if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0)
890 goto write_error;
891 if (r < n) {
893 * We weren't able to write out everything.
894 * This means the buffer is getting full
895 * again. Empty it.
897 if (n < lim)
898 lim = ttyread();
899 n -= r;
900 s += r;
901 } else {
902 /* All bytes have been written. */
903 break;
906 if (FD_ISSET(cmdfd, &rfd))
907 lim = ttyread();
909 return;
911 write_error:
912 die("write error on tty: %s\n", strerror(errno));
915 void
916 ttyresize(int tw, int th)
918 struct winsize w;
920 w.ws_row = term.row;
921 w.ws_col = term.col;
922 w.ws_xpixel = tw;
923 w.ws_ypixel = th;
924 if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
925 fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
928 void
929 ttyhangup()
931 /* Send SIGHUP to shell */
932 kill(pid, SIGHUP);
936 tattrset(int attr)
938 int i, j;
940 for (i = 0; i < term.row-1; i++) {
941 for (j = 0; j < term.col-1; j++) {
942 if (term.line[i][j].mode & attr)
943 return 1;
947 return 0;
950 void
951 tsetdirt(int top, int bot)
953 int i;
955 LIMIT(top, 0, term.row-1);
956 LIMIT(bot, 0, term.row-1);
958 for (i = top; i <= bot; i++)
959 term.dirty[i] = 1;
962 void
963 tsetdirtattr(int attr)
965 int i, j;
967 for (i = 0; i < term.row-1; i++) {
968 for (j = 0; j < term.col-1; j++) {
969 if (term.line[i][j].mode & attr) {
970 tsetdirt(i, i);
971 break;
977 void
978 tfulldirt(void)
980 tsetdirt(0, term.row-1);
983 void
984 tcursor(int mode)
986 static TCursor c[2];
987 int alt = IS_SET(MODE_ALTSCREEN);
989 if (mode == CURSOR_SAVE) {
990 c[alt] = term.c;
991 } else if (mode == CURSOR_LOAD) {
992 term.c = c[alt];
993 tmoveto(c[alt].x, c[alt].y);
997 void
998 treset(void)
1000 uint i;
1002 term.c = (TCursor){{
1003 .mode = ATTR_NULL,
1004 .fg = defaultfg,
1005 .bg = defaultbg
1006 }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
1008 memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1009 for (i = tabspaces; i < term.col; i += tabspaces)
1010 term.tabs[i] = 1;
1011 term.top = 0;
1012 term.bot = term.row - 1;
1013 term.mode = MODE_WRAP|MODE_UTF8;
1014 memset(term.trantbl, CS_USA, sizeof(term.trantbl));
1015 term.charset = 0;
1017 for (i = 0; i < 2; i++) {
1018 tmoveto(0, 0);
1019 tcursor(CURSOR_SAVE);
1020 tclearregion(0, 0, term.col-1, term.row-1);
1021 tswapscreen();
1025 void
1026 tnew(int col, int row)
1028 term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
1029 tresize(col, row);
1030 treset();
1033 void
1034 tswapscreen(void)
1036 Line *tmp = term.line;
1038 term.line = term.alt;
1039 term.alt = tmp;
1040 term.mode ^= MODE_ALTSCREEN;
1041 tfulldirt();
1044 void
1045 tscrolldown(int orig, int n)
1047 int i;
1048 Line temp;
1050 LIMIT(n, 0, term.bot-orig+1);
1052 tsetdirt(orig, term.bot-n);
1053 tclearregion(0, term.bot-n+1, term.col-1, term.bot);
1055 for (i = term.bot; i >= orig+n; i--) {
1056 temp = term.line[i];
1057 term.line[i] = term.line[i-n];
1058 term.line[i-n] = temp;
1061 selscroll(orig, n);
1064 void
1065 tscrollup(int orig, int n)
1067 int i;
1068 Line temp;
1070 LIMIT(n, 0, term.bot-orig+1);
1072 tclearregion(0, orig, term.col-1, orig+n-1);
1073 tsetdirt(orig+n, term.bot);
1075 for (i = orig; i <= term.bot-n; i++) {
1076 temp = term.line[i];
1077 term.line[i] = term.line[i+n];
1078 term.line[i+n] = temp;
1081 selscroll(orig, -n);
1084 void
1085 selscroll(int orig, int n)
1087 if (sel.ob.x == -1)
1088 return;
1090 if (BETWEEN(sel.ob.y, orig, term.bot) || BETWEEN(sel.oe.y, orig, term.bot)) {
1091 if ((sel.ob.y += n) > term.bot || (sel.oe.y += n) < term.top) {
1092 selclear();
1093 return;
1095 if (sel.type == SEL_RECTANGULAR) {
1096 if (sel.ob.y < term.top)
1097 sel.ob.y = term.top;
1098 if (sel.oe.y > term.bot)
1099 sel.oe.y = term.bot;
1100 } else {
1101 if (sel.ob.y < term.top) {
1102 sel.ob.y = term.top;
1103 sel.ob.x = 0;
1105 if (sel.oe.y > term.bot) {
1106 sel.oe.y = term.bot;
1107 sel.oe.x = term.col;
1110 selnormalize();
1114 void
1115 tnewline(int first_col)
1117 int y = term.c.y;
1119 if (y == term.bot) {
1120 tscrollup(term.top, 1);
1121 } else {
1122 y++;
1124 tmoveto(first_col ? 0 : term.c.x, y);
1127 void
1128 csiparse(void)
1130 char *p = csiescseq.buf, *np;
1131 long int v;
1133 csiescseq.narg = 0;
1134 if (*p == '?') {
1135 csiescseq.priv = 1;
1136 p++;
1139 csiescseq.buf[csiescseq.len] = '\0';
1140 while (p < csiescseq.buf+csiescseq.len) {
1141 np = NULL;
1142 v = strtol(p, &np, 10);
1143 if (np == p)
1144 v = 0;
1145 if (v == LONG_MAX || v == LONG_MIN)
1146 v = -1;
1147 csiescseq.arg[csiescseq.narg++] = v;
1148 p = np;
1149 if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ)
1150 break;
1151 p++;
1153 csiescseq.mode[0] = *p++;
1154 csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0';
1157 /* for absolute user moves, when decom is set */
1158 void
1159 tmoveato(int x, int y)
1161 tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0));
1164 void
1165 tmoveto(int x, int y)
1167 int miny, maxy;
1169 if (term.c.state & CURSOR_ORIGIN) {
1170 miny = term.top;
1171 maxy = term.bot;
1172 } else {
1173 miny = 0;
1174 maxy = term.row - 1;
1176 term.c.state &= ~CURSOR_WRAPNEXT;
1177 term.c.x = LIMIT(x, 0, term.col-1);
1178 term.c.y = LIMIT(y, miny, maxy);
1181 void
1182 tsetchar(Rune u, Glyph *attr, int x, int y)
1184 static char *vt100_0[62] = { /* 0x41 - 0x7e */
1185 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1186 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1187 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1188 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1189 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1190 "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1191 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1192 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1196 * The table is proudly stolen from rxvt.
1198 if (term.trantbl[term.charset] == CS_GRAPHIC0 &&
1199 BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
1200 utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ);
1202 if (term.line[y][x].mode & ATTR_WIDE) {
1203 if (x+1 < term.col) {
1204 term.line[y][x+1].u = ' ';
1205 term.line[y][x+1].mode &= ~ATTR_WDUMMY;
1207 } else if (term.line[y][x].mode & ATTR_WDUMMY) {
1208 term.line[y][x-1].u = ' ';
1209 term.line[y][x-1].mode &= ~ATTR_WIDE;
1212 term.dirty[y] = 1;
1213 term.line[y][x] = *attr;
1214 term.line[y][x].u = u;
1217 void
1218 tclearregion(int x1, int y1, int x2, int y2)
1220 int x, y, temp;
1221 Glyph *gp;
1223 if (x1 > x2)
1224 temp = x1, x1 = x2, x2 = temp;
1225 if (y1 > y2)
1226 temp = y1, y1 = y2, y2 = temp;
1228 LIMIT(x1, 0, term.col-1);
1229 LIMIT(x2, 0, term.col-1);
1230 LIMIT(y1, 0, term.row-1);
1231 LIMIT(y2, 0, term.row-1);
1233 for (y = y1; y <= y2; y++) {
1234 term.dirty[y] = 1;
1235 for (x = x1; x <= x2; x++) {
1236 gp = &term.line[y][x];
1237 if (selected(x, y))
1238 selclear();
1239 gp->fg = term.c.attr.fg;
1240 gp->bg = term.c.attr.bg;
1241 gp->mode = 0;
1242 gp->u = ' ';
1247 void
1248 tdeletechar(int n)
1250 int dst, src, size;
1251 Glyph *line;
1253 LIMIT(n, 0, term.col - term.c.x);
1255 dst = term.c.x;
1256 src = term.c.x + n;
1257 size = term.col - src;
1258 line = term.line[term.c.y];
1260 memmove(&line[dst], &line[src], size * sizeof(Glyph));
1261 tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
1264 void
1265 tinsertblank(int n)
1267 int dst, src, size;
1268 Glyph *line;
1270 LIMIT(n, 0, term.col - term.c.x);
1272 dst = term.c.x + n;
1273 src = term.c.x;
1274 size = term.col - dst;
1275 line = term.line[term.c.y];
1277 memmove(&line[dst], &line[src], size * sizeof(Glyph));
1278 tclearregion(src, term.c.y, dst - 1, term.c.y);
1281 void
1282 tinsertblankline(int n)
1284 if (BETWEEN(term.c.y, term.top, term.bot))
1285 tscrolldown(term.c.y, n);
1288 void
1289 tdeleteline(int n)
1291 if (BETWEEN(term.c.y, term.top, term.bot))
1292 tscrollup(term.c.y, n);
1295 int32_t
1296 tdefcolor(int *attr, int *npar, int l)
1298 int32_t idx = -1;
1299 uint r, g, b;
1301 switch (attr[*npar + 1]) {
1302 case 2: /* direct color in RGB space */
1303 if (*npar + 4 >= l) {
1304 fprintf(stderr,
1305 "erresc(38): Incorrect number of parameters (%d)\n",
1306 *npar);
1307 break;
1309 r = attr[*npar + 2];
1310 g = attr[*npar + 3];
1311 b = attr[*npar + 4];
1312 *npar += 4;
1313 if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255))
1314 fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n",
1315 r, g, b);
1316 else
1317 idx = TRUECOLOR(r, g, b);
1318 break;
1319 case 5: /* indexed color */
1320 if (*npar + 2 >= l) {
1321 fprintf(stderr,
1322 "erresc(38): Incorrect number of parameters (%d)\n",
1323 *npar);
1324 break;
1326 *npar += 2;
1327 if (!BETWEEN(attr[*npar], 0, 255))
1328 fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]);
1329 else
1330 idx = attr[*npar];
1331 break;
1332 case 0: /* implemented defined (only foreground) */
1333 case 1: /* transparent */
1334 case 3: /* direct color in CMY space */
1335 case 4: /* direct color in CMYK space */
1336 default:
1337 fprintf(stderr,
1338 "erresc(38): gfx attr %d unknown\n", attr[*npar]);
1339 break;
1342 return idx;
1345 void
1346 tsetattr(int *attr, int l)
1348 int i;
1349 int32_t idx;
1351 for (i = 0; i < l; i++) {
1352 switch (attr[i]) {
1353 case 0:
1354 term.c.attr.mode &= ~(
1355 ATTR_BOLD |
1356 ATTR_FAINT |
1357 ATTR_ITALIC |
1358 ATTR_UNDERLINE |
1359 ATTR_BLINK |
1360 ATTR_REVERSE |
1361 ATTR_INVISIBLE |
1362 ATTR_STRUCK );
1363 term.c.attr.fg = defaultfg;
1364 term.c.attr.bg = defaultbg;
1365 break;
1366 case 1:
1367 term.c.attr.mode |= ATTR_BOLD;
1368 break;
1369 case 2:
1370 term.c.attr.mode |= ATTR_FAINT;
1371 break;
1372 case 3:
1373 term.c.attr.mode |= ATTR_ITALIC;
1374 break;
1375 case 4:
1376 term.c.attr.mode |= ATTR_UNDERLINE;
1377 break;
1378 case 5: /* slow blink */
1379 /* FALLTHROUGH */
1380 case 6: /* rapid blink */
1381 term.c.attr.mode |= ATTR_BLINK;
1382 break;
1383 case 7:
1384 term.c.attr.mode |= ATTR_REVERSE;
1385 break;
1386 case 8:
1387 term.c.attr.mode |= ATTR_INVISIBLE;
1388 break;
1389 case 9:
1390 term.c.attr.mode |= ATTR_STRUCK;
1391 break;
1392 case 22:
1393 term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT);
1394 break;
1395 case 23:
1396 term.c.attr.mode &= ~ATTR_ITALIC;
1397 break;
1398 case 24:
1399 term.c.attr.mode &= ~ATTR_UNDERLINE;
1400 break;
1401 case 25:
1402 term.c.attr.mode &= ~ATTR_BLINK;
1403 break;
1404 case 27:
1405 term.c.attr.mode &= ~ATTR_REVERSE;
1406 break;
1407 case 28:
1408 term.c.attr.mode &= ~ATTR_INVISIBLE;
1409 break;
1410 case 29:
1411 term.c.attr.mode &= ~ATTR_STRUCK;
1412 break;
1413 case 38:
1414 if ((idx = tdefcolor(attr, &i, l)) >= 0)
1415 term.c.attr.fg = idx;
1416 break;
1417 case 39:
1418 term.c.attr.fg = defaultfg;
1419 break;
1420 case 48:
1421 if ((idx = tdefcolor(attr, &i, l)) >= 0)
1422 term.c.attr.bg = idx;
1423 break;
1424 case 49:
1425 term.c.attr.bg = defaultbg;
1426 break;
1427 default:
1428 if (BETWEEN(attr[i], 30, 37)) {
1429 term.c.attr.fg = attr[i] - 30;
1430 } else if (BETWEEN(attr[i], 40, 47)) {
1431 term.c.attr.bg = attr[i] - 40;
1432 } else if (BETWEEN(attr[i], 90, 97)) {
1433 term.c.attr.fg = attr[i] - 90 + 8;
1434 } else if (BETWEEN(attr[i], 100, 107)) {
1435 term.c.attr.bg = attr[i] - 100 + 8;
1436 } else {
1437 fprintf(stderr,
1438 "erresc(default): gfx attr %d unknown\n",
1439 attr[i]);
1440 csidump();
1442 break;
1447 void
1448 tsetscroll(int t, int b)
1450 int temp;
1452 LIMIT(t, 0, term.row-1);
1453 LIMIT(b, 0, term.row-1);
1454 if (t > b) {
1455 temp = t;
1456 t = b;
1457 b = temp;
1459 term.top = t;
1460 term.bot = b;
1463 void
1464 tsetmode(int priv, int set, int *args, int narg)
1466 int alt, *lim;
1468 for (lim = args + narg; args < lim; ++args) {
1469 if (priv) {
1470 switch (*args) {
1471 case 1: /* DECCKM -- Cursor key */
1472 xsetmode(set, MODE_APPCURSOR);
1473 break;
1474 case 5: /* DECSCNM -- Reverse video */
1475 xsetmode(set, MODE_REVERSE);
1476 break;
1477 case 6: /* DECOM -- Origin */
1478 MODBIT(term.c.state, set, CURSOR_ORIGIN);
1479 tmoveato(0, 0);
1480 break;
1481 case 7: /* DECAWM -- Auto wrap */
1482 MODBIT(term.mode, set, MODE_WRAP);
1483 break;
1484 case 0: /* Error (IGNORED) */
1485 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1486 case 3: /* DECCOLM -- Column (IGNORED) */
1487 case 4: /* DECSCLM -- Scroll (IGNORED) */
1488 case 8: /* DECARM -- Auto repeat (IGNORED) */
1489 case 18: /* DECPFF -- Printer feed (IGNORED) */
1490 case 19: /* DECPEX -- Printer extent (IGNORED) */
1491 case 42: /* DECNRCM -- National characters (IGNORED) */
1492 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1493 break;
1494 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1495 xsetmode(!set, MODE_HIDE);
1496 break;
1497 case 9: /* X10 mouse compatibility mode */
1498 xsetpointermotion(0);
1499 xsetmode(0, MODE_MOUSE);
1500 xsetmode(set, MODE_MOUSEX10);
1501 break;
1502 case 1000: /* 1000: report button press */
1503 xsetpointermotion(0);
1504 xsetmode(0, MODE_MOUSE);
1505 xsetmode(set, MODE_MOUSEBTN);
1506 break;
1507 case 1002: /* 1002: report motion on button press */
1508 xsetpointermotion(0);
1509 xsetmode(0, MODE_MOUSE);
1510 xsetmode(set, MODE_MOUSEMOTION);
1511 break;
1512 case 1003: /* 1003: enable all mouse motions */
1513 xsetpointermotion(set);
1514 xsetmode(0, MODE_MOUSE);
1515 xsetmode(set, MODE_MOUSEMANY);
1516 break;
1517 case 1004: /* 1004: send focus events to tty */
1518 xsetmode(set, MODE_FOCUS);
1519 break;
1520 case 1006: /* 1006: extended reporting mode */
1521 xsetmode(set, MODE_MOUSESGR);
1522 break;
1523 case 1034:
1524 xsetmode(set, MODE_8BIT);
1525 break;
1526 case 1049: /* swap screen & set/restore cursor as xterm */
1527 if (!allowaltscreen)
1528 break;
1529 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1530 /* FALLTHROUGH */
1531 case 47: /* swap screen */
1532 case 1047:
1533 if (!allowaltscreen)
1534 break;
1535 alt = IS_SET(MODE_ALTSCREEN);
1536 if (alt) {
1537 tclearregion(0, 0, term.col-1,
1538 term.row-1);
1540 if (set ^ alt) /* set is always 1 or 0 */
1541 tswapscreen();
1542 if (*args != 1049)
1543 break;
1544 /* FALLTHROUGH */
1545 case 1048:
1546 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1547 break;
1548 case 2004: /* 2004: bracketed paste mode */
1549 xsetmode(set, MODE_BRCKTPASTE);
1550 break;
1551 /* Not implemented mouse modes. See comments there. */
1552 case 1001: /* mouse highlight mode; can hang the
1553 terminal by design when implemented. */
1554 case 1005: /* UTF-8 mouse mode; will confuse
1555 applications not supporting UTF-8
1556 and luit. */
1557 case 1015: /* urxvt mangled mouse mode; incompatible
1558 and can be mistaken for other control
1559 codes. */
1560 break;
1561 default:
1562 fprintf(stderr,
1563 "erresc: unknown private set/reset mode %d\n",
1564 *args);
1565 break;
1567 } else {
1568 switch (*args) {
1569 case 0: /* Error (IGNORED) */
1570 break;
1571 case 2:
1572 xsetmode(set, MODE_KBDLOCK);
1573 break;
1574 case 4: /* IRM -- Insertion-replacement */
1575 MODBIT(term.mode, set, MODE_INSERT);
1576 break;
1577 case 12: /* SRM -- Send/Receive */
1578 MODBIT(term.mode, !set, MODE_ECHO);
1579 break;
1580 case 20: /* LNM -- Linefeed/new line */
1581 MODBIT(term.mode, set, MODE_CRLF);
1582 break;
1583 default:
1584 fprintf(stderr,
1585 "erresc: unknown set/reset mode %d\n",
1586 *args);
1587 break;
1593 void
1594 csihandle(void)
1596 char buf[40];
1597 int len;
1599 switch (csiescseq.mode[0]) {
1600 default:
1601 unknown:
1602 fprintf(stderr, "erresc: unknown csi ");
1603 csidump();
1604 /* die(""); */
1605 break;
1606 case '@': /* ICH -- Insert <n> blank char */
1607 DEFAULT(csiescseq.arg[0], 1);
1608 tinsertblank(csiescseq.arg[0]);
1609 break;
1610 case 'A': /* CUU -- Cursor <n> Up */
1611 DEFAULT(csiescseq.arg[0], 1);
1612 tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
1613 break;
1614 case 'B': /* CUD -- Cursor <n> Down */
1615 case 'e': /* VPR --Cursor <n> Down */
1616 DEFAULT(csiescseq.arg[0], 1);
1617 tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
1618 break;
1619 case 'i': /* MC -- Media Copy */
1620 switch (csiescseq.arg[0]) {
1621 case 0:
1622 tdump();
1623 break;
1624 case 1:
1625 tdumpline(term.c.y);
1626 break;
1627 case 2:
1628 tdumpsel();
1629 break;
1630 case 4:
1631 term.mode &= ~MODE_PRINT;
1632 break;
1633 case 5:
1634 term.mode |= MODE_PRINT;
1635 break;
1637 break;
1638 case 'c': /* DA -- Device Attributes */
1639 if (csiescseq.arg[0] == 0)
1640 ttywrite(vtiden, strlen(vtiden), 0);
1641 break;
1642 case 'C': /* CUF -- Cursor <n> Forward */
1643 case 'a': /* HPR -- Cursor <n> Forward */
1644 DEFAULT(csiescseq.arg[0], 1);
1645 tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
1646 break;
1647 case 'D': /* CUB -- Cursor <n> Backward */
1648 DEFAULT(csiescseq.arg[0], 1);
1649 tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
1650 break;
1651 case 'E': /* CNL -- Cursor <n> Down and first col */
1652 DEFAULT(csiescseq.arg[0], 1);
1653 tmoveto(0, term.c.y+csiescseq.arg[0]);
1654 break;
1655 case 'F': /* CPL -- Cursor <n> Up and first col */
1656 DEFAULT(csiescseq.arg[0], 1);
1657 tmoveto(0, term.c.y-csiescseq.arg[0]);
1658 break;
1659 case 'g': /* TBC -- Tabulation clear */
1660 switch (csiescseq.arg[0]) {
1661 case 0: /* clear current tab stop */
1662 term.tabs[term.c.x] = 0;
1663 break;
1664 case 3: /* clear all the tabs */
1665 memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1666 break;
1667 default:
1668 goto unknown;
1670 break;
1671 case 'G': /* CHA -- Move to <col> */
1672 case '`': /* HPA */
1673 DEFAULT(csiescseq.arg[0], 1);
1674 tmoveto(csiescseq.arg[0]-1, term.c.y);
1675 break;
1676 case 'H': /* CUP -- Move to <row> <col> */
1677 case 'f': /* HVP */
1678 DEFAULT(csiescseq.arg[0], 1);
1679 DEFAULT(csiescseq.arg[1], 1);
1680 tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
1681 break;
1682 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1683 DEFAULT(csiescseq.arg[0], 1);
1684 tputtab(csiescseq.arg[0]);
1685 break;
1686 case 'J': /* ED -- Clear screen */
1687 switch (csiescseq.arg[0]) {
1688 case 0: /* below */
1689 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
1690 if (term.c.y < term.row-1) {
1691 tclearregion(0, term.c.y+1, term.col-1,
1692 term.row-1);
1694 break;
1695 case 1: /* above */
1696 if (term.c.y > 1)
1697 tclearregion(0, 0, term.col-1, term.c.y-1);
1698 tclearregion(0, term.c.y, term.c.x, term.c.y);
1699 break;
1700 case 2: /* all */
1701 tclearregion(0, 0, term.col-1, term.row-1);
1702 break;
1703 default:
1704 goto unknown;
1706 break;
1707 case 'K': /* EL -- Clear line */
1708 switch (csiescseq.arg[0]) {
1709 case 0: /* right */
1710 tclearregion(term.c.x, term.c.y, term.col-1,
1711 term.c.y);
1712 break;
1713 case 1: /* left */
1714 tclearregion(0, term.c.y, term.c.x, term.c.y);
1715 break;
1716 case 2: /* all */
1717 tclearregion(0, term.c.y, term.col-1, term.c.y);
1718 break;
1720 break;
1721 case 'S': /* SU -- Scroll <n> line up */
1722 DEFAULT(csiescseq.arg[0], 1);
1723 tscrollup(term.top, csiescseq.arg[0]);
1724 break;
1725 case 'T': /* SD -- Scroll <n> line down */
1726 DEFAULT(csiescseq.arg[0], 1);
1727 tscrolldown(term.top, csiescseq.arg[0]);
1728 break;
1729 case 'L': /* IL -- Insert <n> blank lines */
1730 DEFAULT(csiescseq.arg[0], 1);
1731 tinsertblankline(csiescseq.arg[0]);
1732 break;
1733 case 'l': /* RM -- Reset Mode */
1734 tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
1735 break;
1736 case 'M': /* DL -- Delete <n> lines */
1737 DEFAULT(csiescseq.arg[0], 1);
1738 tdeleteline(csiescseq.arg[0]);
1739 break;
1740 case 'X': /* ECH -- Erase <n> char */
1741 DEFAULT(csiescseq.arg[0], 1);
1742 tclearregion(term.c.x, term.c.y,
1743 term.c.x + csiescseq.arg[0] - 1, term.c.y);
1744 break;
1745 case 'P': /* DCH -- Delete <n> char */
1746 DEFAULT(csiescseq.arg[0], 1);
1747 tdeletechar(csiescseq.arg[0]);
1748 break;
1749 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1750 DEFAULT(csiescseq.arg[0], 1);
1751 tputtab(-csiescseq.arg[0]);
1752 break;
1753 case 'd': /* VPA -- Move to <row> */
1754 DEFAULT(csiescseq.arg[0], 1);
1755 tmoveato(term.c.x, csiescseq.arg[0]-1);
1756 break;
1757 case 'h': /* SM -- Set terminal mode */
1758 tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
1759 break;
1760 case 'm': /* SGR -- Terminal attribute (color) */
1761 tsetattr(csiescseq.arg, csiescseq.narg);
1762 break;
1763 case 'n': /* DSR – Device Status Report (cursor position) */
1764 if (csiescseq.arg[0] == 6) {
1765 len = snprintf(buf, sizeof(buf),"\033[%i;%iR",
1766 term.c.y+1, term.c.x+1);
1767 ttywrite(buf, len, 0);
1769 break;
1770 case 'r': /* DECSTBM -- Set Scrolling Region */
1771 if (csiescseq.priv) {
1772 goto unknown;
1773 } else {
1774 DEFAULT(csiescseq.arg[0], 1);
1775 DEFAULT(csiescseq.arg[1], term.row);
1776 tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
1777 tmoveato(0, 0);
1779 break;
1780 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1781 tcursor(CURSOR_SAVE);
1782 break;
1783 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1784 tcursor(CURSOR_LOAD);
1785 break;
1786 case ' ':
1787 switch (csiescseq.mode[1]) {
1788 case 'q': /* DECSCUSR -- Set Cursor Style */
1789 if (xsetcursor(csiescseq.arg[0]))
1790 goto unknown;
1791 break;
1792 default:
1793 goto unknown;
1795 break;
1799 void
1800 csidump(void)
1802 int i;
1803 uint c;
1805 fprintf(stderr, "ESC[");
1806 for (i = 0; i < csiescseq.len; i++) {
1807 c = csiescseq.buf[i] & 0xff;
1808 if (isprint(c)) {
1809 putc(c, stderr);
1810 } else if (c == '\n') {
1811 fprintf(stderr, "(\\n)");
1812 } else if (c == '\r') {
1813 fprintf(stderr, "(\\r)");
1814 } else if (c == 0x1b) {
1815 fprintf(stderr, "(\\e)");
1816 } else {
1817 fprintf(stderr, "(%02x)", c);
1820 putc('\n', stderr);
1823 void
1824 csireset(void)
1826 memset(&csiescseq, 0, sizeof(csiescseq));
1829 void
1830 strhandle(void)
1832 char *p = NULL, *dec;
1833 int j, narg, par;
1835 term.esc &= ~(ESC_STR_END|ESC_STR);
1836 strparse();
1837 par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
1839 switch (strescseq.type) {
1840 case ']': /* OSC -- Operating System Command */
1841 switch (par) {
1842 case 0:
1843 case 1:
1844 case 2:
1845 if (narg > 1)
1846 xsettitle(strescseq.args[1]);
1847 return;
1848 case 52:
1849 if (narg > 2) {
1850 dec = base64dec(strescseq.args[2]);
1851 if (dec) {
1852 xsetsel(dec);
1853 xclipcopy();
1854 } else {
1855 fprintf(stderr, "erresc: invalid base64\n");
1858 return;
1859 case 4: /* color set */
1860 if (narg < 3)
1861 break;
1862 p = strescseq.args[2];
1863 /* FALLTHROUGH */
1864 case 104: /* color reset, here p = NULL */
1865 j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
1866 if (xsetcolorname(j, p)) {
1867 if (par == 104 && narg <= 1)
1868 return; /* color reset without parameter */
1869 fprintf(stderr, "erresc: invalid color j=%d, p=%s\n",
1870 j, p ? p : "(null)");
1871 } else {
1873 * TODO if defaultbg color is changed, borders
1874 * are dirty
1876 redraw();
1878 return;
1880 break;
1881 case 'k': /* old title set compatibility */
1882 xsettitle(strescseq.args[0]);
1883 return;
1884 case 'P': /* DCS -- Device Control String */
1885 term.mode |= ESC_DCS;
1886 case '_': /* APC -- Application Program Command */
1887 case '^': /* PM -- Privacy Message */
1888 return;
1891 fprintf(stderr, "erresc: unknown str ");
1892 strdump();
1895 void
1896 strparse(void)
1898 int c;
1899 char *p = strescseq.buf;
1901 strescseq.narg = 0;
1902 strescseq.buf[strescseq.len] = '\0';
1904 if (*p == '\0')
1905 return;
1907 while (strescseq.narg < STR_ARG_SIZ) {
1908 strescseq.args[strescseq.narg++] = p;
1909 while ((c = *p) != ';' && c != '\0')
1910 ++p;
1911 if (c == '\0')
1912 return;
1913 *p++ = '\0';
1917 void
1918 strdump(void)
1920 int i;
1921 uint c;
1923 fprintf(stderr, "ESC%c", strescseq.type);
1924 for (i = 0; i < strescseq.len; i++) {
1925 c = strescseq.buf[i] & 0xff;
1926 if (c == '\0') {
1927 putc('\n', stderr);
1928 return;
1929 } else if (isprint(c)) {
1930 putc(c, stderr);
1931 } else if (c == '\n') {
1932 fprintf(stderr, "(\\n)");
1933 } else if (c == '\r') {
1934 fprintf(stderr, "(\\r)");
1935 } else if (c == 0x1b) {
1936 fprintf(stderr, "(\\e)");
1937 } else {
1938 fprintf(stderr, "(%02x)", c);
1941 fprintf(stderr, "ESC\\\n");
1944 void
1945 strreset(void)
1947 memset(&strescseq, 0, sizeof(strescseq));
1950 void
1951 sendbreak(const Arg *arg)
1953 if (tcsendbreak(cmdfd, 0))
1954 perror("Error sending break");
1957 void
1958 tprinter(char *s, size_t len)
1960 if (iofd != -1 && xwrite(iofd, s, len) < 0) {
1961 perror("Error writing to output file");
1962 close(iofd);
1963 iofd = -1;
1967 void
1968 toggleprinter(const Arg *arg)
1970 term.mode ^= MODE_PRINT;
1973 void
1974 printscreen(const Arg *arg)
1976 tdump();
1979 void
1980 printsel(const Arg *arg)
1982 tdumpsel();
1985 void
1986 tdumpsel(void)
1988 char *ptr;
1990 if ((ptr = getsel())) {
1991 tprinter(ptr, strlen(ptr));
1992 free(ptr);
1996 void
1997 tdumpline(int n)
1999 char buf[UTF_SIZ];
2000 Glyph *bp, *end;
2002 bp = &term.line[n][0];
2003 end = &bp[MIN(tlinelen(n), term.col) - 1];
2004 if (bp != end || bp->u != ' ') {
2005 for ( ;bp <= end; ++bp)
2006 tprinter(buf, utf8encode(bp->u, buf));
2008 tprinter("\n", 1);
2011 void
2012 tdump(void)
2014 int i;
2016 for (i = 0; i < term.row; ++i)
2017 tdumpline(i);
2020 void
2021 tputtab(int n)
2023 uint x = term.c.x;
2025 if (n > 0) {
2026 while (x < term.col && n--)
2027 for (++x; x < term.col && !term.tabs[x]; ++x)
2028 /* nothing */ ;
2029 } else if (n < 0) {
2030 while (x > 0 && n++)
2031 for (--x; x > 0 && !term.tabs[x]; --x)
2032 /* nothing */ ;
2034 term.c.x = LIMIT(x, 0, term.col-1);
2037 void
2038 tdefutf8(char ascii)
2040 if (ascii == 'G')
2041 term.mode |= MODE_UTF8;
2042 else if (ascii == '@')
2043 term.mode &= ~MODE_UTF8;
2046 void
2047 tdeftran(char ascii)
2049 static char cs[] = "0B";
2050 static int vcs[] = {CS_GRAPHIC0, CS_USA};
2051 char *p;
2053 if ((p = strchr(cs, ascii)) == NULL) {
2054 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
2055 } else {
2056 term.trantbl[term.icharset] = vcs[p - cs];
2060 void
2061 tdectest(char c)
2063 int x, y;
2065 if (c == '8') { /* DEC screen alignment test. */
2066 for (x = 0; x < term.col; ++x) {
2067 for (y = 0; y < term.row; ++y)
2068 tsetchar('E', &term.c.attr, x, y);
2073 void
2074 tstrsequence(uchar c)
2076 strreset();
2078 switch (c) {
2079 case 0x90: /* DCS -- Device Control String */
2080 c = 'P';
2081 term.esc |= ESC_DCS;
2082 break;
2083 case 0x9f: /* APC -- Application Program Command */
2084 c = '_';
2085 break;
2086 case 0x9e: /* PM -- Privacy Message */
2087 c = '^';
2088 break;
2089 case 0x9d: /* OSC -- Operating System Command */
2090 c = ']';
2091 break;
2093 strescseq.type = c;
2094 term.esc |= ESC_STR;
2097 void
2098 tcontrolcode(uchar ascii)
2100 switch (ascii) {
2101 case '\t': /* HT */
2102 tputtab(1);
2103 return;
2104 case '\b': /* BS */
2105 tmoveto(term.c.x-1, term.c.y);
2106 return;
2107 case '\r': /* CR */
2108 tmoveto(0, term.c.y);
2109 return;
2110 case '\f': /* LF */
2111 case '\v': /* VT */
2112 case '\n': /* LF */
2113 /* go to first col if the mode is set */
2114 tnewline(IS_SET(MODE_CRLF));
2115 return;
2116 case '\a': /* BEL */
2117 if (term.esc & ESC_STR_END) {
2118 /* backwards compatibility to xterm */
2119 strhandle();
2120 } else {
2121 xbell();
2123 break;
2124 case '\033': /* ESC */
2125 csireset();
2126 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
2127 term.esc |= ESC_START;
2128 return;
2129 case '\016': /* SO (LS1 -- Locking shift 1) */
2130 case '\017': /* SI (LS0 -- Locking shift 0) */
2131 term.charset = 1 - (ascii - '\016');
2132 return;
2133 case '\032': /* SUB */
2134 tsetchar('?', &term.c.attr, term.c.x, term.c.y);
2135 case '\030': /* CAN */
2136 csireset();
2137 break;
2138 case '\005': /* ENQ (IGNORED) */
2139 case '\000': /* NUL (IGNORED) */
2140 case '\021': /* XON (IGNORED) */
2141 case '\023': /* XOFF (IGNORED) */
2142 case 0177: /* DEL (IGNORED) */
2143 return;
2144 case 0x80: /* TODO: PAD */
2145 case 0x81: /* TODO: HOP */
2146 case 0x82: /* TODO: BPH */
2147 case 0x83: /* TODO: NBH */
2148 case 0x84: /* TODO: IND */
2149 break;
2150 case 0x85: /* NEL -- Next line */
2151 tnewline(1); /* always go to first col */
2152 break;
2153 case 0x86: /* TODO: SSA */
2154 case 0x87: /* TODO: ESA */
2155 break;
2156 case 0x88: /* HTS -- Horizontal tab stop */
2157 term.tabs[term.c.x] = 1;
2158 break;
2159 case 0x89: /* TODO: HTJ */
2160 case 0x8a: /* TODO: VTS */
2161 case 0x8b: /* TODO: PLD */
2162 case 0x8c: /* TODO: PLU */
2163 case 0x8d: /* TODO: RI */
2164 case 0x8e: /* TODO: SS2 */
2165 case 0x8f: /* TODO: SS3 */
2166 case 0x91: /* TODO: PU1 */
2167 case 0x92: /* TODO: PU2 */
2168 case 0x93: /* TODO: STS */
2169 case 0x94: /* TODO: CCH */
2170 case 0x95: /* TODO: MW */
2171 case 0x96: /* TODO: SPA */
2172 case 0x97: /* TODO: EPA */
2173 case 0x98: /* TODO: SOS */
2174 case 0x99: /* TODO: SGCI */
2175 break;
2176 case 0x9a: /* DECID -- Identify Terminal */
2177 ttywrite(vtiden, strlen(vtiden), 0);
2178 break;
2179 case 0x9b: /* TODO: CSI */
2180 case 0x9c: /* TODO: ST */
2181 break;
2182 case 0x90: /* DCS -- Device Control String */
2183 case 0x9d: /* OSC -- Operating System Command */
2184 case 0x9e: /* PM -- Privacy Message */
2185 case 0x9f: /* APC -- Application Program Command */
2186 tstrsequence(ascii);
2187 return;
2189 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2190 term.esc &= ~(ESC_STR_END|ESC_STR);
2194 * returns 1 when the sequence is finished and it hasn't to read
2195 * more characters for this sequence, otherwise 0
2198 eschandle(uchar ascii)
2200 switch (ascii) {
2201 case '[':
2202 term.esc |= ESC_CSI;
2203 return 0;
2204 case '#':
2205 term.esc |= ESC_TEST;
2206 return 0;
2207 case '%':
2208 term.esc |= ESC_UTF8;
2209 return 0;
2210 case 'P': /* DCS -- Device Control String */
2211 case '_': /* APC -- Application Program Command */
2212 case '^': /* PM -- Privacy Message */
2213 case ']': /* OSC -- Operating System Command */
2214 case 'k': /* old title set compatibility */
2215 tstrsequence(ascii);
2216 return 0;
2217 case 'n': /* LS2 -- Locking shift 2 */
2218 case 'o': /* LS3 -- Locking shift 3 */
2219 term.charset = 2 + (ascii - 'n');
2220 break;
2221 case '(': /* GZD4 -- set primary charset G0 */
2222 case ')': /* G1D4 -- set secondary charset G1 */
2223 case '*': /* G2D4 -- set tertiary charset G2 */
2224 case '+': /* G3D4 -- set quaternary charset G3 */
2225 term.icharset = ascii - '(';
2226 term.esc |= ESC_ALTCHARSET;
2227 return 0;
2228 case 'D': /* IND -- Linefeed */
2229 if (term.c.y == term.bot) {
2230 tscrollup(term.top, 1);
2231 } else {
2232 tmoveto(term.c.x, term.c.y+1);
2234 break;
2235 case 'E': /* NEL -- Next line */
2236 tnewline(1); /* always go to first col */
2237 break;
2238 case 'H': /* HTS -- Horizontal tab stop */
2239 term.tabs[term.c.x] = 1;
2240 break;
2241 case 'M': /* RI -- Reverse index */
2242 if (term.c.y == term.top) {
2243 tscrolldown(term.top, 1);
2244 } else {
2245 tmoveto(term.c.x, term.c.y-1);
2247 break;
2248 case 'Z': /* DECID -- Identify Terminal */
2249 ttywrite(vtiden, strlen(vtiden), 0);
2250 break;
2251 case 'c': /* RIS -- Reset to initial state */
2252 treset();
2253 resettitle();
2254 xloadcols();
2255 break;
2256 case '=': /* DECPAM -- Application keypad */
2257 xsetmode(1, MODE_APPKEYPAD);
2258 break;
2259 case '>': /* DECPNM -- Normal keypad */
2260 xsetmode(0, MODE_APPKEYPAD);
2261 break;
2262 case '7': /* DECSC -- Save Cursor */
2263 tcursor(CURSOR_SAVE);
2264 break;
2265 case '8': /* DECRC -- Restore Cursor */
2266 tcursor(CURSOR_LOAD);
2267 break;
2268 case '\\': /* ST -- String Terminator */
2269 if (term.esc & ESC_STR_END)
2270 strhandle();
2271 break;
2272 default:
2273 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2274 (uchar) ascii, isprint(ascii)? ascii:'.');
2275 break;
2277 return 1;
2280 void
2281 tputc(Rune u)
2283 char c[UTF_SIZ];
2284 int control;
2285 int width, len;
2286 Glyph *gp;
2288 control = ISCONTROL(u);
2289 if (!IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
2290 c[0] = u;
2291 width = len = 1;
2292 } else {
2293 len = utf8encode(u, c);
2294 if (!control && (width = wcwidth(u)) == -1) {
2295 memcpy(c, "\357\277\275", 4); /* UTF_INVALID */
2296 width = 1;
2300 if (IS_SET(MODE_PRINT))
2301 tprinter(c, len);
2304 * STR sequence must be checked before anything else
2305 * because it uses all following characters until it
2306 * receives a ESC, a SUB, a ST or any other C1 control
2307 * character.
2309 if (term.esc & ESC_STR) {
2310 if (u == '\a' || u == 030 || u == 032 || u == 033 ||
2311 ISCONTROLC1(u)) {
2312 term.esc &= ~(ESC_START|ESC_STR|ESC_DCS);
2313 if (IS_SET(MODE_SIXEL)) {
2314 /* TODO: render sixel */;
2315 term.mode &= ~MODE_SIXEL;
2316 return;
2318 term.esc |= ESC_STR_END;
2319 goto check_control_code;
2322 if (IS_SET(MODE_SIXEL)) {
2323 /* TODO: implement sixel mode */
2324 return;
2326 if (term.esc&ESC_DCS && strescseq.len == 0 && u == 'q')
2327 term.mode |= MODE_SIXEL;
2329 if (strescseq.len+len >= sizeof(strescseq.buf)-1) {
2331 * Here is a bug in terminals. If the user never sends
2332 * some code to stop the str or esc command, then st
2333 * will stop responding. But this is better than
2334 * silently failing with unknown characters. At least
2335 * then users will report back.
2337 * In the case users ever get fixed, here is the code:
2340 * term.esc = 0;
2341 * strhandle();
2343 return;
2346 memmove(&strescseq.buf[strescseq.len], c, len);
2347 strescseq.len += len;
2348 return;
2351 check_control_code:
2353 * Actions of control codes must be performed as soon they arrive
2354 * because they can be embedded inside a control sequence, and
2355 * they must not cause conflicts with sequences.
2357 if (control) {
2358 tcontrolcode(u);
2360 * control codes are not shown ever
2362 return;
2363 } else if (term.esc & ESC_START) {
2364 if (term.esc & ESC_CSI) {
2365 csiescseq.buf[csiescseq.len++] = u;
2366 if (BETWEEN(u, 0x40, 0x7E)
2367 || csiescseq.len >= \
2368 sizeof(csiescseq.buf)-1) {
2369 term.esc = 0;
2370 csiparse();
2371 csihandle();
2373 return;
2374 } else if (term.esc & ESC_UTF8) {
2375 tdefutf8(u);
2376 } else if (term.esc & ESC_ALTCHARSET) {
2377 tdeftran(u);
2378 } else if (term.esc & ESC_TEST) {
2379 tdectest(u);
2380 } else {
2381 if (!eschandle(u))
2382 return;
2383 /* sequence already finished */
2385 term.esc = 0;
2387 * All characters which form part of a sequence are not
2388 * printed
2390 return;
2392 if (sel.ob.x != -1 && BETWEEN(term.c.y, sel.ob.y, sel.oe.y))
2393 selclear();
2395 gp = &term.line[term.c.y][term.c.x];
2396 if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
2397 gp->mode |= ATTR_WRAP;
2398 tnewline(1);
2399 gp = &term.line[term.c.y][term.c.x];
2402 if (IS_SET(MODE_INSERT) && term.c.x+width < term.col)
2403 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
2405 if (term.c.x+width > term.col) {
2406 tnewline(1);
2407 gp = &term.line[term.c.y][term.c.x];
2410 tsetchar(u, &term.c.attr, term.c.x, term.c.y);
2412 if (width == 2) {
2413 gp->mode |= ATTR_WIDE;
2414 if (term.c.x+1 < term.col) {
2415 gp[1].u = '\0';
2416 gp[1].mode = ATTR_WDUMMY;
2419 if (term.c.x+width < term.col) {
2420 tmoveto(term.c.x+width, term.c.y);
2421 } else {
2422 term.c.state |= CURSOR_WRAPNEXT;
2427 twrite(const char *buf, int buflen, int show_ctrl)
2429 int charsize;
2430 Rune u;
2431 int n;
2433 for (n = 0; n < buflen; n += charsize) {
2434 if (IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
2435 /* process a complete utf8 char */
2436 charsize = utf8decode(buf + n, &u, buflen - n);
2437 if (charsize == 0)
2438 break;
2439 } else {
2440 u = buf[n] & 0xFF;
2441 charsize = 1;
2443 if (show_ctrl && ISCONTROL(u)) {
2444 if (u & 0x80) {
2445 u &= 0x7f;
2446 tputc('^');
2447 tputc('[');
2448 } else if (u != '\n' && u != '\r' && u != '\t') {
2449 u ^= 0x40;
2450 tputc('^');
2453 tputc(u);
2455 return n;
2458 void
2459 tresize(int col, int row)
2461 int i;
2462 int minrow = MIN(row, term.row);
2463 int mincol = MIN(col, term.col);
2464 int *bp;
2465 TCursor c;
2467 if (col < 1 || row < 1) {
2468 fprintf(stderr,
2469 "tresize: error resizing to %dx%d\n", col, row);
2470 return;
2474 * slide screen to keep cursor where we expect it -
2475 * tscrollup would work here, but we can optimize to
2476 * memmove because we're freeing the earlier lines
2478 for (i = 0; i <= term.c.y - row; i++) {
2479 free(term.line[i]);
2480 free(term.alt[i]);
2482 /* ensure that both src and dst are not NULL */
2483 if (i > 0) {
2484 memmove(term.line, term.line + i, row * sizeof(Line));
2485 memmove(term.alt, term.alt + i, row * sizeof(Line));
2487 for (i += row; i < term.row; i++) {
2488 free(term.line[i]);
2489 free(term.alt[i]);
2492 /* resize to new height */
2493 term.line = xrealloc(term.line, row * sizeof(Line));
2494 term.alt = xrealloc(term.alt, row * sizeof(Line));
2495 term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
2496 term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
2498 /* resize each row to new width, zero-pad if needed */
2499 for (i = 0; i < minrow; i++) {
2500 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
2501 term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph));
2504 /* allocate any new rows */
2505 for (/* i = minrow */; i < row; i++) {
2506 term.line[i] = xmalloc(col * sizeof(Glyph));
2507 term.alt[i] = xmalloc(col * sizeof(Glyph));
2509 if (col > term.col) {
2510 bp = term.tabs + term.col;
2512 memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
2513 while (--bp > term.tabs && !*bp)
2514 /* nothing */ ;
2515 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
2516 *bp = 1;
2518 /* update terminal size */
2519 term.col = col;
2520 term.row = row;
2521 /* reset scrolling region */
2522 tsetscroll(0, row-1);
2523 /* make use of the LIMIT in tmoveto */
2524 tmoveto(term.c.x, term.c.y);
2525 /* Clearing both screens (it makes dirty all lines) */
2526 c = term.c;
2527 for (i = 0; i < 2; i++) {
2528 if (mincol < col && 0 < minrow) {
2529 tclearregion(mincol, 0, col - 1, minrow - 1);
2531 if (0 < col && minrow < row) {
2532 tclearregion(0, minrow, col - 1, row - 1);
2534 tswapscreen();
2535 tcursor(CURSOR_LOAD);
2537 term.c = c;
2540 void
2541 resettitle(void)
2543 xsettitle(NULL);
2546 void
2547 drawregion(int x1, int y1, int x2, int y2)
2549 int y;
2550 for (y = y1; y < y2; y++) {
2551 if (!term.dirty[y])
2552 continue;
2554 term.dirty[y] = 0;
2555 xdrawline(term.line[y], x1, y, x2);
2559 void
2560 draw(void)
2562 int cx = term.c.x;
2564 if (!xstartdraw())
2565 return;
2567 /* adjust cursor position */
2568 LIMIT(term.ocx, 0, term.col-1);
2569 LIMIT(term.ocy, 0, term.row-1);
2570 if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY)
2571 term.ocx--;
2572 if (term.line[term.c.y][cx].mode & ATTR_WDUMMY)
2573 cx--;
2575 drawregion(0, 0, term.col, term.row);
2576 xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
2577 term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
2578 term.ocx = cx, term.ocy = term.c.y;
2579 xfinishdraw();
2580 xximspot(term.ocx, term.ocy);
2583 void
2584 redraw(void)
2586 tfulldirt();
2587 draw();