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