Xinqi Bao's Git

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