Xinqi Bao's Git
d35f89d60f425fffcd5f9ff8edd7e06fda774b06
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) (utf8strchr(worddelimiters, u) != NULL)
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 int 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
[STR_BUF_SIZ
]; /* raw string */
150 int 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 char *utf8strchr(char *, Rune
);
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 utf8strchr(char *s
, Rune u
)
347 for (i
= 0, j
= 0; i
< len
; i
+= j
) {
348 if (!(j
= utf8decode(&s
[i
], &r
, len
- i
)))
358 utf8validate(Rune
*u
, size_t i
)
360 if (!BETWEEN(*u
, utfmin
[i
], utfmax
[i
]) || BETWEEN(*u
, 0xD800, 0xDFFF))
362 for (i
= 1; *u
> utfmax
[i
]; ++i
)
368 static const char base64_digits
[] = {
369 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
370 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0,
371 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
372 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
373 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
374 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0,
375 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
376 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
377 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
378 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
379 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
380 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
384 base64dec_getc(const char **src
)
386 while (**src
&& !isprint(**src
)) (*src
)++;
391 base64dec(const char *src
)
393 size_t in_len
= strlen(src
);
397 in_len
+= 4 - (in_len
% 4);
398 result
= dst
= xmalloc(in_len
/ 4 * 3 + 1);
400 int a
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
401 int b
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
402 int c
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
403 int d
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
405 *dst
++ = (a
<< 2) | ((b
& 0x30) >> 4);
408 *dst
++ = ((b
& 0x0f) << 4) | ((c
& 0x3c) >> 2);
411 *dst
++ = ((c
& 0x03) << 6) | d
;
430 if (term
.line
[y
][i
- 1].mode
& ATTR_WRAP
)
433 while (i
> 0 && term
.line
[y
][i
- 1].u
== ' ')
440 selstart(int col
, int row
, int snap
)
443 sel
.mode
= SEL_EMPTY
;
444 sel
.type
= SEL_REGULAR
;
445 sel
.alt
= IS_SET(MODE_ALTSCREEN
);
447 sel
.oe
.x
= sel
.ob
.x
= col
;
448 sel
.oe
.y
= sel
.ob
.y
= row
;
452 sel
.mode
= SEL_READY
;
453 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
457 selextend(int col
, int row
, int type
, int done
)
459 int oldey
, oldex
, oldsby
, oldsey
, oldtype
;
461 if (sel
.mode
== SEL_IDLE
)
463 if (done
&& sel
.mode
== SEL_EMPTY
) {
479 if (oldey
!= sel
.oe
.y
|| oldex
!= sel
.oe
.x
|| oldtype
!= sel
.type
)
480 tsetdirt(MIN(sel
.nb
.y
, oldsby
), MAX(sel
.ne
.y
, oldsey
));
482 sel
.mode
= done
? SEL_IDLE
: SEL_READY
;
490 if (sel
.type
== SEL_REGULAR
&& sel
.ob
.y
!= sel
.oe
.y
) {
491 sel
.nb
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.ob
.x
: sel
.oe
.x
;
492 sel
.ne
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.oe
.x
: sel
.ob
.x
;
494 sel
.nb
.x
= MIN(sel
.ob
.x
, sel
.oe
.x
);
495 sel
.ne
.x
= MAX(sel
.ob
.x
, sel
.oe
.x
);
497 sel
.nb
.y
= MIN(sel
.ob
.y
, sel
.oe
.y
);
498 sel
.ne
.y
= MAX(sel
.ob
.y
, sel
.oe
.y
);
500 selsnap(&sel
.nb
.x
, &sel
.nb
.y
, -1);
501 selsnap(&sel
.ne
.x
, &sel
.ne
.y
, +1);
503 /* expand selection over line breaks */
504 if (sel
.type
== SEL_RECTANGULAR
)
506 i
= tlinelen(sel
.nb
.y
);
509 if (tlinelen(sel
.ne
.y
) <= sel
.ne
.x
)
510 sel
.ne
.x
= term
.col
- 1;
514 selected(int x
, int y
)
516 if (sel
.mode
== SEL_EMPTY
|| sel
.ob
.x
== -1 ||
517 sel
.alt
!= IS_SET(MODE_ALTSCREEN
))
520 if (sel
.type
== SEL_RECTANGULAR
)
521 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
522 && BETWEEN(x
, sel
.nb
.x
, sel
.ne
.x
);
524 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
525 && (y
!= sel
.nb
.y
|| x
>= sel
.nb
.x
)
526 && (y
!= sel
.ne
.y
|| x
<= sel
.ne
.x
);
530 selsnap(int *x
, int *y
, int direction
)
532 int newx
, newy
, xt
, yt
;
533 int delim
, prevdelim
;
539 * Snap around if the word wraps around at the end or
540 * beginning of a line.
542 prevgp
= &term
.line
[*y
][*x
];
543 prevdelim
= ISDELIM(prevgp
->u
);
545 newx
= *x
+ direction
;
547 if (!BETWEEN(newx
, 0, term
.col
- 1)) {
549 newx
= (newx
+ term
.col
) % term
.col
;
550 if (!BETWEEN(newy
, 0, term
.row
- 1))
556 yt
= newy
, xt
= newx
;
557 if (!(term
.line
[yt
][xt
].mode
& ATTR_WRAP
))
561 if (newx
>= tlinelen(newy
))
564 gp
= &term
.line
[newy
][newx
];
565 delim
= ISDELIM(gp
->u
);
566 if (!(gp
->mode
& ATTR_WDUMMY
) && (delim
!= prevdelim
567 || (delim
&& gp
->u
!= prevgp
->u
)))
578 * Snap around if the the previous line or the current one
579 * has set ATTR_WRAP at its end. Then the whole next or
580 * previous line will be selected.
582 *x
= (direction
< 0) ? 0 : term
.col
- 1;
584 for (; *y
> 0; *y
+= direction
) {
585 if (!(term
.line
[*y
-1][term
.col
-1].mode
590 } else if (direction
> 0) {
591 for (; *y
< term
.row
-1; *y
+= direction
) {
592 if (!(term
.line
[*y
][term
.col
-1].mode
606 int y
, bufsize
, lastx
, linelen
;
612 bufsize
= (term
.col
+1) * (sel
.ne
.y
-sel
.nb
.y
+1) * UTF_SIZ
;
613 ptr
= str
= xmalloc(bufsize
);
615 /* append every set & selected glyph to the selection */
616 for (y
= sel
.nb
.y
; y
<= sel
.ne
.y
; y
++) {
617 if ((linelen
= tlinelen(y
)) == 0) {
622 if (sel
.type
== SEL_RECTANGULAR
) {
623 gp
= &term
.line
[y
][sel
.nb
.x
];
626 gp
= &term
.line
[y
][sel
.nb
.y
== y
? sel
.nb
.x
: 0];
627 lastx
= (sel
.ne
.y
== y
) ? sel
.ne
.x
: term
.col
-1;
629 last
= &term
.line
[y
][MIN(lastx
, linelen
-1)];
630 while (last
>= gp
&& last
->u
== ' ')
633 for ( ; gp
<= last
; ++gp
) {
634 if (gp
->mode
& ATTR_WDUMMY
)
637 ptr
+= utf8encode(gp
->u
, ptr
);
641 * Copy and pasting of line endings is inconsistent
642 * in the inconsistent terminal and GUI world.
643 * The best solution seems like to produce '\n' when
644 * something is copied from st and convert '\n' to
645 * '\r', when something to be pasted is received by
647 * FIXME: Fix the computer world.
649 if ((y
< sel
.ne
.y
|| lastx
>= linelen
) && !(last
->mode
& ATTR_WRAP
))
663 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
667 die(const char *errstr
, ...)
671 va_start(ap
, errstr
);
672 vfprintf(stderr
, errstr
, ap
);
678 execsh(char *cmd
, char **args
)
681 const struct passwd
*pw
;
684 if ((pw
= getpwuid(getuid())) == NULL
) {
686 die("getpwuid: %s\n", strerror(errno
));
688 die("who are you?\n");
691 if ((sh
= getenv("SHELL")) == NULL
)
692 sh
= (pw
->pw_shell
[0]) ? pw
->pw_shell
: cmd
;
700 DEFAULT(args
, ((char *[]) {prog
, NULL
}));
705 setenv("LOGNAME", pw
->pw_name
, 1);
706 setenv("USER", pw
->pw_name
, 1);
707 setenv("SHELL", sh
, 1);
708 setenv("HOME", pw
->pw_dir
, 1);
709 setenv("TERM", termname
, 1);
711 signal(SIGCHLD
, SIG_DFL
);
712 signal(SIGHUP
, SIG_DFL
);
713 signal(SIGINT
, SIG_DFL
);
714 signal(SIGQUIT
, SIG_DFL
);
715 signal(SIGTERM
, SIG_DFL
);
716 signal(SIGALRM
, SIG_DFL
);
728 if ((p
= waitpid(pid
, &stat
, WNOHANG
)) < 0)
729 die("waiting for pid %hd failed: %s\n", pid
, strerror(errno
));
734 if (WIFEXITED(stat
) && WEXITSTATUS(stat
))
735 die("child exited with status %d\n", WEXITSTATUS(stat
));
736 else if (WIFSIGNALED(stat
))
737 die("child terminated due to signal %d\n", WTERMSIG(stat
));
744 char cmd
[_POSIX_ARG_MAX
], **p
, *q
, *s
;
747 if ((n
= strlen(stty_args
)) > sizeof(cmd
)-1)
748 die("incorrect stty parameters\n");
749 memcpy(cmd
, stty_args
, n
);
751 siz
= sizeof(cmd
) - n
;
752 for (p
= args
; p
&& (s
= *p
); ++p
) {
753 if ((n
= strlen(s
)) > siz
-1)
754 die("stty parameter length too long\n");
761 if (system(cmd
) != 0)
762 perror("Couldn't call stty");
766 ttynew(char *line
, char *cmd
, char *out
, char **args
)
771 term
.mode
|= MODE_PRINT
;
772 iofd
= (!strcmp(out
, "-")) ?
773 1 : open(out
, O_WRONLY
| O_CREAT
, 0666);
775 fprintf(stderr
, "Error opening %s:%s\n",
776 out
, strerror(errno
));
781 if ((cmdfd
= open(line
, O_RDWR
)) < 0)
782 die("open line '%s' failed: %s\n",
783 line
, strerror(errno
));
789 /* seems to work fine on linux, openbsd and freebsd */
790 if (openpty(&m
, &s
, NULL
, NULL
, NULL
) < 0)
791 die("openpty failed: %s\n", strerror(errno
));
793 switch (pid
= fork()) {
795 die("fork failed: %s\n", strerror(errno
));
799 setsid(); /* create a new process group */
803 if (ioctl(s
, TIOCSCTTY
, NULL
) < 0)
804 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno
));
808 if (pledge("stdio getpw proc exec", NULL
) == -1)
815 if (pledge("stdio rpath tty proc", NULL
) == -1)
820 signal(SIGCHLD
, sigchld
);
829 static char buf
[BUFSIZ
];
830 static int buflen
= 0;
834 /* append read bytes to unprocessed bytes */
835 if ((ret
= read(cmdfd
, buf
+buflen
, LEN(buf
)-buflen
)) < 0)
836 die("couldn't read from shell: %s\n", strerror(errno
));
839 written
= twrite(buf
, buflen
, 0);
841 /* keep any uncomplete utf8 char for the next call */
843 memmove(buf
, buf
+ written
, buflen
);
849 ttywrite(const char *s
, size_t n
, int may_echo
)
853 if (may_echo
&& IS_SET(MODE_ECHO
))
856 if (!IS_SET(MODE_CRLF
)) {
861 /* This is similar to how the kernel handles ONLCR for ttys */
865 ttywriteraw("\r\n", 2);
867 next
= memchr(s
, '\r', n
);
868 DEFAULT(next
, s
+ n
);
869 ttywriteraw(s
, next
- s
);
877 ttywriteraw(const char *s
, size_t n
)
884 * Remember that we are using a pty, which might be a modem line.
885 * Writing too much will clog the line. That's why we are doing this
887 * FIXME: Migrate the world to Plan 9.
895 /* Check if we can write. */
896 if (pselect(cmdfd
+1, &rfd
, &wfd
, NULL
, NULL
, NULL
) < 0) {
899 die("select failed: %s\n", strerror(errno
));
901 if (FD_ISSET(cmdfd
, &wfd
)) {
903 * Only write the bytes written by ttywrite() or the
904 * default of 256. This seems to be a reasonable value
905 * for a serial line. Bigger values might clog the I/O.
907 if ((r
= write(cmdfd
, s
, (n
< lim
)? n
: lim
)) < 0)
911 * We weren't able to write out everything.
912 * This means the buffer is getting full
920 /* All bytes have been written. */
924 if (FD_ISSET(cmdfd
, &rfd
))
930 die("write error on tty: %s\n", strerror(errno
));
934 ttyresize(int tw
, int th
)
942 if (ioctl(cmdfd
, TIOCSWINSZ
, &w
) < 0)
943 fprintf(stderr
, "Couldn't set window size: %s\n", strerror(errno
));
949 /* Send SIGHUP to shell */
958 for (i
= 0; i
< term
.row
-1; i
++) {
959 for (j
= 0; j
< term
.col
-1; j
++) {
960 if (term
.line
[i
][j
].mode
& attr
)
969 tsetdirt(int top
, int bot
)
973 LIMIT(top
, 0, term
.row
-1);
974 LIMIT(bot
, 0, term
.row
-1);
976 for (i
= top
; i
<= bot
; i
++)
981 tsetdirtattr(int attr
)
985 for (i
= 0; i
< term
.row
-1; i
++) {
986 for (j
= 0; j
< term
.col
-1; j
++) {
987 if (term
.line
[i
][j
].mode
& attr
) {
998 tsetdirt(0, term
.row
-1);
1004 static TCursor c
[2];
1005 int alt
= IS_SET(MODE_ALTSCREEN
);
1007 if (mode
== CURSOR_SAVE
) {
1009 } else if (mode
== CURSOR_LOAD
) {
1011 tmoveto(c
[alt
].x
, c
[alt
].y
);
1020 term
.c
= (TCursor
){{
1024 }, .x
= 0, .y
= 0, .state
= CURSOR_DEFAULT
};
1026 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1027 for (i
= tabspaces
; i
< term
.col
; i
+= tabspaces
)
1030 term
.bot
= term
.row
- 1;
1031 term
.mode
= MODE_WRAP
|MODE_UTF8
;
1032 memset(term
.trantbl
, CS_USA
, sizeof(term
.trantbl
));
1035 for (i
= 0; i
< 2; i
++) {
1037 tcursor(CURSOR_SAVE
);
1038 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1044 tnew(int col
, int row
)
1046 term
= (Term
){ .c
= { .attr
= { .fg
= defaultfg
, .bg
= defaultbg
} } };
1054 Line
*tmp
= term
.line
;
1056 term
.line
= term
.alt
;
1058 term
.mode
^= MODE_ALTSCREEN
;
1063 tscrolldown(int orig
, int n
)
1068 LIMIT(n
, 0, term
.bot
-orig
+1);
1070 tsetdirt(orig
, term
.bot
-n
);
1071 tclearregion(0, term
.bot
-n
+1, term
.col
-1, term
.bot
);
1073 for (i
= term
.bot
; i
>= orig
+n
; i
--) {
1074 temp
= term
.line
[i
];
1075 term
.line
[i
] = term
.line
[i
-n
];
1076 term
.line
[i
-n
] = temp
;
1083 tscrollup(int orig
, int n
)
1088 LIMIT(n
, 0, term
.bot
-orig
+1);
1090 tclearregion(0, orig
, term
.col
-1, orig
+n
-1);
1091 tsetdirt(orig
+n
, term
.bot
);
1093 for (i
= orig
; i
<= term
.bot
-n
; i
++) {
1094 temp
= term
.line
[i
];
1095 term
.line
[i
] = term
.line
[i
+n
];
1096 term
.line
[i
+n
] = temp
;
1099 selscroll(orig
, -n
);
1103 selscroll(int orig
, int n
)
1108 if (BETWEEN(sel
.ob
.y
, orig
, term
.bot
) || BETWEEN(sel
.oe
.y
, orig
, term
.bot
)) {
1109 if ((sel
.ob
.y
+= n
) > term
.bot
|| (sel
.oe
.y
+= n
) < term
.top
) {
1113 if (sel
.type
== SEL_RECTANGULAR
) {
1114 if (sel
.ob
.y
< term
.top
)
1115 sel
.ob
.y
= term
.top
;
1116 if (sel
.oe
.y
> term
.bot
)
1117 sel
.oe
.y
= term
.bot
;
1119 if (sel
.ob
.y
< term
.top
) {
1120 sel
.ob
.y
= term
.top
;
1123 if (sel
.oe
.y
> term
.bot
) {
1124 sel
.oe
.y
= term
.bot
;
1125 sel
.oe
.x
= term
.col
;
1133 tnewline(int first_col
)
1137 if (y
== term
.bot
) {
1138 tscrollup(term
.top
, 1);
1142 tmoveto(first_col
? 0 : term
.c
.x
, y
);
1148 char *p
= csiescseq
.buf
, *np
;
1157 csiescseq
.buf
[csiescseq
.len
] = '\0';
1158 while (p
< csiescseq
.buf
+csiescseq
.len
) {
1160 v
= strtol(p
, &np
, 10);
1163 if (v
== LONG_MAX
|| v
== LONG_MIN
)
1165 csiescseq
.arg
[csiescseq
.narg
++] = v
;
1167 if (*p
!= ';' || csiescseq
.narg
== ESC_ARG_SIZ
)
1171 csiescseq
.mode
[0] = *p
++;
1172 csiescseq
.mode
[1] = (p
< csiescseq
.buf
+csiescseq
.len
) ? *p
: '\0';
1175 /* for absolute user moves, when decom is set */
1177 tmoveato(int x
, int y
)
1179 tmoveto(x
, y
+ ((term
.c
.state
& CURSOR_ORIGIN
) ? term
.top
: 0));
1183 tmoveto(int x
, int y
)
1187 if (term
.c
.state
& CURSOR_ORIGIN
) {
1192 maxy
= term
.row
- 1;
1194 term
.c
.state
&= ~CURSOR_WRAPNEXT
;
1195 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1196 term
.c
.y
= LIMIT(y
, miny
, maxy
);
1200 tsetchar(Rune u
, Glyph
*attr
, int x
, int y
)
1202 static char *vt100_0
[62] = { /* 0x41 - 0x7e */
1203 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1204 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1205 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1206 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1207 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1208 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1209 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1210 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1214 * The table is proudly stolen from rxvt.
1216 if (term
.trantbl
[term
.charset
] == CS_GRAPHIC0
&&
1217 BETWEEN(u
, 0x41, 0x7e) && vt100_0
[u
- 0x41])
1218 utf8decode(vt100_0
[u
- 0x41], &u
, UTF_SIZ
);
1220 if (term
.line
[y
][x
].mode
& ATTR_WIDE
) {
1221 if (x
+1 < term
.col
) {
1222 term
.line
[y
][x
+1].u
= ' ';
1223 term
.line
[y
][x
+1].mode
&= ~ATTR_WDUMMY
;
1225 } else if (term
.line
[y
][x
].mode
& ATTR_WDUMMY
) {
1226 term
.line
[y
][x
-1].u
= ' ';
1227 term
.line
[y
][x
-1].mode
&= ~ATTR_WIDE
;
1231 term
.line
[y
][x
] = *attr
;
1232 term
.line
[y
][x
].u
= u
;
1236 tclearregion(int x1
, int y1
, int x2
, int y2
)
1242 temp
= x1
, x1
= x2
, x2
= temp
;
1244 temp
= y1
, y1
= y2
, y2
= temp
;
1246 LIMIT(x1
, 0, term
.col
-1);
1247 LIMIT(x2
, 0, term
.col
-1);
1248 LIMIT(y1
, 0, term
.row
-1);
1249 LIMIT(y2
, 0, term
.row
-1);
1251 for (y
= y1
; y
<= y2
; y
++) {
1253 for (x
= x1
; x
<= x2
; x
++) {
1254 gp
= &term
.line
[y
][x
];
1257 gp
->fg
= term
.c
.attr
.fg
;
1258 gp
->bg
= term
.c
.attr
.bg
;
1271 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1275 size
= term
.col
- src
;
1276 line
= term
.line
[term
.c
.y
];
1278 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1279 tclearregion(term
.col
-n
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1288 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1292 size
= term
.col
- dst
;
1293 line
= term
.line
[term
.c
.y
];
1295 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1296 tclearregion(src
, term
.c
.y
, dst
- 1, term
.c
.y
);
1300 tinsertblankline(int n
)
1302 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1303 tscrolldown(term
.c
.y
, n
);
1309 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1310 tscrollup(term
.c
.y
, n
);
1314 tdefcolor(int *attr
, int *npar
, int l
)
1319 switch (attr
[*npar
+ 1]) {
1320 case 2: /* direct color in RGB space */
1321 if (*npar
+ 4 >= l
) {
1323 "erresc(38): Incorrect number of parameters (%d)\n",
1327 r
= attr
[*npar
+ 2];
1328 g
= attr
[*npar
+ 3];
1329 b
= attr
[*npar
+ 4];
1331 if (!BETWEEN(r
, 0, 255) || !BETWEEN(g
, 0, 255) || !BETWEEN(b
, 0, 255))
1332 fprintf(stderr
, "erresc: bad rgb color (%u,%u,%u)\n",
1335 idx
= TRUECOLOR(r
, g
, b
);
1337 case 5: /* indexed color */
1338 if (*npar
+ 2 >= l
) {
1340 "erresc(38): Incorrect number of parameters (%d)\n",
1345 if (!BETWEEN(attr
[*npar
], 0, 255))
1346 fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[*npar
]);
1350 case 0: /* implemented defined (only foreground) */
1351 case 1: /* transparent */
1352 case 3: /* direct color in CMY space */
1353 case 4: /* direct color in CMYK space */
1356 "erresc(38): gfx attr %d unknown\n", attr
[*npar
]);
1364 tsetattr(int *attr
, int l
)
1369 for (i
= 0; i
< l
; i
++) {
1372 term
.c
.attr
.mode
&= ~(
1381 term
.c
.attr
.fg
= defaultfg
;
1382 term
.c
.attr
.bg
= defaultbg
;
1385 term
.c
.attr
.mode
|= ATTR_BOLD
;
1388 term
.c
.attr
.mode
|= ATTR_FAINT
;
1391 term
.c
.attr
.mode
|= ATTR_ITALIC
;
1394 term
.c
.attr
.mode
|= ATTR_UNDERLINE
;
1396 case 5: /* slow blink */
1398 case 6: /* rapid blink */
1399 term
.c
.attr
.mode
|= ATTR_BLINK
;
1402 term
.c
.attr
.mode
|= ATTR_REVERSE
;
1405 term
.c
.attr
.mode
|= ATTR_INVISIBLE
;
1408 term
.c
.attr
.mode
|= ATTR_STRUCK
;
1411 term
.c
.attr
.mode
&= ~(ATTR_BOLD
| ATTR_FAINT
);
1414 term
.c
.attr
.mode
&= ~ATTR_ITALIC
;
1417 term
.c
.attr
.mode
&= ~ATTR_UNDERLINE
;
1420 term
.c
.attr
.mode
&= ~ATTR_BLINK
;
1423 term
.c
.attr
.mode
&= ~ATTR_REVERSE
;
1426 term
.c
.attr
.mode
&= ~ATTR_INVISIBLE
;
1429 term
.c
.attr
.mode
&= ~ATTR_STRUCK
;
1432 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1433 term
.c
.attr
.fg
= idx
;
1436 term
.c
.attr
.fg
= defaultfg
;
1439 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1440 term
.c
.attr
.bg
= idx
;
1443 term
.c
.attr
.bg
= defaultbg
;
1446 if (BETWEEN(attr
[i
], 30, 37)) {
1447 term
.c
.attr
.fg
= attr
[i
] - 30;
1448 } else if (BETWEEN(attr
[i
], 40, 47)) {
1449 term
.c
.attr
.bg
= attr
[i
] - 40;
1450 } else if (BETWEEN(attr
[i
], 90, 97)) {
1451 term
.c
.attr
.fg
= attr
[i
] - 90 + 8;
1452 } else if (BETWEEN(attr
[i
], 100, 107)) {
1453 term
.c
.attr
.bg
= attr
[i
] - 100 + 8;
1456 "erresc(default): gfx attr %d unknown\n",
1466 tsetscroll(int t
, int b
)
1470 LIMIT(t
, 0, term
.row
-1);
1471 LIMIT(b
, 0, term
.row
-1);
1482 tsetmode(int priv
, int set
, int *args
, int narg
)
1486 for (lim
= args
+ narg
; args
< lim
; ++args
) {
1489 case 1: /* DECCKM -- Cursor key */
1490 xsetmode(set
, MODE_APPCURSOR
);
1492 case 5: /* DECSCNM -- Reverse video */
1493 xsetmode(set
, MODE_REVERSE
);
1495 case 6: /* DECOM -- Origin */
1496 MODBIT(term
.c
.state
, set
, CURSOR_ORIGIN
);
1499 case 7: /* DECAWM -- Auto wrap */
1500 MODBIT(term
.mode
, set
, MODE_WRAP
);
1502 case 0: /* Error (IGNORED) */
1503 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1504 case 3: /* DECCOLM -- Column (IGNORED) */
1505 case 4: /* DECSCLM -- Scroll (IGNORED) */
1506 case 8: /* DECARM -- Auto repeat (IGNORED) */
1507 case 18: /* DECPFF -- Printer feed (IGNORED) */
1508 case 19: /* DECPEX -- Printer extent (IGNORED) */
1509 case 42: /* DECNRCM -- National characters (IGNORED) */
1510 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1512 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1513 xsetmode(!set
, MODE_HIDE
);
1515 case 9: /* X10 mouse compatibility mode */
1516 xsetpointermotion(0);
1517 xsetmode(0, MODE_MOUSE
);
1518 xsetmode(set
, MODE_MOUSEX10
);
1520 case 1000: /* 1000: report button press */
1521 xsetpointermotion(0);
1522 xsetmode(0, MODE_MOUSE
);
1523 xsetmode(set
, MODE_MOUSEBTN
);
1525 case 1002: /* 1002: report motion on button press */
1526 xsetpointermotion(0);
1527 xsetmode(0, MODE_MOUSE
);
1528 xsetmode(set
, MODE_MOUSEMOTION
);
1530 case 1003: /* 1003: enable all mouse motions */
1531 xsetpointermotion(set
);
1532 xsetmode(0, MODE_MOUSE
);
1533 xsetmode(set
, MODE_MOUSEMANY
);
1535 case 1004: /* 1004: send focus events to tty */
1536 xsetmode(set
, MODE_FOCUS
);
1538 case 1006: /* 1006: extended reporting mode */
1539 xsetmode(set
, MODE_MOUSESGR
);
1542 xsetmode(set
, MODE_8BIT
);
1544 case 1049: /* swap screen & set/restore cursor as xterm */
1545 if (!allowaltscreen
)
1547 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1549 case 47: /* swap screen */
1551 if (!allowaltscreen
)
1553 alt
= IS_SET(MODE_ALTSCREEN
);
1555 tclearregion(0, 0, term
.col
-1,
1558 if (set
^ alt
) /* set is always 1 or 0 */
1564 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1566 case 2004: /* 2004: bracketed paste mode */
1567 xsetmode(set
, MODE_BRCKTPASTE
);
1569 /* Not implemented mouse modes. See comments there. */
1570 case 1001: /* mouse highlight mode; can hang the
1571 terminal by design when implemented. */
1572 case 1005: /* UTF-8 mouse mode; will confuse
1573 applications not supporting UTF-8
1575 case 1015: /* urxvt mangled mouse mode; incompatible
1576 and can be mistaken for other control
1581 "erresc: unknown private set/reset mode %d\n",
1587 case 0: /* Error (IGNORED) */
1590 xsetmode(set
, MODE_KBDLOCK
);
1592 case 4: /* IRM -- Insertion-replacement */
1593 MODBIT(term
.mode
, set
, MODE_INSERT
);
1595 case 12: /* SRM -- Send/Receive */
1596 MODBIT(term
.mode
, !set
, MODE_ECHO
);
1598 case 20: /* LNM -- Linefeed/new line */
1599 MODBIT(term
.mode
, set
, MODE_CRLF
);
1603 "erresc: unknown set/reset mode %d\n",
1617 switch (csiescseq
.mode
[0]) {
1620 fprintf(stderr
, "erresc: unknown csi ");
1624 case '@': /* ICH -- Insert <n> blank char */
1625 DEFAULT(csiescseq
.arg
[0], 1);
1626 tinsertblank(csiescseq
.arg
[0]);
1628 case 'A': /* CUU -- Cursor <n> Up */
1629 DEFAULT(csiescseq
.arg
[0], 1);
1630 tmoveto(term
.c
.x
, term
.c
.y
-csiescseq
.arg
[0]);
1632 case 'B': /* CUD -- Cursor <n> Down */
1633 case 'e': /* VPR --Cursor <n> Down */
1634 DEFAULT(csiescseq
.arg
[0], 1);
1635 tmoveto(term
.c
.x
, term
.c
.y
+csiescseq
.arg
[0]);
1637 case 'i': /* MC -- Media Copy */
1638 switch (csiescseq
.arg
[0]) {
1643 tdumpline(term
.c
.y
);
1649 term
.mode
&= ~MODE_PRINT
;
1652 term
.mode
|= MODE_PRINT
;
1656 case 'c': /* DA -- Device Attributes */
1657 if (csiescseq
.arg
[0] == 0)
1658 ttywrite(vtiden
, strlen(vtiden
), 0);
1660 case 'C': /* CUF -- Cursor <n> Forward */
1661 case 'a': /* HPR -- Cursor <n> Forward */
1662 DEFAULT(csiescseq
.arg
[0], 1);
1663 tmoveto(term
.c
.x
+csiescseq
.arg
[0], term
.c
.y
);
1665 case 'D': /* CUB -- Cursor <n> Backward */
1666 DEFAULT(csiescseq
.arg
[0], 1);
1667 tmoveto(term
.c
.x
-csiescseq
.arg
[0], term
.c
.y
);
1669 case 'E': /* CNL -- Cursor <n> Down and first col */
1670 DEFAULT(csiescseq
.arg
[0], 1);
1671 tmoveto(0, term
.c
.y
+csiescseq
.arg
[0]);
1673 case 'F': /* CPL -- Cursor <n> Up and first col */
1674 DEFAULT(csiescseq
.arg
[0], 1);
1675 tmoveto(0, term
.c
.y
-csiescseq
.arg
[0]);
1677 case 'g': /* TBC -- Tabulation clear */
1678 switch (csiescseq
.arg
[0]) {
1679 case 0: /* clear current tab stop */
1680 term
.tabs
[term
.c
.x
] = 0;
1682 case 3: /* clear all the tabs */
1683 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1689 case 'G': /* CHA -- Move to <col> */
1691 DEFAULT(csiescseq
.arg
[0], 1);
1692 tmoveto(csiescseq
.arg
[0]-1, term
.c
.y
);
1694 case 'H': /* CUP -- Move to <row> <col> */
1696 DEFAULT(csiescseq
.arg
[0], 1);
1697 DEFAULT(csiescseq
.arg
[1], 1);
1698 tmoveato(csiescseq
.arg
[1]-1, csiescseq
.arg
[0]-1);
1700 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1701 DEFAULT(csiescseq
.arg
[0], 1);
1702 tputtab(csiescseq
.arg
[0]);
1704 case 'J': /* ED -- Clear screen */
1705 switch (csiescseq
.arg
[0]) {
1707 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1708 if (term
.c
.y
< term
.row
-1) {
1709 tclearregion(0, term
.c
.y
+1, term
.col
-1,
1715 tclearregion(0, 0, term
.col
-1, term
.c
.y
-1);
1716 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1719 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1725 case 'K': /* EL -- Clear line */
1726 switch (csiescseq
.arg
[0]) {
1728 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1,
1732 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1735 tclearregion(0, term
.c
.y
, term
.col
-1, term
.c
.y
);
1739 case 'S': /* SU -- Scroll <n> line up */
1740 DEFAULT(csiescseq
.arg
[0], 1);
1741 tscrollup(term
.top
, csiescseq
.arg
[0]);
1743 case 'T': /* SD -- Scroll <n> line down */
1744 DEFAULT(csiescseq
.arg
[0], 1);
1745 tscrolldown(term
.top
, csiescseq
.arg
[0]);
1747 case 'L': /* IL -- Insert <n> blank lines */
1748 DEFAULT(csiescseq
.arg
[0], 1);
1749 tinsertblankline(csiescseq
.arg
[0]);
1751 case 'l': /* RM -- Reset Mode */
1752 tsetmode(csiescseq
.priv
, 0, csiescseq
.arg
, csiescseq
.narg
);
1754 case 'M': /* DL -- Delete <n> lines */
1755 DEFAULT(csiescseq
.arg
[0], 1);
1756 tdeleteline(csiescseq
.arg
[0]);
1758 case 'X': /* ECH -- Erase <n> char */
1759 DEFAULT(csiescseq
.arg
[0], 1);
1760 tclearregion(term
.c
.x
, term
.c
.y
,
1761 term
.c
.x
+ csiescseq
.arg
[0] - 1, term
.c
.y
);
1763 case 'P': /* DCH -- Delete <n> char */
1764 DEFAULT(csiescseq
.arg
[0], 1);
1765 tdeletechar(csiescseq
.arg
[0]);
1767 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1768 DEFAULT(csiescseq
.arg
[0], 1);
1769 tputtab(-csiescseq
.arg
[0]);
1771 case 'd': /* VPA -- Move to <row> */
1772 DEFAULT(csiescseq
.arg
[0], 1);
1773 tmoveato(term
.c
.x
, csiescseq
.arg
[0]-1);
1775 case 'h': /* SM -- Set terminal mode */
1776 tsetmode(csiescseq
.priv
, 1, csiescseq
.arg
, csiescseq
.narg
);
1778 case 'm': /* SGR -- Terminal attribute (color) */
1779 tsetattr(csiescseq
.arg
, csiescseq
.narg
);
1781 case 'n': /* DSR – Device Status Report (cursor position) */
1782 if (csiescseq
.arg
[0] == 6) {
1783 len
= snprintf(buf
, sizeof(buf
),"\033[%i;%iR",
1784 term
.c
.y
+1, term
.c
.x
+1);
1785 ttywrite(buf
, len
, 0);
1788 case 'r': /* DECSTBM -- Set Scrolling Region */
1789 if (csiescseq
.priv
) {
1792 DEFAULT(csiescseq
.arg
[0], 1);
1793 DEFAULT(csiescseq
.arg
[1], term
.row
);
1794 tsetscroll(csiescseq
.arg
[0]-1, csiescseq
.arg
[1]-1);
1798 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1799 tcursor(CURSOR_SAVE
);
1801 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1802 tcursor(CURSOR_LOAD
);
1805 switch (csiescseq
.mode
[1]) {
1806 case 'q': /* DECSCUSR -- Set Cursor Style */
1807 if (xsetcursor(csiescseq
.arg
[0]))
1823 fprintf(stderr
, "ESC[");
1824 for (i
= 0; i
< csiescseq
.len
; i
++) {
1825 c
= csiescseq
.buf
[i
] & 0xff;
1828 } else if (c
== '\n') {
1829 fprintf(stderr
, "(\\n)");
1830 } else if (c
== '\r') {
1831 fprintf(stderr
, "(\\r)");
1832 } else if (c
== 0x1b) {
1833 fprintf(stderr
, "(\\e)");
1835 fprintf(stderr
, "(%02x)", c
);
1844 memset(&csiescseq
, 0, sizeof(csiescseq
));
1853 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
1855 par
= (narg
= strescseq
.narg
) ? atoi(strescseq
.args
[0]) : 0;
1857 switch (strescseq
.type
) {
1858 case ']': /* OSC -- Operating System Command */
1864 xsettitle(strescseq
.args
[1]);
1870 dec
= base64dec(strescseq
.args
[2]);
1875 fprintf(stderr
, "erresc: invalid base64\n");
1879 case 4: /* color set */
1882 p
= strescseq
.args
[2];
1884 case 104: /* color reset, here p = NULL */
1885 j
= (narg
> 1) ? atoi(strescseq
.args
[1]) : -1;
1886 if (xsetcolorname(j
, p
)) {
1887 fprintf(stderr
, "erresc: invalid color %s\n", p
);
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 memset(&strescseq
, 0, sizeof(strescseq
));
1968 sendbreak(const Arg
*arg
)
1970 if (tcsendbreak(cmdfd
, 0))
1971 perror("Error sending break");
1975 tprinter(char *s
, size_t len
)
1977 if (iofd
!= -1 && xwrite(iofd
, s
, len
) < 0) {
1978 perror("Error writing to output file");
1985 toggleprinter(const Arg
*arg
)
1987 term
.mode
^= MODE_PRINT
;
1991 printscreen(const Arg
*arg
)
1997 printsel(const Arg
*arg
)
2007 if ((ptr
= getsel())) {
2008 tprinter(ptr
, strlen(ptr
));
2019 bp
= &term
.line
[n
][0];
2020 end
= &bp
[MIN(tlinelen(n
), term
.col
) - 1];
2021 if (bp
!= end
|| bp
->u
!= ' ') {
2022 for ( ;bp
<= end
; ++bp
)
2023 tprinter(buf
, utf8encode(bp
->u
, buf
));
2033 for (i
= 0; i
< term
.row
; ++i
)
2043 while (x
< term
.col
&& n
--)
2044 for (++x
; x
< term
.col
&& !term
.tabs
[x
]; ++x
)
2047 while (x
> 0 && n
++)
2048 for (--x
; x
> 0 && !term
.tabs
[x
]; --x
)
2051 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
2055 tdefutf8(char ascii
)
2058 term
.mode
|= MODE_UTF8
;
2059 else if (ascii
== '@')
2060 term
.mode
&= ~MODE_UTF8
;
2064 tdeftran(char ascii
)
2066 static char cs
[] = "0B";
2067 static int vcs
[] = {CS_GRAPHIC0
, CS_USA
};
2070 if ((p
= strchr(cs
, ascii
)) == NULL
) {
2071 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
2073 term
.trantbl
[term
.icharset
] = vcs
[p
- cs
];
2082 if (c
== '8') { /* DEC screen alignment test. */
2083 for (x
= 0; x
< term
.col
; ++x
) {
2084 for (y
= 0; y
< term
.row
; ++y
)
2085 tsetchar('E', &term
.c
.attr
, x
, y
);
2091 tstrsequence(uchar c
)
2096 case 0x90: /* DCS -- Device Control String */
2098 term
.esc
|= ESC_DCS
;
2100 case 0x9f: /* APC -- Application Program Command */
2103 case 0x9e: /* PM -- Privacy Message */
2106 case 0x9d: /* OSC -- Operating System Command */
2111 term
.esc
|= ESC_STR
;
2115 tcontrolcode(uchar ascii
)
2122 tmoveto(term
.c
.x
-1, term
.c
.y
);
2125 tmoveto(0, term
.c
.y
);
2130 /* go to first col if the mode is set */
2131 tnewline(IS_SET(MODE_CRLF
));
2133 case '\a': /* BEL */
2134 if (term
.esc
& ESC_STR_END
) {
2135 /* backwards compatibility to xterm */
2141 case '\033': /* ESC */
2143 term
.esc
&= ~(ESC_CSI
|ESC_ALTCHARSET
|ESC_TEST
);
2144 term
.esc
|= ESC_START
;
2146 case '\016': /* SO (LS1 -- Locking shift 1) */
2147 case '\017': /* SI (LS0 -- Locking shift 0) */
2148 term
.charset
= 1 - (ascii
- '\016');
2150 case '\032': /* SUB */
2151 tsetchar('?', &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2152 case '\030': /* CAN */
2155 case '\005': /* ENQ (IGNORED) */
2156 case '\000': /* NUL (IGNORED) */
2157 case '\021': /* XON (IGNORED) */
2158 case '\023': /* XOFF (IGNORED) */
2159 case 0177: /* DEL (IGNORED) */
2161 case 0x80: /* TODO: PAD */
2162 case 0x81: /* TODO: HOP */
2163 case 0x82: /* TODO: BPH */
2164 case 0x83: /* TODO: NBH */
2165 case 0x84: /* TODO: IND */
2167 case 0x85: /* NEL -- Next line */
2168 tnewline(1); /* always go to first col */
2170 case 0x86: /* TODO: SSA */
2171 case 0x87: /* TODO: ESA */
2173 case 0x88: /* HTS -- Horizontal tab stop */
2174 term
.tabs
[term
.c
.x
] = 1;
2176 case 0x89: /* TODO: HTJ */
2177 case 0x8a: /* TODO: VTS */
2178 case 0x8b: /* TODO: PLD */
2179 case 0x8c: /* TODO: PLU */
2180 case 0x8d: /* TODO: RI */
2181 case 0x8e: /* TODO: SS2 */
2182 case 0x8f: /* TODO: SS3 */
2183 case 0x91: /* TODO: PU1 */
2184 case 0x92: /* TODO: PU2 */
2185 case 0x93: /* TODO: STS */
2186 case 0x94: /* TODO: CCH */
2187 case 0x95: /* TODO: MW */
2188 case 0x96: /* TODO: SPA */
2189 case 0x97: /* TODO: EPA */
2190 case 0x98: /* TODO: SOS */
2191 case 0x99: /* TODO: SGCI */
2193 case 0x9a: /* DECID -- Identify Terminal */
2194 ttywrite(vtiden
, strlen(vtiden
), 0);
2196 case 0x9b: /* TODO: CSI */
2197 case 0x9c: /* TODO: ST */
2199 case 0x90: /* DCS -- Device Control String */
2200 case 0x9d: /* OSC -- Operating System Command */
2201 case 0x9e: /* PM -- Privacy Message */
2202 case 0x9f: /* APC -- Application Program Command */
2203 tstrsequence(ascii
);
2206 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2207 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
2211 * returns 1 when the sequence is finished and it hasn't to read
2212 * more characters for this sequence, otherwise 0
2215 eschandle(uchar ascii
)
2219 term
.esc
|= ESC_CSI
;
2222 term
.esc
|= ESC_TEST
;
2225 term
.esc
|= ESC_UTF8
;
2227 case 'P': /* DCS -- Device Control String */
2228 case '_': /* APC -- Application Program Command */
2229 case '^': /* PM -- Privacy Message */
2230 case ']': /* OSC -- Operating System Command */
2231 case 'k': /* old title set compatibility */
2232 tstrsequence(ascii
);
2234 case 'n': /* LS2 -- Locking shift 2 */
2235 case 'o': /* LS3 -- Locking shift 3 */
2236 term
.charset
= 2 + (ascii
- 'n');
2238 case '(': /* GZD4 -- set primary charset G0 */
2239 case ')': /* G1D4 -- set secondary charset G1 */
2240 case '*': /* G2D4 -- set tertiary charset G2 */
2241 case '+': /* G3D4 -- set quaternary charset G3 */
2242 term
.icharset
= ascii
- '(';
2243 term
.esc
|= ESC_ALTCHARSET
;
2245 case 'D': /* IND -- Linefeed */
2246 if (term
.c
.y
== term
.bot
) {
2247 tscrollup(term
.top
, 1);
2249 tmoveto(term
.c
.x
, term
.c
.y
+1);
2252 case 'E': /* NEL -- Next line */
2253 tnewline(1); /* always go to first col */
2255 case 'H': /* HTS -- Horizontal tab stop */
2256 term
.tabs
[term
.c
.x
] = 1;
2258 case 'M': /* RI -- Reverse index */
2259 if (term
.c
.y
== term
.top
) {
2260 tscrolldown(term
.top
, 1);
2262 tmoveto(term
.c
.x
, term
.c
.y
-1);
2265 case 'Z': /* DECID -- Identify Terminal */
2266 ttywrite(vtiden
, strlen(vtiden
), 0);
2268 case 'c': /* RIS -- Reset to initial state */
2273 case '=': /* DECPAM -- Application keypad */
2274 xsetmode(1, MODE_APPKEYPAD
);
2276 case '>': /* DECPNM -- Normal keypad */
2277 xsetmode(0, MODE_APPKEYPAD
);
2279 case '7': /* DECSC -- Save Cursor */
2280 tcursor(CURSOR_SAVE
);
2282 case '8': /* DECRC -- Restore Cursor */
2283 tcursor(CURSOR_LOAD
);
2285 case '\\': /* ST -- String Terminator */
2286 if (term
.esc
& ESC_STR_END
)
2290 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2291 (uchar
) ascii
, isprint(ascii
)? ascii
:'.');
2305 control
= ISCONTROL(u
);
2306 if (!IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2310 len
= utf8encode(u
, c
);
2311 if (!control
&& (width
= wcwidth(u
)) == -1) {
2312 memcpy(c
, "\357\277\275", 4); /* UTF_INVALID */
2317 if (IS_SET(MODE_PRINT
))
2321 * STR sequence must be checked before anything else
2322 * because it uses all following characters until it
2323 * receives a ESC, a SUB, a ST or any other C1 control
2326 if (term
.esc
& ESC_STR
) {
2327 if (u
== '\a' || u
== 030 || u
== 032 || u
== 033 ||
2329 term
.esc
&= ~(ESC_START
|ESC_STR
|ESC_DCS
);
2330 if (IS_SET(MODE_SIXEL
)) {
2331 /* TODO: render sixel */;
2332 term
.mode
&= ~MODE_SIXEL
;
2335 term
.esc
|= ESC_STR_END
;
2336 goto check_control_code
;
2339 if (IS_SET(MODE_SIXEL
)) {
2340 /* TODO: implement sixel mode */
2343 if (term
.esc
&ESC_DCS
&& strescseq
.len
== 0 && u
== 'q')
2344 term
.mode
|= MODE_SIXEL
;
2346 if (strescseq
.len
+len
>= sizeof(strescseq
.buf
)-1) {
2348 * Here is a bug in terminals. If the user never sends
2349 * some code to stop the str or esc command, then st
2350 * will stop responding. But this is better than
2351 * silently failing with unknown characters. At least
2352 * then users will report back.
2354 * In the case users ever get fixed, here is the code:
2363 memmove(&strescseq
.buf
[strescseq
.len
], c
, len
);
2364 strescseq
.len
+= len
;
2370 * Actions of control codes must be performed as soon they arrive
2371 * because they can be embedded inside a control sequence, and
2372 * they must not cause conflicts with sequences.
2377 * control codes are not shown ever
2380 } else if (term
.esc
& ESC_START
) {
2381 if (term
.esc
& ESC_CSI
) {
2382 csiescseq
.buf
[csiescseq
.len
++] = u
;
2383 if (BETWEEN(u
, 0x40, 0x7E)
2384 || csiescseq
.len
>= \
2385 sizeof(csiescseq
.buf
)-1) {
2391 } else if (term
.esc
& ESC_UTF8
) {
2393 } else if (term
.esc
& ESC_ALTCHARSET
) {
2395 } else if (term
.esc
& ESC_TEST
) {
2400 /* sequence already finished */
2404 * All characters which form part of a sequence are not
2409 if (sel
.ob
.x
!= -1 && BETWEEN(term
.c
.y
, sel
.ob
.y
, sel
.oe
.y
))
2412 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2413 if (IS_SET(MODE_WRAP
) && (term
.c
.state
& CURSOR_WRAPNEXT
)) {
2414 gp
->mode
|= ATTR_WRAP
;
2416 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2419 if (IS_SET(MODE_INSERT
) && term
.c
.x
+width
< term
.col
)
2420 memmove(gp
+width
, gp
, (term
.col
- term
.c
.x
- width
) * sizeof(Glyph
));
2422 if (term
.c
.x
+width
> term
.col
) {
2424 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2427 tsetchar(u
, &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2430 gp
->mode
|= ATTR_WIDE
;
2431 if (term
.c
.x
+1 < term
.col
) {
2433 gp
[1].mode
= ATTR_WDUMMY
;
2436 if (term
.c
.x
+width
< term
.col
) {
2437 tmoveto(term
.c
.x
+width
, term
.c
.y
);
2439 term
.c
.state
|= CURSOR_WRAPNEXT
;
2444 twrite(const char *buf
, int buflen
, int show_ctrl
)
2450 for (n
= 0; n
< buflen
; n
+= charsize
) {
2451 if (IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2452 /* process a complete utf8 char */
2453 charsize
= utf8decode(buf
+ n
, &u
, buflen
- n
);
2460 if (show_ctrl
&& ISCONTROL(u
)) {
2465 } else if (u
!= '\n' && u
!= '\r' && u
!= '\t') {
2476 tresize(int col
, int row
)
2479 int minrow
= MIN(row
, term
.row
);
2480 int mincol
= MIN(col
, term
.col
);
2484 if (col
< 1 || row
< 1) {
2486 "tresize: error resizing to %dx%d\n", col
, row
);
2491 * slide screen to keep cursor where we expect it -
2492 * tscrollup would work here, but we can optimize to
2493 * memmove because we're freeing the earlier lines
2495 for (i
= 0; i
<= term
.c
.y
- row
; i
++) {
2499 /* ensure that both src and dst are not NULL */
2501 memmove(term
.line
, term
.line
+ i
, row
* sizeof(Line
));
2502 memmove(term
.alt
, term
.alt
+ i
, row
* sizeof(Line
));
2504 for (i
+= row
; i
< term
.row
; i
++) {
2509 /* resize to new height */
2510 term
.line
= xrealloc(term
.line
, row
* sizeof(Line
));
2511 term
.alt
= xrealloc(term
.alt
, row
* sizeof(Line
));
2512 term
.dirty
= xrealloc(term
.dirty
, row
* sizeof(*term
.dirty
));
2513 term
.tabs
= xrealloc(term
.tabs
, col
* sizeof(*term
.tabs
));
2515 /* resize each row to new width, zero-pad if needed */
2516 for (i
= 0; i
< minrow
; i
++) {
2517 term
.line
[i
] = xrealloc(term
.line
[i
], col
* sizeof(Glyph
));
2518 term
.alt
[i
] = xrealloc(term
.alt
[i
], col
* sizeof(Glyph
));
2521 /* allocate any new rows */
2522 for (/* i = minrow */; i
< row
; i
++) {
2523 term
.line
[i
] = xmalloc(col
* sizeof(Glyph
));
2524 term
.alt
[i
] = xmalloc(col
* sizeof(Glyph
));
2526 if (col
> term
.col
) {
2527 bp
= term
.tabs
+ term
.col
;
2529 memset(bp
, 0, sizeof(*term
.tabs
) * (col
- term
.col
));
2530 while (--bp
> term
.tabs
&& !*bp
)
2532 for (bp
+= tabspaces
; bp
< term
.tabs
+ col
; bp
+= tabspaces
)
2535 /* update terminal size */
2538 /* reset scrolling region */
2539 tsetscroll(0, row
-1);
2540 /* make use of the LIMIT in tmoveto */
2541 tmoveto(term
.c
.x
, term
.c
.y
);
2542 /* Clearing both screens (it makes dirty all lines) */
2544 for (i
= 0; i
< 2; i
++) {
2545 if (mincol
< col
&& 0 < minrow
) {
2546 tclearregion(mincol
, 0, col
- 1, minrow
- 1);
2548 if (0 < col
&& minrow
< row
) {
2549 tclearregion(0, minrow
, col
- 1, row
- 1);
2552 tcursor(CURSOR_LOAD
);
2564 drawregion(int x1
, int y1
, int x2
, int y2
)
2567 for (y
= y1
; y
< y2
; y
++) {
2572 xdrawline(term
.line
[y
], x1
, y
, x2
);
2584 /* adjust cursor position */
2585 LIMIT(term
.ocx
, 0, term
.col
-1);
2586 LIMIT(term
.ocy
, 0, term
.row
-1);
2587 if (term
.line
[term
.ocy
][term
.ocx
].mode
& ATTR_WDUMMY
)
2589 if (term
.line
[term
.c
.y
][cx
].mode
& ATTR_WDUMMY
)
2592 drawregion(0, 0, term
.col
, term
.row
);
2593 xdrawcursor(cx
, term
.c
.y
, term
.line
[term
.c
.y
][cx
],
2594 term
.ocx
, term
.ocy
, term
.line
[term
.ocy
][term
.ocx
]);
2595 term
.ocx
= cx
, term
.ocy
= term
.c
.y
;
2597 xximspot(term
.ocx
, term
.ocy
);