Xinqi Bao's Git
59db14483a01f4b153ad1d5ddb85253c9ea785bc
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
;
687 arg
= utmp
? utmp
: sh
;
695 DEFAULT(args
, ((char *[]) {prog
, arg
, NULL
}));
700 setenv("LOGNAME", pw
->pw_name
, 1);
701 setenv("USER", pw
->pw_name
, 1);
702 setenv("SHELL", sh
, 1);
703 setenv("HOME", pw
->pw_dir
, 1);
704 setenv("TERM", termname
, 1);
706 signal(SIGCHLD
, SIG_DFL
);
707 signal(SIGHUP
, SIG_DFL
);
708 signal(SIGINT
, SIG_DFL
);
709 signal(SIGQUIT
, SIG_DFL
);
710 signal(SIGTERM
, SIG_DFL
);
711 signal(SIGALRM
, SIG_DFL
);
723 if ((p
= waitpid(pid
, &stat
, WNOHANG
)) < 0)
724 die("waiting for pid %hd failed: %s\n", pid
, strerror(errno
));
729 if (WIFEXITED(stat
) && WEXITSTATUS(stat
))
730 die("child exited with status %d\n", WEXITSTATUS(stat
));
731 else if (WIFSIGNALED(stat
))
732 die("child terminated due to signal %d\n", WTERMSIG(stat
));
739 char cmd
[_POSIX_ARG_MAX
], **p
, *q
, *s
;
742 if ((n
= strlen(stty_args
)) > sizeof(cmd
)-1)
743 die("incorrect stty parameters\n");
744 memcpy(cmd
, stty_args
, n
);
746 siz
= sizeof(cmd
) - n
;
747 for (p
= args
; p
&& (s
= *p
); ++p
) {
748 if ((n
= strlen(s
)) > siz
-1)
749 die("stty parameter length too long\n");
756 if (system(cmd
) != 0)
757 perror("Couldn't call stty");
761 ttynew(char *line
, char *cmd
, char *out
, char **args
)
766 term
.mode
|= MODE_PRINT
;
767 iofd
= (!strcmp(out
, "-")) ?
768 1 : open(out
, O_WRONLY
| O_CREAT
, 0666);
770 fprintf(stderr
, "Error opening %s:%s\n",
771 out
, strerror(errno
));
776 if ((cmdfd
= open(line
, O_RDWR
)) < 0)
777 die("open line '%s' failed: %s\n",
778 line
, strerror(errno
));
784 /* seems to work fine on linux, openbsd and freebsd */
785 if (openpty(&m
, &s
, NULL
, NULL
, NULL
) < 0)
786 die("openpty failed: %s\n", strerror(errno
));
788 switch (pid
= fork()) {
790 die("fork failed: %s\n", strerror(errno
));
794 setsid(); /* create a new process group */
798 if (ioctl(s
, TIOCSCTTY
, NULL
) < 0)
799 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno
));
803 if (pledge("stdio getpw proc exec", NULL
) == -1)
810 if (pledge("stdio rpath tty proc", NULL
) == -1)
815 signal(SIGCHLD
, sigchld
);
824 static char buf
[BUFSIZ
];
825 static int buflen
= 0;
829 /* append read bytes to unprocessed bytes */
830 ret
= read(cmdfd
, buf
+buflen
, LEN(buf
)-buflen
);
834 fputs("Found EOF in input\n", stderr
);
837 die("couldn't read from shell: %s\n", strerror(errno
));
840 written
= twrite(buf
, buflen
, 0);
842 /* keep any uncomplete utf8 char for the next call */
844 memmove(buf
, buf
+ written
, buflen
);
851 ttywrite(const char *s
, size_t n
, int may_echo
)
855 if (may_echo
&& IS_SET(MODE_ECHO
))
858 if (!IS_SET(MODE_CRLF
)) {
863 /* This is similar to how the kernel handles ONLCR for ttys */
867 ttywriteraw("\r\n", 2);
869 next
= memchr(s
, '\r', n
);
870 DEFAULT(next
, s
+ n
);
871 ttywriteraw(s
, next
- s
);
879 ttywriteraw(const char *s
, size_t n
)
886 * Remember that we are using a pty, which might be a modem line.
887 * Writing too much will clog the line. That's why we are doing this
889 * FIXME: Migrate the world to Plan 9.
897 /* Check if we can write. */
898 if (pselect(cmdfd
+1, &rfd
, &wfd
, NULL
, NULL
, NULL
) < 0) {
901 die("select failed: %s\n", strerror(errno
));
903 if (FD_ISSET(cmdfd
, &wfd
)) {
905 * Only write the bytes written by ttywrite() or the
906 * default of 256. This seems to be a reasonable value
907 * for a serial line. Bigger values might clog the I/O.
909 if ((r
= write(cmdfd
, s
, (n
< lim
)? n
: lim
)) < 0)
913 * We weren't able to write out everything.
914 * This means the buffer is getting full
922 /* All bytes have been written. */
926 if (FD_ISSET(cmdfd
, &rfd
))
932 die("write error on tty: %s\n", strerror(errno
));
936 ttyresize(int tw
, int th
)
944 if (ioctl(cmdfd
, TIOCSWINSZ
, &w
) < 0)
945 fprintf(stderr
, "Couldn't set window size: %s\n", strerror(errno
));
951 /* Send SIGHUP to shell */
960 for (i
= 0; i
< term
.row
-1; i
++) {
961 for (j
= 0; j
< term
.col
-1; j
++) {
962 if (term
.line
[i
][j
].mode
& attr
)
971 tsetdirt(int top
, int bot
)
975 LIMIT(top
, 0, term
.row
-1);
976 LIMIT(bot
, 0, term
.row
-1);
978 for (i
= top
; i
<= bot
; i
++)
983 tsetdirtattr(int attr
)
987 for (i
= 0; i
< term
.row
-1; i
++) {
988 for (j
= 0; j
< term
.col
-1; j
++) {
989 if (term
.line
[i
][j
].mode
& attr
) {
1000 tsetdirt(0, term
.row
-1);
1006 static TCursor c
[2];
1007 int alt
= IS_SET(MODE_ALTSCREEN
);
1009 if (mode
== CURSOR_SAVE
) {
1011 } else if (mode
== CURSOR_LOAD
) {
1013 tmoveto(c
[alt
].x
, c
[alt
].y
);
1022 term
.c
= (TCursor
){{
1026 }, .x
= 0, .y
= 0, .state
= CURSOR_DEFAULT
};
1028 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1029 for (i
= tabspaces
; i
< term
.col
; i
+= tabspaces
)
1032 term
.bot
= term
.row
- 1;
1033 term
.mode
= MODE_WRAP
|MODE_UTF8
;
1034 memset(term
.trantbl
, CS_USA
, sizeof(term
.trantbl
));
1037 for (i
= 0; i
< 2; i
++) {
1039 tcursor(CURSOR_SAVE
);
1040 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1046 tnew(int col
, int row
)
1048 term
= (Term
){ .c
= { .attr
= { .fg
= defaultfg
, .bg
= defaultbg
} } };
1056 Line
*tmp
= term
.line
;
1058 term
.line
= term
.alt
;
1060 term
.mode
^= MODE_ALTSCREEN
;
1065 tscrolldown(int orig
, int n
)
1070 LIMIT(n
, 0, term
.bot
-orig
+1);
1072 tsetdirt(orig
, term
.bot
-n
);
1073 tclearregion(0, term
.bot
-n
+1, term
.col
-1, term
.bot
);
1075 for (i
= term
.bot
; i
>= orig
+n
; i
--) {
1076 temp
= term
.line
[i
];
1077 term
.line
[i
] = term
.line
[i
-n
];
1078 term
.line
[i
-n
] = temp
;
1085 tscrollup(int orig
, int n
)
1090 LIMIT(n
, 0, term
.bot
-orig
+1);
1092 tclearregion(0, orig
, term
.col
-1, orig
+n
-1);
1093 tsetdirt(orig
+n
, term
.bot
);
1095 for (i
= orig
; i
<= term
.bot
-n
; i
++) {
1096 temp
= term
.line
[i
];
1097 term
.line
[i
] = term
.line
[i
+n
];
1098 term
.line
[i
+n
] = temp
;
1101 selscroll(orig
, -n
);
1105 selscroll(int orig
, int n
)
1110 if (BETWEEN(sel
.ob
.y
, orig
, term
.bot
) || BETWEEN(sel
.oe
.y
, orig
, term
.bot
)) {
1111 if ((sel
.ob
.y
+= n
) > term
.bot
|| (sel
.oe
.y
+= n
) < term
.top
) {
1115 if (sel
.type
== SEL_RECTANGULAR
) {
1116 if (sel
.ob
.y
< term
.top
)
1117 sel
.ob
.y
= term
.top
;
1118 if (sel
.oe
.y
> term
.bot
)
1119 sel
.oe
.y
= term
.bot
;
1121 if (sel
.ob
.y
< term
.top
) {
1122 sel
.ob
.y
= term
.top
;
1125 if (sel
.oe
.y
> term
.bot
) {
1126 sel
.oe
.y
= term
.bot
;
1127 sel
.oe
.x
= term
.col
;
1135 tnewline(int first_col
)
1139 if (y
== term
.bot
) {
1140 tscrollup(term
.top
, 1);
1144 tmoveto(first_col
? 0 : term
.c
.x
, y
);
1150 char *p
= csiescseq
.buf
, *np
;
1159 csiescseq
.buf
[csiescseq
.len
] = '\0';
1160 while (p
< csiescseq
.buf
+csiescseq
.len
) {
1162 v
= strtol(p
, &np
, 10);
1165 if (v
== LONG_MAX
|| v
== LONG_MIN
)
1167 csiescseq
.arg
[csiescseq
.narg
++] = v
;
1169 if (*p
!= ';' || csiescseq
.narg
== ESC_ARG_SIZ
)
1173 csiescseq
.mode
[0] = *p
++;
1174 csiescseq
.mode
[1] = (p
< csiescseq
.buf
+csiescseq
.len
) ? *p
: '\0';
1177 /* for absolute user moves, when decom is set */
1179 tmoveato(int x
, int y
)
1181 tmoveto(x
, y
+ ((term
.c
.state
& CURSOR_ORIGIN
) ? term
.top
: 0));
1185 tmoveto(int x
, int y
)
1189 if (term
.c
.state
& CURSOR_ORIGIN
) {
1194 maxy
= term
.row
- 1;
1196 term
.c
.state
&= ~CURSOR_WRAPNEXT
;
1197 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1198 term
.c
.y
= LIMIT(y
, miny
, maxy
);
1202 tsetchar(Rune u
, Glyph
*attr
, int x
, int y
)
1204 static char *vt100_0
[62] = { /* 0x41 - 0x7e */
1205 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1206 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1207 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1208 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1209 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1210 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1211 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1212 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1216 * The table is proudly stolen from rxvt.
1218 if (term
.trantbl
[term
.charset
] == CS_GRAPHIC0
&&
1219 BETWEEN(u
, 0x41, 0x7e) && vt100_0
[u
- 0x41])
1220 utf8decode(vt100_0
[u
- 0x41], &u
, UTF_SIZ
);
1222 if (term
.line
[y
][x
].mode
& ATTR_WIDE
) {
1223 if (x
+1 < term
.col
) {
1224 term
.line
[y
][x
+1].u
= ' ';
1225 term
.line
[y
][x
+1].mode
&= ~ATTR_WDUMMY
;
1227 } else if (term
.line
[y
][x
].mode
& ATTR_WDUMMY
) {
1228 term
.line
[y
][x
-1].u
= ' ';
1229 term
.line
[y
][x
-1].mode
&= ~ATTR_WIDE
;
1233 term
.line
[y
][x
] = *attr
;
1234 term
.line
[y
][x
].u
= u
;
1238 tclearregion(int x1
, int y1
, int x2
, int y2
)
1244 temp
= x1
, x1
= x2
, x2
= temp
;
1246 temp
= y1
, y1
= y2
, y2
= temp
;
1248 LIMIT(x1
, 0, term
.col
-1);
1249 LIMIT(x2
, 0, term
.col
-1);
1250 LIMIT(y1
, 0, term
.row
-1);
1251 LIMIT(y2
, 0, term
.row
-1);
1253 for (y
= y1
; y
<= y2
; y
++) {
1255 for (x
= x1
; x
<= x2
; x
++) {
1256 gp
= &term
.line
[y
][x
];
1259 gp
->fg
= term
.c
.attr
.fg
;
1260 gp
->bg
= term
.c
.attr
.bg
;
1273 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1277 size
= term
.col
- src
;
1278 line
= term
.line
[term
.c
.y
];
1280 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1281 tclearregion(term
.col
-n
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1290 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1294 size
= term
.col
- dst
;
1295 line
= term
.line
[term
.c
.y
];
1297 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1298 tclearregion(src
, term
.c
.y
, dst
- 1, term
.c
.y
);
1302 tinsertblankline(int n
)
1304 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1305 tscrolldown(term
.c
.y
, n
);
1311 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1312 tscrollup(term
.c
.y
, n
);
1316 tdefcolor(int *attr
, int *npar
, int l
)
1321 switch (attr
[*npar
+ 1]) {
1322 case 2: /* direct color in RGB space */
1323 if (*npar
+ 4 >= l
) {
1325 "erresc(38): Incorrect number of parameters (%d)\n",
1329 r
= attr
[*npar
+ 2];
1330 g
= attr
[*npar
+ 3];
1331 b
= attr
[*npar
+ 4];
1333 if (!BETWEEN(r
, 0, 255) || !BETWEEN(g
, 0, 255) || !BETWEEN(b
, 0, 255))
1334 fprintf(stderr
, "erresc: bad rgb color (%u,%u,%u)\n",
1337 idx
= TRUECOLOR(r
, g
, b
);
1339 case 5: /* indexed color */
1340 if (*npar
+ 2 >= l
) {
1342 "erresc(38): Incorrect number of parameters (%d)\n",
1347 if (!BETWEEN(attr
[*npar
], 0, 255))
1348 fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[*npar
]);
1352 case 0: /* implemented defined (only foreground) */
1353 case 1: /* transparent */
1354 case 3: /* direct color in CMY space */
1355 case 4: /* direct color in CMYK space */
1358 "erresc(38): gfx attr %d unknown\n", attr
[*npar
]);
1366 tsetattr(int *attr
, int l
)
1371 for (i
= 0; i
< l
; i
++) {
1374 term
.c
.attr
.mode
&= ~(
1383 term
.c
.attr
.fg
= defaultfg
;
1384 term
.c
.attr
.bg
= defaultbg
;
1387 term
.c
.attr
.mode
|= ATTR_BOLD
;
1390 term
.c
.attr
.mode
|= ATTR_FAINT
;
1393 term
.c
.attr
.mode
|= ATTR_ITALIC
;
1396 term
.c
.attr
.mode
|= ATTR_UNDERLINE
;
1398 case 5: /* slow blink */
1400 case 6: /* rapid blink */
1401 term
.c
.attr
.mode
|= ATTR_BLINK
;
1404 term
.c
.attr
.mode
|= ATTR_REVERSE
;
1407 term
.c
.attr
.mode
|= ATTR_INVISIBLE
;
1410 term
.c
.attr
.mode
|= ATTR_STRUCK
;
1413 term
.c
.attr
.mode
&= ~(ATTR_BOLD
| ATTR_FAINT
);
1416 term
.c
.attr
.mode
&= ~ATTR_ITALIC
;
1419 term
.c
.attr
.mode
&= ~ATTR_UNDERLINE
;
1422 term
.c
.attr
.mode
&= ~ATTR_BLINK
;
1425 term
.c
.attr
.mode
&= ~ATTR_REVERSE
;
1428 term
.c
.attr
.mode
&= ~ATTR_INVISIBLE
;
1431 term
.c
.attr
.mode
&= ~ATTR_STRUCK
;
1434 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1435 term
.c
.attr
.fg
= idx
;
1438 term
.c
.attr
.fg
= defaultfg
;
1441 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1442 term
.c
.attr
.bg
= idx
;
1445 term
.c
.attr
.bg
= defaultbg
;
1448 if (BETWEEN(attr
[i
], 30, 37)) {
1449 term
.c
.attr
.fg
= attr
[i
] - 30;
1450 } else if (BETWEEN(attr
[i
], 40, 47)) {
1451 term
.c
.attr
.bg
= attr
[i
] - 40;
1452 } else if (BETWEEN(attr
[i
], 90, 97)) {
1453 term
.c
.attr
.fg
= attr
[i
] - 90 + 8;
1454 } else if (BETWEEN(attr
[i
], 100, 107)) {
1455 term
.c
.attr
.bg
= attr
[i
] - 100 + 8;
1458 "erresc(default): gfx attr %d unknown\n",
1468 tsetscroll(int t
, int b
)
1472 LIMIT(t
, 0, term
.row
-1);
1473 LIMIT(b
, 0, term
.row
-1);
1484 tsetmode(int priv
, int set
, int *args
, int narg
)
1488 for (lim
= args
+ narg
; args
< lim
; ++args
) {
1491 case 1: /* DECCKM -- Cursor key */
1492 xsetmode(set
, MODE_APPCURSOR
);
1494 case 5: /* DECSCNM -- Reverse video */
1495 xsetmode(set
, MODE_REVERSE
);
1497 case 6: /* DECOM -- Origin */
1498 MODBIT(term
.c
.state
, set
, CURSOR_ORIGIN
);
1501 case 7: /* DECAWM -- Auto wrap */
1502 MODBIT(term
.mode
, set
, MODE_WRAP
);
1504 case 0: /* Error (IGNORED) */
1505 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1506 case 3: /* DECCOLM -- Column (IGNORED) */
1507 case 4: /* DECSCLM -- Scroll (IGNORED) */
1508 case 8: /* DECARM -- Auto repeat (IGNORED) */
1509 case 18: /* DECPFF -- Printer feed (IGNORED) */
1510 case 19: /* DECPEX -- Printer extent (IGNORED) */
1511 case 42: /* DECNRCM -- National characters (IGNORED) */
1512 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1514 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1515 xsetmode(!set
, MODE_HIDE
);
1517 case 9: /* X10 mouse compatibility mode */
1518 xsetpointermotion(0);
1519 xsetmode(0, MODE_MOUSE
);
1520 xsetmode(set
, MODE_MOUSEX10
);
1522 case 1000: /* 1000: report button press */
1523 xsetpointermotion(0);
1524 xsetmode(0, MODE_MOUSE
);
1525 xsetmode(set
, MODE_MOUSEBTN
);
1527 case 1002: /* 1002: report motion on button press */
1528 xsetpointermotion(0);
1529 xsetmode(0, MODE_MOUSE
);
1530 xsetmode(set
, MODE_MOUSEMOTION
);
1532 case 1003: /* 1003: enable all mouse motions */
1533 xsetpointermotion(set
);
1534 xsetmode(0, MODE_MOUSE
);
1535 xsetmode(set
, MODE_MOUSEMANY
);
1537 case 1004: /* 1004: send focus events to tty */
1538 xsetmode(set
, MODE_FOCUS
);
1540 case 1006: /* 1006: extended reporting mode */
1541 xsetmode(set
, MODE_MOUSESGR
);
1544 xsetmode(set
, MODE_8BIT
);
1546 case 1049: /* swap screen & set/restore cursor as xterm */
1547 if (!allowaltscreen
)
1549 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1551 case 47: /* swap screen */
1553 if (!allowaltscreen
)
1555 alt
= IS_SET(MODE_ALTSCREEN
);
1557 tclearregion(0, 0, term
.col
-1,
1560 if (set
^ alt
) /* set is always 1 or 0 */
1566 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1568 case 2004: /* 2004: bracketed paste mode */
1569 xsetmode(set
, MODE_BRCKTPASTE
);
1571 /* Not implemented mouse modes. See comments there. */
1572 case 1001: /* mouse highlight mode; can hang the
1573 terminal by design when implemented. */
1574 case 1005: /* UTF-8 mouse mode; will confuse
1575 applications not supporting UTF-8
1577 case 1015: /* urxvt mangled mouse mode; incompatible
1578 and can be mistaken for other control
1583 "erresc: unknown private set/reset mode %d\n",
1589 case 0: /* Error (IGNORED) */
1592 xsetmode(set
, MODE_KBDLOCK
);
1594 case 4: /* IRM -- Insertion-replacement */
1595 MODBIT(term
.mode
, set
, MODE_INSERT
);
1597 case 12: /* SRM -- Send/Receive */
1598 MODBIT(term
.mode
, !set
, MODE_ECHO
);
1600 case 20: /* LNM -- Linefeed/new line */
1601 MODBIT(term
.mode
, set
, MODE_CRLF
);
1605 "erresc: unknown set/reset mode %d\n",
1619 switch (csiescseq
.mode
[0]) {
1622 fprintf(stderr
, "erresc: unknown csi ");
1626 case '@': /* ICH -- Insert <n> blank char */
1627 DEFAULT(csiescseq
.arg
[0], 1);
1628 tinsertblank(csiescseq
.arg
[0]);
1630 case 'A': /* CUU -- Cursor <n> Up */
1631 DEFAULT(csiescseq
.arg
[0], 1);
1632 tmoveto(term
.c
.x
, term
.c
.y
-csiescseq
.arg
[0]);
1634 case 'B': /* CUD -- Cursor <n> Down */
1635 case 'e': /* VPR --Cursor <n> Down */
1636 DEFAULT(csiescseq
.arg
[0], 1);
1637 tmoveto(term
.c
.x
, term
.c
.y
+csiescseq
.arg
[0]);
1639 case 'i': /* MC -- Media Copy */
1640 switch (csiescseq
.arg
[0]) {
1645 tdumpline(term
.c
.y
);
1651 term
.mode
&= ~MODE_PRINT
;
1654 term
.mode
|= MODE_PRINT
;
1658 case 'c': /* DA -- Device Attributes */
1659 if (csiescseq
.arg
[0] == 0)
1660 ttywrite(vtiden
, strlen(vtiden
), 0);
1662 case 'C': /* CUF -- Cursor <n> Forward */
1663 case 'a': /* HPR -- Cursor <n> Forward */
1664 DEFAULT(csiescseq
.arg
[0], 1);
1665 tmoveto(term
.c
.x
+csiescseq
.arg
[0], term
.c
.y
);
1667 case 'D': /* CUB -- Cursor <n> Backward */
1668 DEFAULT(csiescseq
.arg
[0], 1);
1669 tmoveto(term
.c
.x
-csiescseq
.arg
[0], term
.c
.y
);
1671 case 'E': /* CNL -- Cursor <n> Down and first col */
1672 DEFAULT(csiescseq
.arg
[0], 1);
1673 tmoveto(0, term
.c
.y
+csiescseq
.arg
[0]);
1675 case 'F': /* CPL -- Cursor <n> Up and first col */
1676 DEFAULT(csiescseq
.arg
[0], 1);
1677 tmoveto(0, term
.c
.y
-csiescseq
.arg
[0]);
1679 case 'g': /* TBC -- Tabulation clear */
1680 switch (csiescseq
.arg
[0]) {
1681 case 0: /* clear current tab stop */
1682 term
.tabs
[term
.c
.x
] = 0;
1684 case 3: /* clear all the tabs */
1685 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1691 case 'G': /* CHA -- Move to <col> */
1693 DEFAULT(csiescseq
.arg
[0], 1);
1694 tmoveto(csiescseq
.arg
[0]-1, term
.c
.y
);
1696 case 'H': /* CUP -- Move to <row> <col> */
1698 DEFAULT(csiescseq
.arg
[0], 1);
1699 DEFAULT(csiescseq
.arg
[1], 1);
1700 tmoveato(csiescseq
.arg
[1]-1, csiescseq
.arg
[0]-1);
1702 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1703 DEFAULT(csiescseq
.arg
[0], 1);
1704 tputtab(csiescseq
.arg
[0]);
1706 case 'J': /* ED -- Clear screen */
1707 switch (csiescseq
.arg
[0]) {
1709 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1710 if (term
.c
.y
< term
.row
-1) {
1711 tclearregion(0, term
.c
.y
+1, term
.col
-1,
1717 tclearregion(0, 0, term
.col
-1, term
.c
.y
-1);
1718 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1721 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1727 case 'K': /* EL -- Clear line */
1728 switch (csiescseq
.arg
[0]) {
1730 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1,
1734 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1737 tclearregion(0, term
.c
.y
, term
.col
-1, term
.c
.y
);
1741 case 'S': /* SU -- Scroll <n> line up */
1742 DEFAULT(csiescseq
.arg
[0], 1);
1743 tscrollup(term
.top
, csiescseq
.arg
[0]);
1745 case 'T': /* SD -- Scroll <n> line down */
1746 DEFAULT(csiescseq
.arg
[0], 1);
1747 tscrolldown(term
.top
, csiescseq
.arg
[0]);
1749 case 'L': /* IL -- Insert <n> blank lines */
1750 DEFAULT(csiescseq
.arg
[0], 1);
1751 tinsertblankline(csiescseq
.arg
[0]);
1753 case 'l': /* RM -- Reset Mode */
1754 tsetmode(csiescseq
.priv
, 0, csiescseq
.arg
, csiescseq
.narg
);
1756 case 'M': /* DL -- Delete <n> lines */
1757 DEFAULT(csiescseq
.arg
[0], 1);
1758 tdeleteline(csiescseq
.arg
[0]);
1760 case 'X': /* ECH -- Erase <n> char */
1761 DEFAULT(csiescseq
.arg
[0], 1);
1762 tclearregion(term
.c
.x
, term
.c
.y
,
1763 term
.c
.x
+ csiescseq
.arg
[0] - 1, term
.c
.y
);
1765 case 'P': /* DCH -- Delete <n> char */
1766 DEFAULT(csiescseq
.arg
[0], 1);
1767 tdeletechar(csiescseq
.arg
[0]);
1769 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1770 DEFAULT(csiescseq
.arg
[0], 1);
1771 tputtab(-csiescseq
.arg
[0]);
1773 case 'd': /* VPA -- Move to <row> */
1774 DEFAULT(csiescseq
.arg
[0], 1);
1775 tmoveato(term
.c
.x
, csiescseq
.arg
[0]-1);
1777 case 'h': /* SM -- Set terminal mode */
1778 tsetmode(csiescseq
.priv
, 1, csiescseq
.arg
, csiescseq
.narg
);
1780 case 'm': /* SGR -- Terminal attribute (color) */
1781 tsetattr(csiescseq
.arg
, csiescseq
.narg
);
1783 case 'n': /* DSR – Device Status Report (cursor position) */
1784 if (csiescseq
.arg
[0] == 6) {
1785 len
= snprintf(buf
, sizeof(buf
),"\033[%i;%iR",
1786 term
.c
.y
+1, term
.c
.x
+1);
1787 ttywrite(buf
, len
, 0);
1790 case 'r': /* DECSTBM -- Set Scrolling Region */
1791 if (csiescseq
.priv
) {
1794 DEFAULT(csiescseq
.arg
[0], 1);
1795 DEFAULT(csiescseq
.arg
[1], term
.row
);
1796 tsetscroll(csiescseq
.arg
[0]-1, csiescseq
.arg
[1]-1);
1800 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1801 tcursor(CURSOR_SAVE
);
1803 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1804 tcursor(CURSOR_LOAD
);
1807 switch (csiescseq
.mode
[1]) {
1808 case 'q': /* DECSCUSR -- Set Cursor Style */
1809 if (xsetcursor(csiescseq
.arg
[0]))
1825 fprintf(stderr
, "ESC[");
1826 for (i
= 0; i
< csiescseq
.len
; i
++) {
1827 c
= csiescseq
.buf
[i
] & 0xff;
1830 } else if (c
== '\n') {
1831 fprintf(stderr
, "(\\n)");
1832 } else if (c
== '\r') {
1833 fprintf(stderr
, "(\\r)");
1834 } else if (c
== 0x1b) {
1835 fprintf(stderr
, "(\\e)");
1837 fprintf(stderr
, "(%02x)", c
);
1846 memset(&csiescseq
, 0, sizeof(csiescseq
));
1852 char *p
= NULL
, *dec
;
1855 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
1857 par
= (narg
= strescseq
.narg
) ? atoi(strescseq
.args
[0]) : 0;
1859 switch (strescseq
.type
) {
1860 case ']': /* OSC -- Operating System Command */
1866 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 if (par
== 104 && narg
<= 1)
1888 return; /* color reset without parameter */
1889 fprintf(stderr
, "erresc: invalid color j=%d, p=%s\n",
1890 j
, p
? p
: "(null)");
1893 * TODO if defaultbg color is changed, borders
1901 case 'k': /* old title set compatibility */
1902 xsettitle(strescseq
.args
[0]);
1904 case 'P': /* DCS -- Device Control String */
1905 term
.mode
|= ESC_DCS
;
1906 case '_': /* APC -- Application Program Command */
1907 case '^': /* PM -- Privacy Message */
1911 fprintf(stderr
, "erresc: unknown str ");
1919 char *p
= strescseq
.buf
;
1922 strescseq
.buf
[strescseq
.len
] = '\0';
1927 while (strescseq
.narg
< STR_ARG_SIZ
) {
1928 strescseq
.args
[strescseq
.narg
++] = p
;
1929 while ((c
= *p
) != ';' && c
!= '\0')
1943 fprintf(stderr
, "ESC%c", strescseq
.type
);
1944 for (i
= 0; i
< strescseq
.len
; i
++) {
1945 c
= strescseq
.buf
[i
] & 0xff;
1949 } else if (isprint(c
)) {
1951 } else if (c
== '\n') {
1952 fprintf(stderr
, "(\\n)");
1953 } else if (c
== '\r') {
1954 fprintf(stderr
, "(\\r)");
1955 } else if (c
== 0x1b) {
1956 fprintf(stderr
, "(\\e)");
1958 fprintf(stderr
, "(%02x)", c
);
1961 fprintf(stderr
, "ESC\\\n");
1967 strescseq
= (STREscape
){
1968 .buf
= xrealloc(strescseq
.buf
, STR_BUF_SIZ
),
1974 sendbreak(const Arg
*arg
)
1976 if (tcsendbreak(cmdfd
, 0))
1977 perror("Error sending break");
1981 tprinter(char *s
, size_t len
)
1983 if (iofd
!= -1 && xwrite(iofd
, s
, len
) < 0) {
1984 perror("Error writing to output file");
1991 toggleprinter(const Arg
*arg
)
1993 term
.mode
^= MODE_PRINT
;
1997 printscreen(const Arg
*arg
)
2003 printsel(const Arg
*arg
)
2013 if ((ptr
= getsel())) {
2014 tprinter(ptr
, strlen(ptr
));
2025 bp
= &term
.line
[n
][0];
2026 end
= &bp
[MIN(tlinelen(n
), term
.col
) - 1];
2027 if (bp
!= end
|| bp
->u
!= ' ') {
2028 for ( ;bp
<= end
; ++bp
)
2029 tprinter(buf
, utf8encode(bp
->u
, buf
));
2039 for (i
= 0; i
< term
.row
; ++i
)
2049 while (x
< term
.col
&& n
--)
2050 for (++x
; x
< term
.col
&& !term
.tabs
[x
]; ++x
)
2053 while (x
> 0 && n
++)
2054 for (--x
; x
> 0 && !term
.tabs
[x
]; --x
)
2057 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
2061 tdefutf8(char ascii
)
2064 term
.mode
|= MODE_UTF8
;
2065 else if (ascii
== '@')
2066 term
.mode
&= ~MODE_UTF8
;
2070 tdeftran(char ascii
)
2072 static char cs
[] = "0B";
2073 static int vcs
[] = {CS_GRAPHIC0
, CS_USA
};
2076 if ((p
= strchr(cs
, ascii
)) == NULL
) {
2077 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
2079 term
.trantbl
[term
.icharset
] = vcs
[p
- cs
];
2088 if (c
== '8') { /* DEC screen alignment test. */
2089 for (x
= 0; x
< term
.col
; ++x
) {
2090 for (y
= 0; y
< term
.row
; ++y
)
2091 tsetchar('E', &term
.c
.attr
, x
, y
);
2097 tstrsequence(uchar c
)
2102 case 0x90: /* DCS -- Device Control String */
2104 term
.esc
|= ESC_DCS
;
2106 case 0x9f: /* APC -- Application Program Command */
2109 case 0x9e: /* PM -- Privacy Message */
2112 case 0x9d: /* OSC -- Operating System Command */
2117 term
.esc
|= ESC_STR
;
2121 tcontrolcode(uchar ascii
)
2128 tmoveto(term
.c
.x
-1, term
.c
.y
);
2131 tmoveto(0, term
.c
.y
);
2136 /* go to first col if the mode is set */
2137 tnewline(IS_SET(MODE_CRLF
));
2139 case '\a': /* BEL */
2140 if (term
.esc
& ESC_STR_END
) {
2141 /* backwards compatibility to xterm */
2147 case '\033': /* ESC */
2149 term
.esc
&= ~(ESC_CSI
|ESC_ALTCHARSET
|ESC_TEST
);
2150 term
.esc
|= ESC_START
;
2152 case '\016': /* SO (LS1 -- Locking shift 1) */
2153 case '\017': /* SI (LS0 -- Locking shift 0) */
2154 term
.charset
= 1 - (ascii
- '\016');
2156 case '\032': /* SUB */
2157 tsetchar('?', &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2158 case '\030': /* CAN */
2161 case '\005': /* ENQ (IGNORED) */
2162 case '\000': /* NUL (IGNORED) */
2163 case '\021': /* XON (IGNORED) */
2164 case '\023': /* XOFF (IGNORED) */
2165 case 0177: /* DEL (IGNORED) */
2167 case 0x80: /* TODO: PAD */
2168 case 0x81: /* TODO: HOP */
2169 case 0x82: /* TODO: BPH */
2170 case 0x83: /* TODO: NBH */
2171 case 0x84: /* TODO: IND */
2173 case 0x85: /* NEL -- Next line */
2174 tnewline(1); /* always go to first col */
2176 case 0x86: /* TODO: SSA */
2177 case 0x87: /* TODO: ESA */
2179 case 0x88: /* HTS -- Horizontal tab stop */
2180 term
.tabs
[term
.c
.x
] = 1;
2182 case 0x89: /* TODO: HTJ */
2183 case 0x8a: /* TODO: VTS */
2184 case 0x8b: /* TODO: PLD */
2185 case 0x8c: /* TODO: PLU */
2186 case 0x8d: /* TODO: RI */
2187 case 0x8e: /* TODO: SS2 */
2188 case 0x8f: /* TODO: SS3 */
2189 case 0x91: /* TODO: PU1 */
2190 case 0x92: /* TODO: PU2 */
2191 case 0x93: /* TODO: STS */
2192 case 0x94: /* TODO: CCH */
2193 case 0x95: /* TODO: MW */
2194 case 0x96: /* TODO: SPA */
2195 case 0x97: /* TODO: EPA */
2196 case 0x98: /* TODO: SOS */
2197 case 0x99: /* TODO: SGCI */
2199 case 0x9a: /* DECID -- Identify Terminal */
2200 ttywrite(vtiden
, strlen(vtiden
), 0);
2202 case 0x9b: /* TODO: CSI */
2203 case 0x9c: /* TODO: ST */
2205 case 0x90: /* DCS -- Device Control String */
2206 case 0x9d: /* OSC -- Operating System Command */
2207 case 0x9e: /* PM -- Privacy Message */
2208 case 0x9f: /* APC -- Application Program Command */
2209 tstrsequence(ascii
);
2212 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2213 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
2217 * returns 1 when the sequence is finished and it hasn't to read
2218 * more characters for this sequence, otherwise 0
2221 eschandle(uchar ascii
)
2225 term
.esc
|= ESC_CSI
;
2228 term
.esc
|= ESC_TEST
;
2231 term
.esc
|= ESC_UTF8
;
2233 case 'P': /* DCS -- Device Control String */
2234 case '_': /* APC -- Application Program Command */
2235 case '^': /* PM -- Privacy Message */
2236 case ']': /* OSC -- Operating System Command */
2237 case 'k': /* old title set compatibility */
2238 tstrsequence(ascii
);
2240 case 'n': /* LS2 -- Locking shift 2 */
2241 case 'o': /* LS3 -- Locking shift 3 */
2242 term
.charset
= 2 + (ascii
- 'n');
2244 case '(': /* GZD4 -- set primary charset G0 */
2245 case ')': /* G1D4 -- set secondary charset G1 */
2246 case '*': /* G2D4 -- set tertiary charset G2 */
2247 case '+': /* G3D4 -- set quaternary charset G3 */
2248 term
.icharset
= ascii
- '(';
2249 term
.esc
|= ESC_ALTCHARSET
;
2251 case 'D': /* IND -- Linefeed */
2252 if (term
.c
.y
== term
.bot
) {
2253 tscrollup(term
.top
, 1);
2255 tmoveto(term
.c
.x
, term
.c
.y
+1);
2258 case 'E': /* NEL -- Next line */
2259 tnewline(1); /* always go to first col */
2261 case 'H': /* HTS -- Horizontal tab stop */
2262 term
.tabs
[term
.c
.x
] = 1;
2264 case 'M': /* RI -- Reverse index */
2265 if (term
.c
.y
== term
.top
) {
2266 tscrolldown(term
.top
, 1);
2268 tmoveto(term
.c
.x
, term
.c
.y
-1);
2271 case 'Z': /* DECID -- Identify Terminal */
2272 ttywrite(vtiden
, strlen(vtiden
), 0);
2274 case 'c': /* RIS -- Reset to initial state */
2279 case '=': /* DECPAM -- Application keypad */
2280 xsetmode(1, MODE_APPKEYPAD
);
2282 case '>': /* DECPNM -- Normal keypad */
2283 xsetmode(0, MODE_APPKEYPAD
);
2285 case '7': /* DECSC -- Save Cursor */
2286 tcursor(CURSOR_SAVE
);
2288 case '8': /* DECRC -- Restore Cursor */
2289 tcursor(CURSOR_LOAD
);
2291 case '\\': /* ST -- String Terminator */
2292 if (term
.esc
& ESC_STR_END
)
2296 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2297 (uchar
) ascii
, isprint(ascii
)? ascii
:'.');
2311 control
= ISCONTROL(u
);
2312 if (!IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2316 len
= utf8encode(u
, c
);
2317 if (!control
&& (width
= wcwidth(u
)) == -1) {
2318 memcpy(c
, "\357\277\275", 4); /* UTF_INVALID */
2323 if (IS_SET(MODE_PRINT
))
2327 * STR sequence must be checked before anything else
2328 * because it uses all following characters until it
2329 * receives a ESC, a SUB, a ST or any other C1 control
2332 if (term
.esc
& ESC_STR
) {
2333 if (u
== '\a' || u
== 030 || u
== 032 || u
== 033 ||
2335 term
.esc
&= ~(ESC_START
|ESC_STR
|ESC_DCS
);
2336 if (IS_SET(MODE_SIXEL
)) {
2337 /* TODO: render sixel */;
2338 term
.mode
&= ~MODE_SIXEL
;
2341 term
.esc
|= ESC_STR_END
;
2342 goto check_control_code
;
2345 if (IS_SET(MODE_SIXEL
)) {
2346 /* TODO: implement sixel mode */
2349 if (term
.esc
&ESC_DCS
&& strescseq
.len
== 0 && u
== 'q')
2350 term
.mode
|= MODE_SIXEL
;
2352 if (strescseq
.len
+len
>= strescseq
.siz
) {
2354 * Here is a bug in terminals. If the user never sends
2355 * some code to stop the str or esc command, then st
2356 * will stop responding. But this is better than
2357 * silently failing with unknown characters. At least
2358 * then users will report back.
2360 * In the case users ever get fixed, here is the code:
2366 if (strescseq
.siz
> (SIZE_MAX
- UTF_SIZ
) / 2)
2369 strescseq
.buf
= xrealloc(strescseq
.buf
, strescseq
.siz
);
2372 memmove(&strescseq
.buf
[strescseq
.len
], c
, len
);
2373 strescseq
.len
+= len
;
2379 * Actions of control codes must be performed as soon they arrive
2380 * because they can be embedded inside a control sequence, and
2381 * they must not cause conflicts with sequences.
2386 * control codes are not shown ever
2389 } else if (term
.esc
& ESC_START
) {
2390 if (term
.esc
& ESC_CSI
) {
2391 csiescseq
.buf
[csiescseq
.len
++] = u
;
2392 if (BETWEEN(u
, 0x40, 0x7E)
2393 || csiescseq
.len
>= \
2394 sizeof(csiescseq
.buf
)-1) {
2400 } else if (term
.esc
& ESC_UTF8
) {
2402 } else if (term
.esc
& ESC_ALTCHARSET
) {
2404 } else if (term
.esc
& ESC_TEST
) {
2409 /* sequence already finished */
2413 * All characters which form part of a sequence are not
2418 if (sel
.ob
.x
!= -1 && BETWEEN(term
.c
.y
, sel
.ob
.y
, sel
.oe
.y
))
2421 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2422 if (IS_SET(MODE_WRAP
) && (term
.c
.state
& CURSOR_WRAPNEXT
)) {
2423 gp
->mode
|= ATTR_WRAP
;
2425 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2428 if (IS_SET(MODE_INSERT
) && term
.c
.x
+width
< term
.col
)
2429 memmove(gp
+width
, gp
, (term
.col
- term
.c
.x
- width
) * sizeof(Glyph
));
2431 if (term
.c
.x
+width
> term
.col
) {
2433 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2436 tsetchar(u
, &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2439 gp
->mode
|= ATTR_WIDE
;
2440 if (term
.c
.x
+1 < term
.col
) {
2442 gp
[1].mode
= ATTR_WDUMMY
;
2445 if (term
.c
.x
+width
< term
.col
) {
2446 tmoveto(term
.c
.x
+width
, term
.c
.y
);
2448 term
.c
.state
|= CURSOR_WRAPNEXT
;
2453 twrite(const char *buf
, int buflen
, int show_ctrl
)
2459 for (n
= 0; n
< buflen
; n
+= charsize
) {
2460 if (IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2461 /* process a complete utf8 char */
2462 charsize
= utf8decode(buf
+ n
, &u
, buflen
- n
);
2469 if (show_ctrl
&& ISCONTROL(u
)) {
2474 } else if (u
!= '\n' && u
!= '\r' && u
!= '\t') {
2485 tresize(int col
, int row
)
2488 int minrow
= MIN(row
, term
.row
);
2489 int mincol
= MIN(col
, term
.col
);
2493 if (col
< 1 || row
< 1) {
2495 "tresize: error resizing to %dx%d\n", col
, row
);
2500 * slide screen to keep cursor where we expect it -
2501 * tscrollup would work here, but we can optimize to
2502 * memmove because we're freeing the earlier lines
2504 for (i
= 0; i
<= term
.c
.y
- row
; i
++) {
2508 /* ensure that both src and dst are not NULL */
2510 memmove(term
.line
, term
.line
+ i
, row
* sizeof(Line
));
2511 memmove(term
.alt
, term
.alt
+ i
, row
* sizeof(Line
));
2513 for (i
+= row
; i
< term
.row
; i
++) {
2518 /* resize to new height */
2519 term
.line
= xrealloc(term
.line
, row
* sizeof(Line
));
2520 term
.alt
= xrealloc(term
.alt
, row
* sizeof(Line
));
2521 term
.dirty
= xrealloc(term
.dirty
, row
* sizeof(*term
.dirty
));
2522 term
.tabs
= xrealloc(term
.tabs
, col
* sizeof(*term
.tabs
));
2524 /* resize each row to new width, zero-pad if needed */
2525 for (i
= 0; i
< minrow
; i
++) {
2526 term
.line
[i
] = xrealloc(term
.line
[i
], col
* sizeof(Glyph
));
2527 term
.alt
[i
] = xrealloc(term
.alt
[i
], col
* sizeof(Glyph
));
2530 /* allocate any new rows */
2531 for (/* i = minrow */; i
< row
; i
++) {
2532 term
.line
[i
] = xmalloc(col
* sizeof(Glyph
));
2533 term
.alt
[i
] = xmalloc(col
* sizeof(Glyph
));
2535 if (col
> term
.col
) {
2536 bp
= term
.tabs
+ term
.col
;
2538 memset(bp
, 0, sizeof(*term
.tabs
) * (col
- term
.col
));
2539 while (--bp
> term
.tabs
&& !*bp
)
2541 for (bp
+= tabspaces
; bp
< term
.tabs
+ col
; bp
+= tabspaces
)
2544 /* update terminal size */
2547 /* reset scrolling region */
2548 tsetscroll(0, row
-1);
2549 /* make use of the LIMIT in tmoveto */
2550 tmoveto(term
.c
.x
, term
.c
.y
);
2551 /* Clearing both screens (it makes dirty all lines) */
2553 for (i
= 0; i
< 2; i
++) {
2554 if (mincol
< col
&& 0 < minrow
) {
2555 tclearregion(mincol
, 0, col
- 1, minrow
- 1);
2557 if (0 < col
&& minrow
< row
) {
2558 tclearregion(0, minrow
, col
- 1, row
- 1);
2561 tcursor(CURSOR_LOAD
);
2573 drawregion(int x1
, int y1
, int x2
, int y2
)
2576 for (y
= y1
; y
< y2
; y
++) {
2581 xdrawline(term
.line
[y
], x1
, y
, x2
);
2593 /* adjust cursor position */
2594 LIMIT(term
.ocx
, 0, term
.col
-1);
2595 LIMIT(term
.ocy
, 0, term
.row
-1);
2596 if (term
.line
[term
.ocy
][term
.ocx
].mode
& ATTR_WDUMMY
)
2598 if (term
.line
[term
.c
.y
][cx
].mode
& ATTR_WDUMMY
)
2601 drawregion(0, 0, term
.col
, term
.row
);
2602 xdrawcursor(cx
, term
.c
.y
, term
.line
[term
.c
.y
][cx
],
2603 term
.ocx
, term
.ocy
, term
.line
[term
.ocy
][term
.ocx
]);
2604 term
.ocx
= cx
, term
.ocy
= term
.c
.y
;
2606 xximspot(term
.ocx
, term
.ocy
);