Xinqi Bao's Git

641f8967fff7af39a0d070f57e6a57d8ee34edda
[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 int twrite(const char *, int, int);
165 static void tfulldirt(void);
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(const 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 int written;
772 int ret;
773
774 /* append read bytes to unprocessed bytes */
775 if ((ret = read(cmdfd, buf+buflen, LEN(buf)-buflen)) < 0)
776 die("Couldn't read from shell: %s\n", strerror(errno));
777 buflen += ret;
778
779 written = twrite(buf, buflen, 0);
780 buflen -= written;
781 /* keep any uncomplete utf8 char for the next call */
782 if (buflen > 0)
783 memmove(buf, buf + written, buflen);
784
785 return ret;
786 }
787
788 void
789 ttywrite(const char *s, size_t n)
790 {
791 fd_set wfd, rfd;
792 ssize_t r;
793 size_t lim = 256;
794
795 /*
796 * Remember that we are using a pty, which might be a modem line.
797 * Writing too much will clog the line. That's why we are doing this
798 * dance.
799 * FIXME: Migrate the world to Plan 9.
800 */
801 while (n > 0) {
802 FD_ZERO(&wfd);
803 FD_ZERO(&rfd);
804 FD_SET(cmdfd, &wfd);
805 FD_SET(cmdfd, &rfd);
806
807 /* Check if we can write. */
808 if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) {
809 if (errno == EINTR)
810 continue;
811 die("select failed: %s\n", strerror(errno));
812 }
813 if (FD_ISSET(cmdfd, &wfd)) {
814 /*
815 * Only write the bytes written by ttywrite() or the
816 * default of 256. This seems to be a reasonable value
817 * for a serial line. Bigger values might clog the I/O.
818 */
819 if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0)
820 goto write_error;
821 if (r < n) {
822 /*
823 * We weren't able to write out everything.
824 * This means the buffer is getting full
825 * again. Empty it.
826 */
827 if (n < lim)
828 lim = ttyread();
829 n -= r;
830 s += r;
831 } else {
832 /* All bytes have been written. */
833 break;
834 }
835 }
836 if (FD_ISSET(cmdfd, &rfd))
837 lim = ttyread();
838 }
839 return;
840
841 write_error:
842 die("write error on tty: %s\n", strerror(errno));
843 }
844
845 void
846 ttysend(char *s, size_t n)
847 {
848 ttywrite(s, n);
849 if (IS_SET(MODE_ECHO))
850 twrite(s, n, 1);
851 }
852
853 void
854 ttyresize(int tw, int th)
855 {
856 struct winsize w;
857
858 w.ws_row = term.row;
859 w.ws_col = term.col;
860 w.ws_xpixel = tw;
861 w.ws_ypixel = th;
862 if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
863 fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
864 }
865
866 int
867 tattrset(int attr)
868 {
869 int i, j;
870
871 for (i = 0; i < term.row-1; i++) {
872 for (j = 0; j < term.col-1; j++) {
873 if (term.line[i][j].mode & attr)
874 return 1;
875 }
876 }
877
878 return 0;
879 }
880
881 void
882 tsetdirt(int top, int bot)
883 {
884 int i;
885
886 LIMIT(top, 0, term.row-1);
887 LIMIT(bot, 0, term.row-1);
888
889 for (i = top; i <= bot; i++)
890 term.dirty[i] = 1;
891 }
892
893 void
894 tsetdirtattr(int attr)
895 {
896 int i, j;
897
898 for (i = 0; i < term.row-1; i++) {
899 for (j = 0; j < term.col-1; j++) {
900 if (term.line[i][j].mode & attr) {
901 tsetdirt(i, i);
902 break;
903 }
904 }
905 }
906 }
907
908 void
909 tfulldirt(void)
910 {
911 tsetdirt(0, term.row-1);
912 }
913
914 void
915 tcursor(int mode)
916 {
917 static TCursor c[2];
918 int alt = IS_SET(MODE_ALTSCREEN);
919
920 if (mode == CURSOR_SAVE) {
921 c[alt] = term.c;
922 } else if (mode == CURSOR_LOAD) {
923 term.c = c[alt];
924 tmoveto(c[alt].x, c[alt].y);
925 }
926 }
927
928 void
929 treset(void)
930 {
931 uint i;
932
933 term.c = (TCursor){{
934 .mode = ATTR_NULL,
935 .fg = defaultfg,
936 .bg = defaultbg
937 }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
938
939 memset(term.tabs, 0, term.col * sizeof(*term.tabs));
940 for (i = tabspaces; i < term.col; i += tabspaces)
941 term.tabs[i] = 1;
942 term.top = 0;
943 term.bot = term.row - 1;
944 term.mode = MODE_WRAP|MODE_UTF8;
945 memset(term.trantbl, CS_USA, sizeof(term.trantbl));
946 term.charset = 0;
947
948 for (i = 0; i < 2; i++) {
949 tmoveto(0, 0);
950 tcursor(CURSOR_SAVE);
951 tclearregion(0, 0, term.col-1, term.row-1);
952 tswapscreen();
953 }
954 }
955
956 void
957 tnew(int col, int row)
958 {
959 term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
960 tresize(col, row);
961 term.numlock = 1;
962
963 treset();
964 }
965
966 void
967 tswapscreen(void)
968 {
969 Line *tmp = term.line;
970
971 term.line = term.alt;
972 term.alt = tmp;
973 term.mode ^= MODE_ALTSCREEN;
974 tfulldirt();
975 }
976
977 void
978 tscrolldown(int orig, int n)
979 {
980 int i;
981 Line temp;
982
983 LIMIT(n, 0, term.bot-orig+1);
984
985 tsetdirt(orig, term.bot-n);
986 tclearregion(0, term.bot-n+1, term.col-1, term.bot);
987
988 for (i = term.bot; i >= orig+n; i--) {
989 temp = term.line[i];
990 term.line[i] = term.line[i-n];
991 term.line[i-n] = temp;
992 }
993
994 selscroll(orig, n);
995 }
996
997 void
998 tscrollup(int orig, int n)
999 {
1000 int i;
1001 Line temp;
1002
1003 LIMIT(n, 0, term.bot-orig+1);
1004
1005 tclearregion(0, orig, term.col-1, orig+n-1);
1006 tsetdirt(orig+n, term.bot);
1007
1008 for (i = orig; i <= term.bot-n; i++) {
1009 temp = term.line[i];
1010 term.line[i] = term.line[i+n];
1011 term.line[i+n] = temp;
1012 }
1013
1014 selscroll(orig, -n);
1015 }
1016
1017 void
1018 selscroll(int orig, int n)
1019 {
1020 if (sel.ob.x == -1)
1021 return;
1022
1023 if (BETWEEN(sel.ob.y, orig, term.bot) || BETWEEN(sel.oe.y, orig, term.bot)) {
1024 if ((sel.ob.y += n) > term.bot || (sel.oe.y += n) < term.top) {
1025 selclear();
1026 return;
1027 }
1028 if (sel.type == SEL_RECTANGULAR) {
1029 if (sel.ob.y < term.top)
1030 sel.ob.y = term.top;
1031 if (sel.oe.y > term.bot)
1032 sel.oe.y = term.bot;
1033 } else {
1034 if (sel.ob.y < term.top) {
1035 sel.ob.y = term.top;
1036 sel.ob.x = 0;
1037 }
1038 if (sel.oe.y > term.bot) {
1039 sel.oe.y = term.bot;
1040 sel.oe.x = term.col;
1041 }
1042 }
1043 selnormalize();
1044 }
1045 }
1046
1047 void
1048 tnewline(int first_col)
1049 {
1050 int y = term.c.y;
1051
1052 if (y == term.bot) {
1053 tscrollup(term.top, 1);
1054 } else {
1055 y++;
1056 }
1057 tmoveto(first_col ? 0 : term.c.x, y);
1058 }
1059
1060 void
1061 csiparse(void)
1062 {
1063 char *p = csiescseq.buf, *np;
1064 long int v;
1065
1066 csiescseq.narg = 0;
1067 if (*p == '?') {
1068 csiescseq.priv = 1;
1069 p++;
1070 }
1071
1072 csiescseq.buf[csiescseq.len] = '\0';
1073 while (p < csiescseq.buf+csiescseq.len) {
1074 np = NULL;
1075 v = strtol(p, &np, 10);
1076 if (np == p)
1077 v = 0;
1078 if (v == LONG_MAX || v == LONG_MIN)
1079 v = -1;
1080 csiescseq.arg[csiescseq.narg++] = v;
1081 p = np;
1082 if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ)
1083 break;
1084 p++;
1085 }
1086 csiescseq.mode[0] = *p++;
1087 csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0';
1088 }
1089
1090 /* for absolute user moves, when decom is set */
1091 void
1092 tmoveato(int x, int y)
1093 {
1094 tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0));
1095 }
1096
1097 void
1098 tmoveto(int x, int y)
1099 {
1100 int miny, maxy;
1101
1102 if (term.c.state & CURSOR_ORIGIN) {
1103 miny = term.top;
1104 maxy = term.bot;
1105 } else {
1106 miny = 0;
1107 maxy = term.row - 1;
1108 }
1109 term.c.state &= ~CURSOR_WRAPNEXT;
1110 term.c.x = LIMIT(x, 0, term.col-1);
1111 term.c.y = LIMIT(y, miny, maxy);
1112 }
1113
1114 void
1115 tsetchar(Rune u, Glyph *attr, int x, int y)
1116 {
1117 static char *vt100_0[62] = { /* 0x41 - 0x7e */
1118 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1119 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1120 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1121 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1122 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1123 "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1124 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1125 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1126 };
1127
1128 /*
1129 * The table is proudly stolen from rxvt.
1130 */
1131 if (term.trantbl[term.charset] == CS_GRAPHIC0 &&
1132 BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
1133 utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ);
1134
1135 if (term.line[y][x].mode & ATTR_WIDE) {
1136 if (x+1 < term.col) {
1137 term.line[y][x+1].u = ' ';
1138 term.line[y][x+1].mode &= ~ATTR_WDUMMY;
1139 }
1140 } else if (term.line[y][x].mode & ATTR_WDUMMY) {
1141 term.line[y][x-1].u = ' ';
1142 term.line[y][x-1].mode &= ~ATTR_WIDE;
1143 }
1144
1145 term.dirty[y] = 1;
1146 term.line[y][x] = *attr;
1147 term.line[y][x].u = u;
1148 }
1149
1150 void
1151 tclearregion(int x1, int y1, int x2, int y2)
1152 {
1153 int x, y, temp;
1154 Glyph *gp;
1155
1156 if (x1 > x2)
1157 temp = x1, x1 = x2, x2 = temp;
1158 if (y1 > y2)
1159 temp = y1, y1 = y2, y2 = temp;
1160
1161 LIMIT(x1, 0, term.col-1);
1162 LIMIT(x2, 0, term.col-1);
1163 LIMIT(y1, 0, term.row-1);
1164 LIMIT(y2, 0, term.row-1);
1165
1166 for (y = y1; y <= y2; y++) {
1167 term.dirty[y] = 1;
1168 for (x = x1; x <= x2; x++) {
1169 gp = &term.line[y][x];
1170 if (selected(x, y))
1171 selclear();
1172 gp->fg = term.c.attr.fg;
1173 gp->bg = term.c.attr.bg;
1174 gp->mode = 0;
1175 gp->u = ' ';
1176 }
1177 }
1178 }
1179
1180 void
1181 tdeletechar(int n)
1182 {
1183 int dst, src, size;
1184 Glyph *line;
1185
1186 LIMIT(n, 0, term.col - term.c.x);
1187
1188 dst = term.c.x;
1189 src = term.c.x + n;
1190 size = term.col - src;
1191 line = term.line[term.c.y];
1192
1193 memmove(&line[dst], &line[src], size * sizeof(Glyph));
1194 tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
1195 }
1196
1197 void
1198 tinsertblank(int n)
1199 {
1200 int dst, src, size;
1201 Glyph *line;
1202
1203 LIMIT(n, 0, term.col - term.c.x);
1204
1205 dst = term.c.x + n;
1206 src = term.c.x;
1207 size = term.col - dst;
1208 line = term.line[term.c.y];
1209
1210 memmove(&line[dst], &line[src], size * sizeof(Glyph));
1211 tclearregion(src, term.c.y, dst - 1, term.c.y);
1212 }
1213
1214 void
1215 tinsertblankline(int n)
1216 {
1217 if (BETWEEN(term.c.y, term.top, term.bot))
1218 tscrolldown(term.c.y, n);
1219 }
1220
1221 void
1222 tdeleteline(int n)
1223 {
1224 if (BETWEEN(term.c.y, term.top, term.bot))
1225 tscrollup(term.c.y, n);
1226 }
1227
1228 int32_t
1229 tdefcolor(int *attr, int *npar, int l)
1230 {
1231 int32_t idx = -1;
1232 uint r, g, b;
1233
1234 switch (attr[*npar + 1]) {
1235 case 2: /* direct color in RGB space */
1236 if (*npar + 4 >= l) {
1237 fprintf(stderr,
1238 "erresc(38): Incorrect number of parameters (%d)\n",
1239 *npar);
1240 break;
1241 }
1242 r = attr[*npar + 2];
1243 g = attr[*npar + 3];
1244 b = attr[*npar + 4];
1245 *npar += 4;
1246 if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255))
1247 fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n",
1248 r, g, b);
1249 else
1250 idx = TRUECOLOR(r, g, b);
1251 break;
1252 case 5: /* indexed color */
1253 if (*npar + 2 >= l) {
1254 fprintf(stderr,
1255 "erresc(38): Incorrect number of parameters (%d)\n",
1256 *npar);
1257 break;
1258 }
1259 *npar += 2;
1260 if (!BETWEEN(attr[*npar], 0, 255))
1261 fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]);
1262 else
1263 idx = attr[*npar];
1264 break;
1265 case 0: /* implemented defined (only foreground) */
1266 case 1: /* transparent */
1267 case 3: /* direct color in CMY space */
1268 case 4: /* direct color in CMYK space */
1269 default:
1270 fprintf(stderr,
1271 "erresc(38): gfx attr %d unknown\n", attr[*npar]);
1272 break;
1273 }
1274
1275 return idx;
1276 }
1277
1278 void
1279 tsetattr(int *attr, int l)
1280 {
1281 int i;
1282 int32_t idx;
1283
1284 for (i = 0; i < l; i++) {
1285 switch (attr[i]) {
1286 case 0:
1287 term.c.attr.mode &= ~(
1288 ATTR_BOLD |
1289 ATTR_FAINT |
1290 ATTR_ITALIC |
1291 ATTR_UNDERLINE |
1292 ATTR_BLINK |
1293 ATTR_REVERSE |
1294 ATTR_INVISIBLE |
1295 ATTR_STRUCK );
1296 term.c.attr.fg = defaultfg;
1297 term.c.attr.bg = defaultbg;
1298 break;
1299 case 1:
1300 term.c.attr.mode |= ATTR_BOLD;
1301 break;
1302 case 2:
1303 term.c.attr.mode |= ATTR_FAINT;
1304 break;
1305 case 3:
1306 term.c.attr.mode |= ATTR_ITALIC;
1307 break;
1308 case 4:
1309 term.c.attr.mode |= ATTR_UNDERLINE;
1310 break;
1311 case 5: /* slow blink */
1312 /* FALLTHROUGH */
1313 case 6: /* rapid blink */
1314 term.c.attr.mode |= ATTR_BLINK;
1315 break;
1316 case 7:
1317 term.c.attr.mode |= ATTR_REVERSE;
1318 break;
1319 case 8:
1320 term.c.attr.mode |= ATTR_INVISIBLE;
1321 break;
1322 case 9:
1323 term.c.attr.mode |= ATTR_STRUCK;
1324 break;
1325 case 22:
1326 term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT);
1327 break;
1328 case 23:
1329 term.c.attr.mode &= ~ATTR_ITALIC;
1330 break;
1331 case 24:
1332 term.c.attr.mode &= ~ATTR_UNDERLINE;
1333 break;
1334 case 25:
1335 term.c.attr.mode &= ~ATTR_BLINK;
1336 break;
1337 case 27:
1338 term.c.attr.mode &= ~ATTR_REVERSE;
1339 break;
1340 case 28:
1341 term.c.attr.mode &= ~ATTR_INVISIBLE;
1342 break;
1343 case 29:
1344 term.c.attr.mode &= ~ATTR_STRUCK;
1345 break;
1346 case 38:
1347 if ((idx = tdefcolor(attr, &i, l)) >= 0)
1348 term.c.attr.fg = idx;
1349 break;
1350 case 39:
1351 term.c.attr.fg = defaultfg;
1352 break;
1353 case 48:
1354 if ((idx = tdefcolor(attr, &i, l)) >= 0)
1355 term.c.attr.bg = idx;
1356 break;
1357 case 49:
1358 term.c.attr.bg = defaultbg;
1359 break;
1360 default:
1361 if (BETWEEN(attr[i], 30, 37)) {
1362 term.c.attr.fg = attr[i] - 30;
1363 } else if (BETWEEN(attr[i], 40, 47)) {
1364 term.c.attr.bg = attr[i] - 40;
1365 } else if (BETWEEN(attr[i], 90, 97)) {
1366 term.c.attr.fg = attr[i] - 90 + 8;
1367 } else if (BETWEEN(attr[i], 100, 107)) {
1368 term.c.attr.bg = attr[i] - 100 + 8;
1369 } else {
1370 fprintf(stderr,
1371 "erresc(default): gfx attr %d unknown\n",
1372 attr[i]), csidump();
1373 }
1374 break;
1375 }
1376 }
1377 }
1378
1379 void
1380 tsetscroll(int t, int b)
1381 {
1382 int temp;
1383
1384 LIMIT(t, 0, term.row-1);
1385 LIMIT(b, 0, term.row-1);
1386 if (t > b) {
1387 temp = t;
1388 t = b;
1389 b = temp;
1390 }
1391 term.top = t;
1392 term.bot = b;
1393 }
1394
1395 void
1396 tsetmode(int priv, int set, int *args, int narg)
1397 {
1398 int *lim, mode;
1399 int alt;
1400
1401 for (lim = args + narg; args < lim; ++args) {
1402 if (priv) {
1403 switch (*args) {
1404 case 1: /* DECCKM -- Cursor key */
1405 MODBIT(term.mode, set, MODE_APPCURSOR);
1406 break;
1407 case 5: /* DECSCNM -- Reverse video */
1408 mode = term.mode;
1409 MODBIT(term.mode, set, MODE_REVERSE);
1410 if (mode != term.mode)
1411 redraw();
1412 break;
1413 case 6: /* DECOM -- Origin */
1414 MODBIT(term.c.state, set, CURSOR_ORIGIN);
1415 tmoveato(0, 0);
1416 break;
1417 case 7: /* DECAWM -- Auto wrap */
1418 MODBIT(term.mode, set, MODE_WRAP);
1419 break;
1420 case 0: /* Error (IGNORED) */
1421 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1422 case 3: /* DECCOLM -- Column (IGNORED) */
1423 case 4: /* DECSCLM -- Scroll (IGNORED) */
1424 case 8: /* DECARM -- Auto repeat (IGNORED) */
1425 case 18: /* DECPFF -- Printer feed (IGNORED) */
1426 case 19: /* DECPEX -- Printer extent (IGNORED) */
1427 case 42: /* DECNRCM -- National characters (IGNORED) */
1428 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1429 break;
1430 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1431 MODBIT(term.mode, !set, MODE_HIDE);
1432 break;
1433 case 9: /* X10 mouse compatibility mode */
1434 xsetpointermotion(0);
1435 MODBIT(term.mode, 0, MODE_MOUSE);
1436 MODBIT(term.mode, set, MODE_MOUSEX10);
1437 break;
1438 case 1000: /* 1000: report button press */
1439 xsetpointermotion(0);
1440 MODBIT(term.mode, 0, MODE_MOUSE);
1441 MODBIT(term.mode, set, MODE_MOUSEBTN);
1442 break;
1443 case 1002: /* 1002: report motion on button press */
1444 xsetpointermotion(0);
1445 MODBIT(term.mode, 0, MODE_MOUSE);
1446 MODBIT(term.mode, set, MODE_MOUSEMOTION);
1447 break;
1448 case 1003: /* 1003: enable all mouse motions */
1449 xsetpointermotion(set);
1450 MODBIT(term.mode, 0, MODE_MOUSE);
1451 MODBIT(term.mode, set, MODE_MOUSEMANY);
1452 break;
1453 case 1004: /* 1004: send focus events to tty */
1454 MODBIT(term.mode, set, MODE_FOCUS);
1455 break;
1456 case 1006: /* 1006: extended reporting mode */
1457 MODBIT(term.mode, set, MODE_MOUSESGR);
1458 break;
1459 case 1034:
1460 MODBIT(term.mode, set, MODE_8BIT);
1461 break;
1462 case 1049: /* swap screen & set/restore cursor as xterm */
1463 if (!allowaltscreen)
1464 break;
1465 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1466 /* FALLTHROUGH */
1467 case 47: /* swap screen */
1468 case 1047:
1469 if (!allowaltscreen)
1470 break;
1471 alt = IS_SET(MODE_ALTSCREEN);
1472 if (alt) {
1473 tclearregion(0, 0, term.col-1,
1474 term.row-1);
1475 }
1476 if (set ^ alt) /* set is always 1 or 0 */
1477 tswapscreen();
1478 if (*args != 1049)
1479 break;
1480 /* FALLTHROUGH */
1481 case 1048:
1482 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1483 break;
1484 case 2004: /* 2004: bracketed paste mode */
1485 MODBIT(term.mode, set, MODE_BRCKTPASTE);
1486 break;
1487 /* Not implemented mouse modes. See comments there. */
1488 case 1001: /* mouse highlight mode; can hang the
1489 terminal by design when implemented. */
1490 case 1005: /* UTF-8 mouse mode; will confuse
1491 applications not supporting UTF-8
1492 and luit. */
1493 case 1015: /* urxvt mangled mouse mode; incompatible
1494 and can be mistaken for other control
1495 codes. */
1496 default:
1497 fprintf(stderr,
1498 "erresc: unknown private set/reset mode %d\n",
1499 *args);
1500 break;
1501 }
1502 } else {
1503 switch (*args) {
1504 case 0: /* Error (IGNORED) */
1505 break;
1506 case 2: /* KAM -- keyboard action */
1507 MODBIT(term.mode, set, MODE_KBDLOCK);
1508 break;
1509 case 4: /* IRM -- Insertion-replacement */
1510 MODBIT(term.mode, set, MODE_INSERT);
1511 break;
1512 case 12: /* SRM -- Send/Receive */
1513 MODBIT(term.mode, !set, MODE_ECHO);
1514 break;
1515 case 20: /* LNM -- Linefeed/new line */
1516 MODBIT(term.mode, set, MODE_CRLF);
1517 break;
1518 default:
1519 fprintf(stderr,
1520 "erresc: unknown set/reset mode %d\n",
1521 *args);
1522 break;
1523 }
1524 }
1525 }
1526 }
1527
1528 void
1529 csihandle(void)
1530 {
1531 char buf[40];
1532 int len;
1533
1534 switch (csiescseq.mode[0]) {
1535 default:
1536 unknown:
1537 fprintf(stderr, "erresc: unknown csi ");
1538 csidump();
1539 /* die(""); */
1540 break;
1541 case '@': /* ICH -- Insert <n> blank char */
1542 DEFAULT(csiescseq.arg[0], 1);
1543 tinsertblank(csiescseq.arg[0]);
1544 break;
1545 case 'A': /* CUU -- Cursor <n> Up */
1546 DEFAULT(csiescseq.arg[0], 1);
1547 tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
1548 break;
1549 case 'B': /* CUD -- Cursor <n> Down */
1550 case 'e': /* VPR --Cursor <n> Down */
1551 DEFAULT(csiescseq.arg[0], 1);
1552 tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
1553 break;
1554 case 'i': /* MC -- Media Copy */
1555 switch (csiescseq.arg[0]) {
1556 case 0:
1557 tdump();
1558 break;
1559 case 1:
1560 tdumpline(term.c.y);
1561 break;
1562 case 2:
1563 tdumpsel();
1564 break;
1565 case 4:
1566 term.mode &= ~MODE_PRINT;
1567 break;
1568 case 5:
1569 term.mode |= MODE_PRINT;
1570 break;
1571 }
1572 break;
1573 case 'c': /* DA -- Device Attributes */
1574 if (csiescseq.arg[0] == 0)
1575 ttywrite(vtiden, sizeof(vtiden) - 1);
1576 break;
1577 case 'C': /* CUF -- Cursor <n> Forward */
1578 case 'a': /* HPR -- Cursor <n> Forward */
1579 DEFAULT(csiescseq.arg[0], 1);
1580 tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
1581 break;
1582 case 'D': /* CUB -- Cursor <n> Backward */
1583 DEFAULT(csiescseq.arg[0], 1);
1584 tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
1585 break;
1586 case 'E': /* CNL -- Cursor <n> Down and first col */
1587 DEFAULT(csiescseq.arg[0], 1);
1588 tmoveto(0, term.c.y+csiescseq.arg[0]);
1589 break;
1590 case 'F': /* CPL -- Cursor <n> Up and first col */
1591 DEFAULT(csiescseq.arg[0], 1);
1592 tmoveto(0, term.c.y-csiescseq.arg[0]);
1593 break;
1594 case 'g': /* TBC -- Tabulation clear */
1595 switch (csiescseq.arg[0]) {
1596 case 0: /* clear current tab stop */
1597 term.tabs[term.c.x] = 0;
1598 break;
1599 case 3: /* clear all the tabs */
1600 memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1601 break;
1602 default:
1603 goto unknown;
1604 }
1605 break;
1606 case 'G': /* CHA -- Move to <col> */
1607 case '`': /* HPA */
1608 DEFAULT(csiescseq.arg[0], 1);
1609 tmoveto(csiescseq.arg[0]-1, term.c.y);
1610 break;
1611 case 'H': /* CUP -- Move to <row> <col> */
1612 case 'f': /* HVP */
1613 DEFAULT(csiescseq.arg[0], 1);
1614 DEFAULT(csiescseq.arg[1], 1);
1615 tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
1616 break;
1617 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1618 DEFAULT(csiescseq.arg[0], 1);
1619 tputtab(csiescseq.arg[0]);
1620 break;
1621 case 'J': /* ED -- Clear screen */
1622 selclear();
1623 switch (csiescseq.arg[0]) {
1624 case 0: /* below */
1625 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
1626 if (term.c.y < term.row-1) {
1627 tclearregion(0, term.c.y+1, term.col-1,
1628 term.row-1);
1629 }
1630 break;
1631 case 1: /* above */
1632 if (term.c.y > 1)
1633 tclearregion(0, 0, term.col-1, term.c.y-1);
1634 tclearregion(0, term.c.y, term.c.x, term.c.y);
1635 break;
1636 case 2: /* all */
1637 tclearregion(0, 0, term.col-1, term.row-1);
1638 break;
1639 default:
1640 goto unknown;
1641 }
1642 break;
1643 case 'K': /* EL -- Clear line */
1644 switch (csiescseq.arg[0]) {
1645 case 0: /* right */
1646 tclearregion(term.c.x, term.c.y, term.col-1,
1647 term.c.y);
1648 break;
1649 case 1: /* left */
1650 tclearregion(0, term.c.y, term.c.x, term.c.y);
1651 break;
1652 case 2: /* all */
1653 tclearregion(0, term.c.y, term.col-1, term.c.y);
1654 break;
1655 }
1656 break;
1657 case 'S': /* SU -- Scroll <n> line up */
1658 DEFAULT(csiescseq.arg[0], 1);
1659 tscrollup(term.top, csiescseq.arg[0]);
1660 break;
1661 case 'T': /* SD -- Scroll <n> line down */
1662 DEFAULT(csiescseq.arg[0], 1);
1663 tscrolldown(term.top, csiescseq.arg[0]);
1664 break;
1665 case 'L': /* IL -- Insert <n> blank lines */
1666 DEFAULT(csiescseq.arg[0], 1);
1667 tinsertblankline(csiescseq.arg[0]);
1668 break;
1669 case 'l': /* RM -- Reset Mode */
1670 tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
1671 break;
1672 case 'M': /* DL -- Delete <n> lines */
1673 DEFAULT(csiescseq.arg[0], 1);
1674 tdeleteline(csiescseq.arg[0]);
1675 break;
1676 case 'X': /* ECH -- Erase <n> char */
1677 DEFAULT(csiescseq.arg[0], 1);
1678 tclearregion(term.c.x, term.c.y,
1679 term.c.x + csiescseq.arg[0] - 1, term.c.y);
1680 break;
1681 case 'P': /* DCH -- Delete <n> char */
1682 DEFAULT(csiescseq.arg[0], 1);
1683 tdeletechar(csiescseq.arg[0]);
1684 break;
1685 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1686 DEFAULT(csiescseq.arg[0], 1);
1687 tputtab(-csiescseq.arg[0]);
1688 break;
1689 case 'd': /* VPA -- Move to <row> */
1690 DEFAULT(csiescseq.arg[0], 1);
1691 tmoveato(term.c.x, csiescseq.arg[0]-1);
1692 break;
1693 case 'h': /* SM -- Set terminal mode */
1694 tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
1695 break;
1696 case 'm': /* SGR -- Terminal attribute (color) */
1697 tsetattr(csiescseq.arg, csiescseq.narg);
1698 break;
1699 case 'n': /* DSR – Device Status Report (cursor position) */
1700 if (csiescseq.arg[0] == 6) {
1701 len = snprintf(buf, sizeof(buf),"\033[%i;%iR",
1702 term.c.y+1, term.c.x+1);
1703 ttywrite(buf, len);
1704 }
1705 break;
1706 case 'r': /* DECSTBM -- Set Scrolling Region */
1707 if (csiescseq.priv) {
1708 goto unknown;
1709 } else {
1710 DEFAULT(csiescseq.arg[0], 1);
1711 DEFAULT(csiescseq.arg[1], term.row);
1712 tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
1713 tmoveato(0, 0);
1714 }
1715 break;
1716 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1717 tcursor(CURSOR_SAVE);
1718 break;
1719 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1720 tcursor(CURSOR_LOAD);
1721 break;
1722 case ' ':
1723 switch (csiescseq.mode[1]) {
1724 case 'q': /* DECSCUSR -- Set Cursor Style */
1725 DEFAULT(csiescseq.arg[0], 1);
1726 if (!BETWEEN(csiescseq.arg[0], 0, 6)) {
1727 goto unknown;
1728 }
1729 win.cursor = csiescseq.arg[0];
1730 break;
1731 default:
1732 goto unknown;
1733 }
1734 break;
1735 }
1736 }
1737
1738 void
1739 csidump(void)
1740 {
1741 int i;
1742 uint c;
1743
1744 fprintf(stderr, "ESC[");
1745 for (i = 0; i < csiescseq.len; i++) {
1746 c = csiescseq.buf[i] & 0xff;
1747 if (isprint(c)) {
1748 putc(c, stderr);
1749 } else if (c == '\n') {
1750 fprintf(stderr, "(\\n)");
1751 } else if (c == '\r') {
1752 fprintf(stderr, "(\\r)");
1753 } else if (c == 0x1b) {
1754 fprintf(stderr, "(\\e)");
1755 } else {
1756 fprintf(stderr, "(%02x)", c);
1757 }
1758 }
1759 putc('\n', stderr);
1760 }
1761
1762 void
1763 csireset(void)
1764 {
1765 memset(&csiescseq, 0, sizeof(csiescseq));
1766 }
1767
1768 void
1769 strhandle(void)
1770 {
1771 char *p = NULL;
1772 int j, narg, par;
1773
1774 term.esc &= ~(ESC_STR_END|ESC_STR);
1775 strparse();
1776 par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
1777
1778 switch (strescseq.type) {
1779 case ']': /* OSC -- Operating System Command */
1780 switch (par) {
1781 case 0:
1782 case 1:
1783 case 2:
1784 if (narg > 1)
1785 xsettitle(strescseq.args[1]);
1786 return;
1787 case 52:
1788 if (narg > 2) {
1789 char *dec;
1790
1791 dec = base64dec(strescseq.args[2]);
1792 if (dec) {
1793 xsetsel(dec, CurrentTime);
1794 clipcopy(NULL);
1795 } else {
1796 fprintf(stderr, "erresc: invalid base64\n");
1797 }
1798 }
1799 return;
1800 case 4: /* color set */
1801 if (narg < 3)
1802 break;
1803 p = strescseq.args[2];
1804 /* FALLTHROUGH */
1805 case 104: /* color reset, here p = NULL */
1806 j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
1807 if (xsetcolorname(j, p)) {
1808 fprintf(stderr, "erresc: invalid color %s\n", p);
1809 } else {
1810 /*
1811 * TODO if defaultbg color is changed, borders
1812 * are dirty
1813 */
1814 redraw();
1815 }
1816 return;
1817 }
1818 break;
1819 case 'k': /* old title set compatibility */
1820 xsettitle(strescseq.args[0]);
1821 return;
1822 case 'P': /* DCS -- Device Control String */
1823 term.mode |= ESC_DCS;
1824 case '_': /* APC -- Application Program Command */
1825 case '^': /* PM -- Privacy Message */
1826 return;
1827 }
1828
1829 fprintf(stderr, "erresc: unknown str ");
1830 strdump();
1831 }
1832
1833 void
1834 strparse(void)
1835 {
1836 int c;
1837 char *p = strescseq.buf;
1838
1839 strescseq.narg = 0;
1840 strescseq.buf[strescseq.len] = '\0';
1841
1842 if (*p == '\0')
1843 return;
1844
1845 while (strescseq.narg < STR_ARG_SIZ) {
1846 strescseq.args[strescseq.narg++] = p;
1847 while ((c = *p) != ';' && c != '\0')
1848 ++p;
1849 if (c == '\0')
1850 return;
1851 *p++ = '\0';
1852 }
1853 }
1854
1855 void
1856 strdump(void)
1857 {
1858 int i;
1859 uint c;
1860
1861 fprintf(stderr, "ESC%c", strescseq.type);
1862 for (i = 0; i < strescseq.len; i++) {
1863 c = strescseq.buf[i] & 0xff;
1864 if (c == '\0') {
1865 putc('\n', stderr);
1866 return;
1867 } else if (isprint(c)) {
1868 putc(c, stderr);
1869 } else if (c == '\n') {
1870 fprintf(stderr, "(\\n)");
1871 } else if (c == '\r') {
1872 fprintf(stderr, "(\\r)");
1873 } else if (c == 0x1b) {
1874 fprintf(stderr, "(\\e)");
1875 } else {
1876 fprintf(stderr, "(%02x)", c);
1877 }
1878 }
1879 fprintf(stderr, "ESC\\\n");
1880 }
1881
1882 void
1883 strreset(void)
1884 {
1885 memset(&strescseq, 0, sizeof(strescseq));
1886 }
1887
1888 void
1889 sendbreak(const Arg *arg)
1890 {
1891 if (tcsendbreak(cmdfd, 0))
1892 perror("Error sending break");
1893 }
1894
1895 void
1896 tprinter(char *s, size_t len)
1897 {
1898 if (iofd != -1 && xwrite(iofd, s, len) < 0) {
1899 perror("Error writing to output file");
1900 close(iofd);
1901 iofd = -1;
1902 }
1903 }
1904
1905 void
1906 iso14755(const Arg *arg)
1907 {
1908 FILE *p;
1909 char *us, *e, codepoint[9], uc[UTF_SIZ];
1910 unsigned long utf32;
1911
1912 if (!(p = popen(ISO14755CMD, "r")))
1913 return;
1914
1915 us = fgets(codepoint, sizeof(codepoint), p);
1916 pclose(p);
1917
1918 if (!us || *us == '\0' || *us == '-' || strlen(us) > 7)
1919 return;
1920 if ((utf32 = strtoul(us, &e, 16)) == ULONG_MAX ||
1921 (*e != '\n' && *e != '\0'))
1922 return;
1923
1924 ttysend(uc, utf8encode(utf32, uc));
1925 }
1926
1927 void
1928 toggleprinter(const Arg *arg)
1929 {
1930 term.mode ^= MODE_PRINT;
1931 }
1932
1933 void
1934 printscreen(const Arg *arg)
1935 {
1936 tdump();
1937 }
1938
1939 void
1940 printsel(const Arg *arg)
1941 {
1942 tdumpsel();
1943 }
1944
1945 void
1946 tdumpsel(void)
1947 {
1948 char *ptr;
1949
1950 if ((ptr = getsel())) {
1951 tprinter(ptr, strlen(ptr));
1952 free(ptr);
1953 }
1954 }
1955
1956 void
1957 tdumpline(int n)
1958 {
1959 char buf[UTF_SIZ];
1960 Glyph *bp, *end;
1961
1962 bp = &term.line[n][0];
1963 end = &bp[MIN(tlinelen(n), term.col) - 1];
1964 if (bp != end || bp->u != ' ') {
1965 for ( ;bp <= end; ++bp)
1966 tprinter(buf, utf8encode(bp->u, buf));
1967 }
1968 tprinter("\n", 1);
1969 }
1970
1971 void
1972 tdump(void)
1973 {
1974 int i;
1975
1976 for (i = 0; i < term.row; ++i)
1977 tdumpline(i);
1978 }
1979
1980 void
1981 tputtab(int n)
1982 {
1983 uint x = term.c.x;
1984
1985 if (n > 0) {
1986 while (x < term.col && n--)
1987 for (++x; x < term.col && !term.tabs[x]; ++x)
1988 /* nothing */ ;
1989 } else if (n < 0) {
1990 while (x > 0 && n++)
1991 for (--x; x > 0 && !term.tabs[x]; --x)
1992 /* nothing */ ;
1993 }
1994 term.c.x = LIMIT(x, 0, term.col-1);
1995 }
1996
1997 void
1998 tdefutf8(char ascii)
1999 {
2000 if (ascii == 'G')
2001 term.mode |= MODE_UTF8;
2002 else if (ascii == '@')
2003 term.mode &= ~MODE_UTF8;
2004 }
2005
2006 void
2007 tdeftran(char ascii)
2008 {
2009 static char cs[] = "0B";
2010 static int vcs[] = {CS_GRAPHIC0, CS_USA};
2011 char *p;
2012
2013 if ((p = strchr(cs, ascii)) == NULL) {
2014 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
2015 } else {
2016 term.trantbl[term.icharset] = vcs[p - cs];
2017 }
2018 }
2019
2020 void
2021 tdectest(char c)
2022 {
2023 int x, y;
2024
2025 if (c == '8') { /* DEC screen alignment test. */
2026 for (x = 0; x < term.col; ++x) {
2027 for (y = 0; y < term.row; ++y)
2028 tsetchar('E', &term.c.attr, x, y);
2029 }
2030 }
2031 }
2032
2033 void
2034 tstrsequence(uchar c)
2035 {
2036 strreset();
2037
2038 switch (c) {
2039 case 0x90: /* DCS -- Device Control String */
2040 c = 'P';
2041 term.esc |= ESC_DCS;
2042 break;
2043 case 0x9f: /* APC -- Application Program Command */
2044 c = '_';
2045 break;
2046 case 0x9e: /* PM -- Privacy Message */
2047 c = '^';
2048 break;
2049 case 0x9d: /* OSC -- Operating System Command */
2050 c = ']';
2051 break;
2052 }
2053 strescseq.type = c;
2054 term.esc |= ESC_STR;
2055 }
2056
2057 void
2058 tcontrolcode(uchar ascii)
2059 {
2060 switch (ascii) {
2061 case '\t': /* HT */
2062 tputtab(1);
2063 return;
2064 case '\b': /* BS */
2065 tmoveto(term.c.x-1, term.c.y);
2066 return;
2067 case '\r': /* CR */
2068 tmoveto(0, term.c.y);
2069 return;
2070 case '\f': /* LF */
2071 case '\v': /* VT */
2072 case '\n': /* LF */
2073 /* go to first col if the mode is set */
2074 tnewline(IS_SET(MODE_CRLF));
2075 return;
2076 case '\a': /* BEL */
2077 if (term.esc & ESC_STR_END) {
2078 /* backwards compatibility to xterm */
2079 strhandle();
2080 } else {
2081 xbell();
2082 }
2083 break;
2084 case '\033': /* ESC */
2085 csireset();
2086 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
2087 term.esc |= ESC_START;
2088 return;
2089 case '\016': /* SO (LS1 -- Locking shift 1) */
2090 case '\017': /* SI (LS0 -- Locking shift 0) */
2091 term.charset = 1 - (ascii - '\016');
2092 return;
2093 case '\032': /* SUB */
2094 tsetchar('?', &term.c.attr, term.c.x, term.c.y);
2095 case '\030': /* CAN */
2096 csireset();
2097 break;
2098 case '\005': /* ENQ (IGNORED) */
2099 case '\000': /* NUL (IGNORED) */
2100 case '\021': /* XON (IGNORED) */
2101 case '\023': /* XOFF (IGNORED) */
2102 case 0177: /* DEL (IGNORED) */
2103 return;
2104 case 0x80: /* TODO: PAD */
2105 case 0x81: /* TODO: HOP */
2106 case 0x82: /* TODO: BPH */
2107 case 0x83: /* TODO: NBH */
2108 case 0x84: /* TODO: IND */
2109 break;
2110 case 0x85: /* NEL -- Next line */
2111 tnewline(1); /* always go to first col */
2112 break;
2113 case 0x86: /* TODO: SSA */
2114 case 0x87: /* TODO: ESA */
2115 break;
2116 case 0x88: /* HTS -- Horizontal tab stop */
2117 term.tabs[term.c.x] = 1;
2118 break;
2119 case 0x89: /* TODO: HTJ */
2120 case 0x8a: /* TODO: VTS */
2121 case 0x8b: /* TODO: PLD */
2122 case 0x8c: /* TODO: PLU */
2123 case 0x8d: /* TODO: RI */
2124 case 0x8e: /* TODO: SS2 */
2125 case 0x8f: /* TODO: SS3 */
2126 case 0x91: /* TODO: PU1 */
2127 case 0x92: /* TODO: PU2 */
2128 case 0x93: /* TODO: STS */
2129 case 0x94: /* TODO: CCH */
2130 case 0x95: /* TODO: MW */
2131 case 0x96: /* TODO: SPA */
2132 case 0x97: /* TODO: EPA */
2133 case 0x98: /* TODO: SOS */
2134 case 0x99: /* TODO: SGCI */
2135 break;
2136 case 0x9a: /* DECID -- Identify Terminal */
2137 ttywrite(vtiden, sizeof(vtiden) - 1);
2138 break;
2139 case 0x9b: /* TODO: CSI */
2140 case 0x9c: /* TODO: ST */
2141 break;
2142 case 0x90: /* DCS -- Device Control String */
2143 case 0x9d: /* OSC -- Operating System Command */
2144 case 0x9e: /* PM -- Privacy Message */
2145 case 0x9f: /* APC -- Application Program Command */
2146 tstrsequence(ascii);
2147 return;
2148 }
2149 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2150 term.esc &= ~(ESC_STR_END|ESC_STR);
2151 }
2152
2153 /*
2154 * returns 1 when the sequence is finished and it hasn't to read
2155 * more characters for this sequence, otherwise 0
2156 */
2157 int
2158 eschandle(uchar ascii)
2159 {
2160 switch (ascii) {
2161 case '[':
2162 term.esc |= ESC_CSI;
2163 return 0;
2164 case '#':
2165 term.esc |= ESC_TEST;
2166 return 0;
2167 case '%':
2168 term.esc |= ESC_UTF8;
2169 return 0;
2170 case 'P': /* DCS -- Device Control String */
2171 case '_': /* APC -- Application Program Command */
2172 case '^': /* PM -- Privacy Message */
2173 case ']': /* OSC -- Operating System Command */
2174 case 'k': /* old title set compatibility */
2175 tstrsequence(ascii);
2176 return 0;
2177 case 'n': /* LS2 -- Locking shift 2 */
2178 case 'o': /* LS3 -- Locking shift 3 */
2179 term.charset = 2 + (ascii - 'n');
2180 break;
2181 case '(': /* GZD4 -- set primary charset G0 */
2182 case ')': /* G1D4 -- set secondary charset G1 */
2183 case '*': /* G2D4 -- set tertiary charset G2 */
2184 case '+': /* G3D4 -- set quaternary charset G3 */
2185 term.icharset = ascii - '(';
2186 term.esc |= ESC_ALTCHARSET;
2187 return 0;
2188 case 'D': /* IND -- Linefeed */
2189 if (term.c.y == term.bot) {
2190 tscrollup(term.top, 1);
2191 } else {
2192 tmoveto(term.c.x, term.c.y+1);
2193 }
2194 break;
2195 case 'E': /* NEL -- Next line */
2196 tnewline(1); /* always go to first col */
2197 break;
2198 case 'H': /* HTS -- Horizontal tab stop */
2199 term.tabs[term.c.x] = 1;
2200 break;
2201 case 'M': /* RI -- Reverse index */
2202 if (term.c.y == term.top) {
2203 tscrolldown(term.top, 1);
2204 } else {
2205 tmoveto(term.c.x, term.c.y-1);
2206 }
2207 break;
2208 case 'Z': /* DECID -- Identify Terminal */
2209 ttywrite(vtiden, sizeof(vtiden) - 1);
2210 break;
2211 case 'c': /* RIS -- Reset to inital state */
2212 treset();
2213 resettitle();
2214 xloadcols();
2215 break;
2216 case '=': /* DECPAM -- Application keypad */
2217 term.mode |= MODE_APPKEYPAD;
2218 break;
2219 case '>': /* DECPNM -- Normal keypad */
2220 term.mode &= ~MODE_APPKEYPAD;
2221 break;
2222 case '7': /* DECSC -- Save Cursor */
2223 tcursor(CURSOR_SAVE);
2224 break;
2225 case '8': /* DECRC -- Restore Cursor */
2226 tcursor(CURSOR_LOAD);
2227 break;
2228 case '\\': /* ST -- String Terminator */
2229 if (term.esc & ESC_STR_END)
2230 strhandle();
2231 break;
2232 default:
2233 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2234 (uchar) ascii, isprint(ascii)? ascii:'.');
2235 break;
2236 }
2237 return 1;
2238 }
2239
2240 void
2241 tputc(Rune u)
2242 {
2243 char c[UTF_SIZ];
2244 int control;
2245 int width, len;
2246 Glyph *gp;
2247
2248 control = ISCONTROL(u);
2249 if (!IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
2250 c[0] = u;
2251 width = len = 1;
2252 } else {
2253 len = utf8encode(u, c);
2254 if (!control && (width = wcwidth(u)) == -1) {
2255 memcpy(c, "\357\277\275", 4); /* UTF_INVALID */
2256 width = 1;
2257 }
2258 }
2259
2260 if (IS_SET(MODE_PRINT))
2261 tprinter(c, len);
2262
2263 /*
2264 * STR sequence must be checked before anything else
2265 * because it uses all following characters until it
2266 * receives a ESC, a SUB, a ST or any other C1 control
2267 * character.
2268 */
2269 if (term.esc & ESC_STR) {
2270 if (u == '\a' || u == 030 || u == 032 || u == 033 ||
2271 ISCONTROLC1(u)) {
2272 term.esc &= ~(ESC_START|ESC_STR|ESC_DCS);
2273 if (IS_SET(MODE_SIXEL)) {
2274 /* TODO: render sixel */;
2275 term.mode &= ~MODE_SIXEL;
2276 return;
2277 }
2278 term.esc |= ESC_STR_END;
2279 goto check_control_code;
2280 }
2281
2282
2283 if (IS_SET(MODE_SIXEL)) {
2284 /* TODO: implement sixel mode */
2285 return;
2286 }
2287 if (term.esc&ESC_DCS && strescseq.len == 0 && u == 'q')
2288 term.mode |= MODE_SIXEL;
2289
2290 if (strescseq.len+len >= sizeof(strescseq.buf)-1) {
2291 /*
2292 * Here is a bug in terminals. If the user never sends
2293 * some code to stop the str or esc command, then st
2294 * will stop responding. But this is better than
2295 * silently failing with unknown characters. At least
2296 * then users will report back.
2297 *
2298 * In the case users ever get fixed, here is the code:
2299 */
2300 /*
2301 * term.esc = 0;
2302 * strhandle();
2303 */
2304 return;
2305 }
2306
2307 memmove(&strescseq.buf[strescseq.len], c, len);
2308 strescseq.len += len;
2309 return;
2310 }
2311
2312 check_control_code:
2313 /*
2314 * Actions of control codes must be performed as soon they arrive
2315 * because they can be embedded inside a control sequence, and
2316 * they must not cause conflicts with sequences.
2317 */
2318 if (control) {
2319 tcontrolcode(u);
2320 /*
2321 * control codes are not shown ever
2322 */
2323 return;
2324 } else if (term.esc & ESC_START) {
2325 if (term.esc & ESC_CSI) {
2326 csiescseq.buf[csiescseq.len++] = u;
2327 if (BETWEEN(u, 0x40, 0x7E)
2328 || csiescseq.len >= \
2329 sizeof(csiescseq.buf)-1) {
2330 term.esc = 0;
2331 csiparse();
2332 csihandle();
2333 }
2334 return;
2335 } else if (term.esc & ESC_UTF8) {
2336 tdefutf8(u);
2337 } else if (term.esc & ESC_ALTCHARSET) {
2338 tdeftran(u);
2339 } else if (term.esc & ESC_TEST) {
2340 tdectest(u);
2341 } else {
2342 if (!eschandle(u))
2343 return;
2344 /* sequence already finished */
2345 }
2346 term.esc = 0;
2347 /*
2348 * All characters which form part of a sequence are not
2349 * printed
2350 */
2351 return;
2352 }
2353 if (sel.ob.x != -1 && BETWEEN(term.c.y, sel.ob.y, sel.oe.y))
2354 selclear();
2355
2356 gp = &term.line[term.c.y][term.c.x];
2357 if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
2358 gp->mode |= ATTR_WRAP;
2359 tnewline(1);
2360 gp = &term.line[term.c.y][term.c.x];
2361 }
2362
2363 if (IS_SET(MODE_INSERT) && term.c.x+width < term.col)
2364 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
2365
2366 if (term.c.x+width > term.col) {
2367 tnewline(1);
2368 gp = &term.line[term.c.y][term.c.x];
2369 }
2370
2371 tsetchar(u, &term.c.attr, term.c.x, term.c.y);
2372
2373 if (width == 2) {
2374 gp->mode |= ATTR_WIDE;
2375 if (term.c.x+1 < term.col) {
2376 gp[1].u = '\0';
2377 gp[1].mode = ATTR_WDUMMY;
2378 }
2379 }
2380 if (term.c.x+width < term.col) {
2381 tmoveto(term.c.x+width, term.c.y);
2382 } else {
2383 term.c.state |= CURSOR_WRAPNEXT;
2384 }
2385 }
2386
2387 int
2388 twrite(const char *buf, int buflen, int show_ctrl)
2389 {
2390 int charsize;
2391 Rune u;
2392 int n;
2393
2394 for (n = 0; n < buflen; n += charsize) {
2395 if (IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
2396 /* process a complete utf8 char */
2397 charsize = utf8decode(buf + n, &u, buflen - n);
2398 if (charsize == 0)
2399 break;
2400 } else {
2401 u = buf[n] & 0xFF;
2402 charsize = 1;
2403 }
2404 if (show_ctrl && ISCONTROL(u)) {
2405 if (u & 0x80) {
2406 u &= 0x7f;
2407 tputc('^');
2408 tputc('[');
2409 } else if (u != '\n' && u != '\r' && u != '\t') {
2410 u ^= 0x40;
2411 tputc('^');
2412 }
2413 }
2414 tputc(u);
2415 }
2416 return n;
2417 }
2418
2419 void
2420 tresize(int col, int row)
2421 {
2422 int i;
2423 int minrow = MIN(row, term.row);
2424 int mincol = MIN(col, term.col);
2425 int *bp;
2426 TCursor c;
2427
2428 if (col < 1 || row < 1) {
2429 fprintf(stderr,
2430 "tresize: error resizing to %dx%d\n", col, row);
2431 return;
2432 }
2433
2434 /*
2435 * slide screen to keep cursor where we expect it -
2436 * tscrollup would work here, but we can optimize to
2437 * memmove because we're freeing the earlier lines
2438 */
2439 for (i = 0; i <= term.c.y - row; i++) {
2440 free(term.line[i]);
2441 free(term.alt[i]);
2442 }
2443 /* ensure that both src and dst are not NULL */
2444 if (i > 0) {
2445 memmove(term.line, term.line + i, row * sizeof(Line));
2446 memmove(term.alt, term.alt + i, row * sizeof(Line));
2447 }
2448 for (i += row; i < term.row; i++) {
2449 free(term.line[i]);
2450 free(term.alt[i]);
2451 }
2452
2453 /* resize to new height */
2454 term.line = xrealloc(term.line, row * sizeof(Line));
2455 term.alt = xrealloc(term.alt, row * sizeof(Line));
2456 term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
2457 term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
2458
2459 /* resize each row to new width, zero-pad if needed */
2460 for (i = 0; i < minrow; i++) {
2461 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
2462 term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph));
2463 }
2464
2465 /* allocate any new rows */
2466 for (/* i = minrow */; i < row; i++) {
2467 term.line[i] = xmalloc(col * sizeof(Glyph));
2468 term.alt[i] = xmalloc(col * sizeof(Glyph));
2469 }
2470 if (col > term.col) {
2471 bp = term.tabs + term.col;
2472
2473 memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
2474 while (--bp > term.tabs && !*bp)
2475 /* nothing */ ;
2476 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
2477 *bp = 1;
2478 }
2479 /* update terminal size */
2480 term.col = col;
2481 term.row = row;
2482 /* reset scrolling region */
2483 tsetscroll(0, row-1);
2484 /* make use of the LIMIT in tmoveto */
2485 tmoveto(term.c.x, term.c.y);
2486 /* Clearing both screens (it makes dirty all lines) */
2487 c = term.c;
2488 for (i = 0; i < 2; i++) {
2489 if (mincol < col && 0 < minrow) {
2490 tclearregion(mincol, 0, col - 1, minrow - 1);
2491 }
2492 if (0 < col && minrow < row) {
2493 tclearregion(0, minrow, col - 1, row - 1);
2494 }
2495 tswapscreen();
2496 tcursor(CURSOR_LOAD);
2497 }
2498 term.c = c;
2499 }
2500
2501 void
2502 resettitle(void)
2503 {
2504 xsettitle(NULL);
2505 }
2506
2507 void
2508 redraw(void)
2509 {
2510 tfulldirt();
2511 draw();
2512 }
2513
2514 void
2515 numlock(const Arg *dummy)
2516 {
2517 term.numlock ^= 1;
2518 }