Xinqi Bao's Git

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