Xinqi Bao's Git

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