Xinqi Bao's Git

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