Xinqi Bao's Git

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