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