Xinqi Bao's Git

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