Xinqi Bao's Git

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