Xinqi Bao's Git
f342e5c432fe48621a6f1e247df458f3fc5bea1e
1 /* See LICENSE for license details. */
12 #include <sys/ioctl.h>
13 #include <sys/select.h>
14 #include <sys/types.h>
26 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
28 #elif defined(__FreeBSD__) || defined(__DragonFly__)
33 #define UTF_INVALID 0xFFFD
35 #define ESC_BUF_SIZ (128*UTF_SIZ)
36 #define ESC_ARG_SIZ 16
37 #define STR_BUF_SIZ ESC_BUF_SIZ
38 #define STR_ARG_SIZ ESC_ARG_SIZ
41 #define IS_SET(flag) ((term.mode & (flag)) != 0)
42 #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == '\177')
43 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
44 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
45 #define ISDELIM(u) ((iswspace(u) || iswpunct(u)) && wcschr(extrawordchars, u) == NULL)
50 MODE_ALTSCREEN
= 1 << 2,
58 enum cursor_movement
{
82 ESC_STR
= 4, /* OSC, PM, APC */
84 ESC_STR_END
= 16, /* a final string was encountered */
85 ESC_TEST
= 32, /* Enter in test mode */
91 Glyph attr
; /* current char attributes */
102 * Selection variables:
103 * nb – normalized coordinates of the beginning of the selection
104 * ne – normalized coordinates of the end of the selection
105 * ob – original coordinates of the beginning of the selection
106 * oe – original coordinates of the end of the selection
115 /* Internal representation of the screen */
117 int row
; /* nb row */
118 int col
; /* nb col */
119 Line
*line
; /* screen */
120 Line
*alt
; /* alternate screen */
121 int *dirty
; /* dirtyness of lines */
122 TCursor c
; /* cursor */
123 int ocx
; /* old cursor col */
124 int ocy
; /* old cursor row */
125 int top
; /* top scroll limit */
126 int bot
; /* bottom scroll limit */
127 int mode
; /* terminal mode flags */
128 int esc
; /* escape state flags */
129 char trantbl
[4]; /* charset table translation */
130 int charset
; /* current charset */
131 int icharset
; /* selected charset for sequence */
135 /* CSI Escape sequence structs */
136 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
138 char buf
[ESC_BUF_SIZ
]; /* raw string */
139 int len
; /* raw string length */
141 int arg
[ESC_ARG_SIZ
];
142 int narg
; /* nb of args */
146 /* STR Escape sequence structs */
147 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
149 char type
; /* ESC type ... */
150 char buf
[STR_BUF_SIZ
]; /* raw string */
151 int 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
)) (*src
)++;
374 base64dec(const char *src
)
376 size_t in_len
= strlen(src
);
380 in_len
+= 4 - (in_len
% 4);
381 result
= dst
= xmalloc(in_len
/ 4 * 3 + 1);
383 int a
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
384 int b
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
385 int c
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
386 int d
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
388 *dst
++ = (a
<< 2) | ((b
& 0x30) >> 4);
391 *dst
++ = ((b
& 0x0f) << 4) | ((c
& 0x3c) >> 2);
394 *dst
++ = ((c
& 0x03) << 6) | d
;
413 if (term
.line
[y
][i
- 1].mode
& ATTR_WRAP
)
416 while (i
> 0 && term
.line
[y
][i
- 1].u
== ' ')
423 selstart(int col
, int row
, int snap
)
426 sel
.mode
= SEL_EMPTY
;
427 sel
.type
= SEL_REGULAR
;
428 sel
.alt
= IS_SET(MODE_ALTSCREEN
);
430 sel
.oe
.x
= sel
.ob
.x
= col
;
431 sel
.oe
.y
= sel
.ob
.y
= row
;
435 sel
.mode
= SEL_READY
;
436 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
440 selextend(int col
, int row
, int type
, int done
)
442 int oldey
, oldex
, oldsby
, oldsey
, oldtype
;
444 if (sel
.mode
== SEL_IDLE
)
446 if (done
&& sel
.mode
== SEL_EMPTY
) {
462 if (oldey
!= sel
.oe
.y
|| oldex
!= sel
.oe
.x
|| oldtype
!= sel
.type
)
463 tsetdirt(MIN(sel
.nb
.y
, oldsby
), MAX(sel
.ne
.y
, oldsey
));
465 sel
.mode
= done
? SEL_IDLE
: SEL_READY
;
473 if (sel
.type
== SEL_REGULAR
&& sel
.ob
.y
!= sel
.oe
.y
) {
474 sel
.nb
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.ob
.x
: sel
.oe
.x
;
475 sel
.ne
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.oe
.x
: sel
.ob
.x
;
477 sel
.nb
.x
= MIN(sel
.ob
.x
, sel
.oe
.x
);
478 sel
.ne
.x
= MAX(sel
.ob
.x
, sel
.oe
.x
);
480 sel
.nb
.y
= MIN(sel
.ob
.y
, sel
.oe
.y
);
481 sel
.ne
.y
= MAX(sel
.ob
.y
, sel
.oe
.y
);
483 selsnap(&sel
.nb
.x
, &sel
.nb
.y
, -1);
484 selsnap(&sel
.ne
.x
, &sel
.ne
.y
, +1);
486 /* expand selection over line breaks */
487 if (sel
.type
== SEL_RECTANGULAR
)
489 i
= tlinelen(sel
.nb
.y
);
492 if (tlinelen(sel
.ne
.y
) <= sel
.ne
.x
)
493 sel
.ne
.x
= term
.col
- 1;
497 selected(int x
, int y
)
499 if (sel
.mode
== SEL_EMPTY
|| sel
.ob
.x
== -1 ||
500 sel
.alt
!= IS_SET(MODE_ALTSCREEN
))
503 if (sel
.type
== SEL_RECTANGULAR
)
504 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
505 && BETWEEN(x
, sel
.nb
.x
, sel
.ne
.x
);
507 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
508 && (y
!= sel
.nb
.y
|| x
>= sel
.nb
.x
)
509 && (y
!= sel
.ne
.y
|| x
<= sel
.ne
.x
);
513 selsnap(int *x
, int *y
, int direction
)
515 int newx
, newy
, xt
, yt
;
516 int delim
, prevdelim
;
522 * Snap around if the word wraps around at the end or
523 * beginning of a line.
525 prevgp
= &term
.line
[*y
][*x
];
526 prevdelim
= ISDELIM(prevgp
->u
);
528 newx
= *x
+ direction
;
530 if (!BETWEEN(newx
, 0, term
.col
- 1)) {
532 newx
= (newx
+ term
.col
) % term
.col
;
533 if (!BETWEEN(newy
, 0, term
.row
- 1))
539 yt
= newy
, xt
= newx
;
540 if (!(term
.line
[yt
][xt
].mode
& ATTR_WRAP
))
544 if (newx
>= tlinelen(newy
))
547 gp
= &term
.line
[newy
][newx
];
548 delim
= ISDELIM(gp
->u
);
549 if (!(gp
->mode
& ATTR_WDUMMY
) && (delim
!= prevdelim
550 || (delim
&& gp
->u
!= prevgp
->u
)))
561 * Snap around if the the previous line or the current one
562 * has set ATTR_WRAP at its end. Then the whole next or
563 * previous line will be selected.
565 *x
= (direction
< 0) ? 0 : term
.col
- 1;
567 for (; *y
> 0; *y
+= direction
) {
568 if (!(term
.line
[*y
-1][term
.col
-1].mode
573 } else if (direction
> 0) {
574 for (; *y
< term
.row
-1; *y
+= direction
) {
575 if (!(term
.line
[*y
][term
.col
-1].mode
589 int y
, bufsize
, lastx
, linelen
;
595 bufsize
= (term
.col
+1) * (sel
.ne
.y
-sel
.nb
.y
+1) * UTF_SIZ
;
596 ptr
= str
= xmalloc(bufsize
);
598 /* append every set & selected glyph to the selection */
599 for (y
= sel
.nb
.y
; y
<= sel
.ne
.y
; y
++) {
600 if ((linelen
= tlinelen(y
)) == 0) {
605 if (sel
.type
== SEL_RECTANGULAR
) {
606 gp
= &term
.line
[y
][sel
.nb
.x
];
609 gp
= &term
.line
[y
][sel
.nb
.y
== y
? sel
.nb
.x
: 0];
610 lastx
= (sel
.ne
.y
== y
) ? sel
.ne
.x
: term
.col
-1;
612 last
= &term
.line
[y
][MIN(lastx
, linelen
-1)];
613 while (last
>= gp
&& last
->u
== ' ')
616 for ( ; gp
<= last
; ++gp
) {
617 if (gp
->mode
& ATTR_WDUMMY
)
620 ptr
+= utf8encode(gp
->u
, ptr
);
624 * Copy and pasting of line endings is inconsistent
625 * in the inconsistent terminal and GUI world.
626 * The best solution seems like to produce '\n' when
627 * something is copied from st and convert '\n' to
628 * '\r', when something to be pasted is received by
630 * FIXME: Fix the computer world.
632 if ((y
< sel
.ne
.y
|| lastx
>= linelen
) && !(last
->mode
& ATTR_WRAP
))
646 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
650 die(const char *errstr
, ...)
654 va_start(ap
, errstr
);
655 vfprintf(stderr
, errstr
, ap
);
661 execsh(char *cmd
, char **args
)
664 const struct passwd
*pw
;
667 if ((pw
= getpwuid(getuid())) == NULL
) {
669 die("getpwuid: %s\n", strerror(errno
));
671 die("who are you?\n");
674 if ((sh
= getenv("SHELL")) == NULL
)
675 sh
= (pw
->pw_shell
[0]) ? pw
->pw_shell
: cmd
;
683 DEFAULT(args
, ((char *[]) {prog
, NULL
}));
688 setenv("LOGNAME", pw
->pw_name
, 1);
689 setenv("USER", pw
->pw_name
, 1);
690 setenv("SHELL", sh
, 1);
691 setenv("HOME", pw
->pw_dir
, 1);
692 setenv("TERM", termname
, 1);
694 signal(SIGCHLD
, SIG_DFL
);
695 signal(SIGHUP
, SIG_DFL
);
696 signal(SIGINT
, SIG_DFL
);
697 signal(SIGQUIT
, SIG_DFL
);
698 signal(SIGTERM
, SIG_DFL
);
699 signal(SIGALRM
, SIG_DFL
);
711 if ((p
= waitpid(pid
, &stat
, WNOHANG
)) < 0)
712 die("waiting for pid %hd failed: %s\n", pid
, strerror(errno
));
717 if (WIFEXITED(stat
) && WEXITSTATUS(stat
))
718 die("child exited with status %d\n", WEXITSTATUS(stat
));
719 else if (WIFSIGNALED(stat
))
720 die("child terminated due to signal %d\n", WTERMSIG(stat
));
727 char cmd
[_POSIX_ARG_MAX
], **p
, *q
, *s
;
730 if ((n
= strlen(stty_args
)) > sizeof(cmd
)-1)
731 die("incorrect stty parameters\n");
732 memcpy(cmd
, stty_args
, n
);
734 siz
= sizeof(cmd
) - n
;
735 for (p
= args
; p
&& (s
= *p
); ++p
) {
736 if ((n
= strlen(s
)) > siz
-1)
737 die("stty parameter length too long\n");
744 if (system(cmd
) != 0)
745 perror("Couldn't call stty");
749 ttynew(char *line
, char *cmd
, char *out
, char **args
)
754 term
.mode
|= MODE_PRINT
;
755 iofd
= (!strcmp(out
, "-")) ?
756 1 : open(out
, O_WRONLY
| O_CREAT
, 0666);
758 fprintf(stderr
, "Error opening %s:%s\n",
759 out
, strerror(errno
));
764 if ((cmdfd
= open(line
, O_RDWR
)) < 0)
765 die("open line '%s' failed: %s\n",
766 line
, strerror(errno
));
772 /* seems to work fine on linux, openbsd and freebsd */
773 if (openpty(&m
, &s
, NULL
, NULL
, NULL
) < 0)
774 die("openpty failed: %s\n", strerror(errno
));
776 switch (pid
= fork()) {
778 die("fork failed: %s\n", strerror(errno
));
782 setsid(); /* create a new process group */
786 if (ioctl(s
, TIOCSCTTY
, NULL
) < 0)
787 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno
));
791 if (pledge("stdio getpw proc exec", NULL
) == -1)
798 if (pledge("stdio rpath tty proc", NULL
) == -1)
803 signal(SIGCHLD
, sigchld
);
812 static char buf
[BUFSIZ
];
813 static int buflen
= 0;
817 /* append read bytes to unprocessed bytes */
818 if ((ret
= read(cmdfd
, buf
+buflen
, LEN(buf
)-buflen
)) < 0)
819 die("couldn't read from shell: %s\n", strerror(errno
));
822 written
= twrite(buf
, buflen
, 0);
824 /* keep any uncomplete utf8 char for the next call */
826 memmove(buf
, buf
+ written
, buflen
);
832 ttywrite(const char *s
, size_t n
, int may_echo
)
836 if (may_echo
&& IS_SET(MODE_ECHO
))
839 if (!IS_SET(MODE_CRLF
)) {
844 /* This is similar to how the kernel handles ONLCR for ttys */
848 ttywriteraw("\r\n", 2);
850 next
= memchr(s
, '\r', n
);
851 DEFAULT(next
, s
+ n
);
852 ttywriteraw(s
, next
- s
);
860 ttywriteraw(const char *s
, size_t n
)
867 * Remember that we are using a pty, which might be a modem line.
868 * Writing too much will clog the line. That's why we are doing this
870 * FIXME: Migrate the world to Plan 9.
878 /* Check if we can write. */
879 if (pselect(cmdfd
+1, &rfd
, &wfd
, NULL
, NULL
, NULL
) < 0) {
882 die("select failed: %s\n", strerror(errno
));
884 if (FD_ISSET(cmdfd
, &wfd
)) {
886 * Only write the bytes written by ttywrite() or the
887 * default of 256. This seems to be a reasonable value
888 * for a serial line. Bigger values might clog the I/O.
890 if ((r
= write(cmdfd
, s
, (n
< lim
)? n
: lim
)) < 0)
894 * We weren't able to write out everything.
895 * This means the buffer is getting full
903 /* All bytes have been written. */
907 if (FD_ISSET(cmdfd
, &rfd
))
913 die("write error on tty: %s\n", strerror(errno
));
917 ttyresize(int tw
, int th
)
925 if (ioctl(cmdfd
, TIOCSWINSZ
, &w
) < 0)
926 fprintf(stderr
, "Couldn't set window size: %s\n", strerror(errno
));
932 /* Send SIGHUP to shell */
941 for (i
= 0; i
< term
.row
-1; i
++) {
942 for (j
= 0; j
< term
.col
-1; j
++) {
943 if (term
.line
[i
][j
].mode
& attr
)
952 tsetdirt(int top
, int bot
)
956 LIMIT(top
, 0, term
.row
-1);
957 LIMIT(bot
, 0, term
.row
-1);
959 for (i
= top
; i
<= bot
; i
++)
964 tsetdirtattr(int attr
)
968 for (i
= 0; i
< term
.row
-1; i
++) {
969 for (j
= 0; j
< term
.col
-1; j
++) {
970 if (term
.line
[i
][j
].mode
& attr
) {
981 tsetdirt(0, term
.row
-1);
988 int alt
= IS_SET(MODE_ALTSCREEN
);
990 if (mode
== CURSOR_SAVE
) {
992 } else if (mode
== CURSOR_LOAD
) {
994 tmoveto(c
[alt
].x
, c
[alt
].y
);
1003 term
.c
= (TCursor
){{
1007 }, .x
= 0, .y
= 0, .state
= CURSOR_DEFAULT
};
1009 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1010 for (i
= tabspaces
; i
< term
.col
; i
+= tabspaces
)
1013 term
.bot
= term
.row
- 1;
1014 term
.mode
= MODE_WRAP
|MODE_UTF8
;
1015 memset(term
.trantbl
, CS_USA
, sizeof(term
.trantbl
));
1018 for (i
= 0; i
< 2; i
++) {
1020 tcursor(CURSOR_SAVE
);
1021 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1027 tnew(int col
, int row
)
1029 term
= (Term
){ .c
= { .attr
= { .fg
= defaultfg
, .bg
= defaultbg
} } };
1037 Line
*tmp
= term
.line
;
1039 term
.line
= term
.alt
;
1041 term
.mode
^= MODE_ALTSCREEN
;
1046 tscrolldown(int orig
, int n
)
1051 LIMIT(n
, 0, term
.bot
-orig
+1);
1053 tsetdirt(orig
, term
.bot
-n
);
1054 tclearregion(0, term
.bot
-n
+1, term
.col
-1, term
.bot
);
1056 for (i
= term
.bot
; i
>= orig
+n
; i
--) {
1057 temp
= term
.line
[i
];
1058 term
.line
[i
] = term
.line
[i
-n
];
1059 term
.line
[i
-n
] = temp
;
1066 tscrollup(int orig
, int n
)
1071 LIMIT(n
, 0, term
.bot
-orig
+1);
1073 tclearregion(0, orig
, term
.col
-1, orig
+n
-1);
1074 tsetdirt(orig
+n
, term
.bot
);
1076 for (i
= orig
; i
<= term
.bot
-n
; i
++) {
1077 temp
= term
.line
[i
];
1078 term
.line
[i
] = term
.line
[i
+n
];
1079 term
.line
[i
+n
] = temp
;
1082 selscroll(orig
, -n
);
1086 selscroll(int orig
, int n
)
1091 if (BETWEEN(sel
.ob
.y
, orig
, term
.bot
) || BETWEEN(sel
.oe
.y
, orig
, term
.bot
)) {
1092 if ((sel
.ob
.y
+= n
) > term
.bot
|| (sel
.oe
.y
+= n
) < term
.top
) {
1096 if (sel
.type
== SEL_RECTANGULAR
) {
1097 if (sel
.ob
.y
< term
.top
)
1098 sel
.ob
.y
= term
.top
;
1099 if (sel
.oe
.y
> term
.bot
)
1100 sel
.oe
.y
= term
.bot
;
1102 if (sel
.ob
.y
< term
.top
) {
1103 sel
.ob
.y
= term
.top
;
1106 if (sel
.oe
.y
> term
.bot
) {
1107 sel
.oe
.y
= term
.bot
;
1108 sel
.oe
.x
= term
.col
;
1116 tnewline(int first_col
)
1120 if (y
== term
.bot
) {
1121 tscrollup(term
.top
, 1);
1125 tmoveto(first_col
? 0 : term
.c
.x
, y
);
1131 char *p
= csiescseq
.buf
, *np
;
1140 csiescseq
.buf
[csiescseq
.len
] = '\0';
1141 while (p
< csiescseq
.buf
+csiescseq
.len
) {
1143 v
= strtol(p
, &np
, 10);
1146 if (v
== LONG_MAX
|| v
== LONG_MIN
)
1148 csiescseq
.arg
[csiescseq
.narg
++] = v
;
1150 if (*p
!= ';' || csiescseq
.narg
== ESC_ARG_SIZ
)
1154 csiescseq
.mode
[0] = *p
++;
1155 csiescseq
.mode
[1] = (p
< csiescseq
.buf
+csiescseq
.len
) ? *p
: '\0';
1158 /* for absolute user moves, when decom is set */
1160 tmoveato(int x
, int y
)
1162 tmoveto(x
, y
+ ((term
.c
.state
& CURSOR_ORIGIN
) ? term
.top
: 0));
1166 tmoveto(int x
, int y
)
1170 if (term
.c
.state
& CURSOR_ORIGIN
) {
1175 maxy
= term
.row
- 1;
1177 term
.c
.state
&= ~CURSOR_WRAPNEXT
;
1178 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1179 term
.c
.y
= LIMIT(y
, miny
, maxy
);
1183 tsetchar(Rune u
, Glyph
*attr
, int x
, int y
)
1185 static char *vt100_0
[62] = { /* 0x41 - 0x7e */
1186 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1187 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1188 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1189 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1190 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1191 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1192 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1193 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1197 * The table is proudly stolen from rxvt.
1199 if (term
.trantbl
[term
.charset
] == CS_GRAPHIC0
&&
1200 BETWEEN(u
, 0x41, 0x7e) && vt100_0
[u
- 0x41])
1201 utf8decode(vt100_0
[u
- 0x41], &u
, UTF_SIZ
);
1203 if (term
.line
[y
][x
].mode
& ATTR_WIDE
) {
1204 if (x
+1 < term
.col
) {
1205 term
.line
[y
][x
+1].u
= ' ';
1206 term
.line
[y
][x
+1].mode
&= ~ATTR_WDUMMY
;
1208 } else if (term
.line
[y
][x
].mode
& ATTR_WDUMMY
) {
1209 term
.line
[y
][x
-1].u
= ' ';
1210 term
.line
[y
][x
-1].mode
&= ~ATTR_WIDE
;
1214 term
.line
[y
][x
] = *attr
;
1215 term
.line
[y
][x
].u
= u
;
1219 tclearregion(int x1
, int y1
, int x2
, int y2
)
1225 temp
= x1
, x1
= x2
, x2
= temp
;
1227 temp
= y1
, y1
= y2
, y2
= temp
;
1229 LIMIT(x1
, 0, term
.col
-1);
1230 LIMIT(x2
, 0, term
.col
-1);
1231 LIMIT(y1
, 0, term
.row
-1);
1232 LIMIT(y2
, 0, term
.row
-1);
1234 for (y
= y1
; y
<= y2
; y
++) {
1236 for (x
= x1
; x
<= x2
; x
++) {
1237 gp
= &term
.line
[y
][x
];
1240 gp
->fg
= term
.c
.attr
.fg
;
1241 gp
->bg
= term
.c
.attr
.bg
;
1254 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1258 size
= term
.col
- src
;
1259 line
= term
.line
[term
.c
.y
];
1261 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1262 tclearregion(term
.col
-n
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1271 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1275 size
= term
.col
- dst
;
1276 line
= term
.line
[term
.c
.y
];
1278 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1279 tclearregion(src
, term
.c
.y
, dst
- 1, term
.c
.y
);
1283 tinsertblankline(int n
)
1285 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1286 tscrolldown(term
.c
.y
, n
);
1292 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1293 tscrollup(term
.c
.y
, n
);
1297 tdefcolor(int *attr
, int *npar
, int l
)
1302 switch (attr
[*npar
+ 1]) {
1303 case 2: /* direct color in RGB space */
1304 if (*npar
+ 4 >= l
) {
1306 "erresc(38): Incorrect number of parameters (%d)\n",
1310 r
= attr
[*npar
+ 2];
1311 g
= attr
[*npar
+ 3];
1312 b
= attr
[*npar
+ 4];
1314 if (!BETWEEN(r
, 0, 255) || !BETWEEN(g
, 0, 255) || !BETWEEN(b
, 0, 255))
1315 fprintf(stderr
, "erresc: bad rgb color (%u,%u,%u)\n",
1318 idx
= TRUECOLOR(r
, g
, b
);
1320 case 5: /* indexed color */
1321 if (*npar
+ 2 >= l
) {
1323 "erresc(38): Incorrect number of parameters (%d)\n",
1328 if (!BETWEEN(attr
[*npar
], 0, 255))
1329 fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[*npar
]);
1333 case 0: /* implemented defined (only foreground) */
1334 case 1: /* transparent */
1335 case 3: /* direct color in CMY space */
1336 case 4: /* direct color in CMYK space */
1339 "erresc(38): gfx attr %d unknown\n", attr
[*npar
]);
1347 tsetattr(int *attr
, int l
)
1352 for (i
= 0; i
< l
; i
++) {
1355 term
.c
.attr
.mode
&= ~(
1364 term
.c
.attr
.fg
= defaultfg
;
1365 term
.c
.attr
.bg
= defaultbg
;
1368 term
.c
.attr
.mode
|= ATTR_BOLD
;
1371 term
.c
.attr
.mode
|= ATTR_FAINT
;
1374 term
.c
.attr
.mode
|= ATTR_ITALIC
;
1377 term
.c
.attr
.mode
|= ATTR_UNDERLINE
;
1379 case 5: /* slow blink */
1381 case 6: /* rapid blink */
1382 term
.c
.attr
.mode
|= ATTR_BLINK
;
1385 term
.c
.attr
.mode
|= ATTR_REVERSE
;
1388 term
.c
.attr
.mode
|= ATTR_INVISIBLE
;
1391 term
.c
.attr
.mode
|= ATTR_STRUCK
;
1394 term
.c
.attr
.mode
&= ~(ATTR_BOLD
| ATTR_FAINT
);
1397 term
.c
.attr
.mode
&= ~ATTR_ITALIC
;
1400 term
.c
.attr
.mode
&= ~ATTR_UNDERLINE
;
1403 term
.c
.attr
.mode
&= ~ATTR_BLINK
;
1406 term
.c
.attr
.mode
&= ~ATTR_REVERSE
;
1409 term
.c
.attr
.mode
&= ~ATTR_INVISIBLE
;
1412 term
.c
.attr
.mode
&= ~ATTR_STRUCK
;
1415 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1416 term
.c
.attr
.fg
= idx
;
1419 term
.c
.attr
.fg
= defaultfg
;
1422 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1423 term
.c
.attr
.bg
= idx
;
1426 term
.c
.attr
.bg
= defaultbg
;
1429 if (BETWEEN(attr
[i
], 30, 37)) {
1430 term
.c
.attr
.fg
= attr
[i
] - 30;
1431 } else if (BETWEEN(attr
[i
], 40, 47)) {
1432 term
.c
.attr
.bg
= attr
[i
] - 40;
1433 } else if (BETWEEN(attr
[i
], 90, 97)) {
1434 term
.c
.attr
.fg
= attr
[i
] - 90 + 8;
1435 } else if (BETWEEN(attr
[i
], 100, 107)) {
1436 term
.c
.attr
.bg
= attr
[i
] - 100 + 8;
1439 "erresc(default): gfx attr %d unknown\n",
1449 tsetscroll(int t
, int b
)
1453 LIMIT(t
, 0, term
.row
-1);
1454 LIMIT(b
, 0, term
.row
-1);
1465 tsetmode(int priv
, int set
, int *args
, int narg
)
1469 for (lim
= args
+ narg
; args
< lim
; ++args
) {
1472 case 1: /* DECCKM -- Cursor key */
1473 xsetmode(set
, MODE_APPCURSOR
);
1475 case 5: /* DECSCNM -- Reverse video */
1476 xsetmode(set
, MODE_REVERSE
);
1478 case 6: /* DECOM -- Origin */
1479 MODBIT(term
.c
.state
, set
, CURSOR_ORIGIN
);
1482 case 7: /* DECAWM -- Auto wrap */
1483 MODBIT(term
.mode
, set
, MODE_WRAP
);
1485 case 0: /* Error (IGNORED) */
1486 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1487 case 3: /* DECCOLM -- Column (IGNORED) */
1488 case 4: /* DECSCLM -- Scroll (IGNORED) */
1489 case 8: /* DECARM -- Auto repeat (IGNORED) */
1490 case 18: /* DECPFF -- Printer feed (IGNORED) */
1491 case 19: /* DECPEX -- Printer extent (IGNORED) */
1492 case 42: /* DECNRCM -- National characters (IGNORED) */
1493 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1495 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1496 xsetmode(!set
, MODE_HIDE
);
1498 case 9: /* X10 mouse compatibility mode */
1499 xsetpointermotion(0);
1500 xsetmode(0, MODE_MOUSE
);
1501 xsetmode(set
, MODE_MOUSEX10
);
1503 case 1000: /* 1000: report button press */
1504 xsetpointermotion(0);
1505 xsetmode(0, MODE_MOUSE
);
1506 xsetmode(set
, MODE_MOUSEBTN
);
1508 case 1002: /* 1002: report motion on button press */
1509 xsetpointermotion(0);
1510 xsetmode(0, MODE_MOUSE
);
1511 xsetmode(set
, MODE_MOUSEMOTION
);
1513 case 1003: /* 1003: enable all mouse motions */
1514 xsetpointermotion(set
);
1515 xsetmode(0, MODE_MOUSE
);
1516 xsetmode(set
, MODE_MOUSEMANY
);
1518 case 1004: /* 1004: send focus events to tty */
1519 xsetmode(set
, MODE_FOCUS
);
1521 case 1006: /* 1006: extended reporting mode */
1522 xsetmode(set
, MODE_MOUSESGR
);
1525 xsetmode(set
, MODE_8BIT
);
1527 case 1049: /* swap screen & set/restore cursor as xterm */
1528 if (!allowaltscreen
)
1530 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1532 case 47: /* swap screen */
1534 if (!allowaltscreen
)
1536 alt
= IS_SET(MODE_ALTSCREEN
);
1538 tclearregion(0, 0, term
.col
-1,
1541 if (set
^ alt
) /* set is always 1 or 0 */
1547 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1549 case 2004: /* 2004: bracketed paste mode */
1550 xsetmode(set
, MODE_BRCKTPASTE
);
1552 /* Not implemented mouse modes. See comments there. */
1553 case 1001: /* mouse highlight mode; can hang the
1554 terminal by design when implemented. */
1555 case 1005: /* UTF-8 mouse mode; will confuse
1556 applications not supporting UTF-8
1558 case 1015: /* urxvt mangled mouse mode; incompatible
1559 and can be mistaken for other control
1564 "erresc: unknown private set/reset mode %d\n",
1570 case 0: /* Error (IGNORED) */
1573 xsetmode(set
, MODE_KBDLOCK
);
1575 case 4: /* IRM -- Insertion-replacement */
1576 MODBIT(term
.mode
, set
, MODE_INSERT
);
1578 case 12: /* SRM -- Send/Receive */
1579 MODBIT(term
.mode
, !set
, MODE_ECHO
);
1581 case 20: /* LNM -- Linefeed/new line */
1582 MODBIT(term
.mode
, set
, MODE_CRLF
);
1586 "erresc: unknown set/reset mode %d\n",
1600 switch (csiescseq
.mode
[0]) {
1603 fprintf(stderr
, "erresc: unknown csi ");
1607 case '@': /* ICH -- Insert <n> blank char */
1608 DEFAULT(csiescseq
.arg
[0], 1);
1609 tinsertblank(csiescseq
.arg
[0]);
1611 case 'A': /* CUU -- Cursor <n> Up */
1612 DEFAULT(csiescseq
.arg
[0], 1);
1613 tmoveto(term
.c
.x
, term
.c
.y
-csiescseq
.arg
[0]);
1615 case 'B': /* CUD -- Cursor <n> Down */
1616 case 'e': /* VPR --Cursor <n> Down */
1617 DEFAULT(csiescseq
.arg
[0], 1);
1618 tmoveto(term
.c
.x
, term
.c
.y
+csiescseq
.arg
[0]);
1620 case 'i': /* MC -- Media Copy */
1621 switch (csiescseq
.arg
[0]) {
1626 tdumpline(term
.c
.y
);
1632 term
.mode
&= ~MODE_PRINT
;
1635 term
.mode
|= MODE_PRINT
;
1639 case 'c': /* DA -- Device Attributes */
1640 if (csiescseq
.arg
[0] == 0)
1641 ttywrite(vtiden
, strlen(vtiden
), 0);
1643 case 'C': /* CUF -- Cursor <n> Forward */
1644 case 'a': /* HPR -- Cursor <n> Forward */
1645 DEFAULT(csiescseq
.arg
[0], 1);
1646 tmoveto(term
.c
.x
+csiescseq
.arg
[0], term
.c
.y
);
1648 case 'D': /* CUB -- Cursor <n> Backward */
1649 DEFAULT(csiescseq
.arg
[0], 1);
1650 tmoveto(term
.c
.x
-csiescseq
.arg
[0], term
.c
.y
);
1652 case 'E': /* CNL -- Cursor <n> Down and first col */
1653 DEFAULT(csiescseq
.arg
[0], 1);
1654 tmoveto(0, term
.c
.y
+csiescseq
.arg
[0]);
1656 case 'F': /* CPL -- Cursor <n> Up and first col */
1657 DEFAULT(csiescseq
.arg
[0], 1);
1658 tmoveto(0, term
.c
.y
-csiescseq
.arg
[0]);
1660 case 'g': /* TBC -- Tabulation clear */
1661 switch (csiescseq
.arg
[0]) {
1662 case 0: /* clear current tab stop */
1663 term
.tabs
[term
.c
.x
] = 0;
1665 case 3: /* clear all the tabs */
1666 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1672 case 'G': /* CHA -- Move to <col> */
1674 DEFAULT(csiescseq
.arg
[0], 1);
1675 tmoveto(csiescseq
.arg
[0]-1, term
.c
.y
);
1677 case 'H': /* CUP -- Move to <row> <col> */
1679 DEFAULT(csiescseq
.arg
[0], 1);
1680 DEFAULT(csiescseq
.arg
[1], 1);
1681 tmoveato(csiescseq
.arg
[1]-1, csiescseq
.arg
[0]-1);
1683 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1684 DEFAULT(csiescseq
.arg
[0], 1);
1685 tputtab(csiescseq
.arg
[0]);
1687 case 'J': /* ED -- Clear screen */
1688 switch (csiescseq
.arg
[0]) {
1690 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1691 if (term
.c
.y
< term
.row
-1) {
1692 tclearregion(0, term
.c
.y
+1, term
.col
-1,
1698 tclearregion(0, 0, term
.col
-1, term
.c
.y
-1);
1699 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1702 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1708 case 'K': /* EL -- Clear line */
1709 switch (csiescseq
.arg
[0]) {
1711 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1,
1715 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1718 tclearregion(0, term
.c
.y
, term
.col
-1, term
.c
.y
);
1722 case 'S': /* SU -- Scroll <n> line up */
1723 DEFAULT(csiescseq
.arg
[0], 1);
1724 tscrollup(term
.top
, csiescseq
.arg
[0]);
1726 case 'T': /* SD -- Scroll <n> line down */
1727 DEFAULT(csiescseq
.arg
[0], 1);
1728 tscrolldown(term
.top
, csiescseq
.arg
[0]);
1730 case 'L': /* IL -- Insert <n> blank lines */
1731 DEFAULT(csiescseq
.arg
[0], 1);
1732 tinsertblankline(csiescseq
.arg
[0]);
1734 case 'l': /* RM -- Reset Mode */
1735 tsetmode(csiescseq
.priv
, 0, csiescseq
.arg
, csiescseq
.narg
);
1737 case 'M': /* DL -- Delete <n> lines */
1738 DEFAULT(csiescseq
.arg
[0], 1);
1739 tdeleteline(csiescseq
.arg
[0]);
1741 case 'X': /* ECH -- Erase <n> char */
1742 DEFAULT(csiescseq
.arg
[0], 1);
1743 tclearregion(term
.c
.x
, term
.c
.y
,
1744 term
.c
.x
+ csiescseq
.arg
[0] - 1, term
.c
.y
);
1746 case 'P': /* DCH -- Delete <n> char */
1747 DEFAULT(csiescseq
.arg
[0], 1);
1748 tdeletechar(csiescseq
.arg
[0]);
1750 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1751 DEFAULT(csiescseq
.arg
[0], 1);
1752 tputtab(-csiescseq
.arg
[0]);
1754 case 'd': /* VPA -- Move to <row> */
1755 DEFAULT(csiescseq
.arg
[0], 1);
1756 tmoveato(term
.c
.x
, csiescseq
.arg
[0]-1);
1758 case 'h': /* SM -- Set terminal mode */
1759 tsetmode(csiescseq
.priv
, 1, csiescseq
.arg
, csiescseq
.narg
);
1761 case 'm': /* SGR -- Terminal attribute (color) */
1762 tsetattr(csiescseq
.arg
, csiescseq
.narg
);
1764 case 'n': /* DSR – Device Status Report (cursor position) */
1765 if (csiescseq
.arg
[0] == 6) {
1766 len
= snprintf(buf
, sizeof(buf
),"\033[%i;%iR",
1767 term
.c
.y
+1, term
.c
.x
+1);
1768 ttywrite(buf
, len
, 0);
1771 case 'r': /* DECSTBM -- Set Scrolling Region */
1772 if (csiescseq
.priv
) {
1775 DEFAULT(csiescseq
.arg
[0], 1);
1776 DEFAULT(csiescseq
.arg
[1], term
.row
);
1777 tsetscroll(csiescseq
.arg
[0]-1, csiescseq
.arg
[1]-1);
1781 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1782 tcursor(CURSOR_SAVE
);
1784 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1785 tcursor(CURSOR_LOAD
);
1788 switch (csiescseq
.mode
[1]) {
1789 case 'q': /* DECSCUSR -- Set Cursor Style */
1790 if (xsetcursor(csiescseq
.arg
[0]))
1806 fprintf(stderr
, "ESC[");
1807 for (i
= 0; i
< csiescseq
.len
; i
++) {
1808 c
= csiescseq
.buf
[i
] & 0xff;
1811 } else if (c
== '\n') {
1812 fprintf(stderr
, "(\\n)");
1813 } else if (c
== '\r') {
1814 fprintf(stderr
, "(\\r)");
1815 } else if (c
== 0x1b) {
1816 fprintf(stderr
, "(\\e)");
1818 fprintf(stderr
, "(%02x)", c
);
1827 memset(&csiescseq
, 0, sizeof(csiescseq
));
1833 char *p
= NULL
, *dec
;
1836 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
1838 par
= (narg
= strescseq
.narg
) ? atoi(strescseq
.args
[0]) : 0;
1840 switch (strescseq
.type
) {
1841 case ']': /* OSC -- Operating System Command */
1847 xsettitle(strescseq
.args
[1]);
1851 dec
= base64dec(strescseq
.args
[2]);
1856 fprintf(stderr
, "erresc: invalid base64\n");
1860 case 4: /* color set */
1863 p
= strescseq
.args
[2];
1865 case 104: /* color reset, here p = NULL */
1866 j
= (narg
> 1) ? atoi(strescseq
.args
[1]) : -1;
1867 if (xsetcolorname(j
, p
)) {
1868 fprintf(stderr
, "erresc: invalid color %s\n", p
);
1871 * TODO if defaultbg color is changed, borders
1879 case 'k': /* old title set compatibility */
1880 xsettitle(strescseq
.args
[0]);
1882 case 'P': /* DCS -- Device Control String */
1883 term
.mode
|= ESC_DCS
;
1884 case '_': /* APC -- Application Program Command */
1885 case '^': /* PM -- Privacy Message */
1889 fprintf(stderr
, "erresc: unknown str ");
1897 char *p
= strescseq
.buf
;
1900 strescseq
.buf
[strescseq
.len
] = '\0';
1905 while (strescseq
.narg
< STR_ARG_SIZ
) {
1906 strescseq
.args
[strescseq
.narg
++] = p
;
1907 while ((c
= *p
) != ';' && c
!= '\0')
1921 fprintf(stderr
, "ESC%c", strescseq
.type
);
1922 for (i
= 0; i
< strescseq
.len
; i
++) {
1923 c
= strescseq
.buf
[i
] & 0xff;
1927 } else if (isprint(c
)) {
1929 } else if (c
== '\n') {
1930 fprintf(stderr
, "(\\n)");
1931 } else if (c
== '\r') {
1932 fprintf(stderr
, "(\\r)");
1933 } else if (c
== 0x1b) {
1934 fprintf(stderr
, "(\\e)");
1936 fprintf(stderr
, "(%02x)", c
);
1939 fprintf(stderr
, "ESC\\\n");
1945 memset(&strescseq
, 0, sizeof(strescseq
));
1949 sendbreak(const Arg
*arg
)
1951 if (tcsendbreak(cmdfd
, 0))
1952 perror("Error sending break");
1956 tprinter(char *s
, size_t len
)
1958 if (iofd
!= -1 && xwrite(iofd
, s
, len
) < 0) {
1959 perror("Error writing to output file");
1966 toggleprinter(const Arg
*arg
)
1968 term
.mode
^= MODE_PRINT
;
1972 printscreen(const Arg
*arg
)
1978 printsel(const Arg
*arg
)
1988 if ((ptr
= getsel())) {
1989 tprinter(ptr
, strlen(ptr
));
2000 bp
= &term
.line
[n
][0];
2001 end
= &bp
[MIN(tlinelen(n
), term
.col
) - 1];
2002 if (bp
!= end
|| bp
->u
!= ' ') {
2003 for ( ;bp
<= end
; ++bp
)
2004 tprinter(buf
, utf8encode(bp
->u
, buf
));
2014 for (i
= 0; i
< term
.row
; ++i
)
2024 while (x
< term
.col
&& n
--)
2025 for (++x
; x
< term
.col
&& !term
.tabs
[x
]; ++x
)
2028 while (x
> 0 && n
++)
2029 for (--x
; x
> 0 && !term
.tabs
[x
]; --x
)
2032 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
2036 tdefutf8(char ascii
)
2039 term
.mode
|= MODE_UTF8
;
2040 else if (ascii
== '@')
2041 term
.mode
&= ~MODE_UTF8
;
2045 tdeftran(char ascii
)
2047 static char cs
[] = "0B";
2048 static int vcs
[] = {CS_GRAPHIC0
, CS_USA
};
2051 if ((p
= strchr(cs
, ascii
)) == NULL
) {
2052 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
2054 term
.trantbl
[term
.icharset
] = vcs
[p
- cs
];
2063 if (c
== '8') { /* DEC screen alignment test. */
2064 for (x
= 0; x
< term
.col
; ++x
) {
2065 for (y
= 0; y
< term
.row
; ++y
)
2066 tsetchar('E', &term
.c
.attr
, x
, y
);
2072 tstrsequence(uchar c
)
2077 case 0x90: /* DCS -- Device Control String */
2079 term
.esc
|= ESC_DCS
;
2081 case 0x9f: /* APC -- Application Program Command */
2084 case 0x9e: /* PM -- Privacy Message */
2087 case 0x9d: /* OSC -- Operating System Command */
2092 term
.esc
|= ESC_STR
;
2096 tcontrolcode(uchar ascii
)
2103 tmoveto(term
.c
.x
-1, term
.c
.y
);
2106 tmoveto(0, term
.c
.y
);
2111 /* go to first col if the mode is set */
2112 tnewline(IS_SET(MODE_CRLF
));
2114 case '\a': /* BEL */
2115 if (term
.esc
& ESC_STR_END
) {
2116 /* backwards compatibility to xterm */
2122 case '\033': /* ESC */
2124 term
.esc
&= ~(ESC_CSI
|ESC_ALTCHARSET
|ESC_TEST
);
2125 term
.esc
|= ESC_START
;
2127 case '\016': /* SO (LS1 -- Locking shift 1) */
2128 case '\017': /* SI (LS0 -- Locking shift 0) */
2129 term
.charset
= 1 - (ascii
- '\016');
2131 case '\032': /* SUB */
2132 tsetchar('?', &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2133 case '\030': /* CAN */
2136 case '\005': /* ENQ (IGNORED) */
2137 case '\000': /* NUL (IGNORED) */
2138 case '\021': /* XON (IGNORED) */
2139 case '\023': /* XOFF (IGNORED) */
2140 case 0177: /* DEL (IGNORED) */
2142 case 0x80: /* TODO: PAD */
2143 case 0x81: /* TODO: HOP */
2144 case 0x82: /* TODO: BPH */
2145 case 0x83: /* TODO: NBH */
2146 case 0x84: /* TODO: IND */
2148 case 0x85: /* NEL -- Next line */
2149 tnewline(1); /* always go to first col */
2151 case 0x86: /* TODO: SSA */
2152 case 0x87: /* TODO: ESA */
2154 case 0x88: /* HTS -- Horizontal tab stop */
2155 term
.tabs
[term
.c
.x
] = 1;
2157 case 0x89: /* TODO: HTJ */
2158 case 0x8a: /* TODO: VTS */
2159 case 0x8b: /* TODO: PLD */
2160 case 0x8c: /* TODO: PLU */
2161 case 0x8d: /* TODO: RI */
2162 case 0x8e: /* TODO: SS2 */
2163 case 0x8f: /* TODO: SS3 */
2164 case 0x91: /* TODO: PU1 */
2165 case 0x92: /* TODO: PU2 */
2166 case 0x93: /* TODO: STS */
2167 case 0x94: /* TODO: CCH */
2168 case 0x95: /* TODO: MW */
2169 case 0x96: /* TODO: SPA */
2170 case 0x97: /* TODO: EPA */
2171 case 0x98: /* TODO: SOS */
2172 case 0x99: /* TODO: SGCI */
2174 case 0x9a: /* DECID -- Identify Terminal */
2175 ttywrite(vtiden
, strlen(vtiden
), 0);
2177 case 0x9b: /* TODO: CSI */
2178 case 0x9c: /* TODO: ST */
2180 case 0x90: /* DCS -- Device Control String */
2181 case 0x9d: /* OSC -- Operating System Command */
2182 case 0x9e: /* PM -- Privacy Message */
2183 case 0x9f: /* APC -- Application Program Command */
2184 tstrsequence(ascii
);
2187 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2188 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
2192 * returns 1 when the sequence is finished and it hasn't to read
2193 * more characters for this sequence, otherwise 0
2196 eschandle(uchar ascii
)
2200 term
.esc
|= ESC_CSI
;
2203 term
.esc
|= ESC_TEST
;
2206 term
.esc
|= ESC_UTF8
;
2208 case 'P': /* DCS -- Device Control String */
2209 case '_': /* APC -- Application Program Command */
2210 case '^': /* PM -- Privacy Message */
2211 case ']': /* OSC -- Operating System Command */
2212 case 'k': /* old title set compatibility */
2213 tstrsequence(ascii
);
2215 case 'n': /* LS2 -- Locking shift 2 */
2216 case 'o': /* LS3 -- Locking shift 3 */
2217 term
.charset
= 2 + (ascii
- 'n');
2219 case '(': /* GZD4 -- set primary charset G0 */
2220 case ')': /* G1D4 -- set secondary charset G1 */
2221 case '*': /* G2D4 -- set tertiary charset G2 */
2222 case '+': /* G3D4 -- set quaternary charset G3 */
2223 term
.icharset
= ascii
- '(';
2224 term
.esc
|= ESC_ALTCHARSET
;
2226 case 'D': /* IND -- Linefeed */
2227 if (term
.c
.y
== term
.bot
) {
2228 tscrollup(term
.top
, 1);
2230 tmoveto(term
.c
.x
, term
.c
.y
+1);
2233 case 'E': /* NEL -- Next line */
2234 tnewline(1); /* always go to first col */
2236 case 'H': /* HTS -- Horizontal tab stop */
2237 term
.tabs
[term
.c
.x
] = 1;
2239 case 'M': /* RI -- Reverse index */
2240 if (term
.c
.y
== term
.top
) {
2241 tscrolldown(term
.top
, 1);
2243 tmoveto(term
.c
.x
, term
.c
.y
-1);
2246 case 'Z': /* DECID -- Identify Terminal */
2247 ttywrite(vtiden
, strlen(vtiden
), 0);
2249 case 'c': /* RIS -- Reset to initial state */
2254 case '=': /* DECPAM -- Application keypad */
2255 xsetmode(1, MODE_APPKEYPAD
);
2257 case '>': /* DECPNM -- Normal keypad */
2258 xsetmode(0, MODE_APPKEYPAD
);
2260 case '7': /* DECSC -- Save Cursor */
2261 tcursor(CURSOR_SAVE
);
2263 case '8': /* DECRC -- Restore Cursor */
2264 tcursor(CURSOR_LOAD
);
2266 case '\\': /* ST -- String Terminator */
2267 if (term
.esc
& ESC_STR_END
)
2271 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2272 (uchar
) ascii
, isprint(ascii
)? ascii
:'.');
2286 control
= ISCONTROL(u
);
2287 if (!IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2291 len
= utf8encode(u
, c
);
2292 if (!control
&& (width
= wcwidth(u
)) == -1) {
2293 memcpy(c
, "\357\277\275", 4); /* UTF_INVALID */
2298 if (IS_SET(MODE_PRINT
))
2302 * STR sequence must be checked before anything else
2303 * because it uses all following characters until it
2304 * receives a ESC, a SUB, a ST or any other C1 control
2307 if (term
.esc
& ESC_STR
) {
2308 if (u
== '\a' || u
== 030 || u
== 032 || u
== 033 ||
2310 term
.esc
&= ~(ESC_START
|ESC_STR
|ESC_DCS
);
2311 if (IS_SET(MODE_SIXEL
)) {
2312 /* TODO: render sixel */;
2313 term
.mode
&= ~MODE_SIXEL
;
2316 term
.esc
|= ESC_STR_END
;
2317 goto check_control_code
;
2320 if (IS_SET(MODE_SIXEL
)) {
2321 /* TODO: implement sixel mode */
2324 if (term
.esc
&ESC_DCS
&& strescseq
.len
== 0 && u
== 'q')
2325 term
.mode
|= MODE_SIXEL
;
2327 if (strescseq
.len
+len
>= sizeof(strescseq
.buf
)-1) {
2329 * Here is a bug in terminals. If the user never sends
2330 * some code to stop the str or esc command, then st
2331 * will stop responding. But this is better than
2332 * silently failing with unknown characters. At least
2333 * then users will report back.
2335 * In the case users ever get fixed, here is the code:
2344 memmove(&strescseq
.buf
[strescseq
.len
], c
, len
);
2345 strescseq
.len
+= len
;
2351 * Actions of control codes must be performed as soon they arrive
2352 * because they can be embedded inside a control sequence, and
2353 * they must not cause conflicts with sequences.
2358 * control codes are not shown ever
2361 } else if (term
.esc
& ESC_START
) {
2362 if (term
.esc
& ESC_CSI
) {
2363 csiescseq
.buf
[csiescseq
.len
++] = u
;
2364 if (BETWEEN(u
, 0x40, 0x7E)
2365 || csiescseq
.len
>= \
2366 sizeof(csiescseq
.buf
)-1) {
2372 } else if (term
.esc
& ESC_UTF8
) {
2374 } else if (term
.esc
& ESC_ALTCHARSET
) {
2376 } else if (term
.esc
& ESC_TEST
) {
2381 /* sequence already finished */
2385 * All characters which form part of a sequence are not
2390 if (sel
.ob
.x
!= -1 && BETWEEN(term
.c
.y
, sel
.ob
.y
, sel
.oe
.y
))
2393 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2394 if (IS_SET(MODE_WRAP
) && (term
.c
.state
& CURSOR_WRAPNEXT
)) {
2395 gp
->mode
|= ATTR_WRAP
;
2397 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2400 if (IS_SET(MODE_INSERT
) && term
.c
.x
+width
< term
.col
)
2401 memmove(gp
+width
, gp
, (term
.col
- term
.c
.x
- width
) * sizeof(Glyph
));
2403 if (term
.c
.x
+width
> term
.col
) {
2405 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2408 tsetchar(u
, &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2411 gp
->mode
|= ATTR_WIDE
;
2412 if (term
.c
.x
+1 < term
.col
) {
2414 gp
[1].mode
= ATTR_WDUMMY
;
2417 if (term
.c
.x
+width
< term
.col
) {
2418 tmoveto(term
.c
.x
+width
, term
.c
.y
);
2420 term
.c
.state
|= CURSOR_WRAPNEXT
;
2425 twrite(const char *buf
, int buflen
, int show_ctrl
)
2431 for (n
= 0; n
< buflen
; n
+= charsize
) {
2432 if (IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2433 /* process a complete utf8 char */
2434 charsize
= utf8decode(buf
+ n
, &u
, buflen
- n
);
2441 if (show_ctrl
&& ISCONTROL(u
)) {
2446 } else if (u
!= '\n' && u
!= '\r' && u
!= '\t') {
2457 tresize(int col
, int row
)
2460 int minrow
= MIN(row
, term
.row
);
2461 int mincol
= MIN(col
, term
.col
);
2465 if (col
< 1 || row
< 1) {
2467 "tresize: error resizing to %dx%d\n", col
, row
);
2472 * slide screen to keep cursor where we expect it -
2473 * tscrollup would work here, but we can optimize to
2474 * memmove because we're freeing the earlier lines
2476 for (i
= 0; i
<= term
.c
.y
- row
; i
++) {
2480 /* ensure that both src and dst are not NULL */
2482 memmove(term
.line
, term
.line
+ i
, row
* sizeof(Line
));
2483 memmove(term
.alt
, term
.alt
+ i
, row
* sizeof(Line
));
2485 for (i
+= row
; i
< term
.row
; i
++) {
2490 /* resize to new height */
2491 term
.line
= xrealloc(term
.line
, row
* sizeof(Line
));
2492 term
.alt
= xrealloc(term
.alt
, row
* sizeof(Line
));
2493 term
.dirty
= xrealloc(term
.dirty
, row
* sizeof(*term
.dirty
));
2494 term
.tabs
= xrealloc(term
.tabs
, col
* sizeof(*term
.tabs
));
2496 /* resize each row to new width, zero-pad if needed */
2497 for (i
= 0; i
< minrow
; i
++) {
2498 term
.line
[i
] = xrealloc(term
.line
[i
], col
* sizeof(Glyph
));
2499 term
.alt
[i
] = xrealloc(term
.alt
[i
], col
* sizeof(Glyph
));
2502 /* allocate any new rows */
2503 for (/* i = minrow */; i
< row
; i
++) {
2504 term
.line
[i
] = xmalloc(col
* sizeof(Glyph
));
2505 term
.alt
[i
] = xmalloc(col
* sizeof(Glyph
));
2507 if (col
> term
.col
) {
2508 bp
= term
.tabs
+ term
.col
;
2510 memset(bp
, 0, sizeof(*term
.tabs
) * (col
- term
.col
));
2511 while (--bp
> term
.tabs
&& !*bp
)
2513 for (bp
+= tabspaces
; bp
< term
.tabs
+ col
; bp
+= tabspaces
)
2516 /* update terminal size */
2519 /* reset scrolling region */
2520 tsetscroll(0, row
-1);
2521 /* make use of the LIMIT in tmoveto */
2522 tmoveto(term
.c
.x
, term
.c
.y
);
2523 /* Clearing both screens (it makes dirty all lines) */
2525 for (i
= 0; i
< 2; i
++) {
2526 if (mincol
< col
&& 0 < minrow
) {
2527 tclearregion(mincol
, 0, col
- 1, minrow
- 1);
2529 if (0 < col
&& minrow
< row
) {
2530 tclearregion(0, minrow
, col
- 1, row
- 1);
2533 tcursor(CURSOR_LOAD
);
2545 drawregion(int x1
, int y1
, int x2
, int y2
)
2548 for (y
= y1
; y
< y2
; y
++) {
2553 xdrawline(term
.line
[y
], x1
, y
, x2
);
2565 /* adjust cursor position */
2566 LIMIT(term
.ocx
, 0, term
.col
-1);
2567 LIMIT(term
.ocy
, 0, term
.row
-1);
2568 if (term
.line
[term
.ocy
][term
.ocx
].mode
& ATTR_WDUMMY
)
2570 if (term
.line
[term
.c
.y
][cx
].mode
& ATTR_WDUMMY
)
2573 drawregion(0, 0, term
.col
, term
.row
);
2574 xdrawcursor(cx
, term
.c
.y
, term
.line
[term
.c
.y
][cx
],
2575 term
.ocx
, term
.ocy
, term
.line
[term
.ocy
][term
.ocx
]);
2576 term
.ocx
= cx
, term
.ocy
= term
.c
.y
;
2578 xximspot(term
.ocx
, term
.ocy
);