Xinqi Bao's Git

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