Xinqi Bao's Git

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