Xinqi Bao's Git

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