Xinqi Bao's Git

ttyread: test for EOF while reading tty
[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)) (*src)++;
370 return **src ? *((*src)++) : '='; /* emulate padding if string ends */
371 }
372
373 char *
374 base64dec(const char *src)
375 {
376 size_t in_len = strlen(src);
377 char *result, *dst;
378
379 if (in_len % 4)
380 in_len += 4 - (in_len % 4);
381 result = dst = xmalloc(in_len / 4 * 3 + 1);
382 while (*src) {
383 int a = base64_digits[(unsigned char) base64dec_getc(&src)];
384 int b = base64_digits[(unsigned char) base64dec_getc(&src)];
385 int c = base64_digits[(unsigned char) base64dec_getc(&src)];
386 int d = base64_digits[(unsigned char) base64dec_getc(&src)];
387
388 /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */
389 if (a == -1 || b == -1)
390 break;
391
392 *dst++ = (a << 2) | ((b & 0x30) >> 4);
393 if (c == -1)
394 break;
395 *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2);
396 if (d == -1)
397 break;
398 *dst++ = ((c & 0x03) << 6) | d;
399 }
400 *dst = '\0';
401 return result;
402 }
403
404 void
405 selinit(void)
406 {
407 sel.mode = SEL_IDLE;
408 sel.snap = 0;
409 sel.ob.x = -1;
410 }
411
412 int
413 tlinelen(int y)
414 {
415 int i = term.col;
416
417 if (term.line[y][i - 1].mode & ATTR_WRAP)
418 return i;
419
420 while (i > 0 && term.line[y][i - 1].u == ' ')
421 --i;
422
423 return i;
424 }
425
426 void
427 selstart(int col, int row, int snap)
428 {
429 selclear();
430 sel.mode = SEL_EMPTY;
431 sel.type = SEL_REGULAR;
432 sel.alt = IS_SET(MODE_ALTSCREEN);
433 sel.snap = snap;
434 sel.oe.x = sel.ob.x = col;
435 sel.oe.y = sel.ob.y = row;
436 selnormalize();
437
438 if (sel.snap != 0)
439 sel.mode = SEL_READY;
440 tsetdirt(sel.nb.y, sel.ne.y);
441 }
442
443 void
444 selextend(int col, int row, int type, int done)
445 {
446 int oldey, oldex, oldsby, oldsey, oldtype;
447
448 if (sel.mode == SEL_IDLE)
449 return;
450 if (done && sel.mode == SEL_EMPTY) {
451 selclear();
452 return;
453 }
454
455 oldey = sel.oe.y;
456 oldex = sel.oe.x;
457 oldsby = sel.nb.y;
458 oldsey = sel.ne.y;
459 oldtype = sel.type;
460
461 sel.oe.x = col;
462 sel.oe.y = row;
463 selnormalize();
464 sel.type = type;
465
466 if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY)
467 tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey));
468
469 sel.mode = done ? SEL_IDLE : SEL_READY;
470 }
471
472 void
473 selnormalize(void)
474 {
475 int i;
476
477 if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) {
478 sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x;
479 sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x;
480 } else {
481 sel.nb.x = MIN(sel.ob.x, sel.oe.x);
482 sel.ne.x = MAX(sel.ob.x, sel.oe.x);
483 }
484 sel.nb.y = MIN(sel.ob.y, sel.oe.y);
485 sel.ne.y = MAX(sel.ob.y, sel.oe.y);
486
487 selsnap(&sel.nb.x, &sel.nb.y, -1);
488 selsnap(&sel.ne.x, &sel.ne.y, +1);
489
490 /* expand selection over line breaks */
491 if (sel.type == SEL_RECTANGULAR)
492 return;
493 i = tlinelen(sel.nb.y);
494 if (i < sel.nb.x)
495 sel.nb.x = i;
496 if (tlinelen(sel.ne.y) <= sel.ne.x)
497 sel.ne.x = term.col - 1;
498 }
499
500 int
501 selected(int x, int y)
502 {
503 if (sel.mode == SEL_EMPTY || sel.ob.x == -1 ||
504 sel.alt != IS_SET(MODE_ALTSCREEN))
505 return 0;
506
507 if (sel.type == SEL_RECTANGULAR)
508 return BETWEEN(y, sel.nb.y, sel.ne.y)
509 && BETWEEN(x, sel.nb.x, sel.ne.x);
510
511 return BETWEEN(y, sel.nb.y, sel.ne.y)
512 && (y != sel.nb.y || x >= sel.nb.x)
513 && (y != sel.ne.y || x <= sel.ne.x);
514 }
515
516 void
517 selsnap(int *x, int *y, int direction)
518 {
519 int newx, newy, xt, yt;
520 int delim, prevdelim;
521 Glyph *gp, *prevgp;
522
523 switch (sel.snap) {
524 case SNAP_WORD:
525 /*
526 * Snap around if the word wraps around at the end or
527 * beginning of a line.
528 */
529 prevgp = &term.line[*y][*x];
530 prevdelim = ISDELIM(prevgp->u);
531 for (;;) {
532 newx = *x + direction;
533 newy = *y;
534 if (!BETWEEN(newx, 0, term.col - 1)) {
535 newy += direction;
536 newx = (newx + term.col) % term.col;
537 if (!BETWEEN(newy, 0, term.row - 1))
538 break;
539
540 if (direction > 0)
541 yt = *y, xt = *x;
542 else
543 yt = newy, xt = newx;
544 if (!(term.line[yt][xt].mode & ATTR_WRAP))
545 break;
546 }
547
548 if (newx >= tlinelen(newy))
549 break;
550
551 gp = &term.line[newy][newx];
552 delim = ISDELIM(gp->u);
553 if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
554 || (delim && gp->u != prevgp->u)))
555 break;
556
557 *x = newx;
558 *y = newy;
559 prevgp = gp;
560 prevdelim = delim;
561 }
562 break;
563 case SNAP_LINE:
564 /*
565 * Snap around if the the previous line or the current one
566 * has set ATTR_WRAP at its end. Then the whole next or
567 * previous line will be selected.
568 */
569 *x = (direction < 0) ? 0 : term.col - 1;
570 if (direction < 0) {
571 for (; *y > 0; *y += direction) {
572 if (!(term.line[*y-1][term.col-1].mode
573 & ATTR_WRAP)) {
574 break;
575 }
576 }
577 } else if (direction > 0) {
578 for (; *y < term.row-1; *y += direction) {
579 if (!(term.line[*y][term.col-1].mode
580 & ATTR_WRAP)) {
581 break;
582 }
583 }
584 }
585 break;
586 }
587 }
588
589 char *
590 getsel(void)
591 {
592 char *str, *ptr;
593 int y, bufsize, lastx, linelen;
594 Glyph *gp, *last;
595
596 if (sel.ob.x == -1)
597 return NULL;
598
599 bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ;
600 ptr = str = xmalloc(bufsize);
601
602 /* append every set & selected glyph to the selection */
603 for (y = sel.nb.y; y <= sel.ne.y; y++) {
604 if ((linelen = tlinelen(y)) == 0) {
605 *ptr++ = '\n';
606 continue;
607 }
608
609 if (sel.type == SEL_RECTANGULAR) {
610 gp = &term.line[y][sel.nb.x];
611 lastx = sel.ne.x;
612 } else {
613 gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0];
614 lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
615 }
616 last = &term.line[y][MIN(lastx, linelen-1)];
617 while (last >= gp && last->u == ' ')
618 --last;
619
620 for ( ; gp <= last; ++gp) {
621 if (gp->mode & ATTR_WDUMMY)
622 continue;
623
624 ptr += utf8encode(gp->u, ptr);
625 }
626
627 /*
628 * Copy and pasting of line endings is inconsistent
629 * in the inconsistent terminal and GUI world.
630 * The best solution seems like to produce '\n' when
631 * something is copied from st and convert '\n' to
632 * '\r', when something to be pasted is received by
633 * st.
634 * FIXME: Fix the computer world.
635 */
636 if ((y < sel.ne.y || lastx >= linelen) && !(last->mode & ATTR_WRAP))
637 *ptr++ = '\n';
638 }
639 *ptr = 0;
640 return str;
641 }
642
643 void
644 selclear(void)
645 {
646 if (sel.ob.x == -1)
647 return;
648 sel.mode = SEL_IDLE;
649 sel.ob.x = -1;
650 tsetdirt(sel.nb.y, sel.ne.y);
651 }
652
653 void
654 die(const char *errstr, ...)
655 {
656 va_list ap;
657
658 va_start(ap, errstr);
659 vfprintf(stderr, errstr, ap);
660 va_end(ap);
661 exit(1);
662 }
663
664 void
665 execsh(char *cmd, char **args)
666 {
667 char *sh, *prog, *arg;
668 const struct passwd *pw;
669
670 errno = 0;
671 if ((pw = getpwuid(getuid())) == NULL) {
672 if (errno)
673 die("getpwuid: %s\n", strerror(errno));
674 else
675 die("who are you?\n");
676 }
677
678 if ((sh = getenv("SHELL")) == NULL)
679 sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd;
680
681 if (args) {
682 prog = args[0];
683 arg = NULL;
684 } else if (scroll || utmp) {
685 prog = scroll ? scroll : utmp;
686 arg = scroll ? utmp : NULL;
687 } else {
688 prog = sh;
689 arg = NULL;
690 }
691 DEFAULT(args, ((char *[]) {prog, arg, NULL}));
692
693 unsetenv("COLUMNS");
694 unsetenv("LINES");
695 unsetenv("TERMCAP");
696 setenv("LOGNAME", pw->pw_name, 1);
697 setenv("USER", pw->pw_name, 1);
698 setenv("SHELL", sh, 1);
699 setenv("HOME", pw->pw_dir, 1);
700 setenv("TERM", termname, 1);
701
702 signal(SIGCHLD, SIG_DFL);
703 signal(SIGHUP, SIG_DFL);
704 signal(SIGINT, SIG_DFL);
705 signal(SIGQUIT, SIG_DFL);
706 signal(SIGTERM, SIG_DFL);
707 signal(SIGALRM, SIG_DFL);
708
709 execvp(prog, args);
710 _exit(1);
711 }
712
713 void
714 sigchld(int a)
715 {
716 int stat;
717 pid_t p;
718
719 if ((p = waitpid(pid, &stat, WNOHANG)) < 0)
720 die("waiting for pid %hd failed: %s\n", pid, strerror(errno));
721
722 if (pid != p)
723 return;
724
725 if (WIFEXITED(stat) && WEXITSTATUS(stat))
726 die("child exited with status %d\n", WEXITSTATUS(stat));
727 else if (WIFSIGNALED(stat))
728 die("child terminated due to signal %d\n", WTERMSIG(stat));
729 exit(0);
730 }
731
732 void
733 stty(char **args)
734 {
735 char cmd[_POSIX_ARG_MAX], **p, *q, *s;
736 size_t n, siz;
737
738 if ((n = strlen(stty_args)) > sizeof(cmd)-1)
739 die("incorrect stty parameters\n");
740 memcpy(cmd, stty_args, n);
741 q = cmd + n;
742 siz = sizeof(cmd) - n;
743 for (p = args; p && (s = *p); ++p) {
744 if ((n = strlen(s)) > siz-1)
745 die("stty parameter length too long\n");
746 *q++ = ' ';
747 memcpy(q, s, n);
748 q += n;
749 siz -= n + 1;
750 }
751 *q = '\0';
752 if (system(cmd) != 0)
753 perror("Couldn't call stty");
754 }
755
756 int
757 ttynew(char *line, char *cmd, char *out, char **args)
758 {
759 int m, s;
760
761 if (out) {
762 term.mode |= MODE_PRINT;
763 iofd = (!strcmp(out, "-")) ?
764 1 : open(out, O_WRONLY | O_CREAT, 0666);
765 if (iofd < 0) {
766 fprintf(stderr, "Error opening %s:%s\n",
767 out, strerror(errno));
768 }
769 }
770
771 if (line) {
772 if ((cmdfd = open(line, O_RDWR)) < 0)
773 die("open line '%s' failed: %s\n",
774 line, strerror(errno));
775 dup2(cmdfd, 0);
776 stty(args);
777 return cmdfd;
778 }
779
780 /* seems to work fine on linux, openbsd and freebsd */
781 if (openpty(&m, &s, NULL, NULL, NULL) < 0)
782 die("openpty failed: %s\n", strerror(errno));
783
784 switch (pid = fork()) {
785 case -1:
786 die("fork failed: %s\n", strerror(errno));
787 break;
788 case 0:
789 close(iofd);
790 setsid(); /* create a new process group */
791 dup2(s, 0);
792 dup2(s, 1);
793 dup2(s, 2);
794 if (ioctl(s, TIOCSCTTY, NULL) < 0)
795 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno));
796 close(s);
797 close(m);
798 #ifdef __OpenBSD__
799 if (pledge("stdio getpw proc exec", NULL) == -1)
800 die("pledge\n");
801 #endif
802 execsh(cmd, args);
803 break;
804 default:
805 #ifdef __OpenBSD__
806 if (pledge("stdio rpath tty proc", NULL) == -1)
807 die("pledge\n");
808 #endif
809 close(s);
810 cmdfd = m;
811 signal(SIGCHLD, sigchld);
812 break;
813 }
814 return cmdfd;
815 }
816
817 size_t
818 ttyread(void)
819 {
820 static char buf[BUFSIZ];
821 static int buflen = 0;
822 int written;
823 int ret;
824
825 /* append read bytes to unprocessed bytes */
826 ret = read(cmdfd, buf+buflen, LEN(buf)-buflen);
827
828 switch (ret) {
829 case 0:
830 fputs("Found EOF in input\n", stderr);
831 exit(0);
832 case -1:
833 die("couldn't read from shell: %s\n", strerror(errno));
834 default:
835 buflen += ret;
836 written = twrite(buf, buflen, 0);
837 buflen -= written;
838 /* keep any uncomplete utf8 char for the next call */
839 if (buflen > 0)
840 memmove(buf, buf + written, buflen);
841 return ret;
842
843 }
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 break;
1577 default:
1578 fprintf(stderr,
1579 "erresc: unknown private set/reset mode %d\n",
1580 *args);
1581 break;
1582 }
1583 } else {
1584 switch (*args) {
1585 case 0: /* Error (IGNORED) */
1586 break;
1587 case 2:
1588 xsetmode(set, MODE_KBDLOCK);
1589 break;
1590 case 4: /* IRM -- Insertion-replacement */
1591 MODBIT(term.mode, set, MODE_INSERT);
1592 break;
1593 case 12: /* SRM -- Send/Receive */
1594 MODBIT(term.mode, !set, MODE_ECHO);
1595 break;
1596 case 20: /* LNM -- Linefeed/new line */
1597 MODBIT(term.mode, set, MODE_CRLF);
1598 break;
1599 default:
1600 fprintf(stderr,
1601 "erresc: unknown set/reset mode %d\n",
1602 *args);
1603 break;
1604 }
1605 }
1606 }
1607 }
1608
1609 void
1610 csihandle(void)
1611 {
1612 char buf[40];
1613 int len;
1614
1615 switch (csiescseq.mode[0]) {
1616 default:
1617 unknown:
1618 fprintf(stderr, "erresc: unknown csi ");
1619 csidump();
1620 /* die(""); */
1621 break;
1622 case '@': /* ICH -- Insert <n> blank char */
1623 DEFAULT(csiescseq.arg[0], 1);
1624 tinsertblank(csiescseq.arg[0]);
1625 break;
1626 case 'A': /* CUU -- Cursor <n> Up */
1627 DEFAULT(csiescseq.arg[0], 1);
1628 tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
1629 break;
1630 case 'B': /* CUD -- Cursor <n> Down */
1631 case 'e': /* VPR --Cursor <n> Down */
1632 DEFAULT(csiescseq.arg[0], 1);
1633 tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
1634 break;
1635 case 'i': /* MC -- Media Copy */
1636 switch (csiescseq.arg[0]) {
1637 case 0:
1638 tdump();
1639 break;
1640 case 1:
1641 tdumpline(term.c.y);
1642 break;
1643 case 2:
1644 tdumpsel();
1645 break;
1646 case 4:
1647 term.mode &= ~MODE_PRINT;
1648 break;
1649 case 5:
1650 term.mode |= MODE_PRINT;
1651 break;
1652 }
1653 break;
1654 case 'c': /* DA -- Device Attributes */
1655 if (csiescseq.arg[0] == 0)
1656 ttywrite(vtiden, strlen(vtiden), 0);
1657 break;
1658 case 'C': /* CUF -- Cursor <n> Forward */
1659 case 'a': /* HPR -- Cursor <n> Forward */
1660 DEFAULT(csiescseq.arg[0], 1);
1661 tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
1662 break;
1663 case 'D': /* CUB -- Cursor <n> Backward */
1664 DEFAULT(csiescseq.arg[0], 1);
1665 tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
1666 break;
1667 case 'E': /* CNL -- Cursor <n> Down and first col */
1668 DEFAULT(csiescseq.arg[0], 1);
1669 tmoveto(0, term.c.y+csiescseq.arg[0]);
1670 break;
1671 case 'F': /* CPL -- Cursor <n> Up and first col */
1672 DEFAULT(csiescseq.arg[0], 1);
1673 tmoveto(0, term.c.y-csiescseq.arg[0]);
1674 break;
1675 case 'g': /* TBC -- Tabulation clear */
1676 switch (csiescseq.arg[0]) {
1677 case 0: /* clear current tab stop */
1678 term.tabs[term.c.x] = 0;
1679 break;
1680 case 3: /* clear all the tabs */
1681 memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1682 break;
1683 default:
1684 goto unknown;
1685 }
1686 break;
1687 case 'G': /* CHA -- Move to <col> */
1688 case '`': /* HPA */
1689 DEFAULT(csiescseq.arg[0], 1);
1690 tmoveto(csiescseq.arg[0]-1, term.c.y);
1691 break;
1692 case 'H': /* CUP -- Move to <row> <col> */
1693 case 'f': /* HVP */
1694 DEFAULT(csiescseq.arg[0], 1);
1695 DEFAULT(csiescseq.arg[1], 1);
1696 tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
1697 break;
1698 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1699 DEFAULT(csiescseq.arg[0], 1);
1700 tputtab(csiescseq.arg[0]);
1701 break;
1702 case 'J': /* ED -- Clear screen */
1703 switch (csiescseq.arg[0]) {
1704 case 0: /* below */
1705 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
1706 if (term.c.y < term.row-1) {
1707 tclearregion(0, term.c.y+1, term.col-1,
1708 term.row-1);
1709 }
1710 break;
1711 case 1: /* above */
1712 if (term.c.y > 1)
1713 tclearregion(0, 0, term.col-1, term.c.y-1);
1714 tclearregion(0, term.c.y, term.c.x, term.c.y);
1715 break;
1716 case 2: /* all */
1717 tclearregion(0, 0, term.col-1, term.row-1);
1718 break;
1719 default:
1720 goto unknown;
1721 }
1722 break;
1723 case 'K': /* EL -- Clear line */
1724 switch (csiescseq.arg[0]) {
1725 case 0: /* right */
1726 tclearregion(term.c.x, term.c.y, term.col-1,
1727 term.c.y);
1728 break;
1729 case 1: /* left */
1730 tclearregion(0, term.c.y, term.c.x, term.c.y);
1731 break;
1732 case 2: /* all */
1733 tclearregion(0, term.c.y, term.col-1, term.c.y);
1734 break;
1735 }
1736 break;
1737 case 'S': /* SU -- Scroll <n> line up */
1738 DEFAULT(csiescseq.arg[0], 1);
1739 tscrollup(term.top, csiescseq.arg[0]);
1740 break;
1741 case 'T': /* SD -- Scroll <n> line down */
1742 DEFAULT(csiescseq.arg[0], 1);
1743 tscrolldown(term.top, csiescseq.arg[0]);
1744 break;
1745 case 'L': /* IL -- Insert <n> blank lines */
1746 DEFAULT(csiescseq.arg[0], 1);
1747 tinsertblankline(csiescseq.arg[0]);
1748 break;
1749 case 'l': /* RM -- Reset Mode */
1750 tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
1751 break;
1752 case 'M': /* DL -- Delete <n> lines */
1753 DEFAULT(csiescseq.arg[0], 1);
1754 tdeleteline(csiescseq.arg[0]);
1755 break;
1756 case 'X': /* ECH -- Erase <n> char */
1757 DEFAULT(csiescseq.arg[0], 1);
1758 tclearregion(term.c.x, term.c.y,
1759 term.c.x + csiescseq.arg[0] - 1, term.c.y);
1760 break;
1761 case 'P': /* DCH -- Delete <n> char */
1762 DEFAULT(csiescseq.arg[0], 1);
1763 tdeletechar(csiescseq.arg[0]);
1764 break;
1765 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1766 DEFAULT(csiescseq.arg[0], 1);
1767 tputtab(-csiescseq.arg[0]);
1768 break;
1769 case 'd': /* VPA -- Move to <row> */
1770 DEFAULT(csiescseq.arg[0], 1);
1771 tmoveato(term.c.x, csiescseq.arg[0]-1);
1772 break;
1773 case 'h': /* SM -- Set terminal mode */
1774 tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
1775 break;
1776 case 'm': /* SGR -- Terminal attribute (color) */
1777 tsetattr(csiescseq.arg, csiescseq.narg);
1778 break;
1779 case 'n': /* DSR – Device Status Report (cursor position) */
1780 if (csiescseq.arg[0] == 6) {
1781 len = snprintf(buf, sizeof(buf),"\033[%i;%iR",
1782 term.c.y+1, term.c.x+1);
1783 ttywrite(buf, len, 0);
1784 }
1785 break;
1786 case 'r': /* DECSTBM -- Set Scrolling Region */
1787 if (csiescseq.priv) {
1788 goto unknown;
1789 } else {
1790 DEFAULT(csiescseq.arg[0], 1);
1791 DEFAULT(csiescseq.arg[1], term.row);
1792 tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
1793 tmoveato(0, 0);
1794 }
1795 break;
1796 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1797 tcursor(CURSOR_SAVE);
1798 break;
1799 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1800 tcursor(CURSOR_LOAD);
1801 break;
1802 case ' ':
1803 switch (csiescseq.mode[1]) {
1804 case 'q': /* DECSCUSR -- Set Cursor Style */
1805 if (xsetcursor(csiescseq.arg[0]))
1806 goto unknown;
1807 break;
1808 default:
1809 goto unknown;
1810 }
1811 break;
1812 }
1813 }
1814
1815 void
1816 csidump(void)
1817 {
1818 size_t i;
1819 uint c;
1820
1821 fprintf(stderr, "ESC[");
1822 for (i = 0; i < csiescseq.len; i++) {
1823 c = csiescseq.buf[i] & 0xff;
1824 if (isprint(c)) {
1825 putc(c, stderr);
1826 } else if (c == '\n') {
1827 fprintf(stderr, "(\\n)");
1828 } else if (c == '\r') {
1829 fprintf(stderr, "(\\r)");
1830 } else if (c == 0x1b) {
1831 fprintf(stderr, "(\\e)");
1832 } else {
1833 fprintf(stderr, "(%02x)", c);
1834 }
1835 }
1836 putc('\n', stderr);
1837 }
1838
1839 void
1840 csireset(void)
1841 {
1842 memset(&csiescseq, 0, sizeof(csiescseq));
1843 }
1844
1845 void
1846 strhandle(void)
1847 {
1848 char *p = NULL, *dec;
1849 int j, narg, par;
1850
1851 term.esc &= ~(ESC_STR_END|ESC_STR);
1852 strparse();
1853 par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
1854
1855 switch (strescseq.type) {
1856 case ']': /* OSC -- Operating System Command */
1857 switch (par) {
1858 case 0:
1859 case 1:
1860 case 2:
1861 if (narg > 1)
1862 xsettitle(strescseq.args[1]);
1863 return;
1864 case 52:
1865 if (narg > 2) {
1866 dec = base64dec(strescseq.args[2]);
1867 if (dec) {
1868 xsetsel(dec);
1869 xclipcopy();
1870 } else {
1871 fprintf(stderr, "erresc: invalid base64\n");
1872 }
1873 }
1874 return;
1875 case 4: /* color set */
1876 if (narg < 3)
1877 break;
1878 p = strescseq.args[2];
1879 /* FALLTHROUGH */
1880 case 104: /* color reset, here p = NULL */
1881 j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
1882 if (xsetcolorname(j, p)) {
1883 if (par == 104 && narg <= 1)
1884 return; /* color reset without parameter */
1885 fprintf(stderr, "erresc: invalid color j=%d, p=%s\n",
1886 j, p ? p : "(null)");
1887 } else {
1888 /*
1889 * TODO if defaultbg color is changed, borders
1890 * are dirty
1891 */
1892 redraw();
1893 }
1894 return;
1895 }
1896 break;
1897 case 'k': /* old title set compatibility */
1898 xsettitle(strescseq.args[0]);
1899 return;
1900 case 'P': /* DCS -- Device Control String */
1901 term.mode |= ESC_DCS;
1902 case '_': /* APC -- Application Program Command */
1903 case '^': /* PM -- Privacy Message */
1904 return;
1905 }
1906
1907 fprintf(stderr, "erresc: unknown str ");
1908 strdump();
1909 }
1910
1911 void
1912 strparse(void)
1913 {
1914 int c;
1915 char *p = strescseq.buf;
1916
1917 strescseq.narg = 0;
1918 strescseq.buf[strescseq.len] = '\0';
1919
1920 if (*p == '\0')
1921 return;
1922
1923 while (strescseq.narg < STR_ARG_SIZ) {
1924 strescseq.args[strescseq.narg++] = p;
1925 while ((c = *p) != ';' && c != '\0')
1926 ++p;
1927 if (c == '\0')
1928 return;
1929 *p++ = '\0';
1930 }
1931 }
1932
1933 void
1934 strdump(void)
1935 {
1936 size_t i;
1937 uint c;
1938
1939 fprintf(stderr, "ESC%c", strescseq.type);
1940 for (i = 0; i < strescseq.len; i++) {
1941 c = strescseq.buf[i] & 0xff;
1942 if (c == '\0') {
1943 putc('\n', stderr);
1944 return;
1945 } else if (isprint(c)) {
1946 putc(c, stderr);
1947 } else if (c == '\n') {
1948 fprintf(stderr, "(\\n)");
1949 } else if (c == '\r') {
1950 fprintf(stderr, "(\\r)");
1951 } else if (c == 0x1b) {
1952 fprintf(stderr, "(\\e)");
1953 } else {
1954 fprintf(stderr, "(%02x)", c);
1955 }
1956 }
1957 fprintf(stderr, "ESC\\\n");
1958 }
1959
1960 void
1961 strreset(void)
1962 {
1963 strescseq = (STREscape){
1964 .buf = xrealloc(strescseq.buf, STR_BUF_SIZ),
1965 .siz = STR_BUF_SIZ,
1966 };
1967 }
1968
1969 void
1970 sendbreak(const Arg *arg)
1971 {
1972 if (tcsendbreak(cmdfd, 0))
1973 perror("Error sending break");
1974 }
1975
1976 void
1977 tprinter(char *s, size_t len)
1978 {
1979 if (iofd != -1 && xwrite(iofd, s, len) < 0) {
1980 perror("Error writing to output file");
1981 close(iofd);
1982 iofd = -1;
1983 }
1984 }
1985
1986 void
1987 toggleprinter(const Arg *arg)
1988 {
1989 term.mode ^= MODE_PRINT;
1990 }
1991
1992 void
1993 printscreen(const Arg *arg)
1994 {
1995 tdump();
1996 }
1997
1998 void
1999 printsel(const Arg *arg)
2000 {
2001 tdumpsel();
2002 }
2003
2004 void
2005 tdumpsel(void)
2006 {
2007 char *ptr;
2008
2009 if ((ptr = getsel())) {
2010 tprinter(ptr, strlen(ptr));
2011 free(ptr);
2012 }
2013 }
2014
2015 void
2016 tdumpline(int n)
2017 {
2018 char buf[UTF_SIZ];
2019 Glyph *bp, *end;
2020
2021 bp = &term.line[n][0];
2022 end = &bp[MIN(tlinelen(n), term.col) - 1];
2023 if (bp != end || bp->u != ' ') {
2024 for ( ;bp <= end; ++bp)
2025 tprinter(buf, utf8encode(bp->u, buf));
2026 }
2027 tprinter("\n", 1);
2028 }
2029
2030 void
2031 tdump(void)
2032 {
2033 int i;
2034
2035 for (i = 0; i < term.row; ++i)
2036 tdumpline(i);
2037 }
2038
2039 void
2040 tputtab(int n)
2041 {
2042 uint x = term.c.x;
2043
2044 if (n > 0) {
2045 while (x < term.col && n--)
2046 for (++x; x < term.col && !term.tabs[x]; ++x)
2047 /* nothing */ ;
2048 } else if (n < 0) {
2049 while (x > 0 && n++)
2050 for (--x; x > 0 && !term.tabs[x]; --x)
2051 /* nothing */ ;
2052 }
2053 term.c.x = LIMIT(x, 0, term.col-1);
2054 }
2055
2056 void
2057 tdefutf8(char ascii)
2058 {
2059 if (ascii == 'G')
2060 term.mode |= MODE_UTF8;
2061 else if (ascii == '@')
2062 term.mode &= ~MODE_UTF8;
2063 }
2064
2065 void
2066 tdeftran(char ascii)
2067 {
2068 static char cs[] = "0B";
2069 static int vcs[] = {CS_GRAPHIC0, CS_USA};
2070 char *p;
2071
2072 if ((p = strchr(cs, ascii)) == NULL) {
2073 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
2074 } else {
2075 term.trantbl[term.icharset] = vcs[p - cs];
2076 }
2077 }
2078
2079 void
2080 tdectest(char c)
2081 {
2082 int x, y;
2083
2084 if (c == '8') { /* DEC screen alignment test. */
2085 for (x = 0; x < term.col; ++x) {
2086 for (y = 0; y < term.row; ++y)
2087 tsetchar('E', &term.c.attr, x, y);
2088 }
2089 }
2090 }
2091
2092 void
2093 tstrsequence(uchar c)
2094 {
2095 strreset();
2096
2097 switch (c) {
2098 case 0x90: /* DCS -- Device Control String */
2099 c = 'P';
2100 term.esc |= ESC_DCS;
2101 break;
2102 case 0x9f: /* APC -- Application Program Command */
2103 c = '_';
2104 break;
2105 case 0x9e: /* PM -- Privacy Message */
2106 c = '^';
2107 break;
2108 case 0x9d: /* OSC -- Operating System Command */
2109 c = ']';
2110 break;
2111 }
2112 strescseq.type = c;
2113 term.esc |= ESC_STR;
2114 }
2115
2116 void
2117 tcontrolcode(uchar ascii)
2118 {
2119 switch (ascii) {
2120 case '\t': /* HT */
2121 tputtab(1);
2122 return;
2123 case '\b': /* BS */
2124 tmoveto(term.c.x-1, term.c.y);
2125 return;
2126 case '\r': /* CR */
2127 tmoveto(0, term.c.y);
2128 return;
2129 case '\f': /* LF */
2130 case '\v': /* VT */
2131 case '\n': /* LF */
2132 /* go to first col if the mode is set */
2133 tnewline(IS_SET(MODE_CRLF));
2134 return;
2135 case '\a': /* BEL */
2136 if (term.esc & ESC_STR_END) {
2137 /* backwards compatibility to xterm */
2138 strhandle();
2139 } else {
2140 xbell();
2141 }
2142 break;
2143 case '\033': /* ESC */
2144 csireset();
2145 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
2146 term.esc |= ESC_START;
2147 return;
2148 case '\016': /* SO (LS1 -- Locking shift 1) */
2149 case '\017': /* SI (LS0 -- Locking shift 0) */
2150 term.charset = 1 - (ascii - '\016');
2151 return;
2152 case '\032': /* SUB */
2153 tsetchar('?', &term.c.attr, term.c.x, term.c.y);
2154 case '\030': /* CAN */
2155 csireset();
2156 break;
2157 case '\005': /* ENQ (IGNORED) */
2158 case '\000': /* NUL (IGNORED) */
2159 case '\021': /* XON (IGNORED) */
2160 case '\023': /* XOFF (IGNORED) */
2161 case 0177: /* DEL (IGNORED) */
2162 return;
2163 case 0x80: /* TODO: PAD */
2164 case 0x81: /* TODO: HOP */
2165 case 0x82: /* TODO: BPH */
2166 case 0x83: /* TODO: NBH */
2167 case 0x84: /* TODO: IND */
2168 break;
2169 case 0x85: /* NEL -- Next line */
2170 tnewline(1); /* always go to first col */
2171 break;
2172 case 0x86: /* TODO: SSA */
2173 case 0x87: /* TODO: ESA */
2174 break;
2175 case 0x88: /* HTS -- Horizontal tab stop */
2176 term.tabs[term.c.x] = 1;
2177 break;
2178 case 0x89: /* TODO: HTJ */
2179 case 0x8a: /* TODO: VTS */
2180 case 0x8b: /* TODO: PLD */
2181 case 0x8c: /* TODO: PLU */
2182 case 0x8d: /* TODO: RI */
2183 case 0x8e: /* TODO: SS2 */
2184 case 0x8f: /* TODO: SS3 */
2185 case 0x91: /* TODO: PU1 */
2186 case 0x92: /* TODO: PU2 */
2187 case 0x93: /* TODO: STS */
2188 case 0x94: /* TODO: CCH */
2189 case 0x95: /* TODO: MW */
2190 case 0x96: /* TODO: SPA */
2191 case 0x97: /* TODO: EPA */
2192 case 0x98: /* TODO: SOS */
2193 case 0x99: /* TODO: SGCI */
2194 break;
2195 case 0x9a: /* DECID -- Identify Terminal */
2196 ttywrite(vtiden, strlen(vtiden), 0);
2197 break;
2198 case 0x9b: /* TODO: CSI */
2199 case 0x9c: /* TODO: ST */
2200 break;
2201 case 0x90: /* DCS -- Device Control String */
2202 case 0x9d: /* OSC -- Operating System Command */
2203 case 0x9e: /* PM -- Privacy Message */
2204 case 0x9f: /* APC -- Application Program Command */
2205 tstrsequence(ascii);
2206 return;
2207 }
2208 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2209 term.esc &= ~(ESC_STR_END|ESC_STR);
2210 }
2211
2212 /*
2213 * returns 1 when the sequence is finished and it hasn't to read
2214 * more characters for this sequence, otherwise 0
2215 */
2216 int
2217 eschandle(uchar ascii)
2218 {
2219 switch (ascii) {
2220 case '[':
2221 term.esc |= ESC_CSI;
2222 return 0;
2223 case '#':
2224 term.esc |= ESC_TEST;
2225 return 0;
2226 case '%':
2227 term.esc |= ESC_UTF8;
2228 return 0;
2229 case 'P': /* DCS -- Device Control String */
2230 case '_': /* APC -- Application Program Command */
2231 case '^': /* PM -- Privacy Message */
2232 case ']': /* OSC -- Operating System Command */
2233 case 'k': /* old title set compatibility */
2234 tstrsequence(ascii);
2235 return 0;
2236 case 'n': /* LS2 -- Locking shift 2 */
2237 case 'o': /* LS3 -- Locking shift 3 */
2238 term.charset = 2 + (ascii - 'n');
2239 break;
2240 case '(': /* GZD4 -- set primary charset G0 */
2241 case ')': /* G1D4 -- set secondary charset G1 */
2242 case '*': /* G2D4 -- set tertiary charset G2 */
2243 case '+': /* G3D4 -- set quaternary charset G3 */
2244 term.icharset = ascii - '(';
2245 term.esc |= ESC_ALTCHARSET;
2246 return 0;
2247 case 'D': /* IND -- Linefeed */
2248 if (term.c.y == term.bot) {
2249 tscrollup(term.top, 1);
2250 } else {
2251 tmoveto(term.c.x, term.c.y+1);
2252 }
2253 break;
2254 case 'E': /* NEL -- Next line */
2255 tnewline(1); /* always go to first col */
2256 break;
2257 case 'H': /* HTS -- Horizontal tab stop */
2258 term.tabs[term.c.x] = 1;
2259 break;
2260 case 'M': /* RI -- Reverse index */
2261 if (term.c.y == term.top) {
2262 tscrolldown(term.top, 1);
2263 } else {
2264 tmoveto(term.c.x, term.c.y-1);
2265 }
2266 break;
2267 case 'Z': /* DECID -- Identify Terminal */
2268 ttywrite(vtiden, strlen(vtiden), 0);
2269 break;
2270 case 'c': /* RIS -- Reset to initial state */
2271 treset();
2272 resettitle();
2273 xloadcols();
2274 break;
2275 case '=': /* DECPAM -- Application keypad */
2276 xsetmode(1, MODE_APPKEYPAD);
2277 break;
2278 case '>': /* DECPNM -- Normal keypad */
2279 xsetmode(0, MODE_APPKEYPAD);
2280 break;
2281 case '7': /* DECSC -- Save Cursor */
2282 tcursor(CURSOR_SAVE);
2283 break;
2284 case '8': /* DECRC -- Restore Cursor */
2285 tcursor(CURSOR_LOAD);
2286 break;
2287 case '\\': /* ST -- String Terminator */
2288 if (term.esc & ESC_STR_END)
2289 strhandle();
2290 break;
2291 default:
2292 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2293 (uchar) ascii, isprint(ascii)? ascii:'.');
2294 break;
2295 }
2296 return 1;
2297 }
2298
2299 void
2300 tputc(Rune u)
2301 {
2302 char c[UTF_SIZ];
2303 int control;
2304 int width, len;
2305 Glyph *gp;
2306
2307 control = ISCONTROL(u);
2308 if (!IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
2309 c[0] = u;
2310 width = len = 1;
2311 } else {
2312 len = utf8encode(u, c);
2313 if (!control && (width = wcwidth(u)) == -1) {
2314 memcpy(c, "\357\277\275", 4); /* UTF_INVALID */
2315 width = 1;
2316 }
2317 }
2318
2319 if (IS_SET(MODE_PRINT))
2320 tprinter(c, len);
2321
2322 /*
2323 * STR sequence must be checked before anything else
2324 * because it uses all following characters until it
2325 * receives a ESC, a SUB, a ST or any other C1 control
2326 * character.
2327 */
2328 if (term.esc & ESC_STR) {
2329 if (u == '\a' || u == 030 || u == 032 || u == 033 ||
2330 ISCONTROLC1(u)) {
2331 term.esc &= ~(ESC_START|ESC_STR|ESC_DCS);
2332 if (IS_SET(MODE_SIXEL)) {
2333 /* TODO: render sixel */;
2334 term.mode &= ~MODE_SIXEL;
2335 return;
2336 }
2337 term.esc |= ESC_STR_END;
2338 goto check_control_code;
2339 }
2340
2341 if (IS_SET(MODE_SIXEL)) {
2342 /* TODO: implement sixel mode */
2343 return;
2344 }
2345 if (term.esc&ESC_DCS && strescseq.len == 0 && u == 'q')
2346 term.mode |= MODE_SIXEL;
2347
2348 if (strescseq.len+len >= strescseq.siz) {
2349 /*
2350 * Here is a bug in terminals. If the user never sends
2351 * some code to stop the str or esc command, then st
2352 * will stop responding. But this is better than
2353 * silently failing with unknown characters. At least
2354 * then users will report back.
2355 *
2356 * In the case users ever get fixed, here is the code:
2357 */
2358 /*
2359 * term.esc = 0;
2360 * strhandle();
2361 */
2362 if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2)
2363 return;
2364 strescseq.siz *= 2;
2365 strescseq.buf = xrealloc(strescseq.buf, strescseq.siz);
2366 }
2367
2368 memmove(&strescseq.buf[strescseq.len], c, len);
2369 strescseq.len += len;
2370 return;
2371 }
2372
2373 check_control_code:
2374 /*
2375 * Actions of control codes must be performed as soon they arrive
2376 * because they can be embedded inside a control sequence, and
2377 * they must not cause conflicts with sequences.
2378 */
2379 if (control) {
2380 tcontrolcode(u);
2381 /*
2382 * control codes are not shown ever
2383 */
2384 return;
2385 } else if (term.esc & ESC_START) {
2386 if (term.esc & ESC_CSI) {
2387 csiescseq.buf[csiescseq.len++] = u;
2388 if (BETWEEN(u, 0x40, 0x7E)
2389 || csiescseq.len >= \
2390 sizeof(csiescseq.buf)-1) {
2391 term.esc = 0;
2392 csiparse();
2393 csihandle();
2394 }
2395 return;
2396 } else if (term.esc & ESC_UTF8) {
2397 tdefutf8(u);
2398 } else if (term.esc & ESC_ALTCHARSET) {
2399 tdeftran(u);
2400 } else if (term.esc & ESC_TEST) {
2401 tdectest(u);
2402 } else {
2403 if (!eschandle(u))
2404 return;
2405 /* sequence already finished */
2406 }
2407 term.esc = 0;
2408 /*
2409 * All characters which form part of a sequence are not
2410 * printed
2411 */
2412 return;
2413 }
2414 if (sel.ob.x != -1 && BETWEEN(term.c.y, sel.ob.y, sel.oe.y))
2415 selclear();
2416
2417 gp = &term.line[term.c.y][term.c.x];
2418 if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
2419 gp->mode |= ATTR_WRAP;
2420 tnewline(1);
2421 gp = &term.line[term.c.y][term.c.x];
2422 }
2423
2424 if (IS_SET(MODE_INSERT) && term.c.x+width < term.col)
2425 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
2426
2427 if (term.c.x+width > term.col) {
2428 tnewline(1);
2429 gp = &term.line[term.c.y][term.c.x];
2430 }
2431
2432 tsetchar(u, &term.c.attr, term.c.x, term.c.y);
2433
2434 if (width == 2) {
2435 gp->mode |= ATTR_WIDE;
2436 if (term.c.x+1 < term.col) {
2437 gp[1].u = '\0';
2438 gp[1].mode = ATTR_WDUMMY;
2439 }
2440 }
2441 if (term.c.x+width < term.col) {
2442 tmoveto(term.c.x+width, term.c.y);
2443 } else {
2444 term.c.state |= CURSOR_WRAPNEXT;
2445 }
2446 }
2447
2448 int
2449 twrite(const char *buf, int buflen, int show_ctrl)
2450 {
2451 int charsize;
2452 Rune u;
2453 int n;
2454
2455 for (n = 0; n < buflen; n += charsize) {
2456 if (IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
2457 /* process a complete utf8 char */
2458 charsize = utf8decode(buf + n, &u, buflen - n);
2459 if (charsize == 0)
2460 break;
2461 } else {
2462 u = buf[n] & 0xFF;
2463 charsize = 1;
2464 }
2465 if (show_ctrl && ISCONTROL(u)) {
2466 if (u & 0x80) {
2467 u &= 0x7f;
2468 tputc('^');
2469 tputc('[');
2470 } else if (u != '\n' && u != '\r' && u != '\t') {
2471 u ^= 0x40;
2472 tputc('^');
2473 }
2474 }
2475 tputc(u);
2476 }
2477 return n;
2478 }
2479
2480 void
2481 tresize(int col, int row)
2482 {
2483 int i;
2484 int minrow = MIN(row, term.row);
2485 int mincol = MIN(col, term.col);
2486 int *bp;
2487 TCursor c;
2488
2489 if (col < 1 || row < 1) {
2490 fprintf(stderr,
2491 "tresize: error resizing to %dx%d\n", col, row);
2492 return;
2493 }
2494
2495 /*
2496 * slide screen to keep cursor where we expect it -
2497 * tscrollup would work here, but we can optimize to
2498 * memmove because we're freeing the earlier lines
2499 */
2500 for (i = 0; i <= term.c.y - row; i++) {
2501 free(term.line[i]);
2502 free(term.alt[i]);
2503 }
2504 /* ensure that both src and dst are not NULL */
2505 if (i > 0) {
2506 memmove(term.line, term.line + i, row * sizeof(Line));
2507 memmove(term.alt, term.alt + i, row * sizeof(Line));
2508 }
2509 for (i += row; i < term.row; i++) {
2510 free(term.line[i]);
2511 free(term.alt[i]);
2512 }
2513
2514 /* resize to new height */
2515 term.line = xrealloc(term.line, row * sizeof(Line));
2516 term.alt = xrealloc(term.alt, row * sizeof(Line));
2517 term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
2518 term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
2519
2520 /* resize each row to new width, zero-pad if needed */
2521 for (i = 0; i < minrow; i++) {
2522 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
2523 term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph));
2524 }
2525
2526 /* allocate any new rows */
2527 for (/* i = minrow */; i < row; i++) {
2528 term.line[i] = xmalloc(col * sizeof(Glyph));
2529 term.alt[i] = xmalloc(col * sizeof(Glyph));
2530 }
2531 if (col > term.col) {
2532 bp = term.tabs + term.col;
2533
2534 memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
2535 while (--bp > term.tabs && !*bp)
2536 /* nothing */ ;
2537 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
2538 *bp = 1;
2539 }
2540 /* update terminal size */
2541 term.col = col;
2542 term.row = row;
2543 /* reset scrolling region */
2544 tsetscroll(0, row-1);
2545 /* make use of the LIMIT in tmoveto */
2546 tmoveto(term.c.x, term.c.y);
2547 /* Clearing both screens (it makes dirty all lines) */
2548 c = term.c;
2549 for (i = 0; i < 2; i++) {
2550 if (mincol < col && 0 < minrow) {
2551 tclearregion(mincol, 0, col - 1, minrow - 1);
2552 }
2553 if (0 < col && minrow < row) {
2554 tclearregion(0, minrow, col - 1, row - 1);
2555 }
2556 tswapscreen();
2557 tcursor(CURSOR_LOAD);
2558 }
2559 term.c = c;
2560 }
2561
2562 void
2563 resettitle(void)
2564 {
2565 xsettitle(NULL);
2566 }
2567
2568 void
2569 drawregion(int x1, int y1, int x2, int y2)
2570 {
2571 int y;
2572 for (y = y1; y < y2; y++) {
2573 if (!term.dirty[y])
2574 continue;
2575
2576 term.dirty[y] = 0;
2577 xdrawline(term.line[y], x1, y, x2);
2578 }
2579 }
2580
2581 void
2582 draw(void)
2583 {
2584 int cx = term.c.x;
2585
2586 if (!xstartdraw())
2587 return;
2588
2589 /* adjust cursor position */
2590 LIMIT(term.ocx, 0, term.col-1);
2591 LIMIT(term.ocy, 0, term.row-1);
2592 if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY)
2593 term.ocx--;
2594 if (term.line[term.c.y][cx].mode & ATTR_WDUMMY)
2595 cx--;
2596
2597 drawregion(0, 0, term.col, term.row);
2598 xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
2599 term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
2600 term.ocx = cx, term.ocy = term.c.y;
2601 xfinishdraw();
2602 xximspot(term.ocx, term.ocy);
2603 }
2604
2605 void
2606 redraw(void)
2607 {
2608 tfulldirt();
2609 draw();
2610 }