st.c
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>
19
20#include "st.h"
21#include "win.h"
22
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
30
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 CAR_PER_ARG 4
37#define STR_BUF_SIZ ESC_BUF_SIZ
38#define STR_ARG_SIZ ESC_ARG_SIZ
39
40/* macros */
41#define IS_SET(flag) ((term.mode & (flag)) != 0)
42#define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f)
43#define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
44#define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
45#define ISDELIM(u) (u && wcschr(worddelimiters, u))
46
47enum term_mode {
48 MODE_WRAP = 1 << 0,
49 MODE_INSERT = 1 << 1,
50 MODE_ALTSCREEN = 1 << 2,
51 MODE_CRLF = 1 << 3,
52 MODE_ECHO = 1 << 4,
53 MODE_PRINT = 1 << 5,
54 MODE_UTF8 = 1 << 6,
55};
56
57enum cursor_movement {
58 CURSOR_SAVE,
59 CURSOR_LOAD
60};
61
62enum cursor_state {
63 CURSOR_DEFAULT = 0,
64 CURSOR_WRAPNEXT = 1,
65 CURSOR_ORIGIN = 2
66};
67
68enum charset {
69 CS_GRAPHIC0,
70 CS_GRAPHIC1,
71 CS_UK,
72 CS_USA,
73 CS_MULTI,
74 CS_GER,
75 CS_FIN
76};
77
78enum escape_state {
79 ESC_START = 1,
80 ESC_CSI = 2,
81 ESC_STR = 4, /* DCS, 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};
87
88typedef struct {
89 Glyph attr; /* current char attributes */
90 int x;
91 int y;
92 char state;
93} TCursor;
94
95typedef struct {
96 int mode;
97 int type;
98 int snap;
99 /*
100 * Selection variables:
101 * nb – normalized coordinates of the beginning of the selection
102 * ne – normalized coordinates of the end of the selection
103 * ob – original coordinates of the beginning of the selection
104 * oe – original coordinates of the end of the selection
105 */
106 struct {
107 int x, y;
108 } nb, ne, ob, oe;
109
110 int alt;
111} Selection;
112
113/* Internal representation of the screen */
114typedef struct {
115 int row; /* nb row */
116 int col; /* nb col */
117 Line *line; /* screen */
118 Line *alt; /* alternate screen */
119 int *dirty; /* dirtyness of lines */
120 TCursor c; /* cursor */
121 int ocx; /* old cursor col */
122 int ocy; /* old cursor row */
123 int top; /* top scroll limit */
124 int bot; /* bottom scroll limit */
125 int mode; /* terminal mode flags */
126 int esc; /* escape state flags */
127 char trantbl[4]; /* charset table translation */
128 int charset; /* current charset */
129 int icharset; /* selected charset for sequence */
130 int *tabs;
131 Rune lastc; /* last printed char outside of sequence, 0 if control */
132} Term;
133
134/* CSI Escape sequence structs */
135/* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
136typedef 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 int carg[ESC_ARG_SIZ][CAR_PER_ARG]; /* colon args */
144} CSIEscape;
145
146/* STR Escape sequence structs */
147/* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
148typedef struct {
149 char type; /* ESC type ... */
150 char *buf; /* allocated raw string */
151 size_t siz; /* allocation size */
152 size_t len; /* raw string length */
153 char *args[STR_ARG_SIZ];
154 int narg; /* nb of args */
155} STREscape;
156
157static void execsh(char *, char **);
158static void stty(char **);
159static void sigchld(int);
160static void ttywriteraw(const char *, size_t);
161
162static void csidump(void);
163static void csihandle(void);
164static void readcolonargs(char **, int, int[][CAR_PER_ARG]);
165static void csiparse(void);
166static void csireset(void);
167static void osc_color_response(int, int, int);
168static int eschandle(uchar);
169static void strdump(void);
170static void strhandle(void);
171static void strparse(void);
172static void strreset(void);
173
174static void tprinter(char *, size_t);
175static void tdumpsel(void);
176static void tdumpline(int);
177static void tdump(void);
178static void tclearregion(int, int, int, int);
179static void tcursor(int);
180static void tdeletechar(int);
181static void tdeleteline(int);
182static void tinsertblank(int);
183static void tinsertblankline(int);
184static int tlinelen(int);
185static void tmoveto(int, int);
186static void tmoveato(int, int);
187static void tnewline(int);
188static void tputtab(int);
189static void tputc(Rune);
190static void treset(void);
191static void tscrollup(int, int);
192static void tscrolldown(int, int);
193static void tsetattr(const int *, int);
194static void tsetchar(Rune, const Glyph *, int, int);
195static void tsetdirt(int, int);
196static void tsetscroll(int, int);
197static void tswapscreen(void);
198static void tsetmode(int, int, const int *, int);
199static int twrite(const char *, int, int);
200static void tfulldirt(void);
201static void tcontrolcode(uchar );
202static void tdectest(char );
203static void tdefutf8(char);
204static int32_t tdefcolor(const int *, int *, int);
205static void tdeftran(char);
206static void tstrsequence(uchar);
207
208static void drawregion(int, int, int, int);
209
210static void selnormalize(void);
211static void selscroll(int, int);
212static void selsnap(int *, int *, int);
213
214static size_t utf8decode(const char *, Rune *, size_t);
215static Rune utf8decodebyte(char, size_t *);
216static char utf8encodebyte(Rune, size_t);
217static size_t utf8validate(Rune *, size_t);
218
219static char *base64dec(const char *);
220static char base64dec_getc(const char **);
221
222static ssize_t xwrite(int, const char *, size_t);
223
224/* Globals */
225static Term term;
226static Selection sel;
227static CSIEscape csiescseq;
228static STREscape strescseq;
229static int iofd = 1;
230static int cmdfd;
231static pid_t pid;
232
233static const uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
234static const uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
235static const Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000};
236static const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
237
238ssize_t
239xwrite(int fd, const char *s, size_t len)
240{
241 size_t aux = len;
242 ssize_t r;
243
244 while (len > 0) {
245 r = write(fd, s, len);
246 if (r < 0)
247 return r;
248 len -= r;
249 s += r;
250 }
251
252 return aux;
253}
254
255void *
256xmalloc(size_t len)
257{
258 void *p;
259
260 if (!(p = malloc(len)))
261 die("malloc: %s\n", strerror(errno));
262
263 return p;
264}
265
266void *
267xrealloc(void *p, size_t len)
268{
269 if ((p = realloc(p, len)) == NULL)
270 die("realloc: %s\n", strerror(errno));
271
272 return p;
273}
274
275char *
276xstrdup(const char *s)
277{
278 char *p;
279
280 if ((p = strdup(s)) == NULL)
281 die("strdup: %s\n", strerror(errno));
282
283 return p;
284}
285
286size_t
287utf8decode(const char *c, Rune *u, size_t clen)
288{
289 size_t i, j, len, type;
290 Rune udecoded;
291
292 *u = UTF_INVALID;
293 if (!clen)
294 return 0;
295 udecoded = utf8decodebyte(c[0], &len);
296 if (!BETWEEN(len, 1, UTF_SIZ))
297 return 1;
298 for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
299 udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
300 if (type != 0)
301 return j;
302 }
303 if (j < len)
304 return 0;
305 *u = udecoded;
306 utf8validate(u, len);
307
308 return len;
309}
310
311Rune
312utf8decodebyte(char c, size_t *i)
313{
314 for (*i = 0; *i < LEN(utfmask); ++(*i))
315 if (((uchar)c & utfmask[*i]) == utfbyte[*i])
316 return (uchar)c & ~utfmask[*i];
317
318 return 0;
319}
320
321size_t
322utf8encode(Rune u, char *c)
323{
324 size_t len, i;
325
326 len = utf8validate(&u, 0);
327 if (len > UTF_SIZ)
328 return 0;
329
330 for (i = len - 1; i != 0; --i) {
331 c[i] = utf8encodebyte(u, 0);
332 u >>= 6;
333 }
334 c[0] = utf8encodebyte(u, len);
335
336 return len;
337}
338
339char
340utf8encodebyte(Rune u, size_t i)
341{
342 return utfbyte[i] | (u & ~utfmask[i]);
343}
344
345size_t
346utf8validate(Rune *u, size_t i)
347{
348 if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF))
349 *u = UTF_INVALID;
350 for (i = 1; *u > utfmax[i]; ++i)
351 ;
352
353 return i;
354}
355
356char
357base64dec_getc(const char **src)
358{
359 while (**src && !isprint((unsigned char)**src))
360 (*src)++;
361 return **src ? *((*src)++) : '='; /* emulate padding if string ends */
362}
363
364char *
365base64dec(const char *src)
366{
367 size_t in_len = strlen(src);
368 char *result, *dst;
369 static const char base64_digits[256] = {
370 [43] = 62, 0, 0, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
371 0, 0, 0, -1, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
372 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0,
373 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
374 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
375 };
376
377 if (in_len % 4)
378 in_len += 4 - (in_len % 4);
379 result = dst = xmalloc(in_len / 4 * 3 + 1);
380 while (*src) {
381 int a = base64_digits[(unsigned char) base64dec_getc(&src)];
382 int b = base64_digits[(unsigned char) base64dec_getc(&src)];
383 int c = base64_digits[(unsigned char) base64dec_getc(&src)];
384 int d = base64_digits[(unsigned char) base64dec_getc(&src)];
385
386 /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */
387 if (a == -1 || b == -1)
388 break;
389
390 *dst++ = (a << 2) | ((b & 0x30) >> 4);
391 if (c == -1)
392 break;
393 *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2);
394 if (d == -1)
395 break;
396 *dst++ = ((c & 0x03) << 6) | d;
397 }
398 *dst = '\0';
399 return result;
400}
401
402void
403selinit(void)
404{
405 sel.mode = SEL_IDLE;
406 sel.snap = 0;
407 sel.ob.x = -1;
408}
409
410int
411tlinelen(int y)
412{
413 int i = term.col;
414
415 if (term.line[y][i - 1].mode & ATTR_WRAP)
416 return i;
417
418 while (i > 0 && term.line[y][i - 1].u == ' ')
419 --i;
420
421 return i;
422}
423
424void
425selstart(int col, int row, int snap)
426{
427 selclear();
428 sel.mode = SEL_EMPTY;
429 sel.type = SEL_REGULAR;
430 sel.alt = IS_SET(MODE_ALTSCREEN);
431 sel.snap = snap;
432 sel.oe.x = sel.ob.x = col;
433 sel.oe.y = sel.ob.y = row;
434 selnormalize();
435
436 if (sel.snap != 0)
437 sel.mode = SEL_READY;
438 tsetdirt(sel.nb.y, sel.ne.y);
439}
440
441void
442selextend(int col, int row, int type, int done)
443{
444 int oldey, oldex, oldsby, oldsey, oldtype;
445
446 if (sel.mode == SEL_IDLE)
447 return;
448 if (done && sel.mode == SEL_EMPTY) {
449 selclear();
450 return;
451 }
452
453 oldey = sel.oe.y;
454 oldex = sel.oe.x;
455 oldsby = sel.nb.y;
456 oldsey = sel.ne.y;
457 oldtype = sel.type;
458
459 sel.oe.x = col;
460 sel.oe.y = row;
461 selnormalize();
462 sel.type = type;
463
464 if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY)
465 tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey));
466
467 sel.mode = done ? SEL_IDLE : SEL_READY;
468}
469
470void
471selnormalize(void)
472{
473 int i;
474
475 if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) {
476 sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x;
477 sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x;
478 } else {
479 sel.nb.x = MIN(sel.ob.x, sel.oe.x);
480 sel.ne.x = MAX(sel.ob.x, sel.oe.x);
481 }
482 sel.nb.y = MIN(sel.ob.y, sel.oe.y);
483 sel.ne.y = MAX(sel.ob.y, sel.oe.y);
484
485 selsnap(&sel.nb.x, &sel.nb.y, -1);
486 selsnap(&sel.ne.x, &sel.ne.y, +1);
487
488 /* expand selection over line breaks */
489 if (sel.type == SEL_RECTANGULAR)
490 return;
491 i = tlinelen(sel.nb.y);
492 if (i < sel.nb.x)
493 sel.nb.x = i;
494 if (tlinelen(sel.ne.y) <= sel.ne.x)
495 sel.ne.x = term.col - 1;
496}
497
498int
499selected(int x, int y)
500{
501 if (sel.mode == SEL_EMPTY || sel.ob.x == -1 ||
502 sel.alt != IS_SET(MODE_ALTSCREEN))
503 return 0;
504
505 if (sel.type == SEL_RECTANGULAR)
506 return BETWEEN(y, sel.nb.y, sel.ne.y)
507 && BETWEEN(x, sel.nb.x, sel.ne.x);
508
509 return BETWEEN(y, sel.nb.y, sel.ne.y)
510 && (y != sel.nb.y || x >= sel.nb.x)
511 && (y != sel.ne.y || x <= sel.ne.x);
512}
513
514void
515selsnap(int *x, int *y, int direction)
516{
517 int newx, newy, xt, yt;
518 int delim, prevdelim;
519 const Glyph *gp, *prevgp;
520
521 switch (sel.snap) {
522 case SNAP_WORD:
523 /*
524 * Snap around if the word wraps around at the end or
525 * beginning of a line.
526 */
527 prevgp = &term.line[*y][*x];
528 prevdelim = ISDELIM(prevgp->u);
529 for (;;) {
530 newx = *x + direction;
531 newy = *y;
532 if (!BETWEEN(newx, 0, term.col - 1)) {
533 newy += direction;
534 newx = (newx + term.col) % term.col;
535 if (!BETWEEN(newy, 0, term.row - 1))
536 break;
537
538 if (direction > 0)
539 yt = *y, xt = *x;
540 else
541 yt = newy, xt = newx;
542 if (!(term.line[yt][xt].mode & ATTR_WRAP))
543 break;
544 }
545
546 if (newx >= tlinelen(newy))
547 break;
548
549 gp = &term.line[newy][newx];
550 delim = ISDELIM(gp->u);
551 if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
552 || (delim && gp->u != prevgp->u)))
553 break;
554
555 *x = newx;
556 *y = newy;
557 prevgp = gp;
558 prevdelim = delim;
559 }
560 break;
561 case SNAP_LINE:
562 /*
563 * Snap around if the the previous line or the current one
564 * has set ATTR_WRAP at its end. Then the whole next or
565 * previous line will be selected.
566 */
567 *x = (direction < 0) ? 0 : term.col - 1;
568 if (direction < 0) {
569 for (; *y > 0; *y += direction) {
570 if (!(term.line[*y-1][term.col-1].mode
571 & ATTR_WRAP)) {
572 break;
573 }
574 }
575 } else if (direction > 0) {
576 for (; *y < term.row-1; *y += direction) {
577 if (!(term.line[*y][term.col-1].mode
578 & ATTR_WRAP)) {
579 break;
580 }
581 }
582 }
583 break;
584 }
585}
586
587char *
588getsel(void)
589{
590 char *str, *ptr;
591 int y, bufsize, lastx, linelen;
592 const Glyph *gp, *last;
593
594 if (sel.ob.x == -1)
595 return NULL;
596
597 bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ;
598 ptr = str = xmalloc(bufsize);
599
600 /* append every set & selected glyph to the selection */
601 for (y = sel.nb.y; y <= sel.ne.y; y++) {
602 if ((linelen = tlinelen(y)) == 0) {
603 *ptr++ = '\n';
604 continue;
605 }
606
607 if (sel.type == SEL_RECTANGULAR) {
608 gp = &term.line[y][sel.nb.x];
609 lastx = sel.ne.x;
610 } else {
611 gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0];
612 lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
613 }
614 last = &term.line[y][MIN(lastx, linelen-1)];
615 while (last >= gp && last->u == ' ')
616 --last;
617
618 for ( ; gp <= last; ++gp) {
619 if (gp->mode & ATTR_WDUMMY)
620 continue;
621
622 ptr += utf8encode(gp->u, ptr);
623 }
624
625 /*
626 * Copy and pasting of line endings is inconsistent
627 * in the inconsistent terminal and GUI world.
628 * The best solution seems like to produce '\n' when
629 * something is copied from st and convert '\n' to
630 * '\r', when something to be pasted is received by
631 * st.
632 * FIXME: Fix the computer world.
633 */
634 if ((y < sel.ne.y || lastx >= linelen) &&
635 (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR))
636 *ptr++ = '\n';
637 }
638 *ptr = 0;
639 return str;
640}
641
642void
643selclear(void)
644{
645 if (sel.ob.x == -1)
646 return;
647 sel.mode = SEL_IDLE;
648 sel.ob.x = -1;
649 tsetdirt(sel.nb.y, sel.ne.y);
650}
651
652void
653die(const char *errstr, ...)
654{
655 va_list ap;
656
657 va_start(ap, errstr);
658 vfprintf(stderr, errstr, ap);
659 va_end(ap);
660 exit(1);
661}
662
663void
664execsh(char *cmd, char **args)
665{
666 char *sh, *prog, *arg;
667 const struct passwd *pw;
668
669 errno = 0;
670 if ((pw = getpwuid(getuid())) == NULL) {
671 if (errno)
672 die("getpwuid: %s\n", strerror(errno));
673 else
674 die("who are you?\n");
675 }
676
677 if ((sh = getenv("SHELL")) == NULL)
678 sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd;
679
680 if (args) {
681 prog = args[0];
682 arg = NULL;
683 } else if (scroll) {
684 prog = scroll;
685 arg = utmp ? utmp : sh;
686 } else if (utmp) {
687 prog = utmp;
688 arg = NULL;
689 } else {
690 prog = sh;
691 arg = NULL;
692 }
693 DEFAULT(args, ((char *[]) {prog, arg, NULL}));
694
695 unsetenv("COLUMNS");
696 unsetenv("LINES");
697 unsetenv("TERMCAP");
698 setenv("LOGNAME", pw->pw_name, 1);
699 setenv("USER", pw->pw_name, 1);
700 setenv("SHELL", sh, 1);
701 setenv("HOME", pw->pw_dir, 1);
702 setenv("TERM", termname, 1);
703
704 signal(SIGCHLD, SIG_DFL);
705 signal(SIGHUP, SIG_DFL);
706 signal(SIGINT, SIG_DFL);
707 signal(SIGQUIT, SIG_DFL);
708 signal(SIGTERM, SIG_DFL);
709 signal(SIGALRM, SIG_DFL);
710
711 execvp(prog, args);
712 _exit(1);
713}
714
715void
716sigchld(int a)
717{
718 int stat;
719 pid_t p;
720
721 if ((p = waitpid(pid, &stat, WNOHANG)) < 0)
722 die("waiting for pid %hd failed: %s\n", pid, strerror(errno));
723
724 if (pid != p)
725 return;
726
727 if (WIFEXITED(stat) && WEXITSTATUS(stat))
728 die("child exited with status %d\n", WEXITSTATUS(stat));
729 else if (WIFSIGNALED(stat))
730 die("child terminated due to signal %d\n", WTERMSIG(stat));
731 _exit(0);
732}
733
734void
735stty(char **args)
736{
737 char cmd[_POSIX_ARG_MAX], **p, *q, *s;
738 size_t n, siz;
739
740 if ((n = strlen(stty_args)) > sizeof(cmd)-1)
741 die("incorrect stty parameters\n");
742 memcpy(cmd, stty_args, n);
743 q = cmd + n;
744 siz = sizeof(cmd) - n;
745 for (p = args; p && (s = *p); ++p) {
746 if ((n = strlen(s)) > siz-1)
747 die("stty parameter length too long\n");
748 *q++ = ' ';
749 memcpy(q, s, n);
750 q += n;
751 siz -= n + 1;
752 }
753 *q = '\0';
754 if (system(cmd) != 0)
755 perror("Couldn't call stty");
756}
757
758int
759ttynew(const char *line, char *cmd, const char *out, char **args)
760{
761 int m, s;
762
763 if (out) {
764 term.mode |= MODE_PRINT;
765 iofd = (!strcmp(out, "-")) ?
766 1 : open(out, O_WRONLY | O_CREAT, 0666);
767 if (iofd < 0) {
768 fprintf(stderr, "Error opening %s:%s\n",
769 out, strerror(errno));
770 }
771 }
772
773 if (line) {
774 if ((cmdfd = open(line, O_RDWR)) < 0)
775 die("open line '%s' failed: %s\n",
776 line, strerror(errno));
777 dup2(cmdfd, 0);
778 stty(args);
779 return cmdfd;
780 }
781
782 /* seems to work fine on linux, openbsd and freebsd */
783 if (openpty(&m, &s, NULL, NULL, NULL) < 0)
784 die("openpty failed: %s\n", strerror(errno));
785
786 switch (pid = fork()) {
787 case -1:
788 die("fork failed: %s\n", strerror(errno));
789 break;
790 case 0:
791 close(iofd);
792 close(m);
793 setsid(); /* create a new process group */
794 dup2(s, 0);
795 dup2(s, 1);
796 dup2(s, 2);
797 if (ioctl(s, TIOCSCTTY, NULL) < 0)
798 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno));
799 if (s > 2)
800 close(s);
801#ifdef __OpenBSD__
802 if (pledge("stdio getpw proc exec", NULL) == -1)
803 die("pledge\n");
804#endif
805 execsh(cmd, args);
806 break;
807 default:
808#ifdef __OpenBSD__
809 if (pledge("stdio rpath tty proc", NULL) == -1)
810 die("pledge\n");
811#endif
812 close(s);
813 cmdfd = m;
814 signal(SIGCHLD, sigchld);
815 break;
816 }
817 return cmdfd;
818}
819
820size_t
821ttyread(void)
822{
823 static char buf[BUFSIZ];
824 static int buflen = 0;
825 int ret, written;
826
827 /* append read bytes to unprocessed bytes */
828 ret = read(cmdfd, buf+buflen, LEN(buf)-buflen);
829
830 switch (ret) {
831 case 0:
832 exit(0);
833 case -1:
834 die("couldn't read from shell: %s\n", strerror(errno));
835 default:
836 buflen += ret;
837 written = twrite(buf, buflen, 0);
838 buflen -= written;
839 /* keep any incomplete UTF-8 byte sequence for the next call */
840 if (buflen > 0)
841 memmove(buf, buf + written, buflen);
842 return ret;
843 }
844}
845
846void
847ttywrite(const char *s, size_t n, int may_echo)
848{
849 const char *next;
850
851 if (may_echo && IS_SET(MODE_ECHO))
852 twrite(s, n, 1);
853
854 if (!IS_SET(MODE_CRLF)) {
855 ttywriteraw(s, n);
856 return;
857 }
858
859 /* This is similar to how the kernel handles ONLCR for ttys */
860 while (n > 0) {
861 if (*s == '\r') {
862 next = s + 1;
863 ttywriteraw("\r\n", 2);
864 } else {
865 next = memchr(s, '\r', n);
866 DEFAULT(next, s + n);
867 ttywriteraw(s, next - s);
868 }
869 n -= next - s;
870 s = next;
871 }
872}
873
874void
875ttywriteraw(const char *s, size_t n)
876{
877 fd_set wfd, rfd;
878 ssize_t r;
879 size_t lim = 256;
880
881 /*
882 * Remember that we are using a pty, which might be a modem line.
883 * Writing too much will clog the line. That's why we are doing this
884 * dance.
885 * FIXME: Migrate the world to Plan 9.
886 */
887 while (n > 0) {
888 FD_ZERO(&wfd);
889 FD_ZERO(&rfd);
890 FD_SET(cmdfd, &wfd);
891 FD_SET(cmdfd, &rfd);
892
893 /* Check if we can write. */
894 if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) {
895 if (errno == EINTR)
896 continue;
897 die("select failed: %s\n", strerror(errno));
898 }
899 if (FD_ISSET(cmdfd, &wfd)) {
900 /*
901 * Only write the bytes written by ttywrite() or the
902 * default of 256. This seems to be a reasonable value
903 * for a serial line. Bigger values might clog the I/O.
904 */
905 if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0)
906 goto write_error;
907 if (r < n) {
908 /*
909 * We weren't able to write out everything.
910 * This means the buffer is getting full
911 * again. Empty it.
912 */
913 if (n < lim)
914 lim = ttyread();
915 n -= r;
916 s += r;
917 } else {
918 /* All bytes have been written. */
919 break;
920 }
921 }
922 if (FD_ISSET(cmdfd, &rfd))
923 lim = ttyread();
924 }
925 return;
926
927write_error:
928 die("write error on tty: %s\n", strerror(errno));
929}
930
931void
932ttyresize(int tw, int th)
933{
934 struct winsize w;
935
936 w.ws_row = term.row;
937 w.ws_col = term.col;
938 w.ws_xpixel = tw;
939 w.ws_ypixel = th;
940 if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
941 fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
942}
943
944void
945ttyhangup(void)
946{
947 /* Send SIGHUP to shell */
948 kill(pid, SIGHUP);
949}
950
951int
952tattrset(int attr)
953{
954 int i, j;
955
956 for (i = 0; i < term.row-1; i++) {
957 for (j = 0; j < term.col-1; j++) {
958 if (term.line[i][j].mode & attr)
959 return 1;
960 }
961 }
962
963 return 0;
964}
965
966void
967tsetdirt(int top, int bot)
968{
969 int i;
970
971 LIMIT(top, 0, term.row-1);
972 LIMIT(bot, 0, term.row-1);
973
974 for (i = top; i <= bot; i++)
975 term.dirty[i] = 1;
976}
977
978void
979tsetdirtattr(int attr)
980{
981 int i, j;
982
983 for (i = 0; i < term.row-1; i++) {
984 for (j = 0; j < term.col-1; j++) {
985 if (term.line[i][j].mode & attr) {
986 tsetdirt(i, i);
987 break;
988 }
989 }
990 }
991}
992
993void
994tfulldirt(void)
995{
996 tsetdirt(0, term.row-1);
997}
998
999void
1000tcursor(int mode)
1001{
1002 static TCursor c[2];
1003 int alt = IS_SET(MODE_ALTSCREEN);
1004
1005 if (mode == CURSOR_SAVE) {
1006 c[alt] = term.c;
1007 } else if (mode == CURSOR_LOAD) {
1008 term.c = c[alt];
1009 tmoveto(c[alt].x, c[alt].y);
1010 }
1011}
1012
1013void
1014treset(void)
1015{
1016 uint i;
1017
1018 term.c = (TCursor){{
1019 .mode = ATTR_NULL,
1020 .fg = defaultfg,
1021 .bg = defaultbg
1022 }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
1023
1024 memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1025 for (i = tabspaces; i < term.col; i += tabspaces)
1026 term.tabs[i] = 1;
1027 term.top = 0;
1028 term.bot = term.row - 1;
1029 term.mode = MODE_WRAP|MODE_UTF8;
1030 memset(term.trantbl, CS_USA, sizeof(term.trantbl));
1031 term.charset = 0;
1032
1033 for (i = 0; i < 2; i++) {
1034 tmoveto(0, 0);
1035 tcursor(CURSOR_SAVE);
1036 tclearregion(0, 0, term.col-1, term.row-1);
1037 tswapscreen();
1038 }
1039}
1040
1041void
1042tnew(int col, int row)
1043{
1044 term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
1045 tresize(col, row);
1046 treset();
1047}
1048
1049void
1050tswapscreen(void)
1051{
1052 Line *tmp = term.line;
1053
1054 term.line = term.alt;
1055 term.alt = tmp;
1056 term.mode ^= MODE_ALTSCREEN;
1057 tfulldirt();
1058}
1059
1060void
1061tscrolldown(int orig, int n)
1062{
1063 int i;
1064 Line temp;
1065
1066 LIMIT(n, 0, term.bot-orig+1);
1067
1068 tsetdirt(orig, term.bot-n);
1069 tclearregion(0, term.bot-n+1, term.col-1, term.bot);
1070
1071 for (i = term.bot; i >= orig+n; i--) {
1072 temp = term.line[i];
1073 term.line[i] = term.line[i-n];
1074 term.line[i-n] = temp;
1075 }
1076
1077 selscroll(orig, n);
1078}
1079
1080void
1081tscrollup(int orig, int n)
1082{
1083 int i;
1084 Line temp;
1085
1086 LIMIT(n, 0, term.bot-orig+1);
1087
1088 tclearregion(0, orig, term.col-1, orig+n-1);
1089 tsetdirt(orig+n, term.bot);
1090
1091 for (i = orig; i <= term.bot-n; i++) {
1092 temp = term.line[i];
1093 term.line[i] = term.line[i+n];
1094 term.line[i+n] = temp;
1095 }
1096
1097 selscroll(orig, -n);
1098}
1099
1100void
1101selscroll(int orig, int n)
1102{
1103 if (sel.ob.x == -1 || sel.alt != IS_SET(MODE_ALTSCREEN))
1104 return;
1105
1106 if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) {
1107 selclear();
1108 } else if (BETWEEN(sel.nb.y, orig, term.bot)) {
1109 sel.ob.y += n;
1110 sel.oe.y += n;
1111 if (sel.ob.y < term.top || sel.ob.y > term.bot ||
1112 sel.oe.y < term.top || sel.oe.y > term.bot) {
1113 selclear();
1114 } else {
1115 selnormalize();
1116 }
1117 }
1118}
1119
1120void
1121tnewline(int first_col)
1122{
1123 int y = term.c.y;
1124
1125 if (y == term.bot) {
1126 tscrollup(term.top, 1);
1127 } else {
1128 y++;
1129 }
1130 tmoveto(first_col ? 0 : term.c.x, y);
1131}
1132
1133void
1134readcolonargs(char **p, int cursor, int params[][CAR_PER_ARG])
1135{
1136 int i = 0;
1137 for (; i < CAR_PER_ARG; i++)
1138 params[cursor][i] = -1;
1139
1140 if (**p != ':')
1141 return;
1142
1143 char *np = NULL;
1144 i = 0;
1145
1146 while (**p == ':' && i < CAR_PER_ARG) {
1147 while (**p == ':')
1148 (*p)++;
1149 params[cursor][i] = strtol(*p, &np, 10);
1150 *p = np;
1151 i++;
1152 }
1153}
1154
1155void
1156csiparse(void)
1157{
1158 char *p = csiescseq.buf, *np;
1159 long int v;
1160
1161 csiescseq.narg = 0;
1162 if (*p == '?') {
1163 csiescseq.priv = 1;
1164 p++;
1165 }
1166
1167 csiescseq.buf[csiescseq.len] = '\0';
1168 while (p < csiescseq.buf+csiescseq.len) {
1169 np = NULL;
1170 v = strtol(p, &np, 10);
1171 if (np == p)
1172 v = 0;
1173 if (v == LONG_MAX || v == LONG_MIN)
1174 v = -1;
1175 csiescseq.arg[csiescseq.narg++] = v;
1176 p = np;
1177 readcolonargs(&p, csiescseq.narg-1, csiescseq.carg);
1178 if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ)
1179 break;
1180 p++;
1181 }
1182 csiescseq.mode[0] = *p++;
1183 csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0';
1184}
1185
1186/* for absolute user moves, when decom is set */
1187void
1188tmoveato(int x, int y)
1189{
1190 tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0));
1191}
1192
1193void
1194tmoveto(int x, int y)
1195{
1196 int miny, maxy;
1197
1198 if (term.c.state & CURSOR_ORIGIN) {
1199 miny = term.top;
1200 maxy = term.bot;
1201 } else {
1202 miny = 0;
1203 maxy = term.row - 1;
1204 }
1205 term.c.state &= ~CURSOR_WRAPNEXT;
1206 term.c.x = LIMIT(x, 0, term.col-1);
1207 term.c.y = LIMIT(y, miny, maxy);
1208}
1209
1210void
1211tsetchar(Rune u, const Glyph *attr, int x, int y)
1212{
1213 static const char *vt100_0[62] = { /* 0x41 - 0x7e */
1214 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1215 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1216 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1217 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1218 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1219 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1220 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1221 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1222 };
1223
1224 /*
1225 * The table is proudly stolen from rxvt.
1226 */
1227 if (term.trantbl[term.charset] == CS_GRAPHIC0 &&
1228 BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
1229 utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ);
1230
1231 if (term.line[y][x].mode & ATTR_WIDE) {
1232 if (x+1 < term.col) {
1233 term.line[y][x+1].u = ' ';
1234 term.line[y][x+1].mode &= ~ATTR_WDUMMY;
1235 }
1236 } else if (term.line[y][x].mode & ATTR_WDUMMY) {
1237 term.line[y][x-1].u = ' ';
1238 term.line[y][x-1].mode &= ~ATTR_WIDE;
1239 }
1240
1241 term.dirty[y] = 1;
1242 term.line[y][x] = *attr;
1243 term.line[y][x].u = u;
1244
1245 if (isboxdraw(u))
1246 term.line[y][x].mode |= ATTR_BOXDRAW;
1247}
1248
1249void
1250tclearregion(int x1, int y1, int x2, int y2)
1251{
1252 int x, y, temp;
1253 Glyph *gp;
1254
1255 if (x1 > x2)
1256 temp = x1, x1 = x2, x2 = temp;
1257 if (y1 > y2)
1258 temp = y1, y1 = y2, y2 = temp;
1259
1260 LIMIT(x1, 0, term.col-1);
1261 LIMIT(x2, 0, term.col-1);
1262 LIMIT(y1, 0, term.row-1);
1263 LIMIT(y2, 0, term.row-1);
1264
1265 for (y = y1; y <= y2; y++) {
1266 term.dirty[y] = 1;
1267 for (x = x1; x <= x2; x++) {
1268 gp = &term.line[y][x];
1269 if (selected(x, y))
1270 selclear();
1271 gp->fg = term.c.attr.fg;
1272 gp->bg = term.c.attr.bg;
1273 gp->mode = 0;
1274 gp->u = ' ';
1275 }
1276 }
1277}
1278
1279void
1280tdeletechar(int n)
1281{
1282 int dst, src, size;
1283 Glyph *line;
1284
1285 LIMIT(n, 0, term.col - term.c.x);
1286
1287 dst = term.c.x;
1288 src = term.c.x + n;
1289 size = term.col - src;
1290 line = term.line[term.c.y];
1291
1292 memmove(&line[dst], &line[src], size * sizeof(Glyph));
1293 tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
1294}
1295
1296void
1297tinsertblank(int n)
1298{
1299 int dst, src, size;
1300 Glyph *line;
1301
1302 LIMIT(n, 0, term.col - term.c.x);
1303
1304 dst = term.c.x + n;
1305 src = term.c.x;
1306 size = term.col - dst;
1307 line = term.line[term.c.y];
1308
1309 memmove(&line[dst], &line[src], size * sizeof(Glyph));
1310 tclearregion(src, term.c.y, dst - 1, term.c.y);
1311}
1312
1313void
1314tinsertblankline(int n)
1315{
1316 if (BETWEEN(term.c.y, term.top, term.bot))
1317 tscrolldown(term.c.y, n);
1318}
1319
1320void
1321tdeleteline(int n)
1322{
1323 if (BETWEEN(term.c.y, term.top, term.bot))
1324 tscrollup(term.c.y, n);
1325}
1326
1327int32_t
1328tdefcolor(const int *attr, int *npar, int l)
1329{
1330 int32_t idx = -1;
1331 uint r, g, b;
1332
1333 switch (attr[*npar + 1]) {
1334 case 2: /* direct color in RGB space */
1335 if (*npar + 4 >= l) {
1336 fprintf(stderr,
1337 "erresc(38): Incorrect number of parameters (%d)\n",
1338 *npar);
1339 break;
1340 }
1341 r = attr[*npar + 2];
1342 g = attr[*npar + 3];
1343 b = attr[*npar + 4];
1344 *npar += 4;
1345 if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255))
1346 fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n",
1347 r, g, b);
1348 else
1349 idx = TRUECOLOR(r, g, b);
1350 break;
1351 case 5: /* indexed color */
1352 if (*npar + 2 >= l) {
1353 fprintf(stderr,
1354 "erresc(38): Incorrect number of parameters (%d)\n",
1355 *npar);
1356 break;
1357 }
1358 *npar += 2;
1359 if (!BETWEEN(attr[*npar], 0, 255))
1360 fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]);
1361 else
1362 idx = attr[*npar];
1363 break;
1364 case 0: /* implemented defined (only foreground) */
1365 case 1: /* transparent */
1366 case 3: /* direct color in CMY space */
1367 case 4: /* direct color in CMYK space */
1368 default:
1369 fprintf(stderr,
1370 "erresc(38): gfx attr %d unknown\n", attr[*npar]);
1371 break;
1372 }
1373
1374 return idx;
1375}
1376
1377void
1378tsetattr(const int *attr, int l)
1379{
1380 int i;
1381 int32_t idx;
1382
1383 for (i = 0; i < l; i++) {
1384 switch (attr[i]) {
1385 case 0:
1386 term.c.attr.mode &= ~(
1387 ATTR_BOLD |
1388 ATTR_FAINT |
1389 ATTR_ITALIC |
1390 ATTR_UNDERLINE |
1391 ATTR_BLINK |
1392 ATTR_REVERSE |
1393 ATTR_INVISIBLE |
1394 ATTR_STRUCK );
1395 term.c.attr.fg = defaultfg;
1396 term.c.attr.bg = defaultbg;
1397 term.c.attr.ustyle = -1;
1398 term.c.attr.ucolor[0] = -1;
1399 term.c.attr.ucolor[1] = -1;
1400 term.c.attr.ucolor[2] = -1;
1401 break;
1402 case 1:
1403 term.c.attr.mode |= ATTR_BOLD;
1404 break;
1405 case 2:
1406 term.c.attr.mode |= ATTR_FAINT;
1407 break;
1408 case 3:
1409 term.c.attr.mode |= ATTR_ITALIC;
1410 break;
1411 case 4:
1412 term.c.attr.ustyle = csiescseq.carg[i][0];
1413
1414 if (term.c.attr.ustyle != 0)
1415 term.c.attr.mode |= ATTR_UNDERLINE;
1416 else
1417 term.c.attr.mode &= ~ATTR_UNDERLINE;
1418
1419 term.c.attr.mode ^= ATTR_DIRTYUNDERLINE;
1420 break;
1421 case 5: /* slow blink */
1422 /* FALLTHROUGH */
1423 case 6: /* rapid blink */
1424 term.c.attr.mode |= ATTR_BLINK;
1425 break;
1426 case 7:
1427 term.c.attr.mode |= ATTR_REVERSE;
1428 break;
1429 case 8:
1430 term.c.attr.mode |= ATTR_INVISIBLE;
1431 break;
1432 case 9:
1433 term.c.attr.mode |= ATTR_STRUCK;
1434 break;
1435 case 22:
1436 term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT);
1437 break;
1438 case 23:
1439 term.c.attr.mode &= ~ATTR_ITALIC;
1440 break;
1441 case 24:
1442 term.c.attr.mode &= ~ATTR_UNDERLINE;
1443 break;
1444 case 25:
1445 term.c.attr.mode &= ~ATTR_BLINK;
1446 break;
1447 case 27:
1448 term.c.attr.mode &= ~ATTR_REVERSE;
1449 break;
1450 case 28:
1451 term.c.attr.mode &= ~ATTR_INVISIBLE;
1452 break;
1453 case 29:
1454 term.c.attr.mode &= ~ATTR_STRUCK;
1455 break;
1456 case 38:
1457 if ((idx = tdefcolor(attr, &i, l)) >= 0)
1458 term.c.attr.fg = idx;
1459 break;
1460 case 39:
1461 term.c.attr.fg = defaultfg;
1462 break;
1463 case 48:
1464 if ((idx = tdefcolor(attr, &i, l)) >= 0)
1465 term.c.attr.bg = idx;
1466 break;
1467 case 49:
1468 term.c.attr.bg = defaultbg;
1469 break;
1470 case 58:
1471 term.c.attr.ucolor[0] = csiescseq.carg[i][1];
1472 term.c.attr.ucolor[1] = csiescseq.carg[i][2];
1473 term.c.attr.ucolor[2] = csiescseq.carg[i][3];
1474 term.c.attr.mode ^= ATTR_DIRTYUNDERLINE;
1475 break;
1476 case 59:
1477 term.c.attr.ucolor[0] = -1;
1478 term.c.attr.ucolor[1] = -1;
1479 term.c.attr.ucolor[2] = -1;
1480 term.c.attr.mode ^= ATTR_DIRTYUNDERLINE;
1481 break;
1482 default:
1483 if (BETWEEN(attr[i], 30, 37)) {
1484 term.c.attr.fg = attr[i] - 30;
1485 } else if (BETWEEN(attr[i], 40, 47)) {
1486 term.c.attr.bg = attr[i] - 40;
1487 } else if (BETWEEN(attr[i], 90, 97)) {
1488 term.c.attr.fg = attr[i] - 90 + 8;
1489 } else if (BETWEEN(attr[i], 100, 107)) {
1490 term.c.attr.bg = attr[i] - 100 + 8;
1491 } else {
1492 fprintf(stderr,
1493 "erresc(default): gfx attr %d unknown\n",
1494 attr[i]);
1495 csidump();
1496 }
1497 break;
1498 }
1499 }
1500}
1501
1502void
1503tsetscroll(int t, int b)
1504{
1505 int temp;
1506
1507 LIMIT(t, 0, term.row-1);
1508 LIMIT(b, 0, term.row-1);
1509 if (t > b) {
1510 temp = t;
1511 t = b;
1512 b = temp;
1513 }
1514 term.top = t;
1515 term.bot = b;
1516}
1517
1518void
1519tsetmode(int priv, int set, const int *args, int narg)
1520{
1521 int alt; const int *lim;
1522
1523 for (lim = args + narg; args < lim; ++args) {
1524 if (priv) {
1525 switch (*args) {
1526 case 1: /* DECCKM -- Cursor key */
1527 xsetmode(set, MODE_APPCURSOR);
1528 break;
1529 case 5: /* DECSCNM -- Reverse video */
1530 xsetmode(set, MODE_REVERSE);
1531 break;
1532 case 6: /* DECOM -- Origin */
1533 MODBIT(term.c.state, set, CURSOR_ORIGIN);
1534 tmoveato(0, 0);
1535 break;
1536 case 7: /* DECAWM -- Auto wrap */
1537 MODBIT(term.mode, set, MODE_WRAP);
1538 break;
1539 case 0: /* Error (IGNORED) */
1540 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1541 case 3: /* DECCOLM -- Column (IGNORED) */
1542 case 4: /* DECSCLM -- Scroll (IGNORED) */
1543 case 8: /* DECARM -- Auto repeat (IGNORED) */
1544 case 18: /* DECPFF -- Printer feed (IGNORED) */
1545 case 19: /* DECPEX -- Printer extent (IGNORED) */
1546 case 42: /* DECNRCM -- National characters (IGNORED) */
1547 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1548 break;
1549 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1550 xsetmode(!set, MODE_HIDE);
1551 break;
1552 case 9: /* X10 mouse compatibility mode */
1553 xsetpointermotion(0);
1554 xsetmode(0, MODE_MOUSE);
1555 xsetmode(set, MODE_MOUSEX10);
1556 break;
1557 case 1000: /* 1000: report button press */
1558 xsetpointermotion(0);
1559 xsetmode(0, MODE_MOUSE);
1560 xsetmode(set, MODE_MOUSEBTN);
1561 break;
1562 case 1002: /* 1002: report motion on button press */
1563 xsetpointermotion(0);
1564 xsetmode(0, MODE_MOUSE);
1565 xsetmode(set, MODE_MOUSEMOTION);
1566 break;
1567 case 1003: /* 1003: enable all mouse motions */
1568 xsetpointermotion(set);
1569 xsetmode(0, MODE_MOUSE);
1570 xsetmode(set, MODE_MOUSEMANY);
1571 break;
1572 case 1004: /* 1004: send focus events to tty */
1573 xsetmode(set, MODE_FOCUS);
1574 break;
1575 case 1006: /* 1006: extended reporting mode */
1576 xsetmode(set, MODE_MOUSESGR);
1577 break;
1578 case 1034:
1579 xsetmode(set, MODE_8BIT);
1580 break;
1581 case 1049: /* swap screen & set/restore cursor as xterm */
1582 if (!allowaltscreen)
1583 break;
1584 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1585 /* FALLTHROUGH */
1586 case 47: /* swap screen */
1587 case 1047:
1588 if (!allowaltscreen)
1589 break;
1590 alt = IS_SET(MODE_ALTSCREEN);
1591 if (alt) {
1592 tclearregion(0, 0, term.col-1,
1593 term.row-1);
1594 }
1595 if (set ^ alt) /* set is always 1 or 0 */
1596 tswapscreen();
1597 if (*args != 1049)
1598 break;
1599 /* FALLTHROUGH */
1600 case 1048:
1601 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1602 break;
1603 case 2004: /* 2004: bracketed paste mode */
1604 xsetmode(set, MODE_BRCKTPASTE);
1605 break;
1606 /* Not implemented mouse modes. See comments there. */
1607 case 1001: /* mouse highlight mode; can hang the
1608 terminal by design when implemented. */
1609 case 1005: /* UTF-8 mouse mode; will confuse
1610 applications not supporting UTF-8
1611 and luit. */
1612 case 1015: /* urxvt mangled mouse mode; incompatible
1613 and can be mistaken for other control
1614 codes. */
1615 break;
1616 default:
1617 fprintf(stderr,
1618 "erresc: unknown private set/reset mode %d\n",
1619 *args);
1620 break;
1621 }
1622 } else {
1623 switch (*args) {
1624 case 0: /* Error (IGNORED) */
1625 break;
1626 case 2:
1627 xsetmode(set, MODE_KBDLOCK);
1628 break;
1629 case 4: /* IRM -- Insertion-replacement */
1630 MODBIT(term.mode, set, MODE_INSERT);
1631 break;
1632 case 12: /* SRM -- Send/Receive */
1633 MODBIT(term.mode, !set, MODE_ECHO);
1634 break;
1635 case 20: /* LNM -- Linefeed/new line */
1636 MODBIT(term.mode, set, MODE_CRLF);
1637 break;
1638 default:
1639 fprintf(stderr,
1640 "erresc: unknown set/reset mode %d\n",
1641 *args);
1642 break;
1643 }
1644 }
1645 }
1646}
1647
1648void
1649csihandle(void)
1650{
1651 char buf[40];
1652 int len;
1653
1654 switch (csiescseq.mode[0]) {
1655 default:
1656 unknown:
1657 fprintf(stderr, "erresc: unknown csi ");
1658 csidump();
1659 /* die(""); */
1660 break;
1661 case '@': /* ICH -- Insert <n> blank char */
1662 DEFAULT(csiescseq.arg[0], 1);
1663 tinsertblank(csiescseq.arg[0]);
1664 break;
1665 case 'A': /* CUU -- Cursor <n> Up */
1666 DEFAULT(csiescseq.arg[0], 1);
1667 tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
1668 break;
1669 case 'B': /* CUD -- Cursor <n> Down */
1670 case 'e': /* VPR --Cursor <n> Down */
1671 DEFAULT(csiescseq.arg[0], 1);
1672 tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
1673 break;
1674 case 'i': /* MC -- Media Copy */
1675 switch (csiescseq.arg[0]) {
1676 case 0:
1677 tdump();
1678 break;
1679 case 1:
1680 tdumpline(term.c.y);
1681 break;
1682 case 2:
1683 tdumpsel();
1684 break;
1685 case 4:
1686 term.mode &= ~MODE_PRINT;
1687 break;
1688 case 5:
1689 term.mode |= MODE_PRINT;
1690 break;
1691 }
1692 break;
1693 case 'c': /* DA -- Device Attributes */
1694 if (csiescseq.arg[0] == 0)
1695 ttywrite(vtiden, strlen(vtiden), 0);
1696 break;
1697 case 'b': /* REP -- if last char is printable print it <n> more times */
1698 DEFAULT(csiescseq.arg[0], 1);
1699 if (term.lastc)
1700 while (csiescseq.arg[0]-- > 0)
1701 tputc(term.lastc);
1702 break;
1703 case 'C': /* CUF -- Cursor <n> Forward */
1704 case 'a': /* HPR -- Cursor <n> Forward */
1705 DEFAULT(csiescseq.arg[0], 1);
1706 tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
1707 break;
1708 case 'D': /* CUB -- Cursor <n> Backward */
1709 DEFAULT(csiescseq.arg[0], 1);
1710 tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
1711 break;
1712 case 'E': /* CNL -- Cursor <n> Down and first col */
1713 DEFAULT(csiescseq.arg[0], 1);
1714 tmoveto(0, term.c.y+csiescseq.arg[0]);
1715 break;
1716 case 'F': /* CPL -- Cursor <n> Up and first col */
1717 DEFAULT(csiescseq.arg[0], 1);
1718 tmoveto(0, term.c.y-csiescseq.arg[0]);
1719 break;
1720 case 'g': /* TBC -- Tabulation clear */
1721 switch (csiescseq.arg[0]) {
1722 case 0: /* clear current tab stop */
1723 term.tabs[term.c.x] = 0;
1724 break;
1725 case 3: /* clear all the tabs */
1726 memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1727 break;
1728 default:
1729 goto unknown;
1730 }
1731 break;
1732 case 'G': /* CHA -- Move to <col> */
1733 case '`': /* HPA */
1734 DEFAULT(csiescseq.arg[0], 1);
1735 tmoveto(csiescseq.arg[0]-1, term.c.y);
1736 break;
1737 case 'H': /* CUP -- Move to <row> <col> */
1738 case 'f': /* HVP */
1739 DEFAULT(csiescseq.arg[0], 1);
1740 DEFAULT(csiescseq.arg[1], 1);
1741 tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
1742 break;
1743 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1744 DEFAULT(csiescseq.arg[0], 1);
1745 tputtab(csiescseq.arg[0]);
1746 break;
1747 case 'J': /* ED -- Clear screen */
1748 switch (csiescseq.arg[0]) {
1749 case 0: /* below */
1750 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
1751 if (term.c.y < term.row-1) {
1752 tclearregion(0, term.c.y+1, term.col-1,
1753 term.row-1);
1754 }
1755 break;
1756 case 1: /* above */
1757 if (term.c.y > 1)
1758 tclearregion(0, 0, term.col-1, term.c.y-1);
1759 tclearregion(0, term.c.y, term.c.x, term.c.y);
1760 break;
1761 case 2: /* all */
1762 tclearregion(0, 0, term.col-1, term.row-1);
1763 break;
1764 default:
1765 goto unknown;
1766 }
1767 break;
1768 case 'K': /* EL -- Clear line */
1769 switch (csiescseq.arg[0]) {
1770 case 0: /* right */
1771 tclearregion(term.c.x, term.c.y, term.col-1,
1772 term.c.y);
1773 break;
1774 case 1: /* left */
1775 tclearregion(0, term.c.y, term.c.x, term.c.y);
1776 break;
1777 case 2: /* all */
1778 tclearregion(0, term.c.y, term.col-1, term.c.y);
1779 break;
1780 }
1781 break;
1782 case 'S': /* SU -- Scroll <n> line up */
1783 DEFAULT(csiescseq.arg[0], 1);
1784 tscrollup(term.top, csiescseq.arg[0]);
1785 break;
1786 case 'T': /* SD -- Scroll <n> line down */
1787 DEFAULT(csiescseq.arg[0], 1);
1788 tscrolldown(term.top, csiescseq.arg[0]);
1789 break;
1790 case 'L': /* IL -- Insert <n> blank lines */
1791 DEFAULT(csiescseq.arg[0], 1);
1792 tinsertblankline(csiescseq.arg[0]);
1793 break;
1794 case 'l': /* RM -- Reset Mode */
1795 tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
1796 break;
1797 case 'M': /* DL -- Delete <n> lines */
1798 DEFAULT(csiescseq.arg[0], 1);
1799 tdeleteline(csiescseq.arg[0]);
1800 break;
1801 case 'X': /* ECH -- Erase <n> char */
1802 DEFAULT(csiescseq.arg[0], 1);
1803 tclearregion(term.c.x, term.c.y,
1804 term.c.x + csiescseq.arg[0] - 1, term.c.y);
1805 break;
1806 case 'P': /* DCH -- Delete <n> char */
1807 DEFAULT(csiescseq.arg[0], 1);
1808 tdeletechar(csiescseq.arg[0]);
1809 break;
1810 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1811 DEFAULT(csiescseq.arg[0], 1);
1812 tputtab(-csiescseq.arg[0]);
1813 break;
1814 case 'd': /* VPA -- Move to <row> */
1815 DEFAULT(csiescseq.arg[0], 1);
1816 tmoveato(term.c.x, csiescseq.arg[0]-1);
1817 break;
1818 case 'h': /* SM -- Set terminal mode */
1819 tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
1820 break;
1821 case 'm': /* SGR -- Terminal attribute (color) */
1822 tsetattr(csiescseq.arg, csiescseq.narg);
1823 break;
1824 case 'n': /* DSR -- Device Status Report */
1825 switch (csiescseq.arg[0]) {
1826 case 5: /* Status Report "OK" `0n` */
1827 ttywrite("\033[0n", sizeof("\033[0n") - 1, 0);
1828 break;
1829 case 6: /* Report Cursor Position (CPR) "<row>;<column>R" */
1830 len = snprintf(buf, sizeof(buf), "\033[%i;%iR",
1831 term.c.y+1, term.c.x+1);
1832 ttywrite(buf, len, 0);
1833 break;
1834 default:
1835 goto unknown;
1836 }
1837 break;
1838 case 'r': /* DECSTBM -- Set Scrolling Region */
1839 if (csiescseq.priv) {
1840 goto unknown;
1841 } else {
1842 DEFAULT(csiescseq.arg[0], 1);
1843 DEFAULT(csiescseq.arg[1], term.row);
1844 tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
1845 tmoveato(0, 0);
1846 }
1847 break;
1848 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1849 tcursor(CURSOR_SAVE);
1850 break;
1851 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1852 tcursor(CURSOR_LOAD);
1853 break;
1854 case ' ':
1855 switch (csiescseq.mode[1]) {
1856 case 'q': /* DECSCUSR -- Set Cursor Style */
1857 if (xsetcursor(csiescseq.arg[0]))
1858 goto unknown;
1859 break;
1860 default:
1861 goto unknown;
1862 }
1863 break;
1864 }
1865}
1866
1867void
1868csidump(void)
1869{
1870 size_t i;
1871 uint c;
1872
1873 fprintf(stderr, "ESC[");
1874 for (i = 0; i < csiescseq.len; i++) {
1875 c = csiescseq.buf[i] & 0xff;
1876 if (isprint(c)) {
1877 putc(c, stderr);
1878 } else if (c == '\n') {
1879 fprintf(stderr, "(\\n)");
1880 } else if (c == '\r') {
1881 fprintf(stderr, "(\\r)");
1882 } else if (c == 0x1b) {
1883 fprintf(stderr, "(\\e)");
1884 } else {
1885 fprintf(stderr, "(%02x)", c);
1886 }
1887 }
1888 putc('\n', stderr);
1889}
1890
1891void
1892csireset(void)
1893{
1894 memset(&csiescseq, 0, sizeof(csiescseq));
1895}
1896
1897void
1898osc_color_response(int num, int index, int is_osc4)
1899{
1900 int n;
1901 char buf[32];
1902 unsigned char r, g, b;
1903
1904 if (xgetcolor(is_osc4 ? num : index, &r, &g, &b)) {
1905 fprintf(stderr, "erresc: failed to fetch %s color %d\n",
1906 is_osc4 ? "osc4" : "osc",
1907 is_osc4 ? num : index);
1908 return;
1909 }
1910
1911 n = snprintf(buf, sizeof buf, "\033]%s%d;rgb:%02x%02x/%02x%02x/%02x%02x\007",
1912 is_osc4 ? "4;" : "", num, r, r, g, g, b, b);
1913 if (n < 0 || n >= sizeof(buf)) {
1914 fprintf(stderr, "error: %s while printing %s response\n",
1915 n < 0 ? "snprintf failed" : "truncation occurred",
1916 is_osc4 ? "osc4" : "osc");
1917 } else {
1918 ttywrite(buf, n, 1);
1919 }
1920}
1921
1922void
1923strhandle(void)
1924{
1925 char *p = NULL, *dec;
1926 int j, narg, par;
1927 const struct { int idx; char *str; } osc_table[] = {
1928 { defaultfg, "foreground" },
1929 { defaultbg, "background" },
1930 { defaultcs, "cursor" }
1931 };
1932
1933 term.esc &= ~(ESC_STR_END|ESC_STR);
1934 strparse();
1935 par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
1936
1937 switch (strescseq.type) {
1938 case ']': /* OSC -- Operating System Command */
1939 switch (par) {
1940 case 0:
1941 if (narg > 1) {
1942 xsettitle(strescseq.args[1]);
1943 xseticontitle(strescseq.args[1]);
1944 }
1945 return;
1946 case 1:
1947 if (narg > 1)
1948 xseticontitle(strescseq.args[1]);
1949 return;
1950 case 2:
1951 if (narg > 1)
1952 xsettitle(strescseq.args[1]);
1953 return;
1954 case 52:
1955 if (narg > 2 && allowwindowops) {
1956 dec = base64dec(strescseq.args[2]);
1957 if (dec) {
1958 xsetsel(dec);
1959 xclipcopy();
1960 } else {
1961 fprintf(stderr, "erresc: invalid base64\n");
1962 }
1963 }
1964 return;
1965 case 10:
1966 case 11:
1967 case 12:
1968 if (narg < 2)
1969 break;
1970 p = strescseq.args[1];
1971 if ((j = par - 10) < 0 || j >= LEN(osc_table))
1972 break; /* shouldn't be possible */
1973
1974 if (!strcmp(p, "?")) {
1975 osc_color_response(par, osc_table[j].idx, 0);
1976 } else if (xsetcolorname(osc_table[j].idx, p)) {
1977 fprintf(stderr, "erresc: invalid %s color: %s\n",
1978 osc_table[j].str, p);
1979 } else {
1980 tfulldirt();
1981 }
1982 return;
1983 case 4: /* color set */
1984 if (narg < 3)
1985 break;
1986 p = strescseq.args[2];
1987 /* FALLTHROUGH */
1988 case 104: /* color reset */
1989 j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
1990
1991 if (p && !strcmp(p, "?")) {
1992 osc_color_response(j, 0, 1);
1993 } else if (xsetcolorname(j, p)) {
1994 if (par == 104 && narg <= 1) {
1995 xloadcols();
1996 return; /* color reset without parameter */
1997 }
1998 fprintf(stderr, "erresc: invalid color j=%d, p=%s\n",
1999 j, p ? p : "(null)");
2000 } else {
2001 /*
2002 * TODO if defaultbg color is changed, borders
2003 * are dirty
2004 */
2005 tfulldirt();
2006 }
2007 return;
2008 }
2009 break;
2010 case 'k': /* old title set compatibility */
2011 xsettitle(strescseq.args[0]);
2012 return;
2013 case 'P': /* DCS -- Device Control String */
2014 case '_': /* APC -- Application Program Command */
2015 case '^': /* PM -- Privacy Message */
2016 return;
2017 }
2018
2019 fprintf(stderr, "erresc: unknown str ");
2020 strdump();
2021}
2022
2023void
2024strparse(void)
2025{
2026 int c;
2027 char *p = strescseq.buf;
2028
2029 strescseq.narg = 0;
2030 strescseq.buf[strescseq.len] = '\0';
2031
2032 if (*p == '\0')
2033 return;
2034
2035 while (strescseq.narg < STR_ARG_SIZ) {
2036 strescseq.args[strescseq.narg++] = p;
2037 while ((c = *p) != ';' && c != '\0')
2038 ++p;
2039 if (c == '\0')
2040 return;
2041 *p++ = '\0';
2042 }
2043}
2044
2045void
2046strdump(void)
2047{
2048 size_t i;
2049 uint c;
2050
2051 fprintf(stderr, "ESC%c", strescseq.type);
2052 for (i = 0; i < strescseq.len; i++) {
2053 c = strescseq.buf[i] & 0xff;
2054 if (c == '\0') {
2055 putc('\n', stderr);
2056 return;
2057 } else if (isprint(c)) {
2058 putc(c, stderr);
2059 } else if (c == '\n') {
2060 fprintf(stderr, "(\\n)");
2061 } else if (c == '\r') {
2062 fprintf(stderr, "(\\r)");
2063 } else if (c == 0x1b) {
2064 fprintf(stderr, "(\\e)");
2065 } else {
2066 fprintf(stderr, "(%02x)", c);
2067 }
2068 }
2069 fprintf(stderr, "ESC\\\n");
2070}
2071
2072void
2073strreset(void)
2074{
2075 strescseq = (STREscape){
2076 .buf = xrealloc(strescseq.buf, STR_BUF_SIZ),
2077 .siz = STR_BUF_SIZ,
2078 };
2079}
2080
2081void
2082sendbreak(const Arg *arg)
2083{
2084 if (tcsendbreak(cmdfd, 0))
2085 perror("Error sending break");
2086}
2087
2088void
2089tprinter(char *s, size_t len)
2090{
2091 if (iofd != -1 && xwrite(iofd, s, len) < 0) {
2092 perror("Error writing to output file");
2093 close(iofd);
2094 iofd = -1;
2095 }
2096}
2097
2098void
2099toggleprinter(const Arg *arg)
2100{
2101 term.mode ^= MODE_PRINT;
2102}
2103
2104void
2105printscreen(const Arg *arg)
2106{
2107 tdump();
2108}
2109
2110void
2111printsel(const Arg *arg)
2112{
2113 tdumpsel();
2114}
2115
2116void
2117tdumpsel(void)
2118{
2119 char *ptr;
2120
2121 if ((ptr = getsel())) {
2122 tprinter(ptr, strlen(ptr));
2123 free(ptr);
2124 }
2125}
2126
2127void
2128tdumpline(int n)
2129{
2130 char buf[UTF_SIZ];
2131 const Glyph *bp, *end;
2132
2133 bp = &term.line[n][0];
2134 end = &bp[MIN(tlinelen(n), term.col) - 1];
2135 if (bp != end || bp->u != ' ') {
2136 for ( ; bp <= end; ++bp)
2137 tprinter(buf, utf8encode(bp->u, buf));
2138 }
2139 tprinter("\n", 1);
2140}
2141
2142void
2143tdump(void)
2144{
2145 int i;
2146
2147 for (i = 0; i < term.row; ++i)
2148 tdumpline(i);
2149}
2150
2151void
2152tputtab(int n)
2153{
2154 uint x = term.c.x;
2155
2156 if (n > 0) {
2157 while (x < term.col && n--)
2158 for (++x; x < term.col && !term.tabs[x]; ++x)
2159 /* nothing */ ;
2160 } else if (n < 0) {
2161 while (x > 0 && n++)
2162 for (--x; x > 0 && !term.tabs[x]; --x)
2163 /* nothing */ ;
2164 }
2165 term.c.x = LIMIT(x, 0, term.col-1);
2166}
2167
2168void
2169tdefutf8(char ascii)
2170{
2171 if (ascii == 'G')
2172 term.mode |= MODE_UTF8;
2173 else if (ascii == '@')
2174 term.mode &= ~MODE_UTF8;
2175}
2176
2177void
2178tdeftran(char ascii)
2179{
2180 static char cs[] = "0B";
2181 static int vcs[] = {CS_GRAPHIC0, CS_USA};
2182 char *p;
2183
2184 if ((p = strchr(cs, ascii)) == NULL) {
2185 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
2186 } else {
2187 term.trantbl[term.icharset] = vcs[p - cs];
2188 }
2189}
2190
2191void
2192tdectest(char c)
2193{
2194 int x, y;
2195
2196 if (c == '8') { /* DEC screen alignment test. */
2197 for (x = 0; x < term.col; ++x) {
2198 for (y = 0; y < term.row; ++y)
2199 tsetchar('E', &term.c.attr, x, y);
2200 }
2201 }
2202}
2203
2204void
2205tstrsequence(uchar c)
2206{
2207 switch (c) {
2208 case 0x90: /* DCS -- Device Control String */
2209 c = 'P';
2210 break;
2211 case 0x9f: /* APC -- Application Program Command */
2212 c = '_';
2213 break;
2214 case 0x9e: /* PM -- Privacy Message */
2215 c = '^';
2216 break;
2217 case 0x9d: /* OSC -- Operating System Command */
2218 c = ']';
2219 break;
2220 }
2221 strreset();
2222 strescseq.type = c;
2223 term.esc |= ESC_STR;
2224}
2225
2226void
2227tcontrolcode(uchar ascii)
2228{
2229 switch (ascii) {
2230 case '\t': /* HT */
2231 tputtab(1);
2232 return;
2233 case '\b': /* BS */
2234 tmoveto(term.c.x-1, term.c.y);
2235 return;
2236 case '\r': /* CR */
2237 tmoveto(0, term.c.y);
2238 return;
2239 case '\f': /* LF */
2240 case '\v': /* VT */
2241 case '\n': /* LF */
2242 /* go to first col if the mode is set */
2243 tnewline(IS_SET(MODE_CRLF));
2244 return;
2245 case '\a': /* BEL */
2246 if (term.esc & ESC_STR_END) {
2247 /* backwards compatibility to xterm */
2248 strhandle();
2249 } else {
2250 xbell();
2251 }
2252 break;
2253 case '\033': /* ESC */
2254 csireset();
2255 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
2256 term.esc |= ESC_START;
2257 return;
2258 case '\016': /* SO (LS1 -- Locking shift 1) */
2259 case '\017': /* SI (LS0 -- Locking shift 0) */
2260 term.charset = 1 - (ascii - '\016');
2261 return;
2262 case '\032': /* SUB */
2263 tsetchar('?', &term.c.attr, term.c.x, term.c.y);
2264 /* FALLTHROUGH */
2265 case '\030': /* CAN */
2266 csireset();
2267 break;
2268 case '\005': /* ENQ (IGNORED) */
2269 case '\000': /* NUL (IGNORED) */
2270 case '\021': /* XON (IGNORED) */
2271 case '\023': /* XOFF (IGNORED) */
2272 case 0177: /* DEL (IGNORED) */
2273 return;
2274 case 0x80: /* TODO: PAD */
2275 case 0x81: /* TODO: HOP */
2276 case 0x82: /* TODO: BPH */
2277 case 0x83: /* TODO: NBH */
2278 case 0x84: /* TODO: IND */
2279 break;
2280 case 0x85: /* NEL -- Next line */
2281 tnewline(1); /* always go to first col */
2282 break;
2283 case 0x86: /* TODO: SSA */
2284 case 0x87: /* TODO: ESA */
2285 break;
2286 case 0x88: /* HTS -- Horizontal tab stop */
2287 term.tabs[term.c.x] = 1;
2288 break;
2289 case 0x89: /* TODO: HTJ */
2290 case 0x8a: /* TODO: VTS */
2291 case 0x8b: /* TODO: PLD */
2292 case 0x8c: /* TODO: PLU */
2293 case 0x8d: /* TODO: RI */
2294 case 0x8e: /* TODO: SS2 */
2295 case 0x8f: /* TODO: SS3 */
2296 case 0x91: /* TODO: PU1 */
2297 case 0x92: /* TODO: PU2 */
2298 case 0x93: /* TODO: STS */
2299 case 0x94: /* TODO: CCH */
2300 case 0x95: /* TODO: MW */
2301 case 0x96: /* TODO: SPA */
2302 case 0x97: /* TODO: EPA */
2303 case 0x98: /* TODO: SOS */
2304 case 0x99: /* TODO: SGCI */
2305 break;
2306 case 0x9a: /* DECID -- Identify Terminal */
2307 ttywrite(vtiden, strlen(vtiden), 0);
2308 break;
2309 case 0x9b: /* TODO: CSI */
2310 case 0x9c: /* TODO: ST */
2311 break;
2312 case 0x90: /* DCS -- Device Control String */
2313 case 0x9d: /* OSC -- Operating System Command */
2314 case 0x9e: /* PM -- Privacy Message */
2315 case 0x9f: /* APC -- Application Program Command */
2316 tstrsequence(ascii);
2317 return;
2318 }
2319 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2320 term.esc &= ~(ESC_STR_END|ESC_STR);
2321}
2322
2323/*
2324 * returns 1 when the sequence is finished and it hasn't to read
2325 * more characters for this sequence, otherwise 0
2326 */
2327int
2328eschandle(uchar ascii)
2329{
2330 switch (ascii) {
2331 case '[':
2332 term.esc |= ESC_CSI;
2333 return 0;
2334 case '#':
2335 term.esc |= ESC_TEST;
2336 return 0;
2337 case '%':
2338 term.esc |= ESC_UTF8;
2339 return 0;
2340 case 'P': /* DCS -- Device Control String */
2341 case '_': /* APC -- Application Program Command */
2342 case '^': /* PM -- Privacy Message */
2343 case ']': /* OSC -- Operating System Command */
2344 case 'k': /* old title set compatibility */
2345 tstrsequence(ascii);
2346 return 0;
2347 case 'n': /* LS2 -- Locking shift 2 */
2348 case 'o': /* LS3 -- Locking shift 3 */
2349 term.charset = 2 + (ascii - 'n');
2350 break;
2351 case '(': /* GZD4 -- set primary charset G0 */
2352 case ')': /* G1D4 -- set secondary charset G1 */
2353 case '*': /* G2D4 -- set tertiary charset G2 */
2354 case '+': /* G3D4 -- set quaternary charset G3 */
2355 term.icharset = ascii - '(';
2356 term.esc |= ESC_ALTCHARSET;
2357 return 0;
2358 case 'D': /* IND -- Linefeed */
2359 if (term.c.y == term.bot) {
2360 tscrollup(term.top, 1);
2361 } else {
2362 tmoveto(term.c.x, term.c.y+1);
2363 }
2364 break;
2365 case 'E': /* NEL -- Next line */
2366 tnewline(1); /* always go to first col */
2367 break;
2368 case 'H': /* HTS -- Horizontal tab stop */
2369 term.tabs[term.c.x] = 1;
2370 break;
2371 case 'M': /* RI -- Reverse index */
2372 if (term.c.y == term.top) {
2373 tscrolldown(term.top, 1);
2374 } else {
2375 tmoveto(term.c.x, term.c.y-1);
2376 }
2377 break;
2378 case 'Z': /* DECID -- Identify Terminal */
2379 ttywrite(vtiden, strlen(vtiden), 0);
2380 break;
2381 case 'c': /* RIS -- Reset to initial state */
2382 treset();
2383 resettitle();
2384 xloadcols();
2385 xsetmode(0, MODE_HIDE);
2386 break;
2387 case '=': /* DECPAM -- Application keypad */
2388 xsetmode(1, MODE_APPKEYPAD);
2389 break;
2390 case '>': /* DECPNM -- Normal keypad */
2391 xsetmode(0, MODE_APPKEYPAD);
2392 break;
2393 case '7': /* DECSC -- Save Cursor */
2394 tcursor(CURSOR_SAVE);
2395 break;
2396 case '8': /* DECRC -- Restore Cursor */
2397 tcursor(CURSOR_LOAD);
2398 break;
2399 case '\\': /* ST -- String Terminator */
2400 if (term.esc & ESC_STR_END)
2401 strhandle();
2402 break;
2403 default:
2404 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2405 (uchar) ascii, isprint(ascii)? ascii:'.');
2406 break;
2407 }
2408 return 1;
2409}
2410
2411void
2412tputc(Rune u)
2413{
2414 char c[UTF_SIZ];
2415 int control;
2416 int width, len;
2417 Glyph *gp;
2418
2419 control = ISCONTROL(u);
2420 if (u < 127 || !IS_SET(MODE_UTF8)) {
2421 c[0] = u;
2422 width = len = 1;
2423 } else {
2424 len = utf8encode(u, c);
2425 if (!control && (width = wcwidth(u)) == -1)
2426 width = 1;
2427 }
2428
2429 if (IS_SET(MODE_PRINT))
2430 tprinter(c, len);
2431
2432 /*
2433 * STR sequence must be checked before anything else
2434 * because it uses all following characters until it
2435 * receives a ESC, a SUB, a ST or any other C1 control
2436 * character.
2437 */
2438 if (term.esc & ESC_STR) {
2439 if (u == '\a' || u == 030 || u == 032 || u == 033 ||
2440 ISCONTROLC1(u)) {
2441 term.esc &= ~(ESC_START|ESC_STR);
2442 term.esc |= ESC_STR_END;
2443 goto check_control_code;
2444 }
2445
2446 if (strescseq.len+len >= strescseq.siz) {
2447 /*
2448 * Here is a bug in terminals. If the user never sends
2449 * some code to stop the str or esc command, then st
2450 * will stop responding. But this is better than
2451 * silently failing with unknown characters. At least
2452 * then users will report back.
2453 *
2454 * In the case users ever get fixed, here is the code:
2455 */
2456 /*
2457 * term.esc = 0;
2458 * strhandle();
2459 */
2460 if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2)
2461 return;
2462 strescseq.siz *= 2;
2463 strescseq.buf = xrealloc(strescseq.buf, strescseq.siz);
2464 }
2465
2466 memmove(&strescseq.buf[strescseq.len], c, len);
2467 strescseq.len += len;
2468 return;
2469 }
2470
2471check_control_code:
2472 /*
2473 * Actions of control codes must be performed as soon they arrive
2474 * because they can be embedded inside a control sequence, and
2475 * they must not cause conflicts with sequences.
2476 */
2477 if (control) {
2478 /* in UTF-8 mode ignore handling C1 control characters */
2479 if (IS_SET(MODE_UTF8) && ISCONTROLC1(u))
2480 return;
2481 tcontrolcode(u);
2482 /*
2483 * control codes are not shown ever
2484 */
2485 if (!term.esc)
2486 term.lastc = 0;
2487 return;
2488 } else if (term.esc & ESC_START) {
2489 if (term.esc & ESC_CSI) {
2490 csiescseq.buf[csiescseq.len++] = u;
2491 if (BETWEEN(u, 0x40, 0x7E)
2492 || csiescseq.len >= \
2493 sizeof(csiescseq.buf)-1) {
2494 term.esc = 0;
2495 csiparse();
2496 csihandle();
2497 }
2498 return;
2499 } else if (term.esc & ESC_UTF8) {
2500 tdefutf8(u);
2501 } else if (term.esc & ESC_ALTCHARSET) {
2502 tdeftran(u);
2503 } else if (term.esc & ESC_TEST) {
2504 tdectest(u);
2505 } else {
2506 if (!eschandle(u))
2507 return;
2508 /* sequence already finished */
2509 }
2510 term.esc = 0;
2511 /*
2512 * All characters which form part of a sequence are not
2513 * printed
2514 */
2515 return;
2516 }
2517 if (selected(term.c.x, term.c.y))
2518 selclear();
2519
2520 gp = &term.line[term.c.y][term.c.x];
2521 if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
2522 gp->mode |= ATTR_WRAP;
2523 tnewline(1);
2524 gp = &term.line[term.c.y][term.c.x];
2525 }
2526
2527 if (IS_SET(MODE_INSERT) && term.c.x+width < term.col) {
2528 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
2529 gp->mode &= ~ATTR_WIDE;
2530 }
2531
2532 if (term.c.x+width > term.col) {
2533 if (IS_SET(MODE_WRAP))
2534 tnewline(1);
2535 else
2536 tmoveto(term.col - width, term.c.y);
2537 gp = &term.line[term.c.y][term.c.x];
2538 }
2539
2540 tsetchar(u, &term.c.attr, term.c.x, term.c.y);
2541 term.lastc = u;
2542
2543 if (width == 2) {
2544 gp->mode |= ATTR_WIDE;
2545 if (term.c.x+1 < term.col) {
2546 if (gp[1].mode == ATTR_WIDE && term.c.x+2 < term.col) {
2547 gp[2].u = ' ';
2548 gp[2].mode &= ~ATTR_WDUMMY;
2549 }
2550 gp[1].u = '\0';
2551 gp[1].mode = ATTR_WDUMMY;
2552 }
2553 }
2554 if (term.c.x+width < term.col) {
2555 tmoveto(term.c.x+width, term.c.y);
2556 } else {
2557 term.c.state |= CURSOR_WRAPNEXT;
2558 }
2559}
2560
2561int
2562twrite(const char *buf, int buflen, int show_ctrl)
2563{
2564 int charsize;
2565 Rune u;
2566 int n;
2567
2568 for (n = 0; n < buflen; n += charsize) {
2569 if (IS_SET(MODE_UTF8)) {
2570 /* process a complete utf8 char */
2571 charsize = utf8decode(buf + n, &u, buflen - n);
2572 if (charsize == 0)
2573 break;
2574 } else {
2575 u = buf[n] & 0xFF;
2576 charsize = 1;
2577 }
2578 if (show_ctrl && ISCONTROL(u)) {
2579 if (u & 0x80) {
2580 u &= 0x7f;
2581 tputc('^');
2582 tputc('[');
2583 } else if (u != '\n' && u != '\r' && u != '\t') {
2584 u ^= 0x40;
2585 tputc('^');
2586 }
2587 }
2588 tputc(u);
2589 }
2590 return n;
2591}
2592
2593void
2594tresize(int col, int row)
2595{
2596 int i;
2597 int minrow = MIN(row, term.row);
2598 int mincol = MIN(col, term.col);
2599 int *bp;
2600 TCursor c;
2601
2602 if (col < 1 || row < 1) {
2603 fprintf(stderr,
2604 "tresize: error resizing to %dx%d\n", col, row);
2605 return;
2606 }
2607
2608 /*
2609 * slide screen to keep cursor where we expect it -
2610 * tscrollup would work here, but we can optimize to
2611 * memmove because we're freeing the earlier lines
2612 */
2613 for (i = 0; i <= term.c.y - row; i++) {
2614 free(term.line[i]);
2615 free(term.alt[i]);
2616 }
2617 /* ensure that both src and dst are not NULL */
2618 if (i > 0) {
2619 memmove(term.line, term.line + i, row * sizeof(Line));
2620 memmove(term.alt, term.alt + i, row * sizeof(Line));
2621 }
2622 for (i += row; i < term.row; i++) {
2623 free(term.line[i]);
2624 free(term.alt[i]);
2625 }
2626
2627 /* resize to new height */
2628 term.line = xrealloc(term.line, row * sizeof(Line));
2629 term.alt = xrealloc(term.alt, row * sizeof(Line));
2630 term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
2631 term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
2632
2633 /* resize each row to new width, zero-pad if needed */
2634 for (i = 0; i < minrow; i++) {
2635 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
2636 term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph));
2637 }
2638
2639 /* allocate any new rows */
2640 for (/* i = minrow */; i < row; i++) {
2641 term.line[i] = xmalloc(col * sizeof(Glyph));
2642 term.alt[i] = xmalloc(col * sizeof(Glyph));
2643 }
2644 if (col > term.col) {
2645 bp = term.tabs + term.col;
2646
2647 memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
2648 while (--bp > term.tabs && !*bp)
2649 /* nothing */ ;
2650 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
2651 *bp = 1;
2652 }
2653 /* update terminal size */
2654 term.col = col;
2655 term.row = row;
2656 /* reset scrolling region */
2657 tsetscroll(0, row-1);
2658 /* make use of the LIMIT in tmoveto */
2659 tmoveto(term.c.x, term.c.y);
2660 /* Clearing both screens (it makes dirty all lines) */
2661 c = term.c;
2662 for (i = 0; i < 2; i++) {
2663 if (mincol < col && 0 < minrow) {
2664 tclearregion(mincol, 0, col - 1, minrow - 1);
2665 }
2666 if (0 < col && minrow < row) {
2667 tclearregion(0, minrow, col - 1, row - 1);
2668 }
2669 tswapscreen();
2670 tcursor(CURSOR_LOAD);
2671 }
2672 term.c = c;
2673}
2674
2675void
2676resettitle(void)
2677{
2678 xsettitle(NULL);
2679}
2680
2681void
2682drawregion(int x1, int y1, int x2, int y2)
2683{
2684 int y;
2685
2686 for (y = y1; y < y2; y++) {
2687 if (!term.dirty[y])
2688 continue;
2689
2690 term.dirty[y] = 0;
2691 xdrawline(term.line[y], x1, y, x2);
2692 }
2693}
2694
2695void
2696draw(void)
2697{
2698 int cx = term.c.x, ocx = term.ocx, ocy = term.ocy;
2699
2700 if (!xstartdraw())
2701 return;
2702
2703 /* adjust cursor position */
2704 LIMIT(term.ocx, 0, term.col-1);
2705 LIMIT(term.ocy, 0, term.row-1);
2706 if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY)
2707 term.ocx--;
2708 if (term.line[term.c.y][cx].mode & ATTR_WDUMMY)
2709 cx--;
2710
2711 drawregion(0, 0, term.col, term.row);
2712 xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
2713 term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
2714 term.ocx = cx;
2715 term.ocy = term.c.y;
2716 xfinishdraw();
2717 if (ocx != term.ocx || ocy != term.ocy)
2718 xximspot(term.ocx, term.ocy);
2719}
2720
2721void
2722redraw(void)
2723{
2724 tfulldirt();
2725 draw();
2726}