Xinqi Bao's Git
ae7fa633233a8098a16f891b0010abeef61b11db
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(int *, int);
190 static void tsetchar(Rune
, 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, 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(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 uchar utfbyte
[UTF_SIZ
+ 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
230 static uchar utfmask
[UTF_SIZ
+ 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
231 static Rune utfmin
[UTF_SIZ
+ 1] = { 0, 0, 0x80, 0x800, 0x10000};
232 static 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
));
274 if ((s
= strdup(s
)) == NULL
)
275 die("strdup: %s\n", strerror(errno
));
281 utf8decode(const char *c
, Rune
*u
, size_t clen
)
283 size_t i
, j
, len
, type
;
289 udecoded
= utf8decodebyte(c
[0], &len
);
290 if (!BETWEEN(len
, 1, UTF_SIZ
))
292 for (i
= 1, j
= 1; i
< clen
&& j
< len
; ++i
, ++j
) {
293 udecoded
= (udecoded
<< 6) | utf8decodebyte(c
[i
], &type
);
300 utf8validate(u
, len
);
306 utf8decodebyte(char c
, size_t *i
)
308 for (*i
= 0; *i
< LEN(utfmask
); ++(*i
))
309 if (((uchar
)c
& utfmask
[*i
]) == utfbyte
[*i
])
310 return (uchar
)c
& ~utfmask
[*i
];
316 utf8encode(Rune u
, char *c
)
320 len
= utf8validate(&u
, 0);
324 for (i
= len
- 1; i
!= 0; --i
) {
325 c
[i
] = utf8encodebyte(u
, 0);
328 c
[0] = utf8encodebyte(u
, len
);
334 utf8encodebyte(Rune u
, size_t i
)
336 return utfbyte
[i
] | (u
& ~utfmask
[i
]);
340 utf8validate(Rune
*u
, size_t i
)
342 if (!BETWEEN(*u
, utfmin
[i
], utfmax
[i
]) || BETWEEN(*u
, 0xD800, 0xDFFF))
344 for (i
= 1; *u
> utfmax
[i
]; ++i
)
350 static const char base64_digits
[] = {
351 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
352 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0,
353 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
354 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
355 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
356 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0,
357 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
358 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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
366 base64dec_getc(const char **src
)
368 while (**src
&& !isprint(**src
))
370 return **src
? *((*src
)++) : '='; /* emulate padding if string ends */
374 base64dec(const char *src
)
376 size_t in_len
= strlen(src
);
380 in_len
+= 4 - (in_len
% 4);
381 result
= dst
= xmalloc(in_len
/ 4 * 3 + 1);
383 int a
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
384 int b
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
385 int c
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
386 int d
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
388 /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */
389 if (a
== -1 || b
== -1)
392 *dst
++ = (a
<< 2) | ((b
& 0x30) >> 4);
395 *dst
++ = ((b
& 0x0f) << 4) | ((c
& 0x3c) >> 2);
398 *dst
++ = ((c
& 0x03) << 6) | d
;
417 if (term
.line
[y
][i
- 1].mode
& ATTR_WRAP
)
420 while (i
> 0 && term
.line
[y
][i
- 1].u
== ' ')
427 selstart(int col
, int row
, int snap
)
430 sel
.mode
= SEL_EMPTY
;
431 sel
.type
= SEL_REGULAR
;
432 sel
.alt
= IS_SET(MODE_ALTSCREEN
);
434 sel
.oe
.x
= sel
.ob
.x
= col
;
435 sel
.oe
.y
= sel
.ob
.y
= row
;
439 sel
.mode
= SEL_READY
;
440 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
444 selextend(int col
, int row
, int type
, int done
)
446 int oldey
, oldex
, oldsby
, oldsey
, oldtype
;
448 if (sel
.mode
== SEL_IDLE
)
450 if (done
&& sel
.mode
== SEL_EMPTY
) {
466 if (oldey
!= sel
.oe
.y
|| oldex
!= sel
.oe
.x
|| oldtype
!= sel
.type
|| sel
.mode
== SEL_EMPTY
)
467 tsetdirt(MIN(sel
.nb
.y
, oldsby
), MAX(sel
.ne
.y
, oldsey
));
469 sel
.mode
= done
? SEL_IDLE
: SEL_READY
;
477 if (sel
.type
== SEL_REGULAR
&& sel
.ob
.y
!= sel
.oe
.y
) {
478 sel
.nb
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.ob
.x
: sel
.oe
.x
;
479 sel
.ne
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.oe
.x
: sel
.ob
.x
;
481 sel
.nb
.x
= MIN(sel
.ob
.x
, sel
.oe
.x
);
482 sel
.ne
.x
= MAX(sel
.ob
.x
, sel
.oe
.x
);
484 sel
.nb
.y
= MIN(sel
.ob
.y
, sel
.oe
.y
);
485 sel
.ne
.y
= MAX(sel
.ob
.y
, sel
.oe
.y
);
487 selsnap(&sel
.nb
.x
, &sel
.nb
.y
, -1);
488 selsnap(&sel
.ne
.x
, &sel
.ne
.y
, +1);
490 /* expand selection over line breaks */
491 if (sel
.type
== SEL_RECTANGULAR
)
493 i
= tlinelen(sel
.nb
.y
);
496 if (tlinelen(sel
.ne
.y
) <= sel
.ne
.x
)
497 sel
.ne
.x
= term
.col
- 1;
501 selected(int x
, int y
)
503 if (sel
.mode
== SEL_EMPTY
|| sel
.ob
.x
== -1 ||
504 sel
.alt
!= IS_SET(MODE_ALTSCREEN
))
507 if (sel
.type
== SEL_RECTANGULAR
)
508 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
509 && BETWEEN(x
, sel
.nb
.x
, sel
.ne
.x
);
511 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
512 && (y
!= sel
.nb
.y
|| x
>= sel
.nb
.x
)
513 && (y
!= sel
.ne
.y
|| x
<= sel
.ne
.x
);
517 selsnap(int *x
, int *y
, int direction
)
519 int newx
, newy
, xt
, yt
;
520 int delim
, prevdelim
;
526 * Snap around if the word wraps around at the end or
527 * beginning of a line.
529 prevgp
= &term
.line
[*y
][*x
];
530 prevdelim
= ISDELIM(prevgp
->u
);
532 newx
= *x
+ direction
;
534 if (!BETWEEN(newx
, 0, term
.col
- 1)) {
536 newx
= (newx
+ term
.col
) % term
.col
;
537 if (!BETWEEN(newy
, 0, term
.row
- 1))
543 yt
= newy
, xt
= newx
;
544 if (!(term
.line
[yt
][xt
].mode
& ATTR_WRAP
))
548 if (newx
>= tlinelen(newy
))
551 gp
= &term
.line
[newy
][newx
];
552 delim
= ISDELIM(gp
->u
);
553 if (!(gp
->mode
& ATTR_WDUMMY
) && (delim
!= prevdelim
554 || (delim
&& gp
->u
!= prevgp
->u
)))
565 * Snap around if the the previous line or the current one
566 * has set ATTR_WRAP at its end. Then the whole next or
567 * previous line will be selected.
569 *x
= (direction
< 0) ? 0 : term
.col
- 1;
571 for (; *y
> 0; *y
+= direction
) {
572 if (!(term
.line
[*y
-1][term
.col
-1].mode
577 } else if (direction
> 0) {
578 for (; *y
< term
.row
-1; *y
+= direction
) {
579 if (!(term
.line
[*y
][term
.col
-1].mode
593 int y
, bufsize
, lastx
, linelen
;
599 bufsize
= (term
.col
+1) * (sel
.ne
.y
-sel
.nb
.y
+1) * UTF_SIZ
;
600 ptr
= str
= xmalloc(bufsize
);
602 /* append every set & selected glyph to the selection */
603 for (y
= sel
.nb
.y
; y
<= sel
.ne
.y
; y
++) {
604 if ((linelen
= tlinelen(y
)) == 0) {
609 if (sel
.type
== SEL_RECTANGULAR
) {
610 gp
= &term
.line
[y
][sel
.nb
.x
];
613 gp
= &term
.line
[y
][sel
.nb
.y
== y
? sel
.nb
.x
: 0];
614 lastx
= (sel
.ne
.y
== y
) ? sel
.ne
.x
: term
.col
-1;
616 last
= &term
.line
[y
][MIN(lastx
, linelen
-1)];
617 while (last
>= gp
&& last
->u
== ' ')
620 for ( ; gp
<= last
; ++gp
) {
621 if (gp
->mode
& ATTR_WDUMMY
)
624 ptr
+= utf8encode(gp
->u
, ptr
);
628 * Copy and pasting of line endings is inconsistent
629 * in the inconsistent terminal and GUI world.
630 * The best solution seems like to produce '\n' when
631 * something is copied from st and convert '\n' to
632 * '\r', when something to be pasted is received by
634 * FIXME: Fix the computer world.
636 if ((y
< sel
.ne
.y
|| lastx
>= linelen
) &&
637 (!(last
->mode
& ATTR_WRAP
) || sel
.type
== SEL_RECTANGULAR
))
651 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
655 die(const char *errstr
, ...)
659 va_start(ap
, errstr
);
660 vfprintf(stderr
, errstr
, ap
);
666 execsh(char *cmd
, char **args
)
668 char *sh
, *prog
, *arg
;
669 const struct passwd
*pw
;
672 if ((pw
= getpwuid(getuid())) == NULL
) {
674 die("getpwuid: %s\n", strerror(errno
));
676 die("who are you?\n");
679 if ((sh
= getenv("SHELL")) == NULL
)
680 sh
= (pw
->pw_shell
[0]) ? pw
->pw_shell
: cmd
;
687 arg
= utmp
? utmp
: sh
;
695 DEFAULT(args
, ((char *[]) {prog
, arg
, NULL
}));
700 setenv("LOGNAME", pw
->pw_name
, 1);
701 setenv("USER", pw
->pw_name
, 1);
702 setenv("SHELL", sh
, 1);
703 setenv("HOME", pw
->pw_dir
, 1);
704 setenv("TERM", termname
, 1);
706 signal(SIGCHLD
, SIG_DFL
);
707 signal(SIGHUP
, SIG_DFL
);
708 signal(SIGINT
, SIG_DFL
);
709 signal(SIGQUIT
, SIG_DFL
);
710 signal(SIGTERM
, SIG_DFL
);
711 signal(SIGALRM
, SIG_DFL
);
723 if ((p
= waitpid(pid
, &stat
, WNOHANG
)) < 0)
724 die("waiting for pid %hd failed: %s\n", pid
, strerror(errno
));
729 if (WIFEXITED(stat
) && WEXITSTATUS(stat
))
730 die("child exited with status %d\n", WEXITSTATUS(stat
));
731 else if (WIFSIGNALED(stat
))
732 die("child terminated due to signal %d\n", WTERMSIG(stat
));
739 char cmd
[_POSIX_ARG_MAX
], **p
, *q
, *s
;
742 if ((n
= strlen(stty_args
)) > sizeof(cmd
)-1)
743 die("incorrect stty parameters\n");
744 memcpy(cmd
, stty_args
, n
);
746 siz
= sizeof(cmd
) - n
;
747 for (p
= args
; p
&& (s
= *p
); ++p
) {
748 if ((n
= strlen(s
)) > siz
-1)
749 die("stty parameter length too long\n");
756 if (system(cmd
) != 0)
757 perror("Couldn't call stty");
761 ttynew(char *line
, char *cmd
, char *out
, char **args
)
766 term
.mode
|= MODE_PRINT
;
767 iofd
= (!strcmp(out
, "-")) ?
768 1 : open(out
, O_WRONLY
| O_CREAT
, 0666);
770 fprintf(stderr
, "Error opening %s:%s\n",
771 out
, strerror(errno
));
776 if ((cmdfd
= open(line
, O_RDWR
)) < 0)
777 die("open line '%s' failed: %s\n",
778 line
, strerror(errno
));
784 /* seems to work fine on linux, openbsd and freebsd */
785 if (openpty(&m
, &s
, NULL
, NULL
, NULL
) < 0)
786 die("openpty failed: %s\n", strerror(errno
));
788 switch (pid
= fork()) {
790 die("fork failed: %s\n", strerror(errno
));
794 setsid(); /* create a new process group */
798 if (ioctl(s
, TIOCSCTTY
, NULL
) < 0)
799 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno
));
803 if (pledge("stdio getpw proc exec", NULL
) == -1)
810 if (pledge("stdio rpath tty proc", NULL
) == -1)
815 signal(SIGCHLD
, sigchld
);
824 static char buf
[BUFSIZ
];
825 static int buflen
= 0;
828 /* append read bytes to unprocessed bytes */
829 ret
= read(cmdfd
, buf
+buflen
, LEN(buf
)-buflen
);
835 die("couldn't read from shell: %s\n", strerror(errno
));
838 written
= twrite(buf
, buflen
, 0);
840 /* keep any incomplete UTF-8 byte sequence for the next call */
842 memmove(buf
, buf
+ written
, buflen
);
848 ttywrite(const char *s
, size_t n
, int may_echo
)
852 if (may_echo
&& IS_SET(MODE_ECHO
))
855 if (!IS_SET(MODE_CRLF
)) {
860 /* This is similar to how the kernel handles ONLCR for ttys */
864 ttywriteraw("\r\n", 2);
866 next
= memchr(s
, '\r', n
);
867 DEFAULT(next
, s
+ n
);
868 ttywriteraw(s
, next
- s
);
876 ttywriteraw(const char *s
, size_t n
)
883 * Remember that we are using a pty, which might be a modem line.
884 * Writing too much will clog the line. That's why we are doing this
886 * FIXME: Migrate the world to Plan 9.
894 /* Check if we can write. */
895 if (pselect(cmdfd
+1, &rfd
, &wfd
, NULL
, NULL
, NULL
) < 0) {
898 die("select failed: %s\n", strerror(errno
));
900 if (FD_ISSET(cmdfd
, &wfd
)) {
902 * Only write the bytes written by ttywrite() or the
903 * default of 256. This seems to be a reasonable value
904 * for a serial line. Bigger values might clog the I/O.
906 if ((r
= write(cmdfd
, s
, (n
< lim
)? n
: lim
)) < 0)
910 * We weren't able to write out everything.
911 * This means the buffer is getting full
919 /* All bytes have been written. */
923 if (FD_ISSET(cmdfd
, &rfd
))
929 die("write error on tty: %s\n", strerror(errno
));
933 ttyresize(int tw
, int th
)
941 if (ioctl(cmdfd
, TIOCSWINSZ
, &w
) < 0)
942 fprintf(stderr
, "Couldn't set window size: %s\n", strerror(errno
));
948 /* Send SIGHUP to shell */
957 for (i
= 0; i
< term
.row
-1; i
++) {
958 for (j
= 0; j
< term
.col
-1; j
++) {
959 if (term
.line
[i
][j
].mode
& attr
)
968 tsetdirt(int top
, int bot
)
972 LIMIT(top
, 0, term
.row
-1);
973 LIMIT(bot
, 0, term
.row
-1);
975 for (i
= top
; i
<= bot
; i
++)
980 tsetdirtattr(int attr
)
984 for (i
= 0; i
< term
.row
-1; i
++) {
985 for (j
= 0; j
< term
.col
-1; j
++) {
986 if (term
.line
[i
][j
].mode
& attr
) {
997 tsetdirt(0, term
.row
-1);
1003 static TCursor c
[2];
1004 int alt
= IS_SET(MODE_ALTSCREEN
);
1006 if (mode
== CURSOR_SAVE
) {
1008 } else if (mode
== CURSOR_LOAD
) {
1010 tmoveto(c
[alt
].x
, c
[alt
].y
);
1019 term
.c
= (TCursor
){{
1023 }, .x
= 0, .y
= 0, .state
= CURSOR_DEFAULT
};
1025 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1026 for (i
= tabspaces
; i
< term
.col
; i
+= tabspaces
)
1029 term
.bot
= term
.row
- 1;
1030 term
.mode
= MODE_WRAP
|MODE_UTF8
;
1031 memset(term
.trantbl
, CS_USA
, sizeof(term
.trantbl
));
1034 for (i
= 0; i
< 2; i
++) {
1036 tcursor(CURSOR_SAVE
);
1037 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1043 tnew(int col
, int row
)
1045 term
= (Term
){ .c
= { .attr
= { .fg
= defaultfg
, .bg
= defaultbg
} } };
1053 Line
*tmp
= term
.line
;
1055 term
.line
= term
.alt
;
1057 term
.mode
^= MODE_ALTSCREEN
;
1062 tscrolldown(int orig
, int n
)
1067 LIMIT(n
, 0, term
.bot
-orig
+1);
1069 tsetdirt(orig
, term
.bot
-n
);
1070 tclearregion(0, term
.bot
-n
+1, term
.col
-1, term
.bot
);
1072 for (i
= term
.bot
; i
>= orig
+n
; i
--) {
1073 temp
= term
.line
[i
];
1074 term
.line
[i
] = term
.line
[i
-n
];
1075 term
.line
[i
-n
] = temp
;
1082 tscrollup(int orig
, int n
)
1087 LIMIT(n
, 0, term
.bot
-orig
+1);
1089 tclearregion(0, orig
, term
.col
-1, orig
+n
-1);
1090 tsetdirt(orig
+n
, term
.bot
);
1092 for (i
= orig
; i
<= term
.bot
-n
; i
++) {
1093 temp
= term
.line
[i
];
1094 term
.line
[i
] = term
.line
[i
+n
];
1095 term
.line
[i
+n
] = temp
;
1098 selscroll(orig
, -n
);
1102 selscroll(int orig
, int n
)
1107 if (BETWEEN(sel
.nb
.y
, orig
, term
.bot
) != BETWEEN(sel
.ne
.y
, orig
, term
.bot
)) {
1109 } else if (BETWEEN(sel
.nb
.y
, orig
, term
.bot
)) {
1112 if (sel
.ob
.y
< term
.top
|| sel
.ob
.y
> term
.bot
||
1113 sel
.oe
.y
< term
.top
|| sel
.oe
.y
> term
.bot
) {
1122 tnewline(int first_col
)
1126 if (y
== term
.bot
) {
1127 tscrollup(term
.top
, 1);
1131 tmoveto(first_col
? 0 : term
.c
.x
, y
);
1137 char *p
= csiescseq
.buf
, *np
;
1146 csiescseq
.buf
[csiescseq
.len
] = '\0';
1147 while (p
< csiescseq
.buf
+csiescseq
.len
) {
1149 v
= strtol(p
, &np
, 10);
1152 if (v
== LONG_MAX
|| v
== LONG_MIN
)
1154 csiescseq
.arg
[csiescseq
.narg
++] = v
;
1156 if (*p
!= ';' || csiescseq
.narg
== ESC_ARG_SIZ
)
1160 csiescseq
.mode
[0] = *p
++;
1161 csiescseq
.mode
[1] = (p
< csiescseq
.buf
+csiescseq
.len
) ? *p
: '\0';
1164 /* for absolute user moves, when decom is set */
1166 tmoveato(int x
, int y
)
1168 tmoveto(x
, y
+ ((term
.c
.state
& CURSOR_ORIGIN
) ? term
.top
: 0));
1172 tmoveto(int x
, int y
)
1176 if (term
.c
.state
& CURSOR_ORIGIN
) {
1181 maxy
= term
.row
- 1;
1183 term
.c
.state
&= ~CURSOR_WRAPNEXT
;
1184 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1185 term
.c
.y
= LIMIT(y
, miny
, maxy
);
1189 tsetchar(Rune u
, Glyph
*attr
, int x
, int y
)
1191 static char *vt100_0
[62] = { /* 0x41 - 0x7e */
1192 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1193 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1194 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1195 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1196 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1197 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1198 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1199 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1203 * The table is proudly stolen from rxvt.
1205 if (term
.trantbl
[term
.charset
] == CS_GRAPHIC0
&&
1206 BETWEEN(u
, 0x41, 0x7e) && vt100_0
[u
- 0x41])
1207 utf8decode(vt100_0
[u
- 0x41], &u
, UTF_SIZ
);
1209 if (term
.line
[y
][x
].mode
& ATTR_WIDE
) {
1210 if (x
+1 < term
.col
) {
1211 term
.line
[y
][x
+1].u
= ' ';
1212 term
.line
[y
][x
+1].mode
&= ~ATTR_WDUMMY
;
1214 } else if (term
.line
[y
][x
].mode
& ATTR_WDUMMY
) {
1215 term
.line
[y
][x
-1].u
= ' ';
1216 term
.line
[y
][x
-1].mode
&= ~ATTR_WIDE
;
1220 term
.line
[y
][x
] = *attr
;
1221 term
.line
[y
][x
].u
= u
;
1225 tclearregion(int x1
, int y1
, int x2
, int y2
)
1231 temp
= x1
, x1
= x2
, x2
= temp
;
1233 temp
= y1
, y1
= y2
, y2
= temp
;
1235 LIMIT(x1
, 0, term
.col
-1);
1236 LIMIT(x2
, 0, term
.col
-1);
1237 LIMIT(y1
, 0, term
.row
-1);
1238 LIMIT(y2
, 0, term
.row
-1);
1240 for (y
= y1
; y
<= y2
; y
++) {
1242 for (x
= x1
; x
<= x2
; x
++) {
1243 gp
= &term
.line
[y
][x
];
1246 gp
->fg
= term
.c
.attr
.fg
;
1247 gp
->bg
= term
.c
.attr
.bg
;
1260 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1264 size
= term
.col
- src
;
1265 line
= term
.line
[term
.c
.y
];
1267 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1268 tclearregion(term
.col
-n
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1277 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1281 size
= term
.col
- dst
;
1282 line
= term
.line
[term
.c
.y
];
1284 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1285 tclearregion(src
, term
.c
.y
, dst
- 1, term
.c
.y
);
1289 tinsertblankline(int n
)
1291 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1292 tscrolldown(term
.c
.y
, n
);
1298 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1299 tscrollup(term
.c
.y
, n
);
1303 tdefcolor(int *attr
, int *npar
, int l
)
1308 switch (attr
[*npar
+ 1]) {
1309 case 2: /* direct color in RGB space */
1310 if (*npar
+ 4 >= l
) {
1312 "erresc(38): Incorrect number of parameters (%d)\n",
1316 r
= attr
[*npar
+ 2];
1317 g
= attr
[*npar
+ 3];
1318 b
= attr
[*npar
+ 4];
1320 if (!BETWEEN(r
, 0, 255) || !BETWEEN(g
, 0, 255) || !BETWEEN(b
, 0, 255))
1321 fprintf(stderr
, "erresc: bad rgb color (%u,%u,%u)\n",
1324 idx
= TRUECOLOR(r
, g
, b
);
1326 case 5: /* indexed color */
1327 if (*npar
+ 2 >= l
) {
1329 "erresc(38): Incorrect number of parameters (%d)\n",
1334 if (!BETWEEN(attr
[*npar
], 0, 255))
1335 fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[*npar
]);
1339 case 0: /* implemented defined (only foreground) */
1340 case 1: /* transparent */
1341 case 3: /* direct color in CMY space */
1342 case 4: /* direct color in CMYK space */
1345 "erresc(38): gfx attr %d unknown\n", attr
[*npar
]);
1353 tsetattr(int *attr
, int l
)
1358 for (i
= 0; i
< l
; i
++) {
1361 term
.c
.attr
.mode
&= ~(
1370 term
.c
.attr
.fg
= defaultfg
;
1371 term
.c
.attr
.bg
= defaultbg
;
1374 term
.c
.attr
.mode
|= ATTR_BOLD
;
1377 term
.c
.attr
.mode
|= ATTR_FAINT
;
1380 term
.c
.attr
.mode
|= ATTR_ITALIC
;
1383 term
.c
.attr
.mode
|= ATTR_UNDERLINE
;
1385 case 5: /* slow blink */
1387 case 6: /* rapid blink */
1388 term
.c
.attr
.mode
|= ATTR_BLINK
;
1391 term
.c
.attr
.mode
|= ATTR_REVERSE
;
1394 term
.c
.attr
.mode
|= ATTR_INVISIBLE
;
1397 term
.c
.attr
.mode
|= ATTR_STRUCK
;
1400 term
.c
.attr
.mode
&= ~(ATTR_BOLD
| ATTR_FAINT
);
1403 term
.c
.attr
.mode
&= ~ATTR_ITALIC
;
1406 term
.c
.attr
.mode
&= ~ATTR_UNDERLINE
;
1409 term
.c
.attr
.mode
&= ~ATTR_BLINK
;
1412 term
.c
.attr
.mode
&= ~ATTR_REVERSE
;
1415 term
.c
.attr
.mode
&= ~ATTR_INVISIBLE
;
1418 term
.c
.attr
.mode
&= ~ATTR_STRUCK
;
1421 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1422 term
.c
.attr
.fg
= idx
;
1425 term
.c
.attr
.fg
= defaultfg
;
1428 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1429 term
.c
.attr
.bg
= idx
;
1432 term
.c
.attr
.bg
= defaultbg
;
1435 if (BETWEEN(attr
[i
], 30, 37)) {
1436 term
.c
.attr
.fg
= attr
[i
] - 30;
1437 } else if (BETWEEN(attr
[i
], 40, 47)) {
1438 term
.c
.attr
.bg
= attr
[i
] - 40;
1439 } else if (BETWEEN(attr
[i
], 90, 97)) {
1440 term
.c
.attr
.fg
= attr
[i
] - 90 + 8;
1441 } else if (BETWEEN(attr
[i
], 100, 107)) {
1442 term
.c
.attr
.bg
= attr
[i
] - 100 + 8;
1445 "erresc(default): gfx attr %d unknown\n",
1455 tsetscroll(int t
, int b
)
1459 LIMIT(t
, 0, term
.row
-1);
1460 LIMIT(b
, 0, term
.row
-1);
1471 tsetmode(int priv
, int set
, int *args
, int narg
)
1475 for (lim
= args
+ narg
; args
< lim
; ++args
) {
1478 case 1: /* DECCKM -- Cursor key */
1479 xsetmode(set
, MODE_APPCURSOR
);
1481 case 5: /* DECSCNM -- Reverse video */
1482 xsetmode(set
, MODE_REVERSE
);
1484 case 6: /* DECOM -- Origin */
1485 MODBIT(term
.c
.state
, set
, CURSOR_ORIGIN
);
1488 case 7: /* DECAWM -- Auto wrap */
1489 MODBIT(term
.mode
, set
, MODE_WRAP
);
1491 case 0: /* Error (IGNORED) */
1492 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1493 case 3: /* DECCOLM -- Column (IGNORED) */
1494 case 4: /* DECSCLM -- Scroll (IGNORED) */
1495 case 8: /* DECARM -- Auto repeat (IGNORED) */
1496 case 18: /* DECPFF -- Printer feed (IGNORED) */
1497 case 19: /* DECPEX -- Printer extent (IGNORED) */
1498 case 42: /* DECNRCM -- National characters (IGNORED) */
1499 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1501 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1502 xsetmode(!set
, MODE_HIDE
);
1504 case 9: /* X10 mouse compatibility mode */
1505 xsetpointermotion(0);
1506 xsetmode(0, MODE_MOUSE
);
1507 xsetmode(set
, MODE_MOUSEX10
);
1509 case 1000: /* 1000: report button press */
1510 xsetpointermotion(0);
1511 xsetmode(0, MODE_MOUSE
);
1512 xsetmode(set
, MODE_MOUSEBTN
);
1514 case 1002: /* 1002: report motion on button press */
1515 xsetpointermotion(0);
1516 xsetmode(0, MODE_MOUSE
);
1517 xsetmode(set
, MODE_MOUSEMOTION
);
1519 case 1003: /* 1003: enable all mouse motions */
1520 xsetpointermotion(set
);
1521 xsetmode(0, MODE_MOUSE
);
1522 xsetmode(set
, MODE_MOUSEMANY
);
1524 case 1004: /* 1004: send focus events to tty */
1525 xsetmode(set
, MODE_FOCUS
);
1527 case 1006: /* 1006: extended reporting mode */
1528 xsetmode(set
, MODE_MOUSESGR
);
1531 xsetmode(set
, MODE_8BIT
);
1533 case 1049: /* swap screen & set/restore cursor as xterm */
1534 if (!allowaltscreen
)
1536 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1538 case 47: /* swap screen */
1540 if (!allowaltscreen
)
1542 alt
= IS_SET(MODE_ALTSCREEN
);
1544 tclearregion(0, 0, term
.col
-1,
1547 if (set
^ alt
) /* set is always 1 or 0 */
1553 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1555 case 2004: /* 2004: bracketed paste mode */
1556 xsetmode(set
, MODE_BRCKTPASTE
);
1558 /* Not implemented mouse modes. See comments there. */
1559 case 1001: /* mouse highlight mode; can hang the
1560 terminal by design when implemented. */
1561 case 1005: /* UTF-8 mouse mode; will confuse
1562 applications not supporting UTF-8
1564 case 1015: /* urxvt mangled mouse mode; incompatible
1565 and can be mistaken for other control
1570 "erresc: unknown private set/reset mode %d\n",
1576 case 0: /* Error (IGNORED) */
1579 xsetmode(set
, MODE_KBDLOCK
);
1581 case 4: /* IRM -- Insertion-replacement */
1582 MODBIT(term
.mode
, set
, MODE_INSERT
);
1584 case 12: /* SRM -- Send/Receive */
1585 MODBIT(term
.mode
, !set
, MODE_ECHO
);
1587 case 20: /* LNM -- Linefeed/new line */
1588 MODBIT(term
.mode
, set
, MODE_CRLF
);
1592 "erresc: unknown set/reset mode %d\n",
1606 switch (csiescseq
.mode
[0]) {
1609 fprintf(stderr
, "erresc: unknown csi ");
1613 case '@': /* ICH -- Insert <n> blank char */
1614 DEFAULT(csiescseq
.arg
[0], 1);
1615 tinsertblank(csiescseq
.arg
[0]);
1617 case 'A': /* CUU -- Cursor <n> Up */
1618 DEFAULT(csiescseq
.arg
[0], 1);
1619 tmoveto(term
.c
.x
, term
.c
.y
-csiescseq
.arg
[0]);
1621 case 'B': /* CUD -- Cursor <n> Down */
1622 case 'e': /* VPR --Cursor <n> Down */
1623 DEFAULT(csiescseq
.arg
[0], 1);
1624 tmoveto(term
.c
.x
, term
.c
.y
+csiescseq
.arg
[0]);
1626 case 'i': /* MC -- Media Copy */
1627 switch (csiescseq
.arg
[0]) {
1632 tdumpline(term
.c
.y
);
1638 term
.mode
&= ~MODE_PRINT
;
1641 term
.mode
|= MODE_PRINT
;
1645 case 'c': /* DA -- Device Attributes */
1646 if (csiescseq
.arg
[0] == 0)
1647 ttywrite(vtiden
, strlen(vtiden
), 0);
1649 case 'b': /* REP -- if last char is printable print it <n> more times */
1650 DEFAULT(csiescseq
.arg
[0], 1);
1652 while (csiescseq
.arg
[0]-- > 0)
1655 case 'C': /* CUF -- Cursor <n> Forward */
1656 case 'a': /* HPR -- Cursor <n> Forward */
1657 DEFAULT(csiescseq
.arg
[0], 1);
1658 tmoveto(term
.c
.x
+csiescseq
.arg
[0], term
.c
.y
);
1660 case 'D': /* CUB -- Cursor <n> Backward */
1661 DEFAULT(csiescseq
.arg
[0], 1);
1662 tmoveto(term
.c
.x
-csiescseq
.arg
[0], term
.c
.y
);
1664 case 'E': /* CNL -- Cursor <n> Down and first col */
1665 DEFAULT(csiescseq
.arg
[0], 1);
1666 tmoveto(0, term
.c
.y
+csiescseq
.arg
[0]);
1668 case 'F': /* CPL -- Cursor <n> Up and first col */
1669 DEFAULT(csiescseq
.arg
[0], 1);
1670 tmoveto(0, term
.c
.y
-csiescseq
.arg
[0]);
1672 case 'g': /* TBC -- Tabulation clear */
1673 switch (csiescseq
.arg
[0]) {
1674 case 0: /* clear current tab stop */
1675 term
.tabs
[term
.c
.x
] = 0;
1677 case 3: /* clear all the tabs */
1678 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1684 case 'G': /* CHA -- Move to <col> */
1686 DEFAULT(csiescseq
.arg
[0], 1);
1687 tmoveto(csiescseq
.arg
[0]-1, term
.c
.y
);
1689 case 'H': /* CUP -- Move to <row> <col> */
1691 DEFAULT(csiescseq
.arg
[0], 1);
1692 DEFAULT(csiescseq
.arg
[1], 1);
1693 tmoveato(csiescseq
.arg
[1]-1, csiescseq
.arg
[0]-1);
1695 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1696 DEFAULT(csiescseq
.arg
[0], 1);
1697 tputtab(csiescseq
.arg
[0]);
1699 case 'J': /* ED -- Clear screen */
1700 switch (csiescseq
.arg
[0]) {
1702 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1703 if (term
.c
.y
< term
.row
-1) {
1704 tclearregion(0, term
.c
.y
+1, term
.col
-1,
1710 tclearregion(0, 0, term
.col
-1, term
.c
.y
-1);
1711 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1714 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1720 case 'K': /* EL -- Clear line */
1721 switch (csiescseq
.arg
[0]) {
1723 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1,
1727 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1730 tclearregion(0, term
.c
.y
, term
.col
-1, term
.c
.y
);
1734 case 'S': /* SU -- Scroll <n> line up */
1735 DEFAULT(csiescseq
.arg
[0], 1);
1736 tscrollup(term
.top
, csiescseq
.arg
[0]);
1738 case 'T': /* SD -- Scroll <n> line down */
1739 DEFAULT(csiescseq
.arg
[0], 1);
1740 tscrolldown(term
.top
, csiescseq
.arg
[0]);
1742 case 'L': /* IL -- Insert <n> blank lines */
1743 DEFAULT(csiescseq
.arg
[0], 1);
1744 tinsertblankline(csiescseq
.arg
[0]);
1746 case 'l': /* RM -- Reset Mode */
1747 tsetmode(csiescseq
.priv
, 0, csiescseq
.arg
, csiescseq
.narg
);
1749 case 'M': /* DL -- Delete <n> lines */
1750 DEFAULT(csiescseq
.arg
[0], 1);
1751 tdeleteline(csiescseq
.arg
[0]);
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
.y
);
1758 case 'P': /* DCH -- Delete <n> char */
1759 DEFAULT(csiescseq
.arg
[0], 1);
1760 tdeletechar(csiescseq
.arg
[0]);
1762 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1763 DEFAULT(csiescseq
.arg
[0], 1);
1764 tputtab(-csiescseq
.arg
[0]);
1766 case 'd': /* VPA -- Move to <row> */
1767 DEFAULT(csiescseq
.arg
[0], 1);
1768 tmoveato(term
.c
.x
, csiescseq
.arg
[0]-1);
1770 case 'h': /* SM -- Set terminal mode */
1771 tsetmode(csiescseq
.priv
, 1, csiescseq
.arg
, csiescseq
.narg
);
1773 case 'm': /* SGR -- Terminal attribute (color) */
1774 tsetattr(csiescseq
.arg
, csiescseq
.narg
);
1776 case 'n': /* DSR – Device Status Report (cursor position) */
1777 if (csiescseq
.arg
[0] == 6) {
1778 len
= snprintf(buf
, sizeof(buf
), "\033[%i;%iR",
1779 term
.c
.y
+1, term
.c
.x
+1);
1780 ttywrite(buf
, len
, 0);
1783 case 'r': /* DECSTBM -- Set Scrolling Region */
1784 if (csiescseq
.priv
) {
1787 DEFAULT(csiescseq
.arg
[0], 1);
1788 DEFAULT(csiescseq
.arg
[1], term
.row
);
1789 tsetscroll(csiescseq
.arg
[0]-1, csiescseq
.arg
[1]-1);
1793 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1794 tcursor(CURSOR_SAVE
);
1796 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1797 tcursor(CURSOR_LOAD
);
1800 switch (csiescseq
.mode
[1]) {
1801 case 'q': /* DECSCUSR -- Set Cursor Style */
1802 if (xsetcursor(csiescseq
.arg
[0]))
1818 fprintf(stderr
, "ESC[");
1819 for (i
= 0; i
< csiescseq
.len
; i
++) {
1820 c
= csiescseq
.buf
[i
] & 0xff;
1823 } else if (c
== '\n') {
1824 fprintf(stderr
, "(\\n)");
1825 } else if (c
== '\r') {
1826 fprintf(stderr
, "(\\r)");
1827 } else if (c
== 0x1b) {
1828 fprintf(stderr
, "(\\e)");
1830 fprintf(stderr
, "(%02x)", c
);
1839 memset(&csiescseq
, 0, sizeof(csiescseq
));
1845 char *p
= NULL
, *dec
;
1847 static int winname
= 0;
1849 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
1851 par
= (narg
= strescseq
.narg
) ? atoi(strescseq
.args
[0]) : 0;
1853 switch (strescseq
.type
) {
1854 case ']': /* OSC -- Operating System Command */
1858 xsettitle(strescseq
.args
[1]);
1859 xseticontitle(strescseq
.args
[1]);
1864 xseticontitle(strescseq
.args
[1]);
1868 xsettitle(strescseq
.args
[1]);
1871 if (narg
> 2 && allowwindowops
) {
1872 dec
= base64dec(strescseq
.args
[2]);
1877 fprintf(stderr
, "erresc: invalid base64\n");
1881 case 4: /* color set */
1884 p
= strescseq
.args
[2];
1886 case 104: /* color reset, here p = NULL */
1887 j
= (narg
> 1) ? atoi(strescseq
.args
[1]) : -1;
1888 if (xsetcolorname(j
, p
)) {
1889 if (par
== 104 && narg
<= 1)
1890 return; /* color reset without parameter */
1891 fprintf(stderr
, "erresc: invalid color j=%d, p=%s\n",
1892 j
, p
? p
: "(null)");
1895 * TODO if defaultbg color is changed, borders
1903 case 'k': /* old title set compatibility */
1904 xsettitle(strescseq
.args
[0]);
1906 case 'P': /* DCS -- Device Control String */
1907 case '_': /* APC -- Application Program Command */
1908 case '^': /* PM -- Privacy Message */
1912 fprintf(stderr
, "erresc: unknown str ");
1920 char *p
= strescseq
.buf
;
1923 strescseq
.buf
[strescseq
.len
] = '\0';
1928 while (strescseq
.narg
< STR_ARG_SIZ
) {
1929 strescseq
.args
[strescseq
.narg
++] = p
;
1930 while ((c
= *p
) != ';' && c
!= '\0')
1944 fprintf(stderr
, "ESC%c", strescseq
.type
);
1945 for (i
= 0; i
< strescseq
.len
; i
++) {
1946 c
= strescseq
.buf
[i
] & 0xff;
1950 } else if (isprint(c
)) {
1952 } else if (c
== '\n') {
1953 fprintf(stderr
, "(\\n)");
1954 } else if (c
== '\r') {
1955 fprintf(stderr
, "(\\r)");
1956 } else if (c
== 0x1b) {
1957 fprintf(stderr
, "(\\e)");
1959 fprintf(stderr
, "(%02x)", c
);
1962 fprintf(stderr
, "ESC\\\n");
1968 strescseq
= (STREscape
){
1969 .buf
= xrealloc(strescseq
.buf
, STR_BUF_SIZ
),
1975 sendbreak(const Arg
*arg
)
1977 if (tcsendbreak(cmdfd
, 0))
1978 perror("Error sending break");
1982 tprinter(char *s
, size_t len
)
1984 if (iofd
!= -1 && xwrite(iofd
, s
, len
) < 0) {
1985 perror("Error writing to output file");
1992 toggleprinter(const Arg
*arg
)
1994 term
.mode
^= MODE_PRINT
;
1998 printscreen(const Arg
*arg
)
2004 printsel(const Arg
*arg
)
2014 if ((ptr
= getsel())) {
2015 tprinter(ptr
, strlen(ptr
));
2026 bp
= &term
.line
[n
][0];
2027 end
= &bp
[MIN(tlinelen(n
), term
.col
) - 1];
2028 if (bp
!= end
|| bp
->u
!= ' ') {
2029 for ( ; bp
<= end
; ++bp
)
2030 tprinter(buf
, utf8encode(bp
->u
, buf
));
2040 for (i
= 0; i
< term
.row
; ++i
)
2050 while (x
< term
.col
&& n
--)
2051 for (++x
; x
< term
.col
&& !term
.tabs
[x
]; ++x
)
2054 while (x
> 0 && n
++)
2055 for (--x
; x
> 0 && !term
.tabs
[x
]; --x
)
2058 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
2062 tdefutf8(char ascii
)
2065 term
.mode
|= MODE_UTF8
;
2066 else if (ascii
== '@')
2067 term
.mode
&= ~MODE_UTF8
;
2071 tdeftran(char ascii
)
2073 static char cs
[] = "0B";
2074 static int vcs
[] = {CS_GRAPHIC0
, CS_USA
};
2077 if ((p
= strchr(cs
, ascii
)) == NULL
) {
2078 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
2080 term
.trantbl
[term
.icharset
] = vcs
[p
- cs
];
2089 if (c
== '8') { /* DEC screen alignment test. */
2090 for (x
= 0; x
< term
.col
; ++x
) {
2091 for (y
= 0; y
< term
.row
; ++y
)
2092 tsetchar('E', &term
.c
.attr
, x
, y
);
2098 tstrsequence(uchar c
)
2101 case 0x90: /* DCS -- Device Control String */
2104 case 0x9f: /* APC -- Application Program Command */
2107 case 0x9e: /* PM -- Privacy Message */
2110 case 0x9d: /* OSC -- Operating System Command */
2116 term
.esc
|= ESC_STR
;
2120 tcontrolcode(uchar ascii
)
2127 tmoveto(term
.c
.x
-1, term
.c
.y
);
2130 tmoveto(0, term
.c
.y
);
2135 /* go to first col if the mode is set */
2136 tnewline(IS_SET(MODE_CRLF
));
2138 case '\a': /* BEL */
2139 if (term
.esc
& ESC_STR_END
) {
2140 /* backwards compatibility to xterm */
2146 case '\033': /* ESC */
2148 term
.esc
&= ~(ESC_CSI
|ESC_ALTCHARSET
|ESC_TEST
);
2149 term
.esc
|= ESC_START
;
2151 case '\016': /* SO (LS1 -- Locking shift 1) */
2152 case '\017': /* SI (LS0 -- Locking shift 0) */
2153 term
.charset
= 1 - (ascii
- '\016');
2155 case '\032': /* SUB */
2156 tsetchar('?', &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2158 case '\030': /* CAN */
2161 case '\005': /* ENQ (IGNORED) */
2162 case '\000': /* NUL (IGNORED) */
2163 case '\021': /* XON (IGNORED) */
2164 case '\023': /* XOFF (IGNORED) */
2165 case 0177: /* DEL (IGNORED) */
2167 case 0x80: /* TODO: PAD */
2168 case 0x81: /* TODO: HOP */
2169 case 0x82: /* TODO: BPH */
2170 case 0x83: /* TODO: NBH */
2171 case 0x84: /* TODO: IND */
2173 case 0x85: /* NEL -- Next line */
2174 tnewline(1); /* always go to first col */
2176 case 0x86: /* TODO: SSA */
2177 case 0x87: /* TODO: ESA */
2179 case 0x88: /* HTS -- Horizontal tab stop */
2180 term
.tabs
[term
.c
.x
] = 1;
2182 case 0x89: /* TODO: HTJ */
2183 case 0x8a: /* TODO: VTS */
2184 case 0x8b: /* TODO: PLD */
2185 case 0x8c: /* TODO: PLU */
2186 case 0x8d: /* TODO: RI */
2187 case 0x8e: /* TODO: SS2 */
2188 case 0x8f: /* TODO: SS3 */
2189 case 0x91: /* TODO: PU1 */
2190 case 0x92: /* TODO: PU2 */
2191 case 0x93: /* TODO: STS */
2192 case 0x94: /* TODO: CCH */
2193 case 0x95: /* TODO: MW */
2194 case 0x96: /* TODO: SPA */
2195 case 0x97: /* TODO: EPA */
2196 case 0x98: /* TODO: SOS */
2197 case 0x99: /* TODO: SGCI */
2199 case 0x9a: /* DECID -- Identify Terminal */
2200 ttywrite(vtiden
, strlen(vtiden
), 0);
2202 case 0x9b: /* TODO: CSI */
2203 case 0x9c: /* TODO: ST */
2205 case 0x90: /* DCS -- Device Control String */
2206 case 0x9d: /* OSC -- Operating System Command */
2207 case 0x9e: /* PM -- Privacy Message */
2208 case 0x9f: /* APC -- Application Program Command */
2209 tstrsequence(ascii
);
2212 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2213 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
2217 * returns 1 when the sequence is finished and it hasn't to read
2218 * more characters for this sequence, otherwise 0
2221 eschandle(uchar ascii
)
2225 term
.esc
|= ESC_CSI
;
2228 term
.esc
|= ESC_TEST
;
2231 term
.esc
|= ESC_UTF8
;
2233 case 'P': /* DCS -- Device Control String */
2234 case '_': /* APC -- Application Program Command */
2235 case '^': /* PM -- Privacy Message */
2236 case ']': /* OSC -- Operating System Command */
2237 case 'k': /* old title set compatibility */
2238 tstrsequence(ascii
);
2240 case 'n': /* LS2 -- Locking shift 2 */
2241 case 'o': /* LS3 -- Locking shift 3 */
2242 term
.charset
= 2 + (ascii
- 'n');
2244 case '(': /* GZD4 -- set primary charset G0 */
2245 case ')': /* G1D4 -- set secondary charset G1 */
2246 case '*': /* G2D4 -- set tertiary charset G2 */
2247 case '+': /* G3D4 -- set quaternary charset G3 */
2248 term
.icharset
= ascii
- '(';
2249 term
.esc
|= ESC_ALTCHARSET
;
2251 case 'D': /* IND -- Linefeed */
2252 if (term
.c
.y
== term
.bot
) {
2253 tscrollup(term
.top
, 1);
2255 tmoveto(term
.c
.x
, term
.c
.y
+1);
2258 case 'E': /* NEL -- Next line */
2259 tnewline(1); /* always go to first col */
2261 case 'H': /* HTS -- Horizontal tab stop */
2262 term
.tabs
[term
.c
.x
] = 1;
2264 case 'M': /* RI -- Reverse index */
2265 if (term
.c
.y
== term
.top
) {
2266 tscrolldown(term
.top
, 1);
2268 tmoveto(term
.c
.x
, term
.c
.y
-1);
2271 case 'Z': /* DECID -- Identify Terminal */
2272 ttywrite(vtiden
, strlen(vtiden
), 0);
2274 case 'c': /* RIS -- Reset to initial state */
2279 case '=': /* DECPAM -- Application keypad */
2280 xsetmode(1, MODE_APPKEYPAD
);
2282 case '>': /* DECPNM -- Normal keypad */
2283 xsetmode(0, MODE_APPKEYPAD
);
2285 case '7': /* DECSC -- Save Cursor */
2286 tcursor(CURSOR_SAVE
);
2288 case '8': /* DECRC -- Restore Cursor */
2289 tcursor(CURSOR_LOAD
);
2291 case '\\': /* ST -- String Terminator */
2292 if (term
.esc
& ESC_STR_END
)
2296 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2297 (uchar
) ascii
, isprint(ascii
)? ascii
:'.');
2311 control
= ISCONTROL(u
);
2312 if (u
< 127 || !IS_SET(MODE_UTF8
)) {
2316 len
= utf8encode(u
, c
);
2317 if (!control
&& (width
= wcwidth(u
)) == -1)
2321 if (IS_SET(MODE_PRINT
))
2325 * STR sequence must be checked before anything else
2326 * because it uses all following characters until it
2327 * receives a ESC, a SUB, a ST or any other C1 control
2330 if (term
.esc
& ESC_STR
) {
2331 if (u
== '\a' || u
== 030 || u
== 032 || u
== 033 ||
2333 term
.esc
&= ~(ESC_START
|ESC_STR
);
2334 term
.esc
|= ESC_STR_END
;
2335 goto check_control_code
;
2338 if (strescseq
.len
+len
>= strescseq
.siz
) {
2340 * Here is a bug in terminals. If the user never sends
2341 * some code to stop the str or esc command, then st
2342 * will stop responding. But this is better than
2343 * silently failing with unknown characters. At least
2344 * then users will report back.
2346 * In the case users ever get fixed, here is the code:
2352 if (strescseq
.siz
> (SIZE_MAX
- UTF_SIZ
) / 2)
2355 strescseq
.buf
= xrealloc(strescseq
.buf
, strescseq
.siz
);
2358 memmove(&strescseq
.buf
[strescseq
.len
], c
, len
);
2359 strescseq
.len
+= len
;
2365 * Actions of control codes must be performed as soon they arrive
2366 * because they can be embedded inside a control sequence, and
2367 * they must not cause conflicts with sequences.
2372 * control codes are not shown ever
2377 } else if (term
.esc
& ESC_START
) {
2378 if (term
.esc
& ESC_CSI
) {
2379 csiescseq
.buf
[csiescseq
.len
++] = u
;
2380 if (BETWEEN(u
, 0x40, 0x7E)
2381 || csiescseq
.len
>= \
2382 sizeof(csiescseq
.buf
)-1) {
2388 } else if (term
.esc
& ESC_UTF8
) {
2390 } else if (term
.esc
& ESC_ALTCHARSET
) {
2392 } else if (term
.esc
& ESC_TEST
) {
2397 /* sequence already finished */
2401 * All characters which form part of a sequence are not
2406 if (selected(term
.c
.x
, term
.c
.y
))
2409 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2410 if (IS_SET(MODE_WRAP
) && (term
.c
.state
& CURSOR_WRAPNEXT
)) {
2411 gp
->mode
|= ATTR_WRAP
;
2413 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2416 if (IS_SET(MODE_INSERT
) && term
.c
.x
+width
< term
.col
)
2417 memmove(gp
+width
, gp
, (term
.col
- term
.c
.x
- width
) * sizeof(Glyph
));
2419 if (term
.c
.x
+width
> term
.col
) {
2421 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2424 tsetchar(u
, &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2428 gp
->mode
|= ATTR_WIDE
;
2429 if (term
.c
.x
+1 < term
.col
) {
2431 gp
[1].mode
= ATTR_WDUMMY
;
2434 if (term
.c
.x
+width
< term
.col
) {
2435 tmoveto(term
.c
.x
+width
, term
.c
.y
);
2437 term
.c
.state
|= CURSOR_WRAPNEXT
;
2442 twrite(const char *buf
, int buflen
, int show_ctrl
)
2448 for (n
= 0; n
< buflen
; n
+= charsize
) {
2449 if (IS_SET(MODE_UTF8
)) {
2450 /* process a complete utf8 char */
2451 charsize
= utf8decode(buf
+ n
, &u
, buflen
- n
);
2458 if (show_ctrl
&& ISCONTROL(u
)) {
2463 } else if (u
!= '\n' && u
!= '\r' && u
!= '\t') {
2474 tresize(int col
, int row
)
2477 int minrow
= MIN(row
, term
.row
);
2478 int mincol
= MIN(col
, term
.col
);
2482 if (col
< 1 || row
< 1) {
2484 "tresize: error resizing to %dx%d\n", col
, row
);
2489 * slide screen to keep cursor where we expect it -
2490 * tscrollup would work here, but we can optimize to
2491 * memmove because we're freeing the earlier lines
2493 for (i
= 0; i
<= term
.c
.y
- row
; i
++) {
2497 /* ensure that both src and dst are not NULL */
2499 memmove(term
.line
, term
.line
+ i
, row
* sizeof(Line
));
2500 memmove(term
.alt
, term
.alt
+ i
, row
* sizeof(Line
));
2502 for (i
+= row
; i
< term
.row
; i
++) {
2507 /* resize to new height */
2508 term
.line
= xrealloc(term
.line
, row
* sizeof(Line
));
2509 term
.alt
= xrealloc(term
.alt
, row
* sizeof(Line
));
2510 term
.dirty
= xrealloc(term
.dirty
, row
* sizeof(*term
.dirty
));
2511 term
.tabs
= xrealloc(term
.tabs
, col
* sizeof(*term
.tabs
));
2513 /* resize each row to new width, zero-pad if needed */
2514 for (i
= 0; i
< minrow
; i
++) {
2515 term
.line
[i
] = xrealloc(term
.line
[i
], col
* sizeof(Glyph
));
2516 term
.alt
[i
] = xrealloc(term
.alt
[i
], col
* sizeof(Glyph
));
2519 /* allocate any new rows */
2520 for (/* i = minrow */; i
< row
; i
++) {
2521 term
.line
[i
] = xmalloc(col
* sizeof(Glyph
));
2522 term
.alt
[i
] = xmalloc(col
* sizeof(Glyph
));
2524 if (col
> term
.col
) {
2525 bp
= term
.tabs
+ term
.col
;
2527 memset(bp
, 0, sizeof(*term
.tabs
) * (col
- term
.col
));
2528 while (--bp
> term
.tabs
&& !*bp
)
2530 for (bp
+= tabspaces
; bp
< term
.tabs
+ col
; bp
+= tabspaces
)
2533 /* update terminal size */
2536 /* reset scrolling region */
2537 tsetscroll(0, row
-1);
2538 /* make use of the LIMIT in tmoveto */
2539 tmoveto(term
.c
.x
, term
.c
.y
);
2540 /* Clearing both screens (it makes dirty all lines) */
2542 for (i
= 0; i
< 2; i
++) {
2543 if (mincol
< col
&& 0 < minrow
) {
2544 tclearregion(mincol
, 0, col
- 1, minrow
- 1);
2546 if (0 < col
&& minrow
< row
) {
2547 tclearregion(0, minrow
, col
- 1, row
- 1);
2550 tcursor(CURSOR_LOAD
);
2562 drawregion(int x1
, int y1
, int x2
, int y2
)
2566 for (y
= y1
; y
< y2
; y
++) {
2571 xdrawline(term
.line
[y
], x1
, y
, x2
);
2578 int cx
= term
.c
.x
, ocx
= term
.ocx
, ocy
= term
.ocy
;
2583 /* adjust cursor position */
2584 LIMIT(term
.ocx
, 0, term
.col
-1);
2585 LIMIT(term
.ocy
, 0, term
.row
-1);
2586 if (term
.line
[term
.ocy
][term
.ocx
].mode
& ATTR_WDUMMY
)
2588 if (term
.line
[term
.c
.y
][cx
].mode
& ATTR_WDUMMY
)
2591 drawregion(0, 0, term
.col
, term
.row
);
2592 xdrawcursor(cx
, term
.c
.y
, term
.line
[term
.c
.y
][cx
],
2593 term
.ocx
, term
.ocy
, term
.line
[term
.ocy
][term
.ocx
]);
2595 term
.ocy
= term
.c
.y
;
2597 if (ocx
!= term
.ocx
|| ocy
!= term
.ocy
)
2598 xximspot(term
.ocx
, term
.ocy
);