Xinqi Bao's Git

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