Xinqi Bao's Git
1 /* See LICENSE for license details. */
12 #include <sys/ioctl.h>
13 #include <sys/select.h>
14 #include <sys/types.h>
25 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
27 #elif defined(__FreeBSD__) || defined(__DragonFly__)
32 #define UTF_INVALID 0xFFFD
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
40 #define IS_SET(flag) ((term.mode & (flag)) != 0)
41 #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f)
42 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
43 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
44 #define ISDELIM(u) (u && wcschr(worddelimiters, u))
49 MODE_ALTSCREEN
= 1 << 2,
56 enum cursor_movement
{
80 ESC_STR
= 4, /* DCS, OSC, PM, APC */
82 ESC_STR_END
= 16, /* a final string was encountered */
83 ESC_TEST
= 32, /* Enter in test mode */
88 Glyph attr
; /* current char attributes */
99 * Selection variables:
100 * nb – normalized coordinates of the beginning of the selection
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
112 /* Internal representation of the screen */
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 */
130 Rune lastc
; /* last printed char outside of sequence, 0 if control */
133 /* CSI Escape sequence structs */
134 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
136 char buf
[ESC_BUF_SIZ
]; /* raw string */
137 size_t len
; /* raw string length */
139 int arg
[ESC_ARG_SIZ
];
140 int narg
; /* nb of args */
144 /* STR Escape sequence structs */
145 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
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 */
155 static void execsh(char *, char **);
156 static void stty(char **);
157 static void sigchld(int);
158 static void ttywriteraw(const char *, size_t);
160 static void csidump(void);
161 static void csihandle(void);
162 static void csiparse(void);
163 static void csireset(void);
164 static int eschandle(uchar
);
165 static void strdump(void);
166 static void strhandle(void);
167 static void strparse(void);
168 static void strreset(void);
170 static void tprinter(char *, size_t);
171 static void tdumpsel(void);
172 static void tdumpline(int);
173 static void tdump(void);
174 static void tclearregion(int, int, int, int);
175 static void tcursor(int);
176 static void tdeletechar(int);
177 static void tdeleteline(int);
178 static void tinsertblank(int);
179 static void tinsertblankline(int);
180 static int tlinelen(int);
181 static void tmoveto(int, int);
182 static void tmoveato(int, int);
183 static void tnewline(int);
184 static void tputtab(int);
185 static void tputc(Rune
);
186 static void treset(void);
187 static void tscrollup(int, int);
188 static void tscrolldown(int, int);
189 static void tsetattr(const int *, int);
190 static void tsetchar(Rune
, const Glyph
*, int, int);
191 static void tsetdirt(int, int);
192 static void tsetscroll(int, int);
193 static void tswapscreen(void);
194 static void tsetmode(int, int, const int *, int);
195 static int twrite(const char *, int, int);
196 static void tfulldirt(void);
197 static void tcontrolcode(uchar
);
198 static void tdectest(char );
199 static void tdefutf8(char);
200 static int32_t tdefcolor(const int *, int *, int);
201 static void tdeftran(char);
202 static void tstrsequence(uchar
);
204 static void drawregion(int, int, int, int);
206 static void selnormalize(void);
207 static void selscroll(int, int);
208 static void selsnap(int *, int *, int);
210 static size_t utf8decode(const char *, Rune
*, size_t);
211 static Rune
utf8decodebyte(char, size_t *);
212 static char utf8encodebyte(Rune
, size_t);
213 static size_t utf8validate(Rune
*, size_t);
215 static char *base64dec(const char *);
216 static char base64dec_getc(const char **);
218 static ssize_t
xwrite(int, const char *, size_t);
222 static Selection sel
;
223 static CSIEscape csiescseq
;
224 static STREscape strescseq
;
229 static const uchar utfbyte
[UTF_SIZ
+ 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
230 static const uchar utfmask
[UTF_SIZ
+ 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
231 static const Rune utfmin
[UTF_SIZ
+ 1] = { 0, 0, 0x80, 0x800, 0x10000};
232 static const Rune utfmax
[UTF_SIZ
+ 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
235 xwrite(int fd
, const char *s
, size_t len
)
241 r
= write(fd
, s
, len
);
256 if (!(p
= malloc(len
)))
257 die("malloc: %s\n", strerror(errno
));
263 xrealloc(void *p
, size_t len
)
265 if ((p
= realloc(p
, len
)) == NULL
)
266 die("realloc: %s\n", strerror(errno
));
272 xstrdup(const char *s
)
276 if ((p
= strdup(s
)) == NULL
)
277 die("strdup: %s\n", strerror(errno
));
283 utf8decode(const char *c
, Rune
*u
, size_t clen
)
285 size_t i
, j
, len
, type
;
291 udecoded
= utf8decodebyte(c
[0], &len
);
292 if (!BETWEEN(len
, 1, UTF_SIZ
))
294 for (i
= 1, j
= 1; i
< clen
&& j
< len
; ++i
, ++j
) {
295 udecoded
= (udecoded
<< 6) | utf8decodebyte(c
[i
], &type
);
302 utf8validate(u
, len
);
308 utf8decodebyte(char c
, size_t *i
)
310 for (*i
= 0; *i
< LEN(utfmask
); ++(*i
))
311 if (((uchar
)c
& utfmask
[*i
]) == utfbyte
[*i
])
312 return (uchar
)c
& ~utfmask
[*i
];
318 utf8encode(Rune u
, char *c
)
322 len
= utf8validate(&u
, 0);
326 for (i
= len
- 1; i
!= 0; --i
) {
327 c
[i
] = utf8encodebyte(u
, 0);
330 c
[0] = utf8encodebyte(u
, len
);
336 utf8encodebyte(Rune u
, size_t i
)
338 return utfbyte
[i
] | (u
& ~utfmask
[i
]);
342 utf8validate(Rune
*u
, size_t i
)
344 if (!BETWEEN(*u
, utfmin
[i
], utfmax
[i
]) || BETWEEN(*u
, 0xD800, 0xDFFF))
346 for (i
= 1; *u
> utfmax
[i
]; ++i
)
352 static const char base64_digits
[] = {
353 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
354 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0,
355 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
356 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
357 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
358 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0,
359 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
360 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
361 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
362 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
363 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
364 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
368 base64dec_getc(const char **src
)
370 while (**src
&& !isprint(**src
))
372 return **src
? *((*src
)++) : '='; /* emulate padding if string ends */
376 base64dec(const char *src
)
378 size_t in_len
= strlen(src
);
382 in_len
+= 4 - (in_len
% 4);
383 result
= dst
= xmalloc(in_len
/ 4 * 3 + 1);
385 int a
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
386 int b
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
387 int c
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
388 int d
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
390 /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */
391 if (a
== -1 || b
== -1)
394 *dst
++ = (a
<< 2) | ((b
& 0x30) >> 4);
397 *dst
++ = ((b
& 0x0f) << 4) | ((c
& 0x3c) >> 2);
400 *dst
++ = ((c
& 0x03) << 6) | d
;
419 if (term
.line
[y
][i
- 1].mode
& ATTR_WRAP
)
422 while (i
> 0 && term
.line
[y
][i
- 1].u
== ' ')
429 selstart(int col
, int row
, int snap
)
432 sel
.mode
= SEL_EMPTY
;
433 sel
.type
= SEL_REGULAR
;
434 sel
.alt
= IS_SET(MODE_ALTSCREEN
);
436 sel
.oe
.x
= sel
.ob
.x
= col
;
437 sel
.oe
.y
= sel
.ob
.y
= row
;
441 sel
.mode
= SEL_READY
;
442 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
446 selextend(int col
, int row
, int type
, int done
)
448 int oldey
, oldex
, oldsby
, oldsey
, oldtype
;
450 if (sel
.mode
== SEL_IDLE
)
452 if (done
&& sel
.mode
== SEL_EMPTY
) {
468 if (oldey
!= sel
.oe
.y
|| oldex
!= sel
.oe
.x
|| oldtype
!= sel
.type
|| sel
.mode
== SEL_EMPTY
)
469 tsetdirt(MIN(sel
.nb
.y
, oldsby
), MAX(sel
.ne
.y
, oldsey
));
471 sel
.mode
= done
? SEL_IDLE
: SEL_READY
;
479 if (sel
.type
== SEL_REGULAR
&& sel
.ob
.y
!= sel
.oe
.y
) {
480 sel
.nb
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.ob
.x
: sel
.oe
.x
;
481 sel
.ne
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.oe
.x
: sel
.ob
.x
;
483 sel
.nb
.x
= MIN(sel
.ob
.x
, sel
.oe
.x
);
484 sel
.ne
.x
= MAX(sel
.ob
.x
, sel
.oe
.x
);
486 sel
.nb
.y
= MIN(sel
.ob
.y
, sel
.oe
.y
);
487 sel
.ne
.y
= MAX(sel
.ob
.y
, sel
.oe
.y
);
489 selsnap(&sel
.nb
.x
, &sel
.nb
.y
, -1);
490 selsnap(&sel
.ne
.x
, &sel
.ne
.y
, +1);
492 /* expand selection over line breaks */
493 if (sel
.type
== SEL_RECTANGULAR
)
495 i
= tlinelen(sel
.nb
.y
);
498 if (tlinelen(sel
.ne
.y
) <= sel
.ne
.x
)
499 sel
.ne
.x
= term
.col
- 1;
503 selected(int x
, int y
)
505 if (sel
.mode
== SEL_EMPTY
|| sel
.ob
.x
== -1 ||
506 sel
.alt
!= IS_SET(MODE_ALTSCREEN
))
509 if (sel
.type
== SEL_RECTANGULAR
)
510 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
511 && BETWEEN(x
, sel
.nb
.x
, sel
.ne
.x
);
513 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
514 && (y
!= sel
.nb
.y
|| x
>= sel
.nb
.x
)
515 && (y
!= sel
.ne
.y
|| x
<= sel
.ne
.x
);
519 selsnap(int *x
, int *y
, int direction
)
521 int newx
, newy
, xt
, yt
;
522 int delim
, prevdelim
;
523 const Glyph
*gp
, *prevgp
;
528 * Snap around if the word wraps around at the end or
529 * beginning of a line.
531 prevgp
= &term
.line
[*y
][*x
];
532 prevdelim
= ISDELIM(prevgp
->u
);
534 newx
= *x
+ direction
;
536 if (!BETWEEN(newx
, 0, term
.col
- 1)) {
538 newx
= (newx
+ term
.col
) % term
.col
;
539 if (!BETWEEN(newy
, 0, term
.row
- 1))
545 yt
= newy
, xt
= newx
;
546 if (!(term
.line
[yt
][xt
].mode
& ATTR_WRAP
))
550 if (newx
>= tlinelen(newy
))
553 gp
= &term
.line
[newy
][newx
];
554 delim
= ISDELIM(gp
->u
);
555 if (!(gp
->mode
& ATTR_WDUMMY
) && (delim
!= prevdelim
556 || (delim
&& gp
->u
!= prevgp
->u
)))
567 * Snap around if the the previous line or the current one
568 * has set ATTR_WRAP at its end. Then the whole next or
569 * previous line will be selected.
571 *x
= (direction
< 0) ? 0 : term
.col
- 1;
573 for (; *y
> 0; *y
+= direction
) {
574 if (!(term
.line
[*y
-1][term
.col
-1].mode
579 } else if (direction
> 0) {
580 for (; *y
< term
.row
-1; *y
+= direction
) {
581 if (!(term
.line
[*y
][term
.col
-1].mode
595 int y
, bufsize
, lastx
, linelen
;
596 const Glyph
*gp
, *last
;
601 bufsize
= (term
.col
+1) * (sel
.ne
.y
-sel
.nb
.y
+1) * UTF_SIZ
;
602 ptr
= str
= xmalloc(bufsize
);
604 /* append every set & selected glyph to the selection */
605 for (y
= sel
.nb
.y
; y
<= sel
.ne
.y
; y
++) {
606 if ((linelen
= tlinelen(y
)) == 0) {
611 if (sel
.type
== SEL_RECTANGULAR
) {
612 gp
= &term
.line
[y
][sel
.nb
.x
];
615 gp
= &term
.line
[y
][sel
.nb
.y
== y
? sel
.nb
.x
: 0];
616 lastx
= (sel
.ne
.y
== y
) ? sel
.ne
.x
: term
.col
-1;
618 last
= &term
.line
[y
][MIN(lastx
, linelen
-1)];
619 while (last
>= gp
&& last
->u
== ' ')
622 for ( ; gp
<= last
; ++gp
) {
623 if (gp
->mode
& ATTR_WDUMMY
)
626 ptr
+= utf8encode(gp
->u
, ptr
);
630 * Copy and pasting of line endings is inconsistent
631 * in the inconsistent terminal and GUI world.
632 * The best solution seems like to produce '\n' when
633 * something is copied from st and convert '\n' to
634 * '\r', when something to be pasted is received by
636 * FIXME: Fix the computer world.
638 if ((y
< sel
.ne
.y
|| lastx
>= linelen
) &&
639 (!(last
->mode
& ATTR_WRAP
) || sel
.type
== SEL_RECTANGULAR
))
653 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
657 die(const char *errstr
, ...)
661 va_start(ap
, errstr
);
662 vfprintf(stderr
, errstr
, ap
);
668 execsh(char *cmd
, char **args
)
670 char *sh
, *prog
, *arg
;
671 const struct passwd
*pw
;
674 if ((pw
= getpwuid(getuid())) == NULL
) {
676 die("getpwuid: %s\n", strerror(errno
));
678 die("who are you?\n");
681 if ((sh
= getenv("SHELL")) == NULL
)
682 sh
= (pw
->pw_shell
[0]) ? pw
->pw_shell
: cmd
;
689 arg
= utmp
? utmp
: sh
;
697 DEFAULT(args
, ((char *[]) {prog
, arg
, NULL
}));
702 setenv("LOGNAME", pw
->pw_name
, 1);
703 setenv("USER", pw
->pw_name
, 1);
704 setenv("SHELL", sh
, 1);
705 setenv("HOME", pw
->pw_dir
, 1);
706 setenv("TERM", termname
, 1);
708 signal(SIGCHLD
, SIG_DFL
);
709 signal(SIGHUP
, SIG_DFL
);
710 signal(SIGINT
, SIG_DFL
);
711 signal(SIGQUIT
, SIG_DFL
);
712 signal(SIGTERM
, SIG_DFL
);
713 signal(SIGALRM
, SIG_DFL
);
725 if ((p
= waitpid(pid
, &stat
, WNOHANG
)) < 0)
726 die("waiting for pid %hd failed: %s\n", pid
, strerror(errno
));
731 if (WIFEXITED(stat
) && WEXITSTATUS(stat
))
732 die("child exited with status %d\n", WEXITSTATUS(stat
));
733 else if (WIFSIGNALED(stat
))
734 die("child terminated due to signal %d\n", WTERMSIG(stat
));
741 char cmd
[_POSIX_ARG_MAX
], **p
, *q
, *s
;
744 if ((n
= strlen(stty_args
)) > sizeof(cmd
)-1)
745 die("incorrect stty parameters\n");
746 memcpy(cmd
, stty_args
, n
);
748 siz
= sizeof(cmd
) - n
;
749 for (p
= args
; p
&& (s
= *p
); ++p
) {
750 if ((n
= strlen(s
)) > siz
-1)
751 die("stty parameter length too long\n");
758 if (system(cmd
) != 0)
759 perror("Couldn't call stty");
763 ttynew(const char *line
, char *cmd
, const char *out
, char **args
)
768 term
.mode
|= MODE_PRINT
;
769 iofd
= (!strcmp(out
, "-")) ?
770 1 : open(out
, O_WRONLY
| O_CREAT
, 0666);
772 fprintf(stderr
, "Error opening %s:%s\n",
773 out
, strerror(errno
));
778 if ((cmdfd
= open(line
, O_RDWR
)) < 0)
779 die("open line '%s' failed: %s\n",
780 line
, strerror(errno
));
786 /* seems to work fine on linux, openbsd and freebsd */
787 if (openpty(&m
, &s
, NULL
, NULL
, NULL
) < 0)
788 die("openpty failed: %s\n", strerror(errno
));
790 switch (pid
= fork()) {
792 die("fork failed: %s\n", strerror(errno
));
796 setsid(); /* create a new process group */
800 if (ioctl(s
, TIOCSCTTY
, NULL
) < 0)
801 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno
));
805 if (pledge("stdio getpw proc exec", NULL
) == -1)
812 if (pledge("stdio rpath tty proc", NULL
) == -1)
817 signal(SIGCHLD
, sigchld
);
826 static char buf
[BUFSIZ
];
827 static int buflen
= 0;
830 /* append read bytes to unprocessed bytes */
831 ret
= read(cmdfd
, buf
+buflen
, LEN(buf
)-buflen
);
837 die("couldn't read from shell: %s\n", strerror(errno
));
840 written
= twrite(buf
, buflen
, 0);
842 /* keep any incomplete UTF-8 byte sequence for the next call */
844 memmove(buf
, buf
+ written
, buflen
);
850 ttywrite(const char *s
, size_t n
, int may_echo
)
854 if (may_echo
&& IS_SET(MODE_ECHO
))
857 if (!IS_SET(MODE_CRLF
)) {
862 /* This is similar to how the kernel handles ONLCR for ttys */
866 ttywriteraw("\r\n", 2);
868 next
= memchr(s
, '\r', n
);
869 DEFAULT(next
, s
+ n
);
870 ttywriteraw(s
, next
- s
);
878 ttywriteraw(const char *s
, size_t n
)
885 * Remember that we are using a pty, which might be a modem line.
886 * Writing too much will clog the line. That's why we are doing this
888 * FIXME: Migrate the world to Plan 9.
896 /* Check if we can write. */
897 if (pselect(cmdfd
+1, &rfd
, &wfd
, NULL
, NULL
, NULL
) < 0) {
900 die("select failed: %s\n", strerror(errno
));
902 if (FD_ISSET(cmdfd
, &wfd
)) {
904 * Only write the bytes written by ttywrite() or the
905 * default of 256. This seems to be a reasonable value
906 * for a serial line. Bigger values might clog the I/O.
908 if ((r
= write(cmdfd
, s
, (n
< lim
)? n
: lim
)) < 0)
912 * We weren't able to write out everything.
913 * This means the buffer is getting full
921 /* All bytes have been written. */
925 if (FD_ISSET(cmdfd
, &rfd
))
931 die("write error on tty: %s\n", strerror(errno
));
935 ttyresize(int tw
, int th
)
943 if (ioctl(cmdfd
, TIOCSWINSZ
, &w
) < 0)
944 fprintf(stderr
, "Couldn't set window size: %s\n", strerror(errno
));
950 /* Send SIGHUP to shell */
959 for (i
= 0; i
< term
.row
-1; i
++) {
960 for (j
= 0; j
< term
.col
-1; j
++) {
961 if (term
.line
[i
][j
].mode
& attr
)
970 tsetdirt(int top
, int bot
)
974 LIMIT(top
, 0, term
.row
-1);
975 LIMIT(bot
, 0, term
.row
-1);
977 for (i
= top
; i
<= bot
; i
++)
982 tsetdirtattr(int attr
)
986 for (i
= 0; i
< term
.row
-1; i
++) {
987 for (j
= 0; j
< term
.col
-1; j
++) {
988 if (term
.line
[i
][j
].mode
& attr
) {
999 tsetdirt(0, term
.row
-1);
1005 static TCursor c
[2];
1006 int alt
= IS_SET(MODE_ALTSCREEN
);
1008 if (mode
== CURSOR_SAVE
) {
1010 } else if (mode
== CURSOR_LOAD
) {
1012 tmoveto(c
[alt
].x
, c
[alt
].y
);
1021 term
.c
= (TCursor
){{
1025 }, .x
= 0, .y
= 0, .state
= CURSOR_DEFAULT
};
1027 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1028 for (i
= tabspaces
; i
< term
.col
; i
+= tabspaces
)
1031 term
.bot
= term
.row
- 1;
1032 term
.mode
= MODE_WRAP
|MODE_UTF8
;
1033 memset(term
.trantbl
, CS_USA
, sizeof(term
.trantbl
));
1036 for (i
= 0; i
< 2; i
++) {
1038 tcursor(CURSOR_SAVE
);
1039 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1045 tnew(int col
, int row
)
1047 term
= (Term
){ .c
= { .attr
= { .fg
= defaultfg
, .bg
= defaultbg
} } };
1055 Line
*tmp
= term
.line
;
1057 term
.line
= term
.alt
;
1059 term
.mode
^= MODE_ALTSCREEN
;
1064 tscrolldown(int orig
, int n
)
1069 LIMIT(n
, 0, term
.bot
-orig
+1);
1071 tsetdirt(orig
, term
.bot
-n
);
1072 tclearregion(0, term
.bot
-n
+1, term
.col
-1, term
.bot
);
1074 for (i
= term
.bot
; i
>= orig
+n
; i
--) {
1075 temp
= term
.line
[i
];
1076 term
.line
[i
] = term
.line
[i
-n
];
1077 term
.line
[i
-n
] = temp
;
1084 tscrollup(int orig
, int n
)
1089 LIMIT(n
, 0, term
.bot
-orig
+1);
1091 tclearregion(0, orig
, term
.col
-1, orig
+n
-1);
1092 tsetdirt(orig
+n
, term
.bot
);
1094 for (i
= orig
; i
<= term
.bot
-n
; i
++) {
1095 temp
= term
.line
[i
];
1096 term
.line
[i
] = term
.line
[i
+n
];
1097 term
.line
[i
+n
] = temp
;
1100 selscroll(orig
, -n
);
1104 selscroll(int orig
, int n
)
1109 if (BETWEEN(sel
.nb
.y
, orig
, term
.bot
) != BETWEEN(sel
.ne
.y
, orig
, term
.bot
)) {
1111 } else if (BETWEEN(sel
.nb
.y
, orig
, term
.bot
)) {
1114 if (sel
.ob
.y
< term
.top
|| sel
.ob
.y
> term
.bot
||
1115 sel
.oe
.y
< term
.top
|| sel
.oe
.y
> term
.bot
) {
1124 tnewline(int first_col
)
1128 if (y
== term
.bot
) {
1129 tscrollup(term
.top
, 1);
1133 tmoveto(first_col
? 0 : term
.c
.x
, y
);
1139 char *p
= csiescseq
.buf
, *np
;
1148 csiescseq
.buf
[csiescseq
.len
] = '\0';
1149 while (p
< csiescseq
.buf
+csiescseq
.len
) {
1151 v
= strtol(p
, &np
, 10);
1154 if (v
== LONG_MAX
|| v
== LONG_MIN
)
1156 csiescseq
.arg
[csiescseq
.narg
++] = v
;
1158 if (*p
!= ';' || csiescseq
.narg
== ESC_ARG_SIZ
)
1162 csiescseq
.mode
[0] = *p
++;
1163 csiescseq
.mode
[1] = (p
< csiescseq
.buf
+csiescseq
.len
) ? *p
: '\0';
1166 /* for absolute user moves, when decom is set */
1168 tmoveato(int x
, int y
)
1170 tmoveto(x
, y
+ ((term
.c
.state
& CURSOR_ORIGIN
) ? term
.top
: 0));
1174 tmoveto(int x
, int y
)
1178 if (term
.c
.state
& CURSOR_ORIGIN
) {
1183 maxy
= term
.row
- 1;
1185 term
.c
.state
&= ~CURSOR_WRAPNEXT
;
1186 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1187 term
.c
.y
= LIMIT(y
, miny
, maxy
);
1191 tsetchar(Rune u
, const Glyph
*attr
, int x
, int y
)
1193 static const char *vt100_0
[62] = { /* 0x41 - 0x7e */
1194 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1195 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1196 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1197 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1198 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1199 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1200 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1201 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1205 * The table is proudly stolen from rxvt.
1207 if (term
.trantbl
[term
.charset
] == CS_GRAPHIC0
&&
1208 BETWEEN(u
, 0x41, 0x7e) && vt100_0
[u
- 0x41])
1209 utf8decode(vt100_0
[u
- 0x41], &u
, UTF_SIZ
);
1211 if (term
.line
[y
][x
].mode
& ATTR_WIDE
) {
1212 if (x
+1 < term
.col
) {
1213 term
.line
[y
][x
+1].u
= ' ';
1214 term
.line
[y
][x
+1].mode
&= ~ATTR_WDUMMY
;
1216 } else if (term
.line
[y
][x
].mode
& ATTR_WDUMMY
) {
1217 term
.line
[y
][x
-1].u
= ' ';
1218 term
.line
[y
][x
-1].mode
&= ~ATTR_WIDE
;
1222 term
.line
[y
][x
] = *attr
;
1223 term
.line
[y
][x
].u
= u
;
1227 tclearregion(int x1
, int y1
, int x2
, int y2
)
1233 temp
= x1
, x1
= x2
, x2
= temp
;
1235 temp
= y1
, y1
= y2
, y2
= temp
;
1237 LIMIT(x1
, 0, term
.col
-1);
1238 LIMIT(x2
, 0, term
.col
-1);
1239 LIMIT(y1
, 0, term
.row
-1);
1240 LIMIT(y2
, 0, term
.row
-1);
1242 for (y
= y1
; y
<= y2
; y
++) {
1244 for (x
= x1
; x
<= x2
; x
++) {
1245 gp
= &term
.line
[y
][x
];
1248 gp
->fg
= term
.c
.attr
.fg
;
1249 gp
->bg
= term
.c
.attr
.bg
;
1262 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1266 size
= term
.col
- src
;
1267 line
= term
.line
[term
.c
.y
];
1269 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1270 tclearregion(term
.col
-n
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1279 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1283 size
= term
.col
- dst
;
1284 line
= term
.line
[term
.c
.y
];
1286 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1287 tclearregion(src
, term
.c
.y
, dst
- 1, term
.c
.y
);
1291 tinsertblankline(int n
)
1293 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1294 tscrolldown(term
.c
.y
, n
);
1300 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1301 tscrollup(term
.c
.y
, n
);
1305 tdefcolor(const int *attr
, int *npar
, int l
)
1310 switch (attr
[*npar
+ 1]) {
1311 case 2: /* direct color in RGB space */
1312 if (*npar
+ 4 >= l
) {
1314 "erresc(38): Incorrect number of parameters (%d)\n",
1318 r
= attr
[*npar
+ 2];
1319 g
= attr
[*npar
+ 3];
1320 b
= attr
[*npar
+ 4];
1322 if (!BETWEEN(r
, 0, 255) || !BETWEEN(g
, 0, 255) || !BETWEEN(b
, 0, 255))
1323 fprintf(stderr
, "erresc: bad rgb color (%u,%u,%u)\n",
1326 idx
= TRUECOLOR(r
, g
, b
);
1328 case 5: /* indexed color */
1329 if (*npar
+ 2 >= l
) {
1331 "erresc(38): Incorrect number of parameters (%d)\n",
1336 if (!BETWEEN(attr
[*npar
], 0, 255))
1337 fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[*npar
]);
1341 case 0: /* implemented defined (only foreground) */
1342 case 1: /* transparent */
1343 case 3: /* direct color in CMY space */
1344 case 4: /* direct color in CMYK space */
1347 "erresc(38): gfx attr %d unknown\n", attr
[*npar
]);
1355 tsetattr(const int *attr
, int l
)
1360 for (i
= 0; i
< l
; i
++) {
1363 term
.c
.attr
.mode
&= ~(
1372 term
.c
.attr
.fg
= defaultfg
;
1373 term
.c
.attr
.bg
= defaultbg
;
1376 term
.c
.attr
.mode
|= ATTR_BOLD
;
1379 term
.c
.attr
.mode
|= ATTR_FAINT
;
1382 term
.c
.attr
.mode
|= ATTR_ITALIC
;
1385 term
.c
.attr
.mode
|= ATTR_UNDERLINE
;
1387 case 5: /* slow blink */
1389 case 6: /* rapid blink */
1390 term
.c
.attr
.mode
|= ATTR_BLINK
;
1393 term
.c
.attr
.mode
|= ATTR_REVERSE
;
1396 term
.c
.attr
.mode
|= ATTR_INVISIBLE
;
1399 term
.c
.attr
.mode
|= ATTR_STRUCK
;
1402 term
.c
.attr
.mode
&= ~(ATTR_BOLD
| ATTR_FAINT
);
1405 term
.c
.attr
.mode
&= ~ATTR_ITALIC
;
1408 term
.c
.attr
.mode
&= ~ATTR_UNDERLINE
;
1411 term
.c
.attr
.mode
&= ~ATTR_BLINK
;
1414 term
.c
.attr
.mode
&= ~ATTR_REVERSE
;
1417 term
.c
.attr
.mode
&= ~ATTR_INVISIBLE
;
1420 term
.c
.attr
.mode
&= ~ATTR_STRUCK
;
1423 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1424 term
.c
.attr
.fg
= idx
;
1427 term
.c
.attr
.fg
= defaultfg
;
1430 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1431 term
.c
.attr
.bg
= idx
;
1434 term
.c
.attr
.bg
= defaultbg
;
1437 if (BETWEEN(attr
[i
], 30, 37)) {
1438 term
.c
.attr
.fg
= attr
[i
] - 30;
1439 } else if (BETWEEN(attr
[i
], 40, 47)) {
1440 term
.c
.attr
.bg
= attr
[i
] - 40;
1441 } else if (BETWEEN(attr
[i
], 90, 97)) {
1442 term
.c
.attr
.fg
= attr
[i
] - 90 + 8;
1443 } else if (BETWEEN(attr
[i
], 100, 107)) {
1444 term
.c
.attr
.bg
= attr
[i
] - 100 + 8;
1447 "erresc(default): gfx attr %d unknown\n",
1457 tsetscroll(int t
, int b
)
1461 LIMIT(t
, 0, term
.row
-1);
1462 LIMIT(b
, 0, term
.row
-1);
1473 tsetmode(int priv
, int set
, const int *args
, int narg
)
1475 int alt
; const int *lim
;
1477 for (lim
= args
+ narg
; args
< lim
; ++args
) {
1480 case 1: /* DECCKM -- Cursor key */
1481 xsetmode(set
, MODE_APPCURSOR
);
1483 case 5: /* DECSCNM -- Reverse video */
1484 xsetmode(set
, MODE_REVERSE
);
1486 case 6: /* DECOM -- Origin */
1487 MODBIT(term
.c
.state
, set
, CURSOR_ORIGIN
);
1490 case 7: /* DECAWM -- Auto wrap */
1491 MODBIT(term
.mode
, set
, MODE_WRAP
);
1493 case 0: /* Error (IGNORED) */
1494 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1495 case 3: /* DECCOLM -- Column (IGNORED) */
1496 case 4: /* DECSCLM -- Scroll (IGNORED) */
1497 case 8: /* DECARM -- Auto repeat (IGNORED) */
1498 case 18: /* DECPFF -- Printer feed (IGNORED) */
1499 case 19: /* DECPEX -- Printer extent (IGNORED) */
1500 case 42: /* DECNRCM -- National characters (IGNORED) */
1501 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1503 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1504 xsetmode(!set
, MODE_HIDE
);
1506 case 9: /* X10 mouse compatibility mode */
1507 xsetpointermotion(0);
1508 xsetmode(0, MODE_MOUSE
);
1509 xsetmode(set
, MODE_MOUSEX10
);
1511 case 1000: /* 1000: report button press */
1512 xsetpointermotion(0);
1513 xsetmode(0, MODE_MOUSE
);
1514 xsetmode(set
, MODE_MOUSEBTN
);
1516 case 1002: /* 1002: report motion on button press */
1517 xsetpointermotion(0);
1518 xsetmode(0, MODE_MOUSE
);
1519 xsetmode(set
, MODE_MOUSEMOTION
);
1521 case 1003: /* 1003: enable all mouse motions */
1522 xsetpointermotion(set
);
1523 xsetmode(0, MODE_MOUSE
);
1524 xsetmode(set
, MODE_MOUSEMANY
);
1526 case 1004: /* 1004: send focus events to tty */
1527 xsetmode(set
, MODE_FOCUS
);
1529 case 1006: /* 1006: extended reporting mode */
1530 xsetmode(set
, MODE_MOUSESGR
);
1533 xsetmode(set
, MODE_8BIT
);
1535 case 1049: /* swap screen & set/restore cursor as xterm */
1536 if (!allowaltscreen
)
1538 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1540 case 47: /* swap screen */
1542 if (!allowaltscreen
)
1544 alt
= IS_SET(MODE_ALTSCREEN
);
1546 tclearregion(0, 0, term
.col
-1,
1549 if (set
^ alt
) /* set is always 1 or 0 */
1555 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1557 case 2004: /* 2004: bracketed paste mode */
1558 xsetmode(set
, MODE_BRCKTPASTE
);
1560 /* Not implemented mouse modes. See comments there. */
1561 case 1001: /* mouse highlight mode; can hang the
1562 terminal by design when implemented. */
1563 case 1005: /* UTF-8 mouse mode; will confuse
1564 applications not supporting UTF-8
1566 case 1015: /* urxvt mangled mouse mode; incompatible
1567 and can be mistaken for other control
1572 "erresc: unknown private set/reset mode %d\n",
1578 case 0: /* Error (IGNORED) */
1581 xsetmode(set
, MODE_KBDLOCK
);
1583 case 4: /* IRM -- Insertion-replacement */
1584 MODBIT(term
.mode
, set
, MODE_INSERT
);
1586 case 12: /* SRM -- Send/Receive */
1587 MODBIT(term
.mode
, !set
, MODE_ECHO
);
1589 case 20: /* LNM -- Linefeed/new line */
1590 MODBIT(term
.mode
, set
, MODE_CRLF
);
1594 "erresc: unknown set/reset mode %d\n",
1608 switch (csiescseq
.mode
[0]) {
1611 fprintf(stderr
, "erresc: unknown csi ");
1615 case '@': /* ICH -- Insert <n> blank char */
1616 DEFAULT(csiescseq
.arg
[0], 1);
1617 tinsertblank(csiescseq
.arg
[0]);
1619 case 'A': /* CUU -- Cursor <n> Up */
1620 DEFAULT(csiescseq
.arg
[0], 1);
1621 tmoveto(term
.c
.x
, term
.c
.y
-csiescseq
.arg
[0]);
1623 case 'B': /* CUD -- Cursor <n> Down */
1624 case 'e': /* VPR --Cursor <n> Down */
1625 DEFAULT(csiescseq
.arg
[0], 1);
1626 tmoveto(term
.c
.x
, term
.c
.y
+csiescseq
.arg
[0]);
1628 case 'i': /* MC -- Media Copy */
1629 switch (csiescseq
.arg
[0]) {
1634 tdumpline(term
.c
.y
);
1640 term
.mode
&= ~MODE_PRINT
;
1643 term
.mode
|= MODE_PRINT
;
1647 case 'c': /* DA -- Device Attributes */
1648 if (csiescseq
.arg
[0] == 0)
1649 ttywrite(vtiden
, strlen(vtiden
), 0);
1651 case 'b': /* REP -- if last char is printable print it <n> more times */
1652 DEFAULT(csiescseq
.arg
[0], 1);
1654 while (csiescseq
.arg
[0]-- > 0)
1657 case 'C': /* CUF -- Cursor <n> Forward */
1658 case 'a': /* HPR -- Cursor <n> Forward */
1659 DEFAULT(csiescseq
.arg
[0], 1);
1660 tmoveto(term
.c
.x
+csiescseq
.arg
[0], term
.c
.y
);
1662 case 'D': /* CUB -- Cursor <n> Backward */
1663 DEFAULT(csiescseq
.arg
[0], 1);
1664 tmoveto(term
.c
.x
-csiescseq
.arg
[0], term
.c
.y
);
1666 case 'E': /* CNL -- Cursor <n> Down and first col */
1667 DEFAULT(csiescseq
.arg
[0], 1);
1668 tmoveto(0, term
.c
.y
+csiescseq
.arg
[0]);
1670 case 'F': /* CPL -- Cursor <n> Up and first col */
1671 DEFAULT(csiescseq
.arg
[0], 1);
1672 tmoveto(0, term
.c
.y
-csiescseq
.arg
[0]);
1674 case 'g': /* TBC -- Tabulation clear */
1675 switch (csiescseq
.arg
[0]) {
1676 case 0: /* clear current tab stop */
1677 term
.tabs
[term
.c
.x
] = 0;
1679 case 3: /* clear all the tabs */
1680 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1686 case 'G': /* CHA -- Move to <col> */
1688 DEFAULT(csiescseq
.arg
[0], 1);
1689 tmoveto(csiescseq
.arg
[0]-1, term
.c
.y
);
1691 case 'H': /* CUP -- Move to <row> <col> */
1693 DEFAULT(csiescseq
.arg
[0], 1);
1694 DEFAULT(csiescseq
.arg
[1], 1);
1695 tmoveato(csiescseq
.arg
[1]-1, csiescseq
.arg
[0]-1);
1697 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1698 DEFAULT(csiescseq
.arg
[0], 1);
1699 tputtab(csiescseq
.arg
[0]);
1701 case 'J': /* ED -- Clear screen */
1702 switch (csiescseq
.arg
[0]) {
1704 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1705 if (term
.c
.y
< term
.row
-1) {
1706 tclearregion(0, term
.c
.y
+1, term
.col
-1,
1712 tclearregion(0, 0, term
.col
-1, term
.c
.y
-1);
1713 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1716 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1722 case 'K': /* EL -- Clear line */
1723 switch (csiescseq
.arg
[0]) {
1725 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1,
1729 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1732 tclearregion(0, term
.c
.y
, term
.col
-1, term
.c
.y
);
1736 case 'S': /* SU -- Scroll <n> line up */
1737 DEFAULT(csiescseq
.arg
[0], 1);
1738 tscrollup(term
.top
, csiescseq
.arg
[0]);
1740 case 'T': /* SD -- Scroll <n> line down */
1741 DEFAULT(csiescseq
.arg
[0], 1);
1742 tscrolldown(term
.top
, csiescseq
.arg
[0]);
1744 case 'L': /* IL -- Insert <n> blank lines */
1745 DEFAULT(csiescseq
.arg
[0], 1);
1746 tinsertblankline(csiescseq
.arg
[0]);
1748 case 'l': /* RM -- Reset Mode */
1749 tsetmode(csiescseq
.priv
, 0, csiescseq
.arg
, csiescseq
.narg
);
1751 case 'M': /* DL -- Delete <n> lines */
1752 DEFAULT(csiescseq
.arg
[0], 1);
1753 tdeleteline(csiescseq
.arg
[0]);
1755 case 'X': /* ECH -- Erase <n> char */
1756 DEFAULT(csiescseq
.arg
[0], 1);
1757 tclearregion(term
.c
.x
, term
.c
.y
,
1758 term
.c
.x
+ csiescseq
.arg
[0] - 1, term
.c
.y
);
1760 case 'P': /* DCH -- Delete <n> char */
1761 DEFAULT(csiescseq
.arg
[0], 1);
1762 tdeletechar(csiescseq
.arg
[0]);
1764 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1765 DEFAULT(csiescseq
.arg
[0], 1);
1766 tputtab(-csiescseq
.arg
[0]);
1768 case 'd': /* VPA -- Move to <row> */
1769 DEFAULT(csiescseq
.arg
[0], 1);
1770 tmoveato(term
.c
.x
, csiescseq
.arg
[0]-1);
1772 case 'h': /* SM -- Set terminal mode */
1773 tsetmode(csiescseq
.priv
, 1, csiescseq
.arg
, csiescseq
.narg
);
1775 case 'm': /* SGR -- Terminal attribute (color) */
1776 tsetattr(csiescseq
.arg
, csiescseq
.narg
);
1778 case 'n': /* DSR – Device Status Report (cursor position) */
1779 if (csiescseq
.arg
[0] == 6) {
1780 len
= snprintf(buf
, sizeof(buf
), "\033[%i;%iR",
1781 term
.c
.y
+1, term
.c
.x
+1);
1782 ttywrite(buf
, len
, 0);
1785 case 'r': /* DECSTBM -- Set Scrolling Region */
1786 if (csiescseq
.priv
) {
1789 DEFAULT(csiescseq
.arg
[0], 1);
1790 DEFAULT(csiescseq
.arg
[1], term
.row
);
1791 tsetscroll(csiescseq
.arg
[0]-1, csiescseq
.arg
[1]-1);
1795 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1796 tcursor(CURSOR_SAVE
);
1798 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1799 tcursor(CURSOR_LOAD
);
1802 switch (csiescseq
.mode
[1]) {
1803 case 'q': /* DECSCUSR -- Set Cursor Style */
1804 if (xsetcursor(csiescseq
.arg
[0]))
1820 fprintf(stderr
, "ESC[");
1821 for (i
= 0; i
< csiescseq
.len
; i
++) {
1822 c
= csiescseq
.buf
[i
] & 0xff;
1825 } else if (c
== '\n') {
1826 fprintf(stderr
, "(\\n)");
1827 } else if (c
== '\r') {
1828 fprintf(stderr
, "(\\r)");
1829 } else if (c
== 0x1b) {
1830 fprintf(stderr
, "(\\e)");
1832 fprintf(stderr
, "(%02x)", c
);
1841 memset(&csiescseq
, 0, sizeof(csiescseq
));
1847 char *p
= NULL
, *dec
;
1850 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
1852 par
= (narg
= strescseq
.narg
) ? atoi(strescseq
.args
[0]) : 0;
1854 switch (strescseq
.type
) {
1855 case ']': /* OSC -- Operating System Command */
1859 xsettitle(strescseq
.args
[1]);
1860 xseticontitle(strescseq
.args
[1]);
1865 xseticontitle(strescseq
.args
[1]);
1869 xsettitle(strescseq
.args
[1]);
1872 if (narg
> 2 && allowwindowops
) {
1873 dec
= base64dec(strescseq
.args
[2]);
1878 fprintf(stderr
, "erresc: invalid base64\n");
1882 case 4: /* color set */
1885 p
= strescseq
.args
[2];
1887 case 104: /* color reset, here p = NULL */
1888 j
= (narg
> 1) ? atoi(strescseq
.args
[1]) : -1;
1889 if (xsetcolorname(j
, p
)) {
1890 if (par
== 104 && narg
<= 1)
1891 return; /* color reset without parameter */
1892 fprintf(stderr
, "erresc: invalid color j=%d, p=%s\n",
1893 j
, p
? p
: "(null)");
1896 * TODO if defaultbg color is changed, borders
1904 case 'k': /* old title set compatibility */
1905 xsettitle(strescseq
.args
[0]);
1907 case 'P': /* DCS -- Device Control String */
1908 case '_': /* APC -- Application Program Command */
1909 case '^': /* PM -- Privacy Message */
1913 fprintf(stderr
, "erresc: unknown str ");
1921 char *p
= strescseq
.buf
;
1924 strescseq
.buf
[strescseq
.len
] = '\0';
1929 while (strescseq
.narg
< STR_ARG_SIZ
) {
1930 strescseq
.args
[strescseq
.narg
++] = p
;
1931 while ((c
= *p
) != ';' && c
!= '\0')
1945 fprintf(stderr
, "ESC%c", strescseq
.type
);
1946 for (i
= 0; i
< strescseq
.len
; i
++) {
1947 c
= strescseq
.buf
[i
] & 0xff;
1951 } else if (isprint(c
)) {
1953 } else if (c
== '\n') {
1954 fprintf(stderr
, "(\\n)");
1955 } else if (c
== '\r') {
1956 fprintf(stderr
, "(\\r)");
1957 } else if (c
== 0x1b) {
1958 fprintf(stderr
, "(\\e)");
1960 fprintf(stderr
, "(%02x)", c
);
1963 fprintf(stderr
, "ESC\\\n");
1969 strescseq
= (STREscape
){
1970 .buf
= xrealloc(strescseq
.buf
, STR_BUF_SIZ
),
1976 sendbreak(const Arg
*arg
)
1978 if (tcsendbreak(cmdfd
, 0))
1979 perror("Error sending break");
1983 tprinter(char *s
, size_t len
)
1985 if (iofd
!= -1 && xwrite(iofd
, s
, len
) < 0) {
1986 perror("Error writing to output file");
1993 toggleprinter(const Arg
*arg
)
1995 term
.mode
^= MODE_PRINT
;
1999 printscreen(const Arg
*arg
)
2005 printsel(const Arg
*arg
)
2015 if ((ptr
= getsel())) {
2016 tprinter(ptr
, strlen(ptr
));
2025 const Glyph
*bp
, *end
;
2027 bp
= &term
.line
[n
][0];
2028 end
= &bp
[MIN(tlinelen(n
), term
.col
) - 1];
2029 if (bp
!= end
|| bp
->u
!= ' ') {
2030 for ( ; bp
<= end
; ++bp
)
2031 tprinter(buf
, utf8encode(bp
->u
, buf
));
2041 for (i
= 0; i
< term
.row
; ++i
)
2051 while (x
< term
.col
&& n
--)
2052 for (++x
; x
< term
.col
&& !term
.tabs
[x
]; ++x
)
2055 while (x
> 0 && n
++)
2056 for (--x
; x
> 0 && !term
.tabs
[x
]; --x
)
2059 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
2063 tdefutf8(char ascii
)
2066 term
.mode
|= MODE_UTF8
;
2067 else if (ascii
== '@')
2068 term
.mode
&= ~MODE_UTF8
;
2072 tdeftran(char ascii
)
2074 static char cs
[] = "0B";
2075 static int vcs
[] = {CS_GRAPHIC0
, CS_USA
};
2078 if ((p
= strchr(cs
, ascii
)) == NULL
) {
2079 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
2081 term
.trantbl
[term
.icharset
] = vcs
[p
- cs
];
2090 if (c
== '8') { /* DEC screen alignment test. */
2091 for (x
= 0; x
< term
.col
; ++x
) {
2092 for (y
= 0; y
< term
.row
; ++y
)
2093 tsetchar('E', &term
.c
.attr
, x
, y
);
2099 tstrsequence(uchar c
)
2102 case 0x90: /* DCS -- Device Control String */
2105 case 0x9f: /* APC -- Application Program Command */
2108 case 0x9e: /* PM -- Privacy Message */
2111 case 0x9d: /* OSC -- Operating System Command */
2117 term
.esc
|= ESC_STR
;
2121 tcontrolcode(uchar ascii
)
2128 tmoveto(term
.c
.x
-1, term
.c
.y
);
2131 tmoveto(0, term
.c
.y
);
2136 /* go to first col if the mode is set */
2137 tnewline(IS_SET(MODE_CRLF
));
2139 case '\a': /* BEL */
2140 if (term
.esc
& ESC_STR_END
) {
2141 /* backwards compatibility to xterm */
2147 case '\033': /* ESC */
2149 term
.esc
&= ~(ESC_CSI
|ESC_ALTCHARSET
|ESC_TEST
);
2150 term
.esc
|= ESC_START
;
2152 case '\016': /* SO (LS1 -- Locking shift 1) */
2153 case '\017': /* SI (LS0 -- Locking shift 0) */
2154 term
.charset
= 1 - (ascii
- '\016');
2156 case '\032': /* SUB */
2157 tsetchar('?', &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2159 case '\030': /* CAN */
2162 case '\005': /* ENQ (IGNORED) */
2163 case '\000': /* NUL (IGNORED) */
2164 case '\021': /* XON (IGNORED) */
2165 case '\023': /* XOFF (IGNORED) */
2166 case 0177: /* DEL (IGNORED) */
2168 case 0x80: /* TODO: PAD */
2169 case 0x81: /* TODO: HOP */
2170 case 0x82: /* TODO: BPH */
2171 case 0x83: /* TODO: NBH */
2172 case 0x84: /* TODO: IND */
2174 case 0x85: /* NEL -- Next line */
2175 tnewline(1); /* always go to first col */
2177 case 0x86: /* TODO: SSA */
2178 case 0x87: /* TODO: ESA */
2180 case 0x88: /* HTS -- Horizontal tab stop */
2181 term
.tabs
[term
.c
.x
] = 1;
2183 case 0x89: /* TODO: HTJ */
2184 case 0x8a: /* TODO: VTS */
2185 case 0x8b: /* TODO: PLD */
2186 case 0x8c: /* TODO: PLU */
2187 case 0x8d: /* TODO: RI */
2188 case 0x8e: /* TODO: SS2 */
2189 case 0x8f: /* TODO: SS3 */
2190 case 0x91: /* TODO: PU1 */
2191 case 0x92: /* TODO: PU2 */
2192 case 0x93: /* TODO: STS */
2193 case 0x94: /* TODO: CCH */
2194 case 0x95: /* TODO: MW */
2195 case 0x96: /* TODO: SPA */
2196 case 0x97: /* TODO: EPA */
2197 case 0x98: /* TODO: SOS */
2198 case 0x99: /* TODO: SGCI */
2200 case 0x9a: /* DECID -- Identify Terminal */
2201 ttywrite(vtiden
, strlen(vtiden
), 0);
2203 case 0x9b: /* TODO: CSI */
2204 case 0x9c: /* TODO: ST */
2206 case 0x90: /* DCS -- Device Control String */
2207 case 0x9d: /* OSC -- Operating System Command */
2208 case 0x9e: /* PM -- Privacy Message */
2209 case 0x9f: /* APC -- Application Program Command */
2210 tstrsequence(ascii
);
2213 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2214 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
2218 * returns 1 when the sequence is finished and it hasn't to read
2219 * more characters for this sequence, otherwise 0
2222 eschandle(uchar ascii
)
2226 term
.esc
|= ESC_CSI
;
2229 term
.esc
|= ESC_TEST
;
2232 term
.esc
|= ESC_UTF8
;
2234 case 'P': /* DCS -- Device Control String */
2235 case '_': /* APC -- Application Program Command */
2236 case '^': /* PM -- Privacy Message */
2237 case ']': /* OSC -- Operating System Command */
2238 case 'k': /* old title set compatibility */
2239 tstrsequence(ascii
);
2241 case 'n': /* LS2 -- Locking shift 2 */
2242 case 'o': /* LS3 -- Locking shift 3 */
2243 term
.charset
= 2 + (ascii
- 'n');
2245 case '(': /* GZD4 -- set primary charset G0 */
2246 case ')': /* G1D4 -- set secondary charset G1 */
2247 case '*': /* G2D4 -- set tertiary charset G2 */
2248 case '+': /* G3D4 -- set quaternary charset G3 */
2249 term
.icharset
= ascii
- '(';
2250 term
.esc
|= ESC_ALTCHARSET
;
2252 case 'D': /* IND -- Linefeed */
2253 if (term
.c
.y
== term
.bot
) {
2254 tscrollup(term
.top
, 1);
2256 tmoveto(term
.c
.x
, term
.c
.y
+1);
2259 case 'E': /* NEL -- Next line */
2260 tnewline(1); /* always go to first col */
2262 case 'H': /* HTS -- Horizontal tab stop */
2263 term
.tabs
[term
.c
.x
] = 1;
2265 case 'M': /* RI -- Reverse index */
2266 if (term
.c
.y
== term
.top
) {
2267 tscrolldown(term
.top
, 1);
2269 tmoveto(term
.c
.x
, term
.c
.y
-1);
2272 case 'Z': /* DECID -- Identify Terminal */
2273 ttywrite(vtiden
, strlen(vtiden
), 0);
2275 case 'c': /* RIS -- Reset to initial state */
2280 case '=': /* DECPAM -- Application keypad */
2281 xsetmode(1, MODE_APPKEYPAD
);
2283 case '>': /* DECPNM -- Normal keypad */
2284 xsetmode(0, MODE_APPKEYPAD
);
2286 case '7': /* DECSC -- Save Cursor */
2287 tcursor(CURSOR_SAVE
);
2289 case '8': /* DECRC -- Restore Cursor */
2290 tcursor(CURSOR_LOAD
);
2292 case '\\': /* ST -- String Terminator */
2293 if (term
.esc
& ESC_STR_END
)
2297 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2298 (uchar
) ascii
, isprint(ascii
)? ascii
:'.');
2312 control
= ISCONTROL(u
);
2313 if (u
< 127 || !IS_SET(MODE_UTF8
)) {
2317 len
= utf8encode(u
, c
);
2318 if (!control
&& (width
= wcwidth(u
)) == -1)
2322 if (IS_SET(MODE_PRINT
))
2326 * STR sequence must be checked before anything else
2327 * because it uses all following characters until it
2328 * receives a ESC, a SUB, a ST or any other C1 control
2331 if (term
.esc
& ESC_STR
) {
2332 if (u
== '\a' || u
== 030 || u
== 032 || u
== 033 ||
2334 term
.esc
&= ~(ESC_START
|ESC_STR
);
2335 term
.esc
|= ESC_STR_END
;
2336 goto check_control_code
;
2339 if (strescseq
.len
+len
>= strescseq
.siz
) {
2341 * Here is a bug in terminals. If the user never sends
2342 * some code to stop the str or esc command, then st
2343 * will stop responding. But this is better than
2344 * silently failing with unknown characters. At least
2345 * then users will report back.
2347 * In the case users ever get fixed, here is the code:
2353 if (strescseq
.siz
> (SIZE_MAX
- UTF_SIZ
) / 2)
2356 strescseq
.buf
= xrealloc(strescseq
.buf
, strescseq
.siz
);
2359 memmove(&strescseq
.buf
[strescseq
.len
], c
, len
);
2360 strescseq
.len
+= len
;
2366 * Actions of control codes must be performed as soon they arrive
2367 * because they can be embedded inside a control sequence, and
2368 * they must not cause conflicts with sequences.
2373 * control codes are not shown ever
2378 } else if (term
.esc
& ESC_START
) {
2379 if (term
.esc
& ESC_CSI
) {
2380 csiescseq
.buf
[csiescseq
.len
++] = u
;
2381 if (BETWEEN(u
, 0x40, 0x7E)
2382 || csiescseq
.len
>= \
2383 sizeof(csiescseq
.buf
)-1) {
2389 } else if (term
.esc
& ESC_UTF8
) {
2391 } else if (term
.esc
& ESC_ALTCHARSET
) {
2393 } else if (term
.esc
& ESC_TEST
) {
2398 /* sequence already finished */
2402 * All characters which form part of a sequence are not
2407 if (selected(term
.c
.x
, term
.c
.y
))
2410 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2411 if (IS_SET(MODE_WRAP
) && (term
.c
.state
& CURSOR_WRAPNEXT
)) {
2412 gp
->mode
|= ATTR_WRAP
;
2414 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2417 if (IS_SET(MODE_INSERT
) && term
.c
.x
+width
< term
.col
)
2418 memmove(gp
+width
, gp
, (term
.col
- term
.c
.x
- width
) * sizeof(Glyph
));
2420 if (term
.c
.x
+width
> term
.col
) {
2422 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2425 tsetchar(u
, &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2429 gp
->mode
|= ATTR_WIDE
;
2430 if (term
.c
.x
+1 < term
.col
) {
2432 gp
[1].mode
= ATTR_WDUMMY
;
2435 if (term
.c
.x
+width
< term
.col
) {
2436 tmoveto(term
.c
.x
+width
, term
.c
.y
);
2438 term
.c
.state
|= CURSOR_WRAPNEXT
;
2443 twrite(const char *buf
, int buflen
, int show_ctrl
)
2449 for (n
= 0; n
< buflen
; n
+= charsize
) {
2450 if (IS_SET(MODE_UTF8
)) {
2451 /* process a complete utf8 char */
2452 charsize
= utf8decode(buf
+ n
, &u
, buflen
- n
);
2459 if (show_ctrl
&& ISCONTROL(u
)) {
2464 } else if (u
!= '\n' && u
!= '\r' && u
!= '\t') {
2475 tresize(int col
, int row
)
2478 int minrow
= MIN(row
, term
.row
);
2479 int mincol
= MIN(col
, term
.col
);
2483 if (col
< 1 || row
< 1) {
2485 "tresize: error resizing to %dx%d\n", col
, row
);
2490 * slide screen to keep cursor where we expect it -
2491 * tscrollup would work here, but we can optimize to
2492 * memmove because we're freeing the earlier lines
2494 for (i
= 0; i
<= term
.c
.y
- row
; i
++) {
2498 /* ensure that both src and dst are not NULL */
2500 memmove(term
.line
, term
.line
+ i
, row
* sizeof(Line
));
2501 memmove(term
.alt
, term
.alt
+ i
, row
* sizeof(Line
));
2503 for (i
+= row
; i
< term
.row
; i
++) {
2508 /* resize to new height */
2509 term
.line
= xrealloc(term
.line
, row
* sizeof(Line
));
2510 term
.alt
= xrealloc(term
.alt
, row
* sizeof(Line
));
2511 term
.dirty
= xrealloc(term
.dirty
, row
* sizeof(*term
.dirty
));
2512 term
.tabs
= xrealloc(term
.tabs
, col
* sizeof(*term
.tabs
));
2514 /* resize each row to new width, zero-pad if needed */
2515 for (i
= 0; i
< minrow
; i
++) {
2516 term
.line
[i
] = xrealloc(term
.line
[i
], col
* sizeof(Glyph
));
2517 term
.alt
[i
] = xrealloc(term
.alt
[i
], col
* sizeof(Glyph
));
2520 /* allocate any new rows */
2521 for (/* i = minrow */; i
< row
; i
++) {
2522 term
.line
[i
] = xmalloc(col
* sizeof(Glyph
));
2523 term
.alt
[i
] = xmalloc(col
* sizeof(Glyph
));
2525 if (col
> term
.col
) {
2526 bp
= term
.tabs
+ term
.col
;
2528 memset(bp
, 0, sizeof(*term
.tabs
) * (col
- term
.col
));
2529 while (--bp
> term
.tabs
&& !*bp
)
2531 for (bp
+= tabspaces
; bp
< term
.tabs
+ col
; bp
+= tabspaces
)
2534 /* update terminal size */
2537 /* reset scrolling region */
2538 tsetscroll(0, row
-1);
2539 /* make use of the LIMIT in tmoveto */
2540 tmoveto(term
.c
.x
, term
.c
.y
);
2541 /* Clearing both screens (it makes dirty all lines) */
2543 for (i
= 0; i
< 2; i
++) {
2544 if (mincol
< col
&& 0 < minrow
) {
2545 tclearregion(mincol
, 0, col
- 1, minrow
- 1);
2547 if (0 < col
&& minrow
< row
) {
2548 tclearregion(0, minrow
, col
- 1, row
- 1);
2551 tcursor(CURSOR_LOAD
);
2563 drawregion(int x1
, int y1
, int x2
, int y2
)
2567 for (y
= y1
; y
< y2
; y
++) {
2572 xdrawline(term
.line
[y
], x1
, y
, x2
);
2579 int cx
= term
.c
.x
, ocx
= term
.ocx
, ocy
= term
.ocy
;
2584 /* adjust cursor position */
2585 LIMIT(term
.ocx
, 0, term
.col
-1);
2586 LIMIT(term
.ocy
, 0, term
.row
-1);
2587 if (term
.line
[term
.ocy
][term
.ocx
].mode
& ATTR_WDUMMY
)
2589 if (term
.line
[term
.c
.y
][cx
].mode
& ATTR_WDUMMY
)
2592 drawregion(0, 0, term
.col
, term
.row
);
2593 xdrawcursor(cx
, term
.c
.y
, term
.line
[term
.c
.y
][cx
],
2594 term
.ocx
, term
.ocy
, term
.line
[term
.ocy
][term
.ocx
]);
2596 term
.ocy
= term
.c
.y
;
2598 if (ocx
!= term
.ocx
|| ocy
!= term
.ocy
)
2599 xximspot(term
.ocx
, term
.ocy
);