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