Xinqi Bao's Git

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