Xinqi Bao's Git

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