Xinqi Bao's Git
2ecf8f3b93135169172ee76141514de61a303f1e
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) == '\177')
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,
57 enum cursor_movement
{
81 ESC_STR
= 4, /* OSC, PM, APC */
83 ESC_STR_END
= 16, /* a final string was encountered */
84 ESC_TEST
= 32, /* Enter in test mode */
90 Glyph attr
; /* current char attributes */
101 * Selection variables:
102 * nb – normalized coordinates of the beginning of the selection
103 * ne – normalized coordinates of the end of the selection
104 * ob – original coordinates of the beginning of the selection
105 * oe – original coordinates of the end of the selection
114 /* Internal representation of the screen */
116 int row
; /* nb row */
117 int col
; /* nb col */
118 Line
*line
; /* screen */
119 Line
*alt
; /* alternate screen */
120 int *dirty
; /* dirtyness of lines */
121 TCursor c
; /* cursor */
122 int ocx
; /* old cursor col */
123 int ocy
; /* old cursor row */
124 int top
; /* top scroll limit */
125 int bot
; /* bottom scroll limit */
126 int mode
; /* terminal mode flags */
127 int esc
; /* escape state flags */
128 char trantbl
[4]; /* charset table translation */
129 int charset
; /* current charset */
130 int icharset
; /* selected charset for sequence */
134 /* CSI Escape sequence structs */
135 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
137 char buf
[ESC_BUF_SIZ
]; /* raw string */
138 size_t len
; /* raw string length */
140 int arg
[ESC_ARG_SIZ
];
141 int narg
; /* nb of args */
145 /* STR Escape sequence structs */
146 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
148 char type
; /* ESC type ... */
149 char *buf
; /* allocated raw string */
150 size_t siz
; /* allocation size */
151 size_t len
; /* raw string length */
152 char *args
[STR_ARG_SIZ
];
153 int narg
; /* nb of args */
156 static void execsh(char *, char **);
157 static void stty(char **);
158 static void sigchld(int);
159 static void ttywriteraw(const char *, size_t);
161 static void csidump(void);
162 static void csihandle(void);
163 static void csiparse(void);
164 static void csireset(void);
165 static int eschandle(uchar
);
166 static void strdump(void);
167 static void strhandle(void);
168 static void strparse(void);
169 static void strreset(void);
171 static void tprinter(char *, size_t);
172 static void tdumpsel(void);
173 static void tdumpline(int);
174 static void tdump(void);
175 static void tclearregion(int, int, int, int);
176 static void tcursor(int);
177 static void tdeletechar(int);
178 static void tdeleteline(int);
179 static void tinsertblank(int);
180 static void tinsertblankline(int);
181 static int tlinelen(int);
182 static void tmoveto(int, int);
183 static void tmoveato(int, int);
184 static void tnewline(int);
185 static void tputtab(int);
186 static void tputc(Rune
);
187 static void treset(void);
188 static void tscrollup(int, int);
189 static void tscrolldown(int, int);
190 static void tsetattr(int *, int);
191 static void tsetchar(Rune
, Glyph
*, int, int);
192 static void tsetdirt(int, int);
193 static void tsetscroll(int, int);
194 static void tswapscreen(void);
195 static void tsetmode(int, int, int *, int);
196 static int twrite(const char *, int, int);
197 static void tfulldirt(void);
198 static void tcontrolcode(uchar
);
199 static void tdectest(char );
200 static void tdefutf8(char);
201 static int32_t tdefcolor(int *, int *, int);
202 static void tdeftran(char);
203 static void tstrsequence(uchar
);
205 static void drawregion(int, int, int, int);
207 static void selnormalize(void);
208 static void selscroll(int, int);
209 static void selsnap(int *, int *, int);
211 static size_t utf8decode(const char *, Rune
*, size_t);
212 static Rune
utf8decodebyte(char, size_t *);
213 static char utf8encodebyte(Rune
, size_t);
214 static size_t utf8validate(Rune
*, size_t);
216 static char *base64dec(const char *);
217 static char base64dec_getc(const char **);
219 static ssize_t
xwrite(int, const char *, size_t);
223 static Selection sel
;
224 static CSIEscape csiescseq
;
225 static STREscape strescseq
;
230 static uchar utfbyte
[UTF_SIZ
+ 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
231 static uchar utfmask
[UTF_SIZ
+ 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
232 static Rune utfmin
[UTF_SIZ
+ 1] = { 0, 0, 0x80, 0x800, 0x10000};
233 static Rune utfmax
[UTF_SIZ
+ 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
236 xwrite(int fd
, const char *s
, size_t len
)
242 r
= write(fd
, s
, len
);
257 if (!(p
= malloc(len
)))
258 die("malloc: %s\n", strerror(errno
));
264 xrealloc(void *p
, size_t len
)
266 if ((p
= realloc(p
, len
)) == NULL
)
267 die("realloc: %s\n", strerror(errno
));
275 if ((s
= strdup(s
)) == NULL
)
276 die("strdup: %s\n", strerror(errno
));
282 utf8decode(const char *c
, Rune
*u
, size_t clen
)
284 size_t i
, j
, len
, type
;
290 udecoded
= utf8decodebyte(c
[0], &len
);
291 if (!BETWEEN(len
, 1, UTF_SIZ
))
293 for (i
= 1, j
= 1; i
< clen
&& j
< len
; ++i
, ++j
) {
294 udecoded
= (udecoded
<< 6) | utf8decodebyte(c
[i
], &type
);
301 utf8validate(u
, len
);
307 utf8decodebyte(char c
, size_t *i
)
309 for (*i
= 0; *i
< LEN(utfmask
); ++(*i
))
310 if (((uchar
)c
& utfmask
[*i
]) == utfbyte
[*i
])
311 return (uchar
)c
& ~utfmask
[*i
];
317 utf8encode(Rune u
, char *c
)
321 len
= utf8validate(&u
, 0);
325 for (i
= len
- 1; i
!= 0; --i
) {
326 c
[i
] = utf8encodebyte(u
, 0);
329 c
[0] = utf8encodebyte(u
, len
);
335 utf8encodebyte(Rune u
, size_t i
)
337 return utfbyte
[i
] | (u
& ~utfmask
[i
]);
341 utf8validate(Rune
*u
, size_t i
)
343 if (!BETWEEN(*u
, utfmin
[i
], utfmax
[i
]) || BETWEEN(*u
, 0xD800, 0xDFFF))
345 for (i
= 1; *u
> utfmax
[i
]; ++i
)
351 static const char base64_digits
[] = {
352 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
353 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0,
354 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
355 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
356 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
357 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 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, 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
367 base64dec_getc(const char **src
)
369 while (**src
&& !isprint(**src
))
371 return **src
? *((*src
)++) : '='; /* emulate padding if string ends */
375 base64dec(const char *src
)
377 size_t in_len
= strlen(src
);
381 in_len
+= 4 - (in_len
% 4);
382 result
= dst
= xmalloc(in_len
/ 4 * 3 + 1);
384 int a
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
385 int b
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
386 int c
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
387 int d
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
389 /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */
390 if (a
== -1 || b
== -1)
393 *dst
++ = (a
<< 2) | ((b
& 0x30) >> 4);
396 *dst
++ = ((b
& 0x0f) << 4) | ((c
& 0x3c) >> 2);
399 *dst
++ = ((c
& 0x03) << 6) | d
;
418 if (term
.line
[y
][i
- 1].mode
& ATTR_WRAP
)
421 while (i
> 0 && term
.line
[y
][i
- 1].u
== ' ')
428 selstart(int col
, int row
, int snap
)
431 sel
.mode
= SEL_EMPTY
;
432 sel
.type
= SEL_REGULAR
;
433 sel
.alt
= IS_SET(MODE_ALTSCREEN
);
435 sel
.oe
.x
= sel
.ob
.x
= col
;
436 sel
.oe
.y
= sel
.ob
.y
= row
;
440 sel
.mode
= SEL_READY
;
441 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
445 selextend(int col
, int row
, int type
, int done
)
447 int oldey
, oldex
, oldsby
, oldsey
, oldtype
;
449 if (sel
.mode
== SEL_IDLE
)
451 if (done
&& sel
.mode
== SEL_EMPTY
) {
467 if (oldey
!= sel
.oe
.y
|| oldex
!= sel
.oe
.x
|| oldtype
!= sel
.type
|| sel
.mode
== SEL_EMPTY
)
468 tsetdirt(MIN(sel
.nb
.y
, oldsby
), MAX(sel
.ne
.y
, oldsey
));
470 sel
.mode
= done
? SEL_IDLE
: SEL_READY
;
478 if (sel
.type
== SEL_REGULAR
&& sel
.ob
.y
!= sel
.oe
.y
) {
479 sel
.nb
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.ob
.x
: sel
.oe
.x
;
480 sel
.ne
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.oe
.x
: sel
.ob
.x
;
482 sel
.nb
.x
= MIN(sel
.ob
.x
, sel
.oe
.x
);
483 sel
.ne
.x
= MAX(sel
.ob
.x
, sel
.oe
.x
);
485 sel
.nb
.y
= MIN(sel
.ob
.y
, sel
.oe
.y
);
486 sel
.ne
.y
= MAX(sel
.ob
.y
, sel
.oe
.y
);
488 selsnap(&sel
.nb
.x
, &sel
.nb
.y
, -1);
489 selsnap(&sel
.ne
.x
, &sel
.ne
.y
, +1);
491 /* expand selection over line breaks */
492 if (sel
.type
== SEL_RECTANGULAR
)
494 i
= tlinelen(sel
.nb
.y
);
497 if (tlinelen(sel
.ne
.y
) <= sel
.ne
.x
)
498 sel
.ne
.x
= term
.col
- 1;
502 selected(int x
, int y
)
504 if (sel
.mode
== SEL_EMPTY
|| sel
.ob
.x
== -1 ||
505 sel
.alt
!= IS_SET(MODE_ALTSCREEN
))
508 if (sel
.type
== SEL_RECTANGULAR
)
509 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
510 && BETWEEN(x
, sel
.nb
.x
, sel
.ne
.x
);
512 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
513 && (y
!= sel
.nb
.y
|| x
>= sel
.nb
.x
)
514 && (y
!= sel
.ne
.y
|| x
<= sel
.ne
.x
);
518 selsnap(int *x
, int *y
, int direction
)
520 int newx
, newy
, xt
, yt
;
521 int delim
, prevdelim
;
527 * Snap around if the word wraps around at the end or
528 * beginning of a line.
530 prevgp
= &term
.line
[*y
][*x
];
531 prevdelim
= ISDELIM(prevgp
->u
);
533 newx
= *x
+ direction
;
535 if (!BETWEEN(newx
, 0, term
.col
- 1)) {
537 newx
= (newx
+ term
.col
) % term
.col
;
538 if (!BETWEEN(newy
, 0, term
.row
- 1))
544 yt
= newy
, xt
= newx
;
545 if (!(term
.line
[yt
][xt
].mode
& ATTR_WRAP
))
549 if (newx
>= tlinelen(newy
))
552 gp
= &term
.line
[newy
][newx
];
553 delim
= ISDELIM(gp
->u
);
554 if (!(gp
->mode
& ATTR_WDUMMY
) && (delim
!= prevdelim
555 || (delim
&& gp
->u
!= prevgp
->u
)))
566 * Snap around if the the previous line or the current one
567 * has set ATTR_WRAP at its end. Then the whole next or
568 * previous line will be selected.
570 *x
= (direction
< 0) ? 0 : term
.col
- 1;
572 for (; *y
> 0; *y
+= direction
) {
573 if (!(term
.line
[*y
-1][term
.col
-1].mode
578 } else if (direction
> 0) {
579 for (; *y
< term
.row
-1; *y
+= direction
) {
580 if (!(term
.line
[*y
][term
.col
-1].mode
594 int y
, bufsize
, lastx
, linelen
;
600 bufsize
= (term
.col
+1) * (sel
.ne
.y
-sel
.nb
.y
+1) * UTF_SIZ
;
601 ptr
= str
= xmalloc(bufsize
);
603 /* append every set & selected glyph to the selection */
604 for (y
= sel
.nb
.y
; y
<= sel
.ne
.y
; y
++) {
605 if ((linelen
= tlinelen(y
)) == 0) {
610 if (sel
.type
== SEL_RECTANGULAR
) {
611 gp
= &term
.line
[y
][sel
.nb
.x
];
614 gp
= &term
.line
[y
][sel
.nb
.y
== y
? sel
.nb
.x
: 0];
615 lastx
= (sel
.ne
.y
== y
) ? sel
.ne
.x
: term
.col
-1;
617 last
= &term
.line
[y
][MIN(lastx
, linelen
-1)];
618 while (last
>= gp
&& last
->u
== ' ')
621 for ( ; gp
<= last
; ++gp
) {
622 if (gp
->mode
& ATTR_WDUMMY
)
625 ptr
+= utf8encode(gp
->u
, ptr
);
629 * Copy and pasting of line endings is inconsistent
630 * in the inconsistent terminal and GUI world.
631 * The best solution seems like to produce '\n' when
632 * something is copied from st and convert '\n' to
633 * '\r', when something to be pasted is received by
635 * FIXME: Fix the computer world.
637 if ((y
< sel
.ne
.y
|| lastx
>= linelen
) && !(last
->mode
& ATTR_WRAP
))
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
;
685 } else if (scroll
|| utmp
) {
686 prog
= scroll
? scroll
: utmp
;
687 arg
= scroll
? utmp
: NULL
;
692 DEFAULT(args
, ((char *[]) {prog
, arg
, NULL
}));
697 setenv("LOGNAME", pw
->pw_name
, 1);
698 setenv("USER", pw
->pw_name
, 1);
699 setenv("SHELL", sh
, 1);
700 setenv("HOME", pw
->pw_dir
, 1);
701 setenv("TERM", termname
, 1);
703 signal(SIGCHLD
, SIG_DFL
);
704 signal(SIGHUP
, SIG_DFL
);
705 signal(SIGINT
, SIG_DFL
);
706 signal(SIGQUIT
, SIG_DFL
);
707 signal(SIGTERM
, SIG_DFL
);
708 signal(SIGALRM
, SIG_DFL
);
720 if ((p
= waitpid(pid
, &stat
, WNOHANG
)) < 0)
721 die("waiting for pid %hd failed: %s\n", pid
, strerror(errno
));
726 if (WIFEXITED(stat
) && WEXITSTATUS(stat
))
727 die("child exited with status %d\n", WEXITSTATUS(stat
));
728 else if (WIFSIGNALED(stat
))
729 die("child terminated due to signal %d\n", WTERMSIG(stat
));
736 char cmd
[_POSIX_ARG_MAX
], **p
, *q
, *s
;
739 if ((n
= strlen(stty_args
)) > sizeof(cmd
)-1)
740 die("incorrect stty parameters\n");
741 memcpy(cmd
, stty_args
, n
);
743 siz
= sizeof(cmd
) - n
;
744 for (p
= args
; p
&& (s
= *p
); ++p
) {
745 if ((n
= strlen(s
)) > siz
-1)
746 die("stty parameter length too long\n");
753 if (system(cmd
) != 0)
754 perror("Couldn't call stty");
758 ttynew(char *line
, char *cmd
, char *out
, char **args
)
763 term
.mode
|= MODE_PRINT
;
764 iofd
= (!strcmp(out
, "-")) ?
765 1 : open(out
, O_WRONLY
| O_CREAT
, 0666);
767 fprintf(stderr
, "Error opening %s:%s\n",
768 out
, strerror(errno
));
773 if ((cmdfd
= open(line
, O_RDWR
)) < 0)
774 die("open line '%s' failed: %s\n",
775 line
, strerror(errno
));
781 /* seems to work fine on linux, openbsd and freebsd */
782 if (openpty(&m
, &s
, NULL
, NULL
, NULL
) < 0)
783 die("openpty failed: %s\n", strerror(errno
));
785 switch (pid
= fork()) {
787 die("fork failed: %s\n", strerror(errno
));
791 setsid(); /* create a new process group */
795 if (ioctl(s
, TIOCSCTTY
, NULL
) < 0)
796 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno
));
800 if (pledge("stdio getpw proc exec", NULL
) == -1)
807 if (pledge("stdio rpath tty proc", NULL
) == -1)
812 signal(SIGCHLD
, sigchld
);
821 static char buf
[BUFSIZ
];
822 static int buflen
= 0;
826 /* append read bytes to unprocessed bytes */
827 ret
= read(cmdfd
, buf
+buflen
, LEN(buf
)-buflen
);
831 fputs("Found EOF in input\n", stderr
);
834 die("couldn't read from shell: %s\n", strerror(errno
));
837 written
= twrite(buf
, buflen
, 0);
839 /* keep any uncomplete utf8 char for the next call */
841 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
.ob
.y
, orig
, term
.bot
) || BETWEEN(sel
.oe
.y
, orig
, term
.bot
)) {
1108 if ((sel
.ob
.y
+= n
) > term
.bot
|| (sel
.oe
.y
+= n
) < term
.top
) {
1112 if (sel
.type
== SEL_RECTANGULAR
) {
1113 if (sel
.ob
.y
< term
.top
)
1114 sel
.ob
.y
= term
.top
;
1115 if (sel
.oe
.y
> term
.bot
)
1116 sel
.oe
.y
= term
.bot
;
1118 if (sel
.ob
.y
< term
.top
) {
1119 sel
.ob
.y
= term
.top
;
1122 if (sel
.oe
.y
> term
.bot
) {
1123 sel
.oe
.y
= term
.bot
;
1124 sel
.oe
.x
= term
.col
;
1132 tnewline(int first_col
)
1136 if (y
== term
.bot
) {
1137 tscrollup(term
.top
, 1);
1141 tmoveto(first_col
? 0 : term
.c
.x
, y
);
1147 char *p
= csiescseq
.buf
, *np
;
1156 csiescseq
.buf
[csiescseq
.len
] = '\0';
1157 while (p
< csiescseq
.buf
+csiescseq
.len
) {
1159 v
= strtol(p
, &np
, 10);
1162 if (v
== LONG_MAX
|| v
== LONG_MIN
)
1164 csiescseq
.arg
[csiescseq
.narg
++] = v
;
1166 if (*p
!= ';' || csiescseq
.narg
== ESC_ARG_SIZ
)
1170 csiescseq
.mode
[0] = *p
++;
1171 csiescseq
.mode
[1] = (p
< csiescseq
.buf
+csiescseq
.len
) ? *p
: '\0';
1174 /* for absolute user moves, when decom is set */
1176 tmoveato(int x
, int y
)
1178 tmoveto(x
, y
+ ((term
.c
.state
& CURSOR_ORIGIN
) ? term
.top
: 0));
1182 tmoveto(int x
, int y
)
1186 if (term
.c
.state
& CURSOR_ORIGIN
) {
1191 maxy
= term
.row
- 1;
1193 term
.c
.state
&= ~CURSOR_WRAPNEXT
;
1194 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1195 term
.c
.y
= LIMIT(y
, miny
, maxy
);
1199 tsetchar(Rune u
, Glyph
*attr
, int x
, int y
)
1201 static char *vt100_0
[62] = { /* 0x41 - 0x7e */
1202 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1203 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1204 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1205 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1206 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1207 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1208 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1209 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1213 * The table is proudly stolen from rxvt.
1215 if (term
.trantbl
[term
.charset
] == CS_GRAPHIC0
&&
1216 BETWEEN(u
, 0x41, 0x7e) && vt100_0
[u
- 0x41])
1217 utf8decode(vt100_0
[u
- 0x41], &u
, UTF_SIZ
);
1219 if (term
.line
[y
][x
].mode
& ATTR_WIDE
) {
1220 if (x
+1 < term
.col
) {
1221 term
.line
[y
][x
+1].u
= ' ';
1222 term
.line
[y
][x
+1].mode
&= ~ATTR_WDUMMY
;
1224 } else if (term
.line
[y
][x
].mode
& ATTR_WDUMMY
) {
1225 term
.line
[y
][x
-1].u
= ' ';
1226 term
.line
[y
][x
-1].mode
&= ~ATTR_WIDE
;
1230 term
.line
[y
][x
] = *attr
;
1231 term
.line
[y
][x
].u
= u
;
1235 tclearregion(int x1
, int y1
, int x2
, int y2
)
1241 temp
= x1
, x1
= x2
, x2
= temp
;
1243 temp
= y1
, y1
= y2
, y2
= temp
;
1245 LIMIT(x1
, 0, term
.col
-1);
1246 LIMIT(x2
, 0, term
.col
-1);
1247 LIMIT(y1
, 0, term
.row
-1);
1248 LIMIT(y2
, 0, term
.row
-1);
1250 for (y
= y1
; y
<= y2
; y
++) {
1252 for (x
= x1
; x
<= x2
; x
++) {
1253 gp
= &term
.line
[y
][x
];
1256 gp
->fg
= term
.c
.attr
.fg
;
1257 gp
->bg
= term
.c
.attr
.bg
;
1270 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1274 size
= term
.col
- src
;
1275 line
= term
.line
[term
.c
.y
];
1277 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1278 tclearregion(term
.col
-n
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1287 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1291 size
= term
.col
- dst
;
1292 line
= term
.line
[term
.c
.y
];
1294 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1295 tclearregion(src
, term
.c
.y
, dst
- 1, term
.c
.y
);
1299 tinsertblankline(int n
)
1301 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1302 tscrolldown(term
.c
.y
, n
);
1308 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1309 tscrollup(term
.c
.y
, n
);
1313 tdefcolor(int *attr
, int *npar
, int l
)
1318 switch (attr
[*npar
+ 1]) {
1319 case 2: /* direct color in RGB space */
1320 if (*npar
+ 4 >= l
) {
1322 "erresc(38): Incorrect number of parameters (%d)\n",
1326 r
= attr
[*npar
+ 2];
1327 g
= attr
[*npar
+ 3];
1328 b
= attr
[*npar
+ 4];
1330 if (!BETWEEN(r
, 0, 255) || !BETWEEN(g
, 0, 255) || !BETWEEN(b
, 0, 255))
1331 fprintf(stderr
, "erresc: bad rgb color (%u,%u,%u)\n",
1334 idx
= TRUECOLOR(r
, g
, b
);
1336 case 5: /* indexed color */
1337 if (*npar
+ 2 >= l
) {
1339 "erresc(38): Incorrect number of parameters (%d)\n",
1344 if (!BETWEEN(attr
[*npar
], 0, 255))
1345 fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[*npar
]);
1349 case 0: /* implemented defined (only foreground) */
1350 case 1: /* transparent */
1351 case 3: /* direct color in CMY space */
1352 case 4: /* direct color in CMYK space */
1355 "erresc(38): gfx attr %d unknown\n", attr
[*npar
]);
1363 tsetattr(int *attr
, int l
)
1368 for (i
= 0; i
< l
; i
++) {
1371 term
.c
.attr
.mode
&= ~(
1380 term
.c
.attr
.fg
= defaultfg
;
1381 term
.c
.attr
.bg
= defaultbg
;
1384 term
.c
.attr
.mode
|= ATTR_BOLD
;
1387 term
.c
.attr
.mode
|= ATTR_FAINT
;
1390 term
.c
.attr
.mode
|= ATTR_ITALIC
;
1393 term
.c
.attr
.mode
|= ATTR_UNDERLINE
;
1395 case 5: /* slow blink */
1397 case 6: /* rapid blink */
1398 term
.c
.attr
.mode
|= ATTR_BLINK
;
1401 term
.c
.attr
.mode
|= ATTR_REVERSE
;
1404 term
.c
.attr
.mode
|= ATTR_INVISIBLE
;
1407 term
.c
.attr
.mode
|= ATTR_STRUCK
;
1410 term
.c
.attr
.mode
&= ~(ATTR_BOLD
| ATTR_FAINT
);
1413 term
.c
.attr
.mode
&= ~ATTR_ITALIC
;
1416 term
.c
.attr
.mode
&= ~ATTR_UNDERLINE
;
1419 term
.c
.attr
.mode
&= ~ATTR_BLINK
;
1422 term
.c
.attr
.mode
&= ~ATTR_REVERSE
;
1425 term
.c
.attr
.mode
&= ~ATTR_INVISIBLE
;
1428 term
.c
.attr
.mode
&= ~ATTR_STRUCK
;
1431 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1432 term
.c
.attr
.fg
= idx
;
1435 term
.c
.attr
.fg
= defaultfg
;
1438 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1439 term
.c
.attr
.bg
= idx
;
1442 term
.c
.attr
.bg
= defaultbg
;
1445 if (BETWEEN(attr
[i
], 30, 37)) {
1446 term
.c
.attr
.fg
= attr
[i
] - 30;
1447 } else if (BETWEEN(attr
[i
], 40, 47)) {
1448 term
.c
.attr
.bg
= attr
[i
] - 40;
1449 } else if (BETWEEN(attr
[i
], 90, 97)) {
1450 term
.c
.attr
.fg
= attr
[i
] - 90 + 8;
1451 } else if (BETWEEN(attr
[i
], 100, 107)) {
1452 term
.c
.attr
.bg
= attr
[i
] - 100 + 8;
1455 "erresc(default): gfx attr %d unknown\n",
1465 tsetscroll(int t
, int b
)
1469 LIMIT(t
, 0, term
.row
-1);
1470 LIMIT(b
, 0, term
.row
-1);
1481 tsetmode(int priv
, int set
, int *args
, int narg
)
1485 for (lim
= args
+ narg
; args
< lim
; ++args
) {
1488 case 1: /* DECCKM -- Cursor key */
1489 xsetmode(set
, MODE_APPCURSOR
);
1491 case 5: /* DECSCNM -- Reverse video */
1492 xsetmode(set
, MODE_REVERSE
);
1494 case 6: /* DECOM -- Origin */
1495 MODBIT(term
.c
.state
, set
, CURSOR_ORIGIN
);
1498 case 7: /* DECAWM -- Auto wrap */
1499 MODBIT(term
.mode
, set
, MODE_WRAP
);
1501 case 0: /* Error (IGNORED) */
1502 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1503 case 3: /* DECCOLM -- Column (IGNORED) */
1504 case 4: /* DECSCLM -- Scroll (IGNORED) */
1505 case 8: /* DECARM -- Auto repeat (IGNORED) */
1506 case 18: /* DECPFF -- Printer feed (IGNORED) */
1507 case 19: /* DECPEX -- Printer extent (IGNORED) */
1508 case 42: /* DECNRCM -- National characters (IGNORED) */
1509 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1511 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1512 xsetmode(!set
, MODE_HIDE
);
1514 case 9: /* X10 mouse compatibility mode */
1515 xsetpointermotion(0);
1516 xsetmode(0, MODE_MOUSE
);
1517 xsetmode(set
, MODE_MOUSEX10
);
1519 case 1000: /* 1000: report button press */
1520 xsetpointermotion(0);
1521 xsetmode(0, MODE_MOUSE
);
1522 xsetmode(set
, MODE_MOUSEBTN
);
1524 case 1002: /* 1002: report motion on button press */
1525 xsetpointermotion(0);
1526 xsetmode(0, MODE_MOUSE
);
1527 xsetmode(set
, MODE_MOUSEMOTION
);
1529 case 1003: /* 1003: enable all mouse motions */
1530 xsetpointermotion(set
);
1531 xsetmode(0, MODE_MOUSE
);
1532 xsetmode(set
, MODE_MOUSEMANY
);
1534 case 1004: /* 1004: send focus events to tty */
1535 xsetmode(set
, MODE_FOCUS
);
1537 case 1006: /* 1006: extended reporting mode */
1538 xsetmode(set
, MODE_MOUSESGR
);
1541 xsetmode(set
, MODE_8BIT
);
1543 case 1049: /* swap screen & set/restore cursor as xterm */
1544 if (!allowaltscreen
)
1546 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1548 case 47: /* swap screen */
1550 if (!allowaltscreen
)
1552 alt
= IS_SET(MODE_ALTSCREEN
);
1554 tclearregion(0, 0, term
.col
-1,
1557 if (set
^ alt
) /* set is always 1 or 0 */
1563 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1565 case 2004: /* 2004: bracketed paste mode */
1566 xsetmode(set
, MODE_BRCKTPASTE
);
1568 /* Not implemented mouse modes. See comments there. */
1569 case 1001: /* mouse highlight mode; can hang the
1570 terminal by design when implemented. */
1571 case 1005: /* UTF-8 mouse mode; will confuse
1572 applications not supporting UTF-8
1574 case 1015: /* urxvt mangled mouse mode; incompatible
1575 and can be mistaken for other control
1580 "erresc: unknown private set/reset mode %d\n",
1586 case 0: /* Error (IGNORED) */
1589 xsetmode(set
, MODE_KBDLOCK
);
1591 case 4: /* IRM -- Insertion-replacement */
1592 MODBIT(term
.mode
, set
, MODE_INSERT
);
1594 case 12: /* SRM -- Send/Receive */
1595 MODBIT(term
.mode
, !set
, MODE_ECHO
);
1597 case 20: /* LNM -- Linefeed/new line */
1598 MODBIT(term
.mode
, set
, MODE_CRLF
);
1602 "erresc: unknown set/reset mode %d\n",
1616 switch (csiescseq
.mode
[0]) {
1619 fprintf(stderr
, "erresc: unknown csi ");
1623 case '@': /* ICH -- Insert <n> blank char */
1624 DEFAULT(csiescseq
.arg
[0], 1);
1625 tinsertblank(csiescseq
.arg
[0]);
1627 case 'A': /* CUU -- Cursor <n> Up */
1628 DEFAULT(csiescseq
.arg
[0], 1);
1629 tmoveto(term
.c
.x
, term
.c
.y
-csiescseq
.arg
[0]);
1631 case 'B': /* CUD -- Cursor <n> Down */
1632 case 'e': /* VPR --Cursor <n> Down */
1633 DEFAULT(csiescseq
.arg
[0], 1);
1634 tmoveto(term
.c
.x
, term
.c
.y
+csiescseq
.arg
[0]);
1636 case 'i': /* MC -- Media Copy */
1637 switch (csiescseq
.arg
[0]) {
1642 tdumpline(term
.c
.y
);
1648 term
.mode
&= ~MODE_PRINT
;
1651 term
.mode
|= MODE_PRINT
;
1655 case 'c': /* DA -- Device Attributes */
1656 if (csiescseq
.arg
[0] == 0)
1657 ttywrite(vtiden
, strlen(vtiden
), 0);
1659 case 'C': /* CUF -- Cursor <n> Forward */
1660 case 'a': /* HPR -- Cursor <n> Forward */
1661 DEFAULT(csiescseq
.arg
[0], 1);
1662 tmoveto(term
.c
.x
+csiescseq
.arg
[0], term
.c
.y
);
1664 case 'D': /* CUB -- Cursor <n> Backward */
1665 DEFAULT(csiescseq
.arg
[0], 1);
1666 tmoveto(term
.c
.x
-csiescseq
.arg
[0], term
.c
.y
);
1668 case 'E': /* CNL -- Cursor <n> Down and first col */
1669 DEFAULT(csiescseq
.arg
[0], 1);
1670 tmoveto(0, term
.c
.y
+csiescseq
.arg
[0]);
1672 case 'F': /* CPL -- Cursor <n> Up and first col */
1673 DEFAULT(csiescseq
.arg
[0], 1);
1674 tmoveto(0, term
.c
.y
-csiescseq
.arg
[0]);
1676 case 'g': /* TBC -- Tabulation clear */
1677 switch (csiescseq
.arg
[0]) {
1678 case 0: /* clear current tab stop */
1679 term
.tabs
[term
.c
.x
] = 0;
1681 case 3: /* clear all the tabs */
1682 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1688 case 'G': /* CHA -- Move to <col> */
1690 DEFAULT(csiescseq
.arg
[0], 1);
1691 tmoveto(csiescseq
.arg
[0]-1, term
.c
.y
);
1693 case 'H': /* CUP -- Move to <row> <col> */
1695 DEFAULT(csiescseq
.arg
[0], 1);
1696 DEFAULT(csiescseq
.arg
[1], 1);
1697 tmoveato(csiescseq
.arg
[1]-1, csiescseq
.arg
[0]-1);
1699 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1700 DEFAULT(csiescseq
.arg
[0], 1);
1701 tputtab(csiescseq
.arg
[0]);
1703 case 'J': /* ED -- Clear screen */
1704 switch (csiescseq
.arg
[0]) {
1706 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1707 if (term
.c
.y
< term
.row
-1) {
1708 tclearregion(0, term
.c
.y
+1, term
.col
-1,
1714 tclearregion(0, 0, term
.col
-1, term
.c
.y
-1);
1715 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1718 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1724 case 'K': /* EL -- Clear line */
1725 switch (csiescseq
.arg
[0]) {
1727 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1,
1731 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1734 tclearregion(0, term
.c
.y
, term
.col
-1, term
.c
.y
);
1738 case 'S': /* SU -- Scroll <n> line up */
1739 DEFAULT(csiescseq
.arg
[0], 1);
1740 tscrollup(term
.top
, csiescseq
.arg
[0]);
1742 case 'T': /* SD -- Scroll <n> line down */
1743 DEFAULT(csiescseq
.arg
[0], 1);
1744 tscrolldown(term
.top
, csiescseq
.arg
[0]);
1746 case 'L': /* IL -- Insert <n> blank lines */
1747 DEFAULT(csiescseq
.arg
[0], 1);
1748 tinsertblankline(csiescseq
.arg
[0]);
1750 case 'l': /* RM -- Reset Mode */
1751 tsetmode(csiescseq
.priv
, 0, csiescseq
.arg
, csiescseq
.narg
);
1753 case 'M': /* DL -- Delete <n> lines */
1754 DEFAULT(csiescseq
.arg
[0], 1);
1755 tdeleteline(csiescseq
.arg
[0]);
1757 case 'X': /* ECH -- Erase <n> char */
1758 DEFAULT(csiescseq
.arg
[0], 1);
1759 tclearregion(term
.c
.x
, term
.c
.y
,
1760 term
.c
.x
+ csiescseq
.arg
[0] - 1, term
.c
.y
);
1762 case 'P': /* DCH -- Delete <n> char */
1763 DEFAULT(csiescseq
.arg
[0], 1);
1764 tdeletechar(csiescseq
.arg
[0]);
1766 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1767 DEFAULT(csiescseq
.arg
[0], 1);
1768 tputtab(-csiescseq
.arg
[0]);
1770 case 'd': /* VPA -- Move to <row> */
1771 DEFAULT(csiescseq
.arg
[0], 1);
1772 tmoveato(term
.c
.x
, csiescseq
.arg
[0]-1);
1774 case 'h': /* SM -- Set terminal mode */
1775 tsetmode(csiescseq
.priv
, 1, csiescseq
.arg
, csiescseq
.narg
);
1777 case 'm': /* SGR -- Terminal attribute (color) */
1778 tsetattr(csiescseq
.arg
, csiescseq
.narg
);
1780 case 'n': /* DSR – Device Status Report (cursor position) */
1781 if (csiescseq
.arg
[0] == 6) {
1782 len
= snprintf(buf
, sizeof(buf
),"\033[%i;%iR",
1783 term
.c
.y
+1, term
.c
.x
+1);
1784 ttywrite(buf
, len
, 0);
1787 case 'r': /* DECSTBM -- Set Scrolling Region */
1788 if (csiescseq
.priv
) {
1791 DEFAULT(csiescseq
.arg
[0], 1);
1792 DEFAULT(csiescseq
.arg
[1], term
.row
);
1793 tsetscroll(csiescseq
.arg
[0]-1, csiescseq
.arg
[1]-1);
1797 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1798 tcursor(CURSOR_SAVE
);
1800 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1801 tcursor(CURSOR_LOAD
);
1804 switch (csiescseq
.mode
[1]) {
1805 case 'q': /* DECSCUSR -- Set Cursor Style */
1806 if (xsetcursor(csiescseq
.arg
[0]))
1822 fprintf(stderr
, "ESC[");
1823 for (i
= 0; i
< csiescseq
.len
; i
++) {
1824 c
= csiescseq
.buf
[i
] & 0xff;
1827 } else if (c
== '\n') {
1828 fprintf(stderr
, "(\\n)");
1829 } else if (c
== '\r') {
1830 fprintf(stderr
, "(\\r)");
1831 } else if (c
== 0x1b) {
1832 fprintf(stderr
, "(\\e)");
1834 fprintf(stderr
, "(%02x)", c
);
1843 memset(&csiescseq
, 0, sizeof(csiescseq
));
1849 char *p
= NULL
, *dec
;
1852 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
1854 par
= (narg
= strescseq
.narg
) ? atoi(strescseq
.args
[0]) : 0;
1856 switch (strescseq
.type
) {
1857 case ']': /* OSC -- Operating System Command */
1863 xsettitle(strescseq
.args
[1]);
1867 dec
= base64dec(strescseq
.args
[2]);
1872 fprintf(stderr
, "erresc: invalid base64\n");
1876 case 4: /* color set */
1879 p
= strescseq
.args
[2];
1881 case 104: /* color reset, here p = NULL */
1882 j
= (narg
> 1) ? atoi(strescseq
.args
[1]) : -1;
1883 if (xsetcolorname(j
, p
)) {
1884 if (par
== 104 && narg
<= 1)
1885 return; /* color reset without parameter */
1886 fprintf(stderr
, "erresc: invalid color j=%d, p=%s\n",
1887 j
, p
? p
: "(null)");
1890 * TODO if defaultbg color is changed, borders
1898 case 'k': /* old title set compatibility */
1899 xsettitle(strescseq
.args
[0]);
1901 case 'P': /* DCS -- Device Control String */
1902 term
.mode
|= ESC_DCS
;
1903 case '_': /* APC -- Application Program Command */
1904 case '^': /* PM -- Privacy Message */
1908 fprintf(stderr
, "erresc: unknown str ");
1916 char *p
= strescseq
.buf
;
1919 strescseq
.buf
[strescseq
.len
] = '\0';
1924 while (strescseq
.narg
< STR_ARG_SIZ
) {
1925 strescseq
.args
[strescseq
.narg
++] = p
;
1926 while ((c
= *p
) != ';' && c
!= '\0')
1940 fprintf(stderr
, "ESC%c", strescseq
.type
);
1941 for (i
= 0; i
< strescseq
.len
; i
++) {
1942 c
= strescseq
.buf
[i
] & 0xff;
1946 } else if (isprint(c
)) {
1948 } else if (c
== '\n') {
1949 fprintf(stderr
, "(\\n)");
1950 } else if (c
== '\r') {
1951 fprintf(stderr
, "(\\r)");
1952 } else if (c
== 0x1b) {
1953 fprintf(stderr
, "(\\e)");
1955 fprintf(stderr
, "(%02x)", c
);
1958 fprintf(stderr
, "ESC\\\n");
1964 strescseq
= (STREscape
){
1965 .buf
= xrealloc(strescseq
.buf
, STR_BUF_SIZ
),
1971 sendbreak(const Arg
*arg
)
1973 if (tcsendbreak(cmdfd
, 0))
1974 perror("Error sending break");
1978 tprinter(char *s
, size_t len
)
1980 if (iofd
!= -1 && xwrite(iofd
, s
, len
) < 0) {
1981 perror("Error writing to output file");
1988 toggleprinter(const Arg
*arg
)
1990 term
.mode
^= MODE_PRINT
;
1994 printscreen(const Arg
*arg
)
2000 printsel(const Arg
*arg
)
2010 if ((ptr
= getsel())) {
2011 tprinter(ptr
, strlen(ptr
));
2022 bp
= &term
.line
[n
][0];
2023 end
= &bp
[MIN(tlinelen(n
), term
.col
) - 1];
2024 if (bp
!= end
|| bp
->u
!= ' ') {
2025 for ( ;bp
<= end
; ++bp
)
2026 tprinter(buf
, utf8encode(bp
->u
, buf
));
2036 for (i
= 0; i
< term
.row
; ++i
)
2046 while (x
< term
.col
&& n
--)
2047 for (++x
; x
< term
.col
&& !term
.tabs
[x
]; ++x
)
2050 while (x
> 0 && n
++)
2051 for (--x
; x
> 0 && !term
.tabs
[x
]; --x
)
2054 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
2058 tdefutf8(char ascii
)
2061 term
.mode
|= MODE_UTF8
;
2062 else if (ascii
== '@')
2063 term
.mode
&= ~MODE_UTF8
;
2067 tdeftran(char ascii
)
2069 static char cs
[] = "0B";
2070 static int vcs
[] = {CS_GRAPHIC0
, CS_USA
};
2073 if ((p
= strchr(cs
, ascii
)) == NULL
) {
2074 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
2076 term
.trantbl
[term
.icharset
] = vcs
[p
- cs
];
2085 if (c
== '8') { /* DEC screen alignment test. */
2086 for (x
= 0; x
< term
.col
; ++x
) {
2087 for (y
= 0; y
< term
.row
; ++y
)
2088 tsetchar('E', &term
.c
.attr
, x
, y
);
2094 tstrsequence(uchar c
)
2099 case 0x90: /* DCS -- Device Control String */
2101 term
.esc
|= ESC_DCS
;
2103 case 0x9f: /* APC -- Application Program Command */
2106 case 0x9e: /* PM -- Privacy Message */
2109 case 0x9d: /* OSC -- Operating System Command */
2114 term
.esc
|= ESC_STR
;
2118 tcontrolcode(uchar ascii
)
2125 tmoveto(term
.c
.x
-1, term
.c
.y
);
2128 tmoveto(0, term
.c
.y
);
2133 /* go to first col if the mode is set */
2134 tnewline(IS_SET(MODE_CRLF
));
2136 case '\a': /* BEL */
2137 if (term
.esc
& ESC_STR_END
) {
2138 /* backwards compatibility to xterm */
2144 case '\033': /* ESC */
2146 term
.esc
&= ~(ESC_CSI
|ESC_ALTCHARSET
|ESC_TEST
);
2147 term
.esc
|= ESC_START
;
2149 case '\016': /* SO (LS1 -- Locking shift 1) */
2150 case '\017': /* SI (LS0 -- Locking shift 0) */
2151 term
.charset
= 1 - (ascii
- '\016');
2153 case '\032': /* SUB */
2154 tsetchar('?', &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2155 case '\030': /* CAN */
2158 case '\005': /* ENQ (IGNORED) */
2159 case '\000': /* NUL (IGNORED) */
2160 case '\021': /* XON (IGNORED) */
2161 case '\023': /* XOFF (IGNORED) */
2162 case 0177: /* DEL (IGNORED) */
2164 case 0x80: /* TODO: PAD */
2165 case 0x81: /* TODO: HOP */
2166 case 0x82: /* TODO: BPH */
2167 case 0x83: /* TODO: NBH */
2168 case 0x84: /* TODO: IND */
2170 case 0x85: /* NEL -- Next line */
2171 tnewline(1); /* always go to first col */
2173 case 0x86: /* TODO: SSA */
2174 case 0x87: /* TODO: ESA */
2176 case 0x88: /* HTS -- Horizontal tab stop */
2177 term
.tabs
[term
.c
.x
] = 1;
2179 case 0x89: /* TODO: HTJ */
2180 case 0x8a: /* TODO: VTS */
2181 case 0x8b: /* TODO: PLD */
2182 case 0x8c: /* TODO: PLU */
2183 case 0x8d: /* TODO: RI */
2184 case 0x8e: /* TODO: SS2 */
2185 case 0x8f: /* TODO: SS3 */
2186 case 0x91: /* TODO: PU1 */
2187 case 0x92: /* TODO: PU2 */
2188 case 0x93: /* TODO: STS */
2189 case 0x94: /* TODO: CCH */
2190 case 0x95: /* TODO: MW */
2191 case 0x96: /* TODO: SPA */
2192 case 0x97: /* TODO: EPA */
2193 case 0x98: /* TODO: SOS */
2194 case 0x99: /* TODO: SGCI */
2196 case 0x9a: /* DECID -- Identify Terminal */
2197 ttywrite(vtiden
, strlen(vtiden
), 0);
2199 case 0x9b: /* TODO: CSI */
2200 case 0x9c: /* TODO: ST */
2202 case 0x90: /* DCS -- Device Control String */
2203 case 0x9d: /* OSC -- Operating System Command */
2204 case 0x9e: /* PM -- Privacy Message */
2205 case 0x9f: /* APC -- Application Program Command */
2206 tstrsequence(ascii
);
2209 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2210 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
2214 * returns 1 when the sequence is finished and it hasn't to read
2215 * more characters for this sequence, otherwise 0
2218 eschandle(uchar ascii
)
2222 term
.esc
|= ESC_CSI
;
2225 term
.esc
|= ESC_TEST
;
2228 term
.esc
|= ESC_UTF8
;
2230 case 'P': /* DCS -- Device Control String */
2231 case '_': /* APC -- Application Program Command */
2232 case '^': /* PM -- Privacy Message */
2233 case ']': /* OSC -- Operating System Command */
2234 case 'k': /* old title set compatibility */
2235 tstrsequence(ascii
);
2237 case 'n': /* LS2 -- Locking shift 2 */
2238 case 'o': /* LS3 -- Locking shift 3 */
2239 term
.charset
= 2 + (ascii
- 'n');
2241 case '(': /* GZD4 -- set primary charset G0 */
2242 case ')': /* G1D4 -- set secondary charset G1 */
2243 case '*': /* G2D4 -- set tertiary charset G2 */
2244 case '+': /* G3D4 -- set quaternary charset G3 */
2245 term
.icharset
= ascii
- '(';
2246 term
.esc
|= ESC_ALTCHARSET
;
2248 case 'D': /* IND -- Linefeed */
2249 if (term
.c
.y
== term
.bot
) {
2250 tscrollup(term
.top
, 1);
2252 tmoveto(term
.c
.x
, term
.c
.y
+1);
2255 case 'E': /* NEL -- Next line */
2256 tnewline(1); /* always go to first col */
2258 case 'H': /* HTS -- Horizontal tab stop */
2259 term
.tabs
[term
.c
.x
] = 1;
2261 case 'M': /* RI -- Reverse index */
2262 if (term
.c
.y
== term
.top
) {
2263 tscrolldown(term
.top
, 1);
2265 tmoveto(term
.c
.x
, term
.c
.y
-1);
2268 case 'Z': /* DECID -- Identify Terminal */
2269 ttywrite(vtiden
, strlen(vtiden
), 0);
2271 case 'c': /* RIS -- Reset to initial state */
2276 case '=': /* DECPAM -- Application keypad */
2277 xsetmode(1, MODE_APPKEYPAD
);
2279 case '>': /* DECPNM -- Normal keypad */
2280 xsetmode(0, MODE_APPKEYPAD
);
2282 case '7': /* DECSC -- Save Cursor */
2283 tcursor(CURSOR_SAVE
);
2285 case '8': /* DECRC -- Restore Cursor */
2286 tcursor(CURSOR_LOAD
);
2288 case '\\': /* ST -- String Terminator */
2289 if (term
.esc
& ESC_STR_END
)
2293 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2294 (uchar
) ascii
, isprint(ascii
)? ascii
:'.');
2308 control
= ISCONTROL(u
);
2309 if (!IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2313 len
= utf8encode(u
, c
);
2314 if (!control
&& (width
= wcwidth(u
)) == -1) {
2315 memcpy(c
, "\357\277\275", 4); /* UTF_INVALID */
2320 if (IS_SET(MODE_PRINT
))
2324 * STR sequence must be checked before anything else
2325 * because it uses all following characters until it
2326 * receives a ESC, a SUB, a ST or any other C1 control
2329 if (term
.esc
& ESC_STR
) {
2330 if (u
== '\a' || u
== 030 || u
== 032 || u
== 033 ||
2332 term
.esc
&= ~(ESC_START
|ESC_STR
|ESC_DCS
);
2333 if (IS_SET(MODE_SIXEL
)) {
2334 /* TODO: render sixel */;
2335 term
.mode
&= ~MODE_SIXEL
;
2338 term
.esc
|= ESC_STR_END
;
2339 goto check_control_code
;
2342 if (IS_SET(MODE_SIXEL
)) {
2343 /* TODO: implement sixel mode */
2346 if (term
.esc
&ESC_DCS
&& strescseq
.len
== 0 && u
== 'q')
2347 term
.mode
|= MODE_SIXEL
;
2349 if (strescseq
.len
+len
>= strescseq
.siz
) {
2351 * Here is a bug in terminals. If the user never sends
2352 * some code to stop the str or esc command, then st
2353 * will stop responding. But this is better than
2354 * silently failing with unknown characters. At least
2355 * then users will report back.
2357 * In the case users ever get fixed, here is the code:
2363 if (strescseq
.siz
> (SIZE_MAX
- UTF_SIZ
) / 2)
2366 strescseq
.buf
= xrealloc(strescseq
.buf
, strescseq
.siz
);
2369 memmove(&strescseq
.buf
[strescseq
.len
], c
, len
);
2370 strescseq
.len
+= len
;
2376 * Actions of control codes must be performed as soon they arrive
2377 * because they can be embedded inside a control sequence, and
2378 * they must not cause conflicts with sequences.
2383 * control codes are not shown ever
2386 } else if (term
.esc
& ESC_START
) {
2387 if (term
.esc
& ESC_CSI
) {
2388 csiescseq
.buf
[csiescseq
.len
++] = u
;
2389 if (BETWEEN(u
, 0x40, 0x7E)
2390 || csiescseq
.len
>= \
2391 sizeof(csiescseq
.buf
)-1) {
2397 } else if (term
.esc
& ESC_UTF8
) {
2399 } else if (term
.esc
& ESC_ALTCHARSET
) {
2401 } else if (term
.esc
& ESC_TEST
) {
2406 /* sequence already finished */
2410 * All characters which form part of a sequence are not
2415 if (sel
.ob
.x
!= -1 && BETWEEN(term
.c
.y
, sel
.ob
.y
, sel
.oe
.y
))
2418 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2419 if (IS_SET(MODE_WRAP
) && (term
.c
.state
& CURSOR_WRAPNEXT
)) {
2420 gp
->mode
|= ATTR_WRAP
;
2422 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2425 if (IS_SET(MODE_INSERT
) && term
.c
.x
+width
< term
.col
)
2426 memmove(gp
+width
, gp
, (term
.col
- term
.c
.x
- width
) * sizeof(Glyph
));
2428 if (term
.c
.x
+width
> term
.col
) {
2430 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2433 tsetchar(u
, &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2436 gp
->mode
|= ATTR_WIDE
;
2437 if (term
.c
.x
+1 < term
.col
) {
2439 gp
[1].mode
= ATTR_WDUMMY
;
2442 if (term
.c
.x
+width
< term
.col
) {
2443 tmoveto(term
.c
.x
+width
, term
.c
.y
);
2445 term
.c
.state
|= CURSOR_WRAPNEXT
;
2450 twrite(const char *buf
, int buflen
, int show_ctrl
)
2456 for (n
= 0; n
< buflen
; n
+= charsize
) {
2457 if (IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2458 /* process a complete utf8 char */
2459 charsize
= utf8decode(buf
+ n
, &u
, buflen
- n
);
2466 if (show_ctrl
&& ISCONTROL(u
)) {
2471 } else if (u
!= '\n' && u
!= '\r' && u
!= '\t') {
2482 tresize(int col
, int row
)
2485 int minrow
= MIN(row
, term
.row
);
2486 int mincol
= MIN(col
, term
.col
);
2490 if (col
< 1 || row
< 1) {
2492 "tresize: error resizing to %dx%d\n", col
, row
);
2497 * slide screen to keep cursor where we expect it -
2498 * tscrollup would work here, but we can optimize to
2499 * memmove because we're freeing the earlier lines
2501 for (i
= 0; i
<= term
.c
.y
- row
; i
++) {
2505 /* ensure that both src and dst are not NULL */
2507 memmove(term
.line
, term
.line
+ i
, row
* sizeof(Line
));
2508 memmove(term
.alt
, term
.alt
+ i
, row
* sizeof(Line
));
2510 for (i
+= row
; i
< term
.row
; i
++) {
2515 /* resize to new height */
2516 term
.line
= xrealloc(term
.line
, row
* sizeof(Line
));
2517 term
.alt
= xrealloc(term
.alt
, row
* sizeof(Line
));
2518 term
.dirty
= xrealloc(term
.dirty
, row
* sizeof(*term
.dirty
));
2519 term
.tabs
= xrealloc(term
.tabs
, col
* sizeof(*term
.tabs
));
2521 /* resize each row to new width, zero-pad if needed */
2522 for (i
= 0; i
< minrow
; i
++) {
2523 term
.line
[i
] = xrealloc(term
.line
[i
], col
* sizeof(Glyph
));
2524 term
.alt
[i
] = xrealloc(term
.alt
[i
], col
* sizeof(Glyph
));
2527 /* allocate any new rows */
2528 for (/* i = minrow */; i
< row
; i
++) {
2529 term
.line
[i
] = xmalloc(col
* sizeof(Glyph
));
2530 term
.alt
[i
] = xmalloc(col
* sizeof(Glyph
));
2532 if (col
> term
.col
) {
2533 bp
= term
.tabs
+ term
.col
;
2535 memset(bp
, 0, sizeof(*term
.tabs
) * (col
- term
.col
));
2536 while (--bp
> term
.tabs
&& !*bp
)
2538 for (bp
+= tabspaces
; bp
< term
.tabs
+ col
; bp
+= tabspaces
)
2541 /* update terminal size */
2544 /* reset scrolling region */
2545 tsetscroll(0, row
-1);
2546 /* make use of the LIMIT in tmoveto */
2547 tmoveto(term
.c
.x
, term
.c
.y
);
2548 /* Clearing both screens (it makes dirty all lines) */
2550 for (i
= 0; i
< 2; i
++) {
2551 if (mincol
< col
&& 0 < minrow
) {
2552 tclearregion(mincol
, 0, col
- 1, minrow
- 1);
2554 if (0 < col
&& minrow
< row
) {
2555 tclearregion(0, minrow
, col
- 1, row
- 1);
2558 tcursor(CURSOR_LOAD
);
2570 drawregion(int x1
, int y1
, int x2
, int y2
)
2573 for (y
= y1
; y
< y2
; y
++) {
2578 xdrawline(term
.line
[y
], x1
, y
, x2
);
2590 /* adjust cursor position */
2591 LIMIT(term
.ocx
, 0, term
.col
-1);
2592 LIMIT(term
.ocy
, 0, term
.row
-1);
2593 if (term
.line
[term
.ocy
][term
.ocx
].mode
& ATTR_WDUMMY
)
2595 if (term
.line
[term
.c
.y
][cx
].mode
& ATTR_WDUMMY
)
2598 drawregion(0, 0, term
.col
, term
.row
);
2599 xdrawcursor(cx
, term
.c
.y
, term
.line
[term
.c
.y
][cx
],
2600 term
.ocx
, term
.ocy
, term
.line
[term
.ocy
][term
.ocx
]);
2601 term
.ocx
= cx
, term
.ocy
= term
.c
.y
;
2603 xximspot(term
.ocx
, term
.ocy
);