Xinqi Bao's Git

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