Increase XmbLookupString buffer
[st.git] / st.c
blob3e4841079a90ce76e83a9cbe361b7a46e71ff9a2
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;
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 else if (utmp)
684 prog = utmp;
685 else
686 prog = sh;
687 DEFAULT(args, ((char *[]) {prog, NULL}));
689 unsetenv("COLUMNS");
690 unsetenv("LINES");
691 unsetenv("TERMCAP");
692 setenv("LOGNAME", pw->pw_name, 1);
693 setenv("USER", pw->pw_name, 1);
694 setenv("SHELL", sh, 1);
695 setenv("HOME", pw->pw_dir, 1);
696 setenv("TERM", termname, 1);
698 signal(SIGCHLD, SIG_DFL);
699 signal(SIGHUP, SIG_DFL);
700 signal(SIGINT, SIG_DFL);
701 signal(SIGQUIT, SIG_DFL);
702 signal(SIGTERM, SIG_DFL);
703 signal(SIGALRM, SIG_DFL);
705 execvp(prog, args);
706 _exit(1);
709 void
710 sigchld(int a)
712 int stat;
713 pid_t p;
715 if ((p = waitpid(pid, &stat, WNOHANG)) < 0)
716 die("waiting for pid %hd failed: %s\n", pid, strerror(errno));
718 if (pid != p)
719 return;
721 if (WIFEXITED(stat) && WEXITSTATUS(stat))
722 die("child exited with status %d\n", WEXITSTATUS(stat));
723 else if (WIFSIGNALED(stat))
724 die("child terminated due to signal %d\n", WTERMSIG(stat));
725 exit(0);
728 void
729 stty(char **args)
731 char cmd[_POSIX_ARG_MAX], **p, *q, *s;
732 size_t n, siz;
734 if ((n = strlen(stty_args)) > sizeof(cmd)-1)
735 die("incorrect stty parameters\n");
736 memcpy(cmd, stty_args, n);
737 q = cmd + n;
738 siz = sizeof(cmd) - n;
739 for (p = args; p && (s = *p); ++p) {
740 if ((n = strlen(s)) > siz-1)
741 die("stty parameter length too long\n");
742 *q++ = ' ';
743 memcpy(q, s, n);
744 q += n;
745 siz -= n + 1;
747 *q = '\0';
748 if (system(cmd) != 0)
749 perror("Couldn't call stty");
753 ttynew(char *line, char *cmd, char *out, char **args)
755 int m, s;
757 if (out) {
758 term.mode |= MODE_PRINT;
759 iofd = (!strcmp(out, "-")) ?
760 1 : open(out, O_WRONLY | O_CREAT, 0666);
761 if (iofd < 0) {
762 fprintf(stderr, "Error opening %s:%s\n",
763 out, strerror(errno));
767 if (line) {
768 if ((cmdfd = open(line, O_RDWR)) < 0)
769 die("open line '%s' failed: %s\n",
770 line, strerror(errno));
771 dup2(cmdfd, 0);
772 stty(args);
773 return cmdfd;
776 /* seems to work fine on linux, openbsd and freebsd */
777 if (openpty(&m, &s, NULL, NULL, NULL) < 0)
778 die("openpty failed: %s\n", strerror(errno));
780 switch (pid = fork()) {
781 case -1:
782 die("fork failed: %s\n", strerror(errno));
783 break;
784 case 0:
785 close(iofd);
786 setsid(); /* create a new process group */
787 dup2(s, 0);
788 dup2(s, 1);
789 dup2(s, 2);
790 if (ioctl(s, TIOCSCTTY, NULL) < 0)
791 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno));
792 close(s);
793 close(m);
794 #ifdef __OpenBSD__
795 if (pledge("stdio getpw proc exec", NULL) == -1)
796 die("pledge\n");
797 #endif
798 execsh(cmd, args);
799 break;
800 default:
801 #ifdef __OpenBSD__
802 if (pledge("stdio rpath tty proc", NULL) == -1)
803 die("pledge\n");
804 #endif
805 close(s);
806 cmdfd = m;
807 signal(SIGCHLD, sigchld);
808 break;
810 return cmdfd;
813 size_t
814 ttyread(void)
816 static char buf[BUFSIZ];
817 static int buflen = 0;
818 int written;
819 int ret;
821 /* append read bytes to unprocessed bytes */
822 if ((ret = read(cmdfd, buf+buflen, LEN(buf)-buflen)) < 0)
823 die("couldn't read from shell: %s\n", strerror(errno));
824 buflen += ret;
826 written = twrite(buf, buflen, 0);
827 buflen -= written;
828 /* keep any uncomplete utf8 char for the next call */
829 if (buflen > 0)
830 memmove(buf, buf + written, buflen);
832 return ret;
835 void
836 ttywrite(const char *s, size_t n, int may_echo)
838 const char *next;
840 if (may_echo && IS_SET(MODE_ECHO))
841 twrite(s, n, 1);
843 if (!IS_SET(MODE_CRLF)) {
844 ttywriteraw(s, n);
845 return;
848 /* This is similar to how the kernel handles ONLCR for ttys */
849 while (n > 0) {
850 if (*s == '\r') {
851 next = s + 1;
852 ttywriteraw("\r\n", 2);
853 } else {
854 next = memchr(s, '\r', n);
855 DEFAULT(next, s + n);
856 ttywriteraw(s, next - s);
858 n -= next - s;
859 s = next;
863 void
864 ttywriteraw(const char *s, size_t n)
866 fd_set wfd, rfd;
867 ssize_t r;
868 size_t lim = 256;
871 * Remember that we are using a pty, which might be a modem line.
872 * Writing too much will clog the line. That's why we are doing this
873 * dance.
874 * FIXME: Migrate the world to Plan 9.
876 while (n > 0) {
877 FD_ZERO(&wfd);
878 FD_ZERO(&rfd);
879 FD_SET(cmdfd, &wfd);
880 FD_SET(cmdfd, &rfd);
882 /* Check if we can write. */
883 if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) {
884 if (errno == EINTR)
885 continue;
886 die("select failed: %s\n", strerror(errno));
888 if (FD_ISSET(cmdfd, &wfd)) {
890 * Only write the bytes written by ttywrite() or the
891 * default of 256. This seems to be a reasonable value
892 * for a serial line. Bigger values might clog the I/O.
894 if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0)
895 goto write_error;
896 if (r < n) {
898 * We weren't able to write out everything.
899 * This means the buffer is getting full
900 * again. Empty it.
902 if (n < lim)
903 lim = ttyread();
904 n -= r;
905 s += r;
906 } else {
907 /* All bytes have been written. */
908 break;
911 if (FD_ISSET(cmdfd, &rfd))
912 lim = ttyread();
914 return;
916 write_error:
917 die("write error on tty: %s\n", strerror(errno));
920 void
921 ttyresize(int tw, int th)
923 struct winsize w;
925 w.ws_row = term.row;
926 w.ws_col = term.col;
927 w.ws_xpixel = tw;
928 w.ws_ypixel = th;
929 if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
930 fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
933 void
934 ttyhangup()
936 /* Send SIGHUP to shell */
937 kill(pid, SIGHUP);
941 tattrset(int attr)
943 int i, j;
945 for (i = 0; i < term.row-1; i++) {
946 for (j = 0; j < term.col-1; j++) {
947 if (term.line[i][j].mode & attr)
948 return 1;
952 return 0;
955 void
956 tsetdirt(int top, int bot)
958 int i;
960 LIMIT(top, 0, term.row-1);
961 LIMIT(bot, 0, term.row-1);
963 for (i = top; i <= bot; i++)
964 term.dirty[i] = 1;
967 void
968 tsetdirtattr(int attr)
970 int i, j;
972 for (i = 0; i < term.row-1; i++) {
973 for (j = 0; j < term.col-1; j++) {
974 if (term.line[i][j].mode & attr) {
975 tsetdirt(i, i);
976 break;
982 void
983 tfulldirt(void)
985 tsetdirt(0, term.row-1);
988 void
989 tcursor(int mode)
991 static TCursor c[2];
992 int alt = IS_SET(MODE_ALTSCREEN);
994 if (mode == CURSOR_SAVE) {
995 c[alt] = term.c;
996 } else if (mode == CURSOR_LOAD) {
997 term.c = c[alt];
998 tmoveto(c[alt].x, c[alt].y);
1002 void
1003 treset(void)
1005 uint i;
1007 term.c = (TCursor){{
1008 .mode = ATTR_NULL,
1009 .fg = defaultfg,
1010 .bg = defaultbg
1011 }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
1013 memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1014 for (i = tabspaces; i < term.col; i += tabspaces)
1015 term.tabs[i] = 1;
1016 term.top = 0;
1017 term.bot = term.row - 1;
1018 term.mode = MODE_WRAP|MODE_UTF8;
1019 memset(term.trantbl, CS_USA, sizeof(term.trantbl));
1020 term.charset = 0;
1022 for (i = 0; i < 2; i++) {
1023 tmoveto(0, 0);
1024 tcursor(CURSOR_SAVE);
1025 tclearregion(0, 0, term.col-1, term.row-1);
1026 tswapscreen();
1030 void
1031 tnew(int col, int row)
1033 term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
1034 tresize(col, row);
1035 treset();
1038 void
1039 tswapscreen(void)
1041 Line *tmp = term.line;
1043 term.line = term.alt;
1044 term.alt = tmp;
1045 term.mode ^= MODE_ALTSCREEN;
1046 tfulldirt();
1049 void
1050 tscrolldown(int orig, int n)
1052 int i;
1053 Line temp;
1055 LIMIT(n, 0, term.bot-orig+1);
1057 tsetdirt(orig, term.bot-n);
1058 tclearregion(0, term.bot-n+1, term.col-1, term.bot);
1060 for (i = term.bot; i >= orig+n; i--) {
1061 temp = term.line[i];
1062 term.line[i] = term.line[i-n];
1063 term.line[i-n] = temp;
1066 selscroll(orig, n);
1069 void
1070 tscrollup(int orig, int n)
1072 int i;
1073 Line temp;
1075 LIMIT(n, 0, term.bot-orig+1);
1077 tclearregion(0, orig, term.col-1, orig+n-1);
1078 tsetdirt(orig+n, term.bot);
1080 for (i = orig; i <= term.bot-n; i++) {
1081 temp = term.line[i];
1082 term.line[i] = term.line[i+n];
1083 term.line[i+n] = temp;
1086 selscroll(orig, -n);
1089 void
1090 selscroll(int orig, int n)
1092 if (sel.ob.x == -1)
1093 return;
1095 if (BETWEEN(sel.ob.y, orig, term.bot) || BETWEEN(sel.oe.y, orig, term.bot)) {
1096 if ((sel.ob.y += n) > term.bot || (sel.oe.y += n) < term.top) {
1097 selclear();
1098 return;
1100 if (sel.type == SEL_RECTANGULAR) {
1101 if (sel.ob.y < term.top)
1102 sel.ob.y = term.top;
1103 if (sel.oe.y > term.bot)
1104 sel.oe.y = term.bot;
1105 } else {
1106 if (sel.ob.y < term.top) {
1107 sel.ob.y = term.top;
1108 sel.ob.x = 0;
1110 if (sel.oe.y > term.bot) {
1111 sel.oe.y = term.bot;
1112 sel.oe.x = term.col;
1115 selnormalize();
1119 void
1120 tnewline(int first_col)
1122 int y = term.c.y;
1124 if (y == term.bot) {
1125 tscrollup(term.top, 1);
1126 } else {
1127 y++;
1129 tmoveto(first_col ? 0 : term.c.x, y);
1132 void
1133 csiparse(void)
1135 char *p = csiescseq.buf, *np;
1136 long int v;
1138 csiescseq.narg = 0;
1139 if (*p == '?') {
1140 csiescseq.priv = 1;
1141 p++;
1144 csiescseq.buf[csiescseq.len] = '\0';
1145 while (p < csiescseq.buf+csiescseq.len) {
1146 np = NULL;
1147 v = strtol(p, &np, 10);
1148 if (np == p)
1149 v = 0;
1150 if (v == LONG_MAX || v == LONG_MIN)
1151 v = -1;
1152 csiescseq.arg[csiescseq.narg++] = v;
1153 p = np;
1154 if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ)
1155 break;
1156 p++;
1158 csiescseq.mode[0] = *p++;
1159 csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0';
1162 /* for absolute user moves, when decom is set */
1163 void
1164 tmoveato(int x, int y)
1166 tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0));
1169 void
1170 tmoveto(int x, int y)
1172 int miny, maxy;
1174 if (term.c.state & CURSOR_ORIGIN) {
1175 miny = term.top;
1176 maxy = term.bot;
1177 } else {
1178 miny = 0;
1179 maxy = term.row - 1;
1181 term.c.state &= ~CURSOR_WRAPNEXT;
1182 term.c.x = LIMIT(x, 0, term.col-1);
1183 term.c.y = LIMIT(y, miny, maxy);
1186 void
1187 tsetchar(Rune u, Glyph *attr, int x, int y)
1189 static char *vt100_0[62] = { /* 0x41 - 0x7e */
1190 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1191 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1192 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1193 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1194 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1195 "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1196 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1197 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1201 * The table is proudly stolen from rxvt.
1203 if (term.trantbl[term.charset] == CS_GRAPHIC0 &&
1204 BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
1205 utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ);
1207 if (term.line[y][x].mode & ATTR_WIDE) {
1208 if (x+1 < term.col) {
1209 term.line[y][x+1].u = ' ';
1210 term.line[y][x+1].mode &= ~ATTR_WDUMMY;
1212 } else if (term.line[y][x].mode & ATTR_WDUMMY) {
1213 term.line[y][x-1].u = ' ';
1214 term.line[y][x-1].mode &= ~ATTR_WIDE;
1217 term.dirty[y] = 1;
1218 term.line[y][x] = *attr;
1219 term.line[y][x].u = u;
1222 void
1223 tclearregion(int x1, int y1, int x2, int y2)
1225 int x, y, temp;
1226 Glyph *gp;
1228 if (x1 > x2)
1229 temp = x1, x1 = x2, x2 = temp;
1230 if (y1 > y2)
1231 temp = y1, y1 = y2, y2 = temp;
1233 LIMIT(x1, 0, term.col-1);
1234 LIMIT(x2, 0, term.col-1);
1235 LIMIT(y1, 0, term.row-1);
1236 LIMIT(y2, 0, term.row-1);
1238 for (y = y1; y <= y2; y++) {
1239 term.dirty[y] = 1;
1240 for (x = x1; x <= x2; x++) {
1241 gp = &term.line[y][x];
1242 if (selected(x, y))
1243 selclear();
1244 gp->fg = term.c.attr.fg;
1245 gp->bg = term.c.attr.bg;
1246 gp->mode = 0;
1247 gp->u = ' ';
1252 void
1253 tdeletechar(int n)
1255 int dst, src, size;
1256 Glyph *line;
1258 LIMIT(n, 0, term.col - term.c.x);
1260 dst = term.c.x;
1261 src = term.c.x + n;
1262 size = term.col - src;
1263 line = term.line[term.c.y];
1265 memmove(&line[dst], &line[src], size * sizeof(Glyph));
1266 tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
1269 void
1270 tinsertblank(int n)
1272 int dst, src, size;
1273 Glyph *line;
1275 LIMIT(n, 0, term.col - term.c.x);
1277 dst = term.c.x + n;
1278 src = term.c.x;
1279 size = term.col - dst;
1280 line = term.line[term.c.y];
1282 memmove(&line[dst], &line[src], size * sizeof(Glyph));
1283 tclearregion(src, term.c.y, dst - 1, term.c.y);
1286 void
1287 tinsertblankline(int n)
1289 if (BETWEEN(term.c.y, term.top, term.bot))
1290 tscrolldown(term.c.y, n);
1293 void
1294 tdeleteline(int n)
1296 if (BETWEEN(term.c.y, term.top, term.bot))
1297 tscrollup(term.c.y, n);
1300 int32_t
1301 tdefcolor(int *attr, int *npar, int l)
1303 int32_t idx = -1;
1304 uint r, g, b;
1306 switch (attr[*npar + 1]) {
1307 case 2: /* direct color in RGB space */
1308 if (*npar + 4 >= l) {
1309 fprintf(stderr,
1310 "erresc(38): Incorrect number of parameters (%d)\n",
1311 *npar);
1312 break;
1314 r = attr[*npar + 2];
1315 g = attr[*npar + 3];
1316 b = attr[*npar + 4];
1317 *npar += 4;
1318 if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255))
1319 fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n",
1320 r, g, b);
1321 else
1322 idx = TRUECOLOR(r, g, b);
1323 break;
1324 case 5: /* indexed color */
1325 if (*npar + 2 >= l) {
1326 fprintf(stderr,
1327 "erresc(38): Incorrect number of parameters (%d)\n",
1328 *npar);
1329 break;
1331 *npar += 2;
1332 if (!BETWEEN(attr[*npar], 0, 255))
1333 fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]);
1334 else
1335 idx = attr[*npar];
1336 break;
1337 case 0: /* implemented defined (only foreground) */
1338 case 1: /* transparent */
1339 case 3: /* direct color in CMY space */
1340 case 4: /* direct color in CMYK space */
1341 default:
1342 fprintf(stderr,
1343 "erresc(38): gfx attr %d unknown\n", attr[*npar]);
1344 break;
1347 return idx;
1350 void
1351 tsetattr(int *attr, int l)
1353 int i;
1354 int32_t idx;
1356 for (i = 0; i < l; i++) {
1357 switch (attr[i]) {
1358 case 0:
1359 term.c.attr.mode &= ~(
1360 ATTR_BOLD |
1361 ATTR_FAINT |
1362 ATTR_ITALIC |
1363 ATTR_UNDERLINE |
1364 ATTR_BLINK |
1365 ATTR_REVERSE |
1366 ATTR_INVISIBLE |
1367 ATTR_STRUCK );
1368 term.c.attr.fg = defaultfg;
1369 term.c.attr.bg = defaultbg;
1370 break;
1371 case 1:
1372 term.c.attr.mode |= ATTR_BOLD;
1373 break;
1374 case 2:
1375 term.c.attr.mode |= ATTR_FAINT;
1376 break;
1377 case 3:
1378 term.c.attr.mode |= ATTR_ITALIC;
1379 break;
1380 case 4:
1381 term.c.attr.mode |= ATTR_UNDERLINE;
1382 break;
1383 case 5: /* slow blink */
1384 /* FALLTHROUGH */
1385 case 6: /* rapid blink */
1386 term.c.attr.mode |= ATTR_BLINK;
1387 break;
1388 case 7:
1389 term.c.attr.mode |= ATTR_REVERSE;
1390 break;
1391 case 8:
1392 term.c.attr.mode |= ATTR_INVISIBLE;
1393 break;
1394 case 9:
1395 term.c.attr.mode |= ATTR_STRUCK;
1396 break;
1397 case 22:
1398 term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT);
1399 break;
1400 case 23:
1401 term.c.attr.mode &= ~ATTR_ITALIC;
1402 break;
1403 case 24:
1404 term.c.attr.mode &= ~ATTR_UNDERLINE;
1405 break;
1406 case 25:
1407 term.c.attr.mode &= ~ATTR_BLINK;
1408 break;
1409 case 27:
1410 term.c.attr.mode &= ~ATTR_REVERSE;
1411 break;
1412 case 28:
1413 term.c.attr.mode &= ~ATTR_INVISIBLE;
1414 break;
1415 case 29:
1416 term.c.attr.mode &= ~ATTR_STRUCK;
1417 break;
1418 case 38:
1419 if ((idx = tdefcolor(attr, &i, l)) >= 0)
1420 term.c.attr.fg = idx;
1421 break;
1422 case 39:
1423 term.c.attr.fg = defaultfg;
1424 break;
1425 case 48:
1426 if ((idx = tdefcolor(attr, &i, l)) >= 0)
1427 term.c.attr.bg = idx;
1428 break;
1429 case 49:
1430 term.c.attr.bg = defaultbg;
1431 break;
1432 default:
1433 if (BETWEEN(attr[i], 30, 37)) {
1434 term.c.attr.fg = attr[i] - 30;
1435 } else if (BETWEEN(attr[i], 40, 47)) {
1436 term.c.attr.bg = attr[i] - 40;
1437 } else if (BETWEEN(attr[i], 90, 97)) {
1438 term.c.attr.fg = attr[i] - 90 + 8;
1439 } else if (BETWEEN(attr[i], 100, 107)) {
1440 term.c.attr.bg = attr[i] - 100 + 8;
1441 } else {
1442 fprintf(stderr,
1443 "erresc(default): gfx attr %d unknown\n",
1444 attr[i]);
1445 csidump();
1447 break;
1452 void
1453 tsetscroll(int t, int b)
1455 int temp;
1457 LIMIT(t, 0, term.row-1);
1458 LIMIT(b, 0, term.row-1);
1459 if (t > b) {
1460 temp = t;
1461 t = b;
1462 b = temp;
1464 term.top = t;
1465 term.bot = b;
1468 void
1469 tsetmode(int priv, int set, int *args, int narg)
1471 int alt, *lim;
1473 for (lim = args + narg; args < lim; ++args) {
1474 if (priv) {
1475 switch (*args) {
1476 case 1: /* DECCKM -- Cursor key */
1477 xsetmode(set, MODE_APPCURSOR);
1478 break;
1479 case 5: /* DECSCNM -- Reverse video */
1480 xsetmode(set, MODE_REVERSE);
1481 break;
1482 case 6: /* DECOM -- Origin */
1483 MODBIT(term.c.state, set, CURSOR_ORIGIN);
1484 tmoveato(0, 0);
1485 break;
1486 case 7: /* DECAWM -- Auto wrap */
1487 MODBIT(term.mode, set, MODE_WRAP);
1488 break;
1489 case 0: /* Error (IGNORED) */
1490 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1491 case 3: /* DECCOLM -- Column (IGNORED) */
1492 case 4: /* DECSCLM -- Scroll (IGNORED) */
1493 case 8: /* DECARM -- Auto repeat (IGNORED) */
1494 case 18: /* DECPFF -- Printer feed (IGNORED) */
1495 case 19: /* DECPEX -- Printer extent (IGNORED) */
1496 case 42: /* DECNRCM -- National characters (IGNORED) */
1497 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1498 break;
1499 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1500 xsetmode(!set, MODE_HIDE);
1501 break;
1502 case 9: /* X10 mouse compatibility mode */
1503 xsetpointermotion(0);
1504 xsetmode(0, MODE_MOUSE);
1505 xsetmode(set, MODE_MOUSEX10);
1506 break;
1507 case 1000: /* 1000: report button press */
1508 xsetpointermotion(0);
1509 xsetmode(0, MODE_MOUSE);
1510 xsetmode(set, MODE_MOUSEBTN);
1511 break;
1512 case 1002: /* 1002: report motion on button press */
1513 xsetpointermotion(0);
1514 xsetmode(0, MODE_MOUSE);
1515 xsetmode(set, MODE_MOUSEMOTION);
1516 break;
1517 case 1003: /* 1003: enable all mouse motions */
1518 xsetpointermotion(set);
1519 xsetmode(0, MODE_MOUSE);
1520 xsetmode(set, MODE_MOUSEMANY);
1521 break;
1522 case 1004: /* 1004: send focus events to tty */
1523 xsetmode(set, MODE_FOCUS);
1524 break;
1525 case 1006: /* 1006: extended reporting mode */
1526 xsetmode(set, MODE_MOUSESGR);
1527 break;
1528 case 1034:
1529 xsetmode(set, MODE_8BIT);
1530 break;
1531 case 1049: /* swap screen & set/restore cursor as xterm */
1532 if (!allowaltscreen)
1533 break;
1534 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1535 /* FALLTHROUGH */
1536 case 47: /* swap screen */
1537 case 1047:
1538 if (!allowaltscreen)
1539 break;
1540 alt = IS_SET(MODE_ALTSCREEN);
1541 if (alt) {
1542 tclearregion(0, 0, term.col-1,
1543 term.row-1);
1545 if (set ^ alt) /* set is always 1 or 0 */
1546 tswapscreen();
1547 if (*args != 1049)
1548 break;
1549 /* FALLTHROUGH */
1550 case 1048:
1551 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1552 break;
1553 case 2004: /* 2004: bracketed paste mode */
1554 xsetmode(set, MODE_BRCKTPASTE);
1555 break;
1556 /* Not implemented mouse modes. See comments there. */
1557 case 1001: /* mouse highlight mode; can hang the
1558 terminal by design when implemented. */
1559 case 1005: /* UTF-8 mouse mode; will confuse
1560 applications not supporting UTF-8
1561 and luit. */
1562 case 1015: /* urxvt mangled mouse mode; incompatible
1563 and can be mistaken for other control
1564 codes. */
1565 break;
1566 default:
1567 fprintf(stderr,
1568 "erresc: unknown private set/reset mode %d\n",
1569 *args);
1570 break;
1572 } else {
1573 switch (*args) {
1574 case 0: /* Error (IGNORED) */
1575 break;
1576 case 2:
1577 xsetmode(set, MODE_KBDLOCK);
1578 break;
1579 case 4: /* IRM -- Insertion-replacement */
1580 MODBIT(term.mode, set, MODE_INSERT);
1581 break;
1582 case 12: /* SRM -- Send/Receive */
1583 MODBIT(term.mode, !set, MODE_ECHO);
1584 break;
1585 case 20: /* LNM -- Linefeed/new line */
1586 MODBIT(term.mode, set, MODE_CRLF);
1587 break;
1588 default:
1589 fprintf(stderr,
1590 "erresc: unknown set/reset mode %d\n",
1591 *args);
1592 break;
1598 void
1599 csihandle(void)
1601 char buf[40];
1602 int len;
1604 switch (csiescseq.mode[0]) {
1605 default:
1606 unknown:
1607 fprintf(stderr, "erresc: unknown csi ");
1608 csidump();
1609 /* die(""); */
1610 break;
1611 case '@': /* ICH -- Insert <n> blank char */
1612 DEFAULT(csiescseq.arg[0], 1);
1613 tinsertblank(csiescseq.arg[0]);
1614 break;
1615 case 'A': /* CUU -- Cursor <n> Up */
1616 DEFAULT(csiescseq.arg[0], 1);
1617 tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
1618 break;
1619 case 'B': /* CUD -- Cursor <n> Down */
1620 case 'e': /* VPR --Cursor <n> Down */
1621 DEFAULT(csiescseq.arg[0], 1);
1622 tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
1623 break;
1624 case 'i': /* MC -- Media Copy */
1625 switch (csiescseq.arg[0]) {
1626 case 0:
1627 tdump();
1628 break;
1629 case 1:
1630 tdumpline(term.c.y);
1631 break;
1632 case 2:
1633 tdumpsel();
1634 break;
1635 case 4:
1636 term.mode &= ~MODE_PRINT;
1637 break;
1638 case 5:
1639 term.mode |= MODE_PRINT;
1640 break;
1642 break;
1643 case 'c': /* DA -- Device Attributes */
1644 if (csiescseq.arg[0] == 0)
1645 ttywrite(vtiden, strlen(vtiden), 0);
1646 break;
1647 case 'C': /* CUF -- Cursor <n> Forward */
1648 case 'a': /* HPR -- Cursor <n> Forward */
1649 DEFAULT(csiescseq.arg[0], 1);
1650 tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
1651 break;
1652 case 'D': /* CUB -- Cursor <n> Backward */
1653 DEFAULT(csiescseq.arg[0], 1);
1654 tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
1655 break;
1656 case 'E': /* CNL -- Cursor <n> Down and first col */
1657 DEFAULT(csiescseq.arg[0], 1);
1658 tmoveto(0, term.c.y+csiescseq.arg[0]);
1659 break;
1660 case 'F': /* CPL -- Cursor <n> Up and first col */
1661 DEFAULT(csiescseq.arg[0], 1);
1662 tmoveto(0, term.c.y-csiescseq.arg[0]);
1663 break;
1664 case 'g': /* TBC -- Tabulation clear */
1665 switch (csiescseq.arg[0]) {
1666 case 0: /* clear current tab stop */
1667 term.tabs[term.c.x] = 0;
1668 break;
1669 case 3: /* clear all the tabs */
1670 memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1671 break;
1672 default:
1673 goto unknown;
1675 break;
1676 case 'G': /* CHA -- Move to <col> */
1677 case '`': /* HPA */
1678 DEFAULT(csiescseq.arg[0], 1);
1679 tmoveto(csiescseq.arg[0]-1, term.c.y);
1680 break;
1681 case 'H': /* CUP -- Move to <row> <col> */
1682 case 'f': /* HVP */
1683 DEFAULT(csiescseq.arg[0], 1);
1684 DEFAULT(csiescseq.arg[1], 1);
1685 tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
1686 break;
1687 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1688 DEFAULT(csiescseq.arg[0], 1);
1689 tputtab(csiescseq.arg[0]);
1690 break;
1691 case 'J': /* ED -- Clear screen */
1692 switch (csiescseq.arg[0]) {
1693 case 0: /* below */
1694 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
1695 if (term.c.y < term.row-1) {
1696 tclearregion(0, term.c.y+1, term.col-1,
1697 term.row-1);
1699 break;
1700 case 1: /* above */
1701 if (term.c.y > 1)
1702 tclearregion(0, 0, term.col-1, term.c.y-1);
1703 tclearregion(0, term.c.y, term.c.x, term.c.y);
1704 break;
1705 case 2: /* all */
1706 tclearregion(0, 0, term.col-1, term.row-1);
1707 break;
1708 default:
1709 goto unknown;
1711 break;
1712 case 'K': /* EL -- Clear line */
1713 switch (csiescseq.arg[0]) {
1714 case 0: /* right */
1715 tclearregion(term.c.x, term.c.y, term.col-1,
1716 term.c.y);
1717 break;
1718 case 1: /* left */
1719 tclearregion(0, term.c.y, term.c.x, term.c.y);
1720 break;
1721 case 2: /* all */
1722 tclearregion(0, term.c.y, term.col-1, term.c.y);
1723 break;
1725 break;
1726 case 'S': /* SU -- Scroll <n> line up */
1727 DEFAULT(csiescseq.arg[0], 1);
1728 tscrollup(term.top, csiescseq.arg[0]);
1729 break;
1730 case 'T': /* SD -- Scroll <n> line down */
1731 DEFAULT(csiescseq.arg[0], 1);
1732 tscrolldown(term.top, csiescseq.arg[0]);
1733 break;
1734 case 'L': /* IL -- Insert <n> blank lines */
1735 DEFAULT(csiescseq.arg[0], 1);
1736 tinsertblankline(csiescseq.arg[0]);
1737 break;
1738 case 'l': /* RM -- Reset Mode */
1739 tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
1740 break;
1741 case 'M': /* DL -- Delete <n> lines */
1742 DEFAULT(csiescseq.arg[0], 1);
1743 tdeleteline(csiescseq.arg[0]);
1744 break;
1745 case 'X': /* ECH -- Erase <n> char */
1746 DEFAULT(csiescseq.arg[0], 1);
1747 tclearregion(term.c.x, term.c.y,
1748 term.c.x + csiescseq.arg[0] - 1, term.c.y);
1749 break;
1750 case 'P': /* DCH -- Delete <n> char */
1751 DEFAULT(csiescseq.arg[0], 1);
1752 tdeletechar(csiescseq.arg[0]);
1753 break;
1754 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1755 DEFAULT(csiescseq.arg[0], 1);
1756 tputtab(-csiescseq.arg[0]);
1757 break;
1758 case 'd': /* VPA -- Move to <row> */
1759 DEFAULT(csiescseq.arg[0], 1);
1760 tmoveato(term.c.x, csiescseq.arg[0]-1);
1761 break;
1762 case 'h': /* SM -- Set terminal mode */
1763 tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
1764 break;
1765 case 'm': /* SGR -- Terminal attribute (color) */
1766 tsetattr(csiescseq.arg, csiescseq.narg);
1767 break;
1768 case 'n': /* DSR – Device Status Report (cursor position) */
1769 if (csiescseq.arg[0] == 6) {
1770 len = snprintf(buf, sizeof(buf),"\033[%i;%iR",
1771 term.c.y+1, term.c.x+1);
1772 ttywrite(buf, len, 0);
1774 break;
1775 case 'r': /* DECSTBM -- Set Scrolling Region */
1776 if (csiescseq.priv) {
1777 goto unknown;
1778 } else {
1779 DEFAULT(csiescseq.arg[0], 1);
1780 DEFAULT(csiescseq.arg[1], term.row);
1781 tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
1782 tmoveato(0, 0);
1784 break;
1785 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1786 tcursor(CURSOR_SAVE);
1787 break;
1788 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1789 tcursor(CURSOR_LOAD);
1790 break;
1791 case ' ':
1792 switch (csiescseq.mode[1]) {
1793 case 'q': /* DECSCUSR -- Set Cursor Style */
1794 if (xsetcursor(csiescseq.arg[0]))
1795 goto unknown;
1796 break;
1797 default:
1798 goto unknown;
1800 break;
1804 void
1805 csidump(void)
1807 size_t i;
1808 uint c;
1810 fprintf(stderr, "ESC[");
1811 for (i = 0; i < csiescseq.len; i++) {
1812 c = csiescseq.buf[i] & 0xff;
1813 if (isprint(c)) {
1814 putc(c, stderr);
1815 } else if (c == '\n') {
1816 fprintf(stderr, "(\\n)");
1817 } else if (c == '\r') {
1818 fprintf(stderr, "(\\r)");
1819 } else if (c == 0x1b) {
1820 fprintf(stderr, "(\\e)");
1821 } else {
1822 fprintf(stderr, "(%02x)", c);
1825 putc('\n', stderr);
1828 void
1829 csireset(void)
1831 memset(&csiescseq, 0, sizeof(csiescseq));
1834 void
1835 strhandle(void)
1837 char *p = NULL, *dec;
1838 int j, narg, par;
1840 term.esc &= ~(ESC_STR_END|ESC_STR);
1841 strparse();
1842 par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
1844 switch (strescseq.type) {
1845 case ']': /* OSC -- Operating System Command */
1846 switch (par) {
1847 case 0:
1848 case 1:
1849 case 2:
1850 if (narg > 1)
1851 xsettitle(strescseq.args[1]);
1852 return;
1853 case 52:
1854 if (narg > 2) {
1855 dec = base64dec(strescseq.args[2]);
1856 if (dec) {
1857 xsetsel(dec);
1858 xclipcopy();
1859 } else {
1860 fprintf(stderr, "erresc: invalid base64\n");
1863 return;
1864 case 4: /* color set */
1865 if (narg < 3)
1866 break;
1867 p = strescseq.args[2];
1868 /* FALLTHROUGH */
1869 case 104: /* color reset, here p = NULL */
1870 j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
1871 if (xsetcolorname(j, p)) {
1872 if (par == 104 && narg <= 1)
1873 return; /* color reset without parameter */
1874 fprintf(stderr, "erresc: invalid color j=%d, p=%s\n",
1875 j, p ? p : "(null)");
1876 } else {
1878 * TODO if defaultbg color is changed, borders
1879 * are dirty
1881 redraw();
1883 return;
1885 break;
1886 case 'k': /* old title set compatibility */
1887 xsettitle(strescseq.args[0]);
1888 return;
1889 case 'P': /* DCS -- Device Control String */
1890 term.mode |= ESC_DCS;
1891 case '_': /* APC -- Application Program Command */
1892 case '^': /* PM -- Privacy Message */
1893 return;
1896 fprintf(stderr, "erresc: unknown str ");
1897 strdump();
1900 void
1901 strparse(void)
1903 int c;
1904 char *p = strescseq.buf;
1906 strescseq.narg = 0;
1907 strescseq.buf[strescseq.len] = '\0';
1909 if (*p == '\0')
1910 return;
1912 while (strescseq.narg < STR_ARG_SIZ) {
1913 strescseq.args[strescseq.narg++] = p;
1914 while ((c = *p) != ';' && c != '\0')
1915 ++p;
1916 if (c == '\0')
1917 return;
1918 *p++ = '\0';
1922 void
1923 strdump(void)
1925 size_t i;
1926 uint c;
1928 fprintf(stderr, "ESC%c", strescseq.type);
1929 for (i = 0; i < strescseq.len; i++) {
1930 c = strescseq.buf[i] & 0xff;
1931 if (c == '\0') {
1932 putc('\n', stderr);
1933 return;
1934 } else if (isprint(c)) {
1935 putc(c, stderr);
1936 } else if (c == '\n') {
1937 fprintf(stderr, "(\\n)");
1938 } else if (c == '\r') {
1939 fprintf(stderr, "(\\r)");
1940 } else if (c == 0x1b) {
1941 fprintf(stderr, "(\\e)");
1942 } else {
1943 fprintf(stderr, "(%02x)", c);
1946 fprintf(stderr, "ESC\\\n");
1949 void
1950 strreset(void)
1952 strescseq = (STREscape){
1953 .buf = xrealloc(strescseq.buf, STR_BUF_SIZ),
1954 .siz = STR_BUF_SIZ,
1958 void
1959 sendbreak(const Arg *arg)
1961 if (tcsendbreak(cmdfd, 0))
1962 perror("Error sending break");
1965 void
1966 tprinter(char *s, size_t len)
1968 if (iofd != -1 && xwrite(iofd, s, len) < 0) {
1969 perror("Error writing to output file");
1970 close(iofd);
1971 iofd = -1;
1975 void
1976 toggleprinter(const Arg *arg)
1978 term.mode ^= MODE_PRINT;
1981 void
1982 printscreen(const Arg *arg)
1984 tdump();
1987 void
1988 printsel(const Arg *arg)
1990 tdumpsel();
1993 void
1994 tdumpsel(void)
1996 char *ptr;
1998 if ((ptr = getsel())) {
1999 tprinter(ptr, strlen(ptr));
2000 free(ptr);
2004 void
2005 tdumpline(int n)
2007 char buf[UTF_SIZ];
2008 Glyph *bp, *end;
2010 bp = &term.line[n][0];
2011 end = &bp[MIN(tlinelen(n), term.col) - 1];
2012 if (bp != end || bp->u != ' ') {
2013 for ( ;bp <= end; ++bp)
2014 tprinter(buf, utf8encode(bp->u, buf));
2016 tprinter("\n", 1);
2019 void
2020 tdump(void)
2022 int i;
2024 for (i = 0; i < term.row; ++i)
2025 tdumpline(i);
2028 void
2029 tputtab(int n)
2031 uint x = term.c.x;
2033 if (n > 0) {
2034 while (x < term.col && n--)
2035 for (++x; x < term.col && !term.tabs[x]; ++x)
2036 /* nothing */ ;
2037 } else if (n < 0) {
2038 while (x > 0 && n++)
2039 for (--x; x > 0 && !term.tabs[x]; --x)
2040 /* nothing */ ;
2042 term.c.x = LIMIT(x, 0, term.col-1);
2045 void
2046 tdefutf8(char ascii)
2048 if (ascii == 'G')
2049 term.mode |= MODE_UTF8;
2050 else if (ascii == '@')
2051 term.mode &= ~MODE_UTF8;
2054 void
2055 tdeftran(char ascii)
2057 static char cs[] = "0B";
2058 static int vcs[] = {CS_GRAPHIC0, CS_USA};
2059 char *p;
2061 if ((p = strchr(cs, ascii)) == NULL) {
2062 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
2063 } else {
2064 term.trantbl[term.icharset] = vcs[p - cs];
2068 void
2069 tdectest(char c)
2071 int x, y;
2073 if (c == '8') { /* DEC screen alignment test. */
2074 for (x = 0; x < term.col; ++x) {
2075 for (y = 0; y < term.row; ++y)
2076 tsetchar('E', &term.c.attr, x, y);
2081 void
2082 tstrsequence(uchar c)
2084 strreset();
2086 switch (c) {
2087 case 0x90: /* DCS -- Device Control String */
2088 c = 'P';
2089 term.esc |= ESC_DCS;
2090 break;
2091 case 0x9f: /* APC -- Application Program Command */
2092 c = '_';
2093 break;
2094 case 0x9e: /* PM -- Privacy Message */
2095 c = '^';
2096 break;
2097 case 0x9d: /* OSC -- Operating System Command */
2098 c = ']';
2099 break;
2101 strescseq.type = c;
2102 term.esc |= ESC_STR;
2105 void
2106 tcontrolcode(uchar ascii)
2108 switch (ascii) {
2109 case '\t': /* HT */
2110 tputtab(1);
2111 return;
2112 case '\b': /* BS */
2113 tmoveto(term.c.x-1, term.c.y);
2114 return;
2115 case '\r': /* CR */
2116 tmoveto(0, term.c.y);
2117 return;
2118 case '\f': /* LF */
2119 case '\v': /* VT */
2120 case '\n': /* LF */
2121 /* go to first col if the mode is set */
2122 tnewline(IS_SET(MODE_CRLF));
2123 return;
2124 case '\a': /* BEL */
2125 if (term.esc & ESC_STR_END) {
2126 /* backwards compatibility to xterm */
2127 strhandle();
2128 } else {
2129 xbell();
2131 break;
2132 case '\033': /* ESC */
2133 csireset();
2134 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
2135 term.esc |= ESC_START;
2136 return;
2137 case '\016': /* SO (LS1 -- Locking shift 1) */
2138 case '\017': /* SI (LS0 -- Locking shift 0) */
2139 term.charset = 1 - (ascii - '\016');
2140 return;
2141 case '\032': /* SUB */
2142 tsetchar('?', &term.c.attr, term.c.x, term.c.y);
2143 case '\030': /* CAN */
2144 csireset();
2145 break;
2146 case '\005': /* ENQ (IGNORED) */
2147 case '\000': /* NUL (IGNORED) */
2148 case '\021': /* XON (IGNORED) */
2149 case '\023': /* XOFF (IGNORED) */
2150 case 0177: /* DEL (IGNORED) */
2151 return;
2152 case 0x80: /* TODO: PAD */
2153 case 0x81: /* TODO: HOP */
2154 case 0x82: /* TODO: BPH */
2155 case 0x83: /* TODO: NBH */
2156 case 0x84: /* TODO: IND */
2157 break;
2158 case 0x85: /* NEL -- Next line */
2159 tnewline(1); /* always go to first col */
2160 break;
2161 case 0x86: /* TODO: SSA */
2162 case 0x87: /* TODO: ESA */
2163 break;
2164 case 0x88: /* HTS -- Horizontal tab stop */
2165 term.tabs[term.c.x] = 1;
2166 break;
2167 case 0x89: /* TODO: HTJ */
2168 case 0x8a: /* TODO: VTS */
2169 case 0x8b: /* TODO: PLD */
2170 case 0x8c: /* TODO: PLU */
2171 case 0x8d: /* TODO: RI */
2172 case 0x8e: /* TODO: SS2 */
2173 case 0x8f: /* TODO: SS3 */
2174 case 0x91: /* TODO: PU1 */
2175 case 0x92: /* TODO: PU2 */
2176 case 0x93: /* TODO: STS */
2177 case 0x94: /* TODO: CCH */
2178 case 0x95: /* TODO: MW */
2179 case 0x96: /* TODO: SPA */
2180 case 0x97: /* TODO: EPA */
2181 case 0x98: /* TODO: SOS */
2182 case 0x99: /* TODO: SGCI */
2183 break;
2184 case 0x9a: /* DECID -- Identify Terminal */
2185 ttywrite(vtiden, strlen(vtiden), 0);
2186 break;
2187 case 0x9b: /* TODO: CSI */
2188 case 0x9c: /* TODO: ST */
2189 break;
2190 case 0x90: /* DCS -- Device Control String */
2191 case 0x9d: /* OSC -- Operating System Command */
2192 case 0x9e: /* PM -- Privacy Message */
2193 case 0x9f: /* APC -- Application Program Command */
2194 tstrsequence(ascii);
2195 return;
2197 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2198 term.esc &= ~(ESC_STR_END|ESC_STR);
2202 * returns 1 when the sequence is finished and it hasn't to read
2203 * more characters for this sequence, otherwise 0
2206 eschandle(uchar ascii)
2208 switch (ascii) {
2209 case '[':
2210 term.esc |= ESC_CSI;
2211 return 0;
2212 case '#':
2213 term.esc |= ESC_TEST;
2214 return 0;
2215 case '%':
2216 term.esc |= ESC_UTF8;
2217 return 0;
2218 case 'P': /* DCS -- Device Control String */
2219 case '_': /* APC -- Application Program Command */
2220 case '^': /* PM -- Privacy Message */
2221 case ']': /* OSC -- Operating System Command */
2222 case 'k': /* old title set compatibility */
2223 tstrsequence(ascii);
2224 return 0;
2225 case 'n': /* LS2 -- Locking shift 2 */
2226 case 'o': /* LS3 -- Locking shift 3 */
2227 term.charset = 2 + (ascii - 'n');
2228 break;
2229 case '(': /* GZD4 -- set primary charset G0 */
2230 case ')': /* G1D4 -- set secondary charset G1 */
2231 case '*': /* G2D4 -- set tertiary charset G2 */
2232 case '+': /* G3D4 -- set quaternary charset G3 */
2233 term.icharset = ascii - '(';
2234 term.esc |= ESC_ALTCHARSET;
2235 return 0;
2236 case 'D': /* IND -- Linefeed */
2237 if (term.c.y == term.bot) {
2238 tscrollup(term.top, 1);
2239 } else {
2240 tmoveto(term.c.x, term.c.y+1);
2242 break;
2243 case 'E': /* NEL -- Next line */
2244 tnewline(1); /* always go to first col */
2245 break;
2246 case 'H': /* HTS -- Horizontal tab stop */
2247 term.tabs[term.c.x] = 1;
2248 break;
2249 case 'M': /* RI -- Reverse index */
2250 if (term.c.y == term.top) {
2251 tscrolldown(term.top, 1);
2252 } else {
2253 tmoveto(term.c.x, term.c.y-1);
2255 break;
2256 case 'Z': /* DECID -- Identify Terminal */
2257 ttywrite(vtiden, strlen(vtiden), 0);
2258 break;
2259 case 'c': /* RIS -- Reset to initial state */
2260 treset();
2261 resettitle();
2262 xloadcols();
2263 break;
2264 case '=': /* DECPAM -- Application keypad */
2265 xsetmode(1, MODE_APPKEYPAD);
2266 break;
2267 case '>': /* DECPNM -- Normal keypad */
2268 xsetmode(0, MODE_APPKEYPAD);
2269 break;
2270 case '7': /* DECSC -- Save Cursor */
2271 tcursor(CURSOR_SAVE);
2272 break;
2273 case '8': /* DECRC -- Restore Cursor */
2274 tcursor(CURSOR_LOAD);
2275 break;
2276 case '\\': /* ST -- String Terminator */
2277 if (term.esc & ESC_STR_END)
2278 strhandle();
2279 break;
2280 default:
2281 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2282 (uchar) ascii, isprint(ascii)? ascii:'.');
2283 break;
2285 return 1;
2288 void
2289 tputc(Rune u)
2291 char c[UTF_SIZ];
2292 int control;
2293 int width, len;
2294 Glyph *gp;
2296 control = ISCONTROL(u);
2297 if (!IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
2298 c[0] = u;
2299 width = len = 1;
2300 } else {
2301 len = utf8encode(u, c);
2302 if (!control && (width = wcwidth(u)) == -1) {
2303 memcpy(c, "\357\277\275", 4); /* UTF_INVALID */
2304 width = 1;
2308 if (IS_SET(MODE_PRINT))
2309 tprinter(c, len);
2312 * STR sequence must be checked before anything else
2313 * because it uses all following characters until it
2314 * receives a ESC, a SUB, a ST or any other C1 control
2315 * character.
2317 if (term.esc & ESC_STR) {
2318 if (u == '\a' || u == 030 || u == 032 || u == 033 ||
2319 ISCONTROLC1(u)) {
2320 term.esc &= ~(ESC_START|ESC_STR|ESC_DCS);
2321 if (IS_SET(MODE_SIXEL)) {
2322 /* TODO: render sixel */;
2323 term.mode &= ~MODE_SIXEL;
2324 return;
2326 term.esc |= ESC_STR_END;
2327 goto check_control_code;
2330 if (IS_SET(MODE_SIXEL)) {
2331 /* TODO: implement sixel mode */
2332 return;
2334 if (term.esc&ESC_DCS && strescseq.len == 0 && u == 'q')
2335 term.mode |= MODE_SIXEL;
2337 if (strescseq.len+len >= strescseq.siz) {
2339 * Here is a bug in terminals. If the user never sends
2340 * some code to stop the str or esc command, then st
2341 * will stop responding. But this is better than
2342 * silently failing with unknown characters. At least
2343 * then users will report back.
2345 * In the case users ever get fixed, here is the code:
2348 * term.esc = 0;
2349 * strhandle();
2351 if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2)
2352 return;
2353 strescseq.siz *= 2;
2354 strescseq.buf = xrealloc(strescseq.buf, strescseq.siz);
2357 memmove(&strescseq.buf[strescseq.len], c, len);
2358 strescseq.len += len;
2359 return;
2362 check_control_code:
2364 * Actions of control codes must be performed as soon they arrive
2365 * because they can be embedded inside a control sequence, and
2366 * they must not cause conflicts with sequences.
2368 if (control) {
2369 tcontrolcode(u);
2371 * control codes are not shown ever
2373 return;
2374 } else if (term.esc & ESC_START) {
2375 if (term.esc & ESC_CSI) {
2376 csiescseq.buf[csiescseq.len++] = u;
2377 if (BETWEEN(u, 0x40, 0x7E)
2378 || csiescseq.len >= \
2379 sizeof(csiescseq.buf)-1) {
2380 term.esc = 0;
2381 csiparse();
2382 csihandle();
2384 return;
2385 } else if (term.esc & ESC_UTF8) {
2386 tdefutf8(u);
2387 } else if (term.esc & ESC_ALTCHARSET) {
2388 tdeftran(u);
2389 } else if (term.esc & ESC_TEST) {
2390 tdectest(u);
2391 } else {
2392 if (!eschandle(u))
2393 return;
2394 /* sequence already finished */
2396 term.esc = 0;
2398 * All characters which form part of a sequence are not
2399 * printed
2401 return;
2403 if (sel.ob.x != -1 && BETWEEN(term.c.y, sel.ob.y, sel.oe.y))
2404 selclear();
2406 gp = &term.line[term.c.y][term.c.x];
2407 if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
2408 gp->mode |= ATTR_WRAP;
2409 tnewline(1);
2410 gp = &term.line[term.c.y][term.c.x];
2413 if (IS_SET(MODE_INSERT) && term.c.x+width < term.col)
2414 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
2416 if (term.c.x+width > term.col) {
2417 tnewline(1);
2418 gp = &term.line[term.c.y][term.c.x];
2421 tsetchar(u, &term.c.attr, term.c.x, term.c.y);
2423 if (width == 2) {
2424 gp->mode |= ATTR_WIDE;
2425 if (term.c.x+1 < term.col) {
2426 gp[1].u = '\0';
2427 gp[1].mode = ATTR_WDUMMY;
2430 if (term.c.x+width < term.col) {
2431 tmoveto(term.c.x+width, term.c.y);
2432 } else {
2433 term.c.state |= CURSOR_WRAPNEXT;
2438 twrite(const char *buf, int buflen, int show_ctrl)
2440 int charsize;
2441 Rune u;
2442 int n;
2444 for (n = 0; n < buflen; n += charsize) {
2445 if (IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
2446 /* process a complete utf8 char */
2447 charsize = utf8decode(buf + n, &u, buflen - n);
2448 if (charsize == 0)
2449 break;
2450 } else {
2451 u = buf[n] & 0xFF;
2452 charsize = 1;
2454 if (show_ctrl && ISCONTROL(u)) {
2455 if (u & 0x80) {
2456 u &= 0x7f;
2457 tputc('^');
2458 tputc('[');
2459 } else if (u != '\n' && u != '\r' && u != '\t') {
2460 u ^= 0x40;
2461 tputc('^');
2464 tputc(u);
2466 return n;
2469 void
2470 tresize(int col, int row)
2472 int i;
2473 int minrow = MIN(row, term.row);
2474 int mincol = MIN(col, term.col);
2475 int *bp;
2476 TCursor c;
2478 if (col < 1 || row < 1) {
2479 fprintf(stderr,
2480 "tresize: error resizing to %dx%d\n", col, row);
2481 return;
2485 * slide screen to keep cursor where we expect it -
2486 * tscrollup would work here, but we can optimize to
2487 * memmove because we're freeing the earlier lines
2489 for (i = 0; i <= term.c.y - row; i++) {
2490 free(term.line[i]);
2491 free(term.alt[i]);
2493 /* ensure that both src and dst are not NULL */
2494 if (i > 0) {
2495 memmove(term.line, term.line + i, row * sizeof(Line));
2496 memmove(term.alt, term.alt + i, row * sizeof(Line));
2498 for (i += row; i < term.row; i++) {
2499 free(term.line[i]);
2500 free(term.alt[i]);
2503 /* resize to new height */
2504 term.line = xrealloc(term.line, row * sizeof(Line));
2505 term.alt = xrealloc(term.alt, row * sizeof(Line));
2506 term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
2507 term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
2509 /* resize each row to new width, zero-pad if needed */
2510 for (i = 0; i < minrow; i++) {
2511 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
2512 term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph));
2515 /* allocate any new rows */
2516 for (/* i = minrow */; i < row; i++) {
2517 term.line[i] = xmalloc(col * sizeof(Glyph));
2518 term.alt[i] = xmalloc(col * sizeof(Glyph));
2520 if (col > term.col) {
2521 bp = term.tabs + term.col;
2523 memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
2524 while (--bp > term.tabs && !*bp)
2525 /* nothing */ ;
2526 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
2527 *bp = 1;
2529 /* update terminal size */
2530 term.col = col;
2531 term.row = row;
2532 /* reset scrolling region */
2533 tsetscroll(0, row-1);
2534 /* make use of the LIMIT in tmoveto */
2535 tmoveto(term.c.x, term.c.y);
2536 /* Clearing both screens (it makes dirty all lines) */
2537 c = term.c;
2538 for (i = 0; i < 2; i++) {
2539 if (mincol < col && 0 < minrow) {
2540 tclearregion(mincol, 0, col - 1, minrow - 1);
2542 if (0 < col && minrow < row) {
2543 tclearregion(0, minrow, col - 1, row - 1);
2545 tswapscreen();
2546 tcursor(CURSOR_LOAD);
2548 term.c = c;
2551 void
2552 resettitle(void)
2554 xsettitle(NULL);
2557 void
2558 drawregion(int x1, int y1, int x2, int y2)
2560 int y;
2561 for (y = y1; y < y2; y++) {
2562 if (!term.dirty[y])
2563 continue;
2565 term.dirty[y] = 0;
2566 xdrawline(term.line[y], x1, y, x2);
2570 void
2571 draw(void)
2573 int cx = term.c.x;
2575 if (!xstartdraw())
2576 return;
2578 /* adjust cursor position */
2579 LIMIT(term.ocx, 0, term.col-1);
2580 LIMIT(term.ocy, 0, term.row-1);
2581 if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY)
2582 term.ocx--;
2583 if (term.line[term.c.y][cx].mode & ATTR_WDUMMY)
2584 cx--;
2586 drawregion(0, 0, term.col, term.row);
2587 xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
2588 term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
2589 term.ocx = cx, term.ocy = term.c.y;
2590 xfinishdraw();
2591 xximspot(term.ocx, term.ocy);
2594 void
2595 redraw(void)
2597 tfulldirt();
2598 draw();