Xinqi Bao's Git

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