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