Xinqi Bao's Git

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