Xinqi Bao's Git
1 /* See LICENSE for license details. */
12 #include <sys/ioctl.h>
13 #include <sys/select.h>
14 #include <sys/types.h>
25 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
27 #elif defined(__FreeBSD__) || defined(__DragonFly__)
32 #define UTF_INVALID 0xFFFD
34 #define ESC_BUF_SIZ (128*UTF_SIZ)
35 #define ESC_ARG_SIZ 16
36 #define STR_BUF_SIZ ESC_BUF_SIZ
37 #define STR_ARG_SIZ ESC_ARG_SIZ
40 #define IS_SET(flag) ((term.mode & (flag)) != 0)
41 #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f)
42 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
43 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
44 #define ISDELIM(u) (u && wcschr(worddelimiters, u))
49 MODE_ALTSCREEN
= 1 << 2,
56 enum cursor_movement
{
80 ESC_STR
= 4, /* DCS, OSC, PM, APC */
82 ESC_STR_END
= 16, /* a final string was encountered */
83 ESC_TEST
= 32, /* Enter in test mode */
88 Glyph attr
; /* current char attributes */
99 * Selection variables:
100 * nb – normalized coordinates of the beginning of the selection
101 * ne – normalized coordinates of the end of the selection
102 * ob – original coordinates of the beginning of the selection
103 * oe – original coordinates of the end of the selection
112 /* Internal representation of the screen */
114 int row
; /* nb row */
115 int col
; /* nb col */
116 Line
*line
; /* screen */
117 Line
*alt
; /* alternate screen */
118 int *dirty
; /* dirtyness of lines */
119 TCursor c
; /* cursor */
120 int ocx
; /* old cursor col */
121 int ocy
; /* old cursor row */
122 int top
; /* top scroll limit */
123 int bot
; /* bottom scroll limit */
124 int mode
; /* terminal mode flags */
125 int esc
; /* escape state flags */
126 char trantbl
[4]; /* charset table translation */
127 int charset
; /* current charset */
128 int icharset
; /* selected charset for sequence */
130 Rune lastc
; /* last printed char outside of sequence, 0 if control */
133 /* CSI Escape sequence structs */
134 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
136 char buf
[ESC_BUF_SIZ
]; /* raw string */
137 size_t len
; /* raw string length */
139 int arg
[ESC_ARG_SIZ
];
140 int narg
; /* nb of args */
144 /* STR Escape sequence structs */
145 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
147 char type
; /* ESC type ... */
148 char *buf
; /* allocated raw string */
149 size_t siz
; /* allocation size */
150 size_t len
; /* raw string length */
151 char *args
[STR_ARG_SIZ
];
152 int narg
; /* nb of args */
155 static void execsh(char *, char **);
156 static void stty(char **);
157 static void sigchld(int);
158 static void ttywriteraw(const char *, size_t);
160 static void csidump(void);
161 static void csihandle(void);
162 static void csiparse(void);
163 static void csireset(void);
164 static int eschandle(uchar
);
165 static void strdump(void);
166 static void strhandle(void);
167 static void strparse(void);
168 static void strreset(void);
170 static void tprinter(char *, size_t);
171 static void tdumpsel(void);
172 static void tdumpline(int);
173 static void tdump(void);
174 static void tclearregion(int, int, int, int);
175 static void tcursor(int);
176 static void tdeletechar(int);
177 static void tdeleteline(int);
178 static void tinsertblank(int);
179 static void tinsertblankline(int);
180 static int tlinelen(int);
181 static void tmoveto(int, int);
182 static void tmoveato(int, int);
183 static void tnewline(int);
184 static void tputtab(int);
185 static void tputc(Rune
);
186 static void treset(void);
187 static void tscrollup(int, int);
188 static void tscrolldown(int, int);
189 static void tsetattr(const int *, int);
190 static void tsetchar(Rune
, const Glyph
*, int, int);
191 static void tsetdirt(int, int);
192 static void tsetscroll(int, int);
193 static void tswapscreen(void);
194 static void tsetmode(int, int, const int *, int);
195 static int twrite(const char *, int, int);
196 static void tfulldirt(void);
197 static void tcontrolcode(uchar
);
198 static void tdectest(char );
199 static void tdefutf8(char);
200 static int32_t tdefcolor(const int *, int *, int);
201 static void tdeftran(char);
202 static void tstrsequence(uchar
);
204 static void drawregion(int, int, int, int);
206 static void selnormalize(void);
207 static void selscroll(int, int);
208 static void selsnap(int *, int *, int);
210 static size_t utf8decode(const char *, Rune
*, size_t);
211 static Rune
utf8decodebyte(char, size_t *);
212 static char utf8encodebyte(Rune
, size_t);
213 static size_t utf8validate(Rune
*, size_t);
215 static char *base64dec(const char *);
216 static char base64dec_getc(const char **);
218 static ssize_t
xwrite(int, const char *, size_t);
222 static Selection sel
;
223 static CSIEscape csiescseq
;
224 static STREscape strescseq
;
229 static const uchar utfbyte
[UTF_SIZ
+ 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
230 static const uchar utfmask
[UTF_SIZ
+ 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
231 static const Rune utfmin
[UTF_SIZ
+ 1] = { 0, 0, 0x80, 0x800, 0x10000};
232 static const Rune utfmax
[UTF_SIZ
+ 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
235 xwrite(int fd
, const char *s
, size_t len
)
241 r
= write(fd
, s
, len
);
256 if (!(p
= malloc(len
)))
257 die("malloc: %s\n", strerror(errno
));
263 xrealloc(void *p
, size_t len
)
265 if ((p
= realloc(p
, len
)) == NULL
)
266 die("realloc: %s\n", strerror(errno
));
272 xstrdup(const char *s
)
276 if ((p
= strdup(s
)) == NULL
)
277 die("strdup: %s\n", strerror(errno
));
283 utf8decode(const char *c
, Rune
*u
, size_t clen
)
285 size_t i
, j
, len
, type
;
291 udecoded
= utf8decodebyte(c
[0], &len
);
292 if (!BETWEEN(len
, 1, UTF_SIZ
))
294 for (i
= 1, j
= 1; i
< clen
&& j
< len
; ++i
, ++j
) {
295 udecoded
= (udecoded
<< 6) | utf8decodebyte(c
[i
], &type
);
302 utf8validate(u
, len
);
308 utf8decodebyte(char c
, size_t *i
)
310 for (*i
= 0; *i
< LEN(utfmask
); ++(*i
))
311 if (((uchar
)c
& utfmask
[*i
]) == utfbyte
[*i
])
312 return (uchar
)c
& ~utfmask
[*i
];
318 utf8encode(Rune u
, char *c
)
322 len
= utf8validate(&u
, 0);
326 for (i
= len
- 1; i
!= 0; --i
) {
327 c
[i
] = utf8encodebyte(u
, 0);
330 c
[0] = utf8encodebyte(u
, len
);
336 utf8encodebyte(Rune u
, size_t i
)
338 return utfbyte
[i
] | (u
& ~utfmask
[i
]);
342 utf8validate(Rune
*u
, size_t i
)
344 if (!BETWEEN(*u
, utfmin
[i
], utfmax
[i
]) || BETWEEN(*u
, 0xD800, 0xDFFF))
346 for (i
= 1; *u
> utfmax
[i
]; ++i
)
352 static const char base64_digits
[] = {
353 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
354 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0,
355 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
356 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
357 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
358 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
364 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
368 base64dec_getc(const char **src
)
370 while (**src
&& !isprint(**src
))
372 return **src
? *((*src
)++) : '='; /* emulate padding if string ends */
376 base64dec(const char *src
)
378 size_t in_len
= strlen(src
);
382 in_len
+= 4 - (in_len
% 4);
383 result
= dst
= xmalloc(in_len
/ 4 * 3 + 1);
385 int a
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
386 int b
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
387 int c
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
388 int d
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
390 /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */
391 if (a
== -1 || b
== -1)
394 *dst
++ = (a
<< 2) | ((b
& 0x30) >> 4);
397 *dst
++ = ((b
& 0x0f) << 4) | ((c
& 0x3c) >> 2);
400 *dst
++ = ((c
& 0x03) << 6) | d
;
419 if (term
.line
[y
][i
- 1].mode
& ATTR_WRAP
)
422 while (i
> 0 && term
.line
[y
][i
- 1].u
== ' ')
429 selstart(int col
, int row
, int snap
)
432 sel
.mode
= SEL_EMPTY
;
433 sel
.type
= SEL_REGULAR
;
434 sel
.alt
= IS_SET(MODE_ALTSCREEN
);
436 sel
.oe
.x
= sel
.ob
.x
= col
;
437 sel
.oe
.y
= sel
.ob
.y
= row
;
441 sel
.mode
= SEL_READY
;
442 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
446 selextend(int col
, int row
, int type
, int done
)
448 int oldey
, oldex
, oldsby
, oldsey
, oldtype
;
450 if (sel
.mode
== SEL_IDLE
)
452 if (done
&& sel
.mode
== SEL_EMPTY
) {
468 if (oldey
!= sel
.oe
.y
|| oldex
!= sel
.oe
.x
|| oldtype
!= sel
.type
|| sel
.mode
== SEL_EMPTY
)
469 tsetdirt(MIN(sel
.nb
.y
, oldsby
), MAX(sel
.ne
.y
, oldsey
));
471 sel
.mode
= done
? SEL_IDLE
: SEL_READY
;
479 if (sel
.type
== SEL_REGULAR
&& sel
.ob
.y
!= sel
.oe
.y
) {
480 sel
.nb
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.ob
.x
: sel
.oe
.x
;
481 sel
.ne
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.oe
.x
: sel
.ob
.x
;
483 sel
.nb
.x
= MIN(sel
.ob
.x
, sel
.oe
.x
);
484 sel
.ne
.x
= MAX(sel
.ob
.x
, sel
.oe
.x
);
486 sel
.nb
.y
= MIN(sel
.ob
.y
, sel
.oe
.y
);
487 sel
.ne
.y
= MAX(sel
.ob
.y
, sel
.oe
.y
);
489 selsnap(&sel
.nb
.x
, &sel
.nb
.y
, -1);
490 selsnap(&sel
.ne
.x
, &sel
.ne
.y
, +1);
492 /* expand selection over line breaks */
493 if (sel
.type
== SEL_RECTANGULAR
)
495 i
= tlinelen(sel
.nb
.y
);
498 if (tlinelen(sel
.ne
.y
) <= sel
.ne
.x
)
499 sel
.ne
.x
= term
.col
- 1;
503 selected(int x
, int y
)
505 if (sel
.mode
== SEL_EMPTY
|| sel
.ob
.x
== -1 ||
506 sel
.alt
!= IS_SET(MODE_ALTSCREEN
))
509 if (sel
.type
== SEL_RECTANGULAR
)
510 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
511 && BETWEEN(x
, sel
.nb
.x
, sel
.ne
.x
);
513 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
514 && (y
!= sel
.nb
.y
|| x
>= sel
.nb
.x
)
515 && (y
!= sel
.ne
.y
|| x
<= sel
.ne
.x
);
519 selsnap(int *x
, int *y
, int direction
)
521 int newx
, newy
, xt
, yt
;
522 int delim
, prevdelim
;
523 const Glyph
*gp
, *prevgp
;
528 * Snap around if the word wraps around at the end or
529 * beginning of a line.
531 prevgp
= &term
.line
[*y
][*x
];
532 prevdelim
= ISDELIM(prevgp
->u
);
534 newx
= *x
+ direction
;
536 if (!BETWEEN(newx
, 0, term
.col
- 1)) {
538 newx
= (newx
+ term
.col
) % term
.col
;
539 if (!BETWEEN(newy
, 0, term
.row
- 1))
545 yt
= newy
, xt
= newx
;
546 if (!(term
.line
[yt
][xt
].mode
& ATTR_WRAP
))
550 if (newx
>= tlinelen(newy
))
553 gp
= &term
.line
[newy
][newx
];
554 delim
= ISDELIM(gp
->u
);
555 if (!(gp
->mode
& ATTR_WDUMMY
) && (delim
!= prevdelim
556 || (delim
&& gp
->u
!= prevgp
->u
)))
567 * Snap around if the the previous line or the current one
568 * has set ATTR_WRAP at its end. Then the whole next or
569 * previous line will be selected.
571 *x
= (direction
< 0) ? 0 : term
.col
- 1;
573 for (; *y
> 0; *y
+= direction
) {
574 if (!(term
.line
[*y
-1][term
.col
-1].mode
579 } else if (direction
> 0) {
580 for (; *y
< term
.row
-1; *y
+= direction
) {
581 if (!(term
.line
[*y
][term
.col
-1].mode
595 int y
, bufsize
, lastx
, linelen
;
596 const Glyph
*gp
, *last
;
601 bufsize
= (term
.col
+1) * (sel
.ne
.y
-sel
.nb
.y
+1) * UTF_SIZ
;
602 ptr
= str
= xmalloc(bufsize
);
604 /* append every set & selected glyph to the selection */
605 for (y
= sel
.nb
.y
; y
<= sel
.ne
.y
; y
++) {
606 if ((linelen
= tlinelen(y
)) == 0) {
611 if (sel
.type
== SEL_RECTANGULAR
) {
612 gp
= &term
.line
[y
][sel
.nb
.x
];
615 gp
= &term
.line
[y
][sel
.nb
.y
== y
? sel
.nb
.x
: 0];
616 lastx
= (sel
.ne
.y
== y
) ? sel
.ne
.x
: term
.col
-1;
618 last
= &term
.line
[y
][MIN(lastx
, linelen
-1)];
619 while (last
>= gp
&& last
->u
== ' ')
622 for ( ; gp
<= last
; ++gp
) {
623 if (gp
->mode
& ATTR_WDUMMY
)
626 ptr
+= utf8encode(gp
->u
, ptr
);
630 * Copy and pasting of line endings is inconsistent
631 * in the inconsistent terminal and GUI world.
632 * The best solution seems like to produce '\n' when
633 * something is copied from st and convert '\n' to
634 * '\r', when something to be pasted is received by
636 * FIXME: Fix the computer world.
638 if ((y
< sel
.ne
.y
|| lastx
>= linelen
) &&
639 (!(last
->mode
& ATTR_WRAP
) || sel
.type
== SEL_RECTANGULAR
))
653 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
657 die(const char *errstr
, ...)
661 va_start(ap
, errstr
);
662 vfprintf(stderr
, errstr
, ap
);
668 execsh(char *cmd
, char **args
)
670 char *sh
, *prog
, *arg
;
671 const struct passwd
*pw
;
674 if ((pw
= getpwuid(getuid())) == NULL
) {
676 die("getpwuid: %s\n", strerror(errno
));
678 die("who are you?\n");
681 if ((sh
= getenv("SHELL")) == NULL
)
682 sh
= (pw
->pw_shell
[0]) ? pw
->pw_shell
: cmd
;
689 arg
= utmp
? utmp
: sh
;
697 DEFAULT(args
, ((char *[]) {prog
, arg
, NULL
}));
702 setenv("LOGNAME", pw
->pw_name
, 1);
703 setenv("USER", pw
->pw_name
, 1);
704 setenv("SHELL", sh
, 1);
705 setenv("HOME", pw
->pw_dir
, 1);
706 setenv("TERM", termname
, 1);
708 signal(SIGCHLD
, SIG_DFL
);
709 signal(SIGHUP
, SIG_DFL
);
710 signal(SIGINT
, SIG_DFL
);
711 signal(SIGQUIT
, SIG_DFL
);
712 signal(SIGTERM
, SIG_DFL
);
713 signal(SIGALRM
, SIG_DFL
);
725 if ((p
= waitpid(pid
, &stat
, WNOHANG
)) < 0)
726 die("waiting for pid %hd failed: %s\n", pid
, strerror(errno
));
731 if (WIFEXITED(stat
) && WEXITSTATUS(stat
))
732 die("child exited with status %d\n", WEXITSTATUS(stat
));
733 else if (WIFSIGNALED(stat
))
734 die("child terminated due to signal %d\n", WTERMSIG(stat
));
741 char cmd
[_POSIX_ARG_MAX
], **p
, *q
, *s
;
744 if ((n
= strlen(stty_args
)) > sizeof(cmd
)-1)
745 die("incorrect stty parameters\n");
746 memcpy(cmd
, stty_args
, n
);
748 siz
= sizeof(cmd
) - n
;
749 for (p
= args
; p
&& (s
= *p
); ++p
) {
750 if ((n
= strlen(s
)) > siz
-1)
751 die("stty parameter length too long\n");
758 if (system(cmd
) != 0)
759 perror("Couldn't call stty");
763 ttynew(const char *line
, char *cmd
, const char *out
, char **args
)
768 term
.mode
|= MODE_PRINT
;
769 iofd
= (!strcmp(out
, "-")) ?
770 1 : open(out
, O_WRONLY
| O_CREAT
, 0666);
772 fprintf(stderr
, "Error opening %s:%s\n",
773 out
, strerror(errno
));
778 if ((cmdfd
= open(line
, O_RDWR
)) < 0)
779 die("open line '%s' failed: %s\n",
780 line
, strerror(errno
));
786 /* seems to work fine on linux, openbsd and freebsd */
787 if (openpty(&m
, &s
, NULL
, NULL
, NULL
) < 0)
788 die("openpty failed: %s\n", strerror(errno
));
790 switch (pid
= fork()) {
792 die("fork failed: %s\n", strerror(errno
));
797 setsid(); /* create a new process group */
801 if (ioctl(s
, TIOCSCTTY
, NULL
) < 0)
802 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno
));
806 if (pledge("stdio getpw proc exec", NULL
) == -1)
813 if (pledge("stdio rpath tty proc", NULL
) == -1)
818 signal(SIGCHLD
, sigchld
);
827 static char buf
[BUFSIZ
];
828 static int buflen
= 0;
831 /* append read bytes to unprocessed bytes */
832 ret
= read(cmdfd
, buf
+buflen
, LEN(buf
)-buflen
);
838 die("couldn't read from shell: %s\n", strerror(errno
));
841 written
= twrite(buf
, buflen
, 0);
843 /* keep any incomplete UTF-8 byte sequence for the next call */
845 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
.nb
.y
, orig
, term
.bot
) != BETWEEN(sel
.ne
.y
, orig
, term
.bot
)) {
1112 } else if (BETWEEN(sel
.nb
.y
, orig
, term
.bot
)) {
1115 if (sel
.ob
.y
< term
.top
|| sel
.ob
.y
> term
.bot
||
1116 sel
.oe
.y
< term
.top
|| sel
.oe
.y
> term
.bot
) {
1125 tnewline(int first_col
)
1129 if (y
== term
.bot
) {
1130 tscrollup(term
.top
, 1);
1134 tmoveto(first_col
? 0 : term
.c
.x
, y
);
1140 char *p
= csiescseq
.buf
, *np
;
1149 csiescseq
.buf
[csiescseq
.len
] = '\0';
1150 while (p
< csiescseq
.buf
+csiescseq
.len
) {
1152 v
= strtol(p
, &np
, 10);
1155 if (v
== LONG_MAX
|| v
== LONG_MIN
)
1157 csiescseq
.arg
[csiescseq
.narg
++] = v
;
1159 if (*p
!= ';' || csiescseq
.narg
== ESC_ARG_SIZ
)
1163 csiescseq
.mode
[0] = *p
++;
1164 csiescseq
.mode
[1] = (p
< csiescseq
.buf
+csiescseq
.len
) ? *p
: '\0';
1167 /* for absolute user moves, when decom is set */
1169 tmoveato(int x
, int y
)
1171 tmoveto(x
, y
+ ((term
.c
.state
& CURSOR_ORIGIN
) ? term
.top
: 0));
1175 tmoveto(int x
, int y
)
1179 if (term
.c
.state
& CURSOR_ORIGIN
) {
1184 maxy
= term
.row
- 1;
1186 term
.c
.state
&= ~CURSOR_WRAPNEXT
;
1187 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1188 term
.c
.y
= LIMIT(y
, miny
, maxy
);
1192 tsetchar(Rune u
, const Glyph
*attr
, int x
, int y
)
1194 static const char *vt100_0
[62] = { /* 0x41 - 0x7e */
1195 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1196 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1197 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1198 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1199 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1200 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1201 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1202 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1206 * The table is proudly stolen from rxvt.
1208 if (term
.trantbl
[term
.charset
] == CS_GRAPHIC0
&&
1209 BETWEEN(u
, 0x41, 0x7e) && vt100_0
[u
- 0x41])
1210 utf8decode(vt100_0
[u
- 0x41], &u
, UTF_SIZ
);
1212 if (term
.line
[y
][x
].mode
& ATTR_WIDE
) {
1213 if (x
+1 < term
.col
) {
1214 term
.line
[y
][x
+1].u
= ' ';
1215 term
.line
[y
][x
+1].mode
&= ~ATTR_WDUMMY
;
1217 } else if (term
.line
[y
][x
].mode
& ATTR_WDUMMY
) {
1218 term
.line
[y
][x
-1].u
= ' ';
1219 term
.line
[y
][x
-1].mode
&= ~ATTR_WIDE
;
1223 term
.line
[y
][x
] = *attr
;
1224 term
.line
[y
][x
].u
= u
;
1228 tclearregion(int x1
, int y1
, int x2
, int y2
)
1234 temp
= x1
, x1
= x2
, x2
= temp
;
1236 temp
= y1
, y1
= y2
, y2
= temp
;
1238 LIMIT(x1
, 0, term
.col
-1);
1239 LIMIT(x2
, 0, term
.col
-1);
1240 LIMIT(y1
, 0, term
.row
-1);
1241 LIMIT(y2
, 0, term
.row
-1);
1243 for (y
= y1
; y
<= y2
; y
++) {
1245 for (x
= x1
; x
<= x2
; x
++) {
1246 gp
= &term
.line
[y
][x
];
1249 gp
->fg
= term
.c
.attr
.fg
;
1250 gp
->bg
= term
.c
.attr
.bg
;
1263 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1267 size
= term
.col
- src
;
1268 line
= term
.line
[term
.c
.y
];
1270 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1271 tclearregion(term
.col
-n
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1280 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1284 size
= term
.col
- dst
;
1285 line
= term
.line
[term
.c
.y
];
1287 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1288 tclearregion(src
, term
.c
.y
, dst
- 1, term
.c
.y
);
1292 tinsertblankline(int n
)
1294 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1295 tscrolldown(term
.c
.y
, n
);
1301 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1302 tscrollup(term
.c
.y
, n
);
1306 tdefcolor(const int *attr
, int *npar
, int l
)
1311 switch (attr
[*npar
+ 1]) {
1312 case 2: /* direct color in RGB space */
1313 if (*npar
+ 4 >= l
) {
1315 "erresc(38): Incorrect number of parameters (%d)\n",
1319 r
= attr
[*npar
+ 2];
1320 g
= attr
[*npar
+ 3];
1321 b
= attr
[*npar
+ 4];
1323 if (!BETWEEN(r
, 0, 255) || !BETWEEN(g
, 0, 255) || !BETWEEN(b
, 0, 255))
1324 fprintf(stderr
, "erresc: bad rgb color (%u,%u,%u)\n",
1327 idx
= TRUECOLOR(r
, g
, b
);
1329 case 5: /* indexed color */
1330 if (*npar
+ 2 >= l
) {
1332 "erresc(38): Incorrect number of parameters (%d)\n",
1337 if (!BETWEEN(attr
[*npar
], 0, 255))
1338 fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[*npar
]);
1342 case 0: /* implemented defined (only foreground) */
1343 case 1: /* transparent */
1344 case 3: /* direct color in CMY space */
1345 case 4: /* direct color in CMYK space */
1348 "erresc(38): gfx attr %d unknown\n", attr
[*npar
]);
1356 tsetattr(const int *attr
, int l
)
1361 for (i
= 0; i
< l
; i
++) {
1364 term
.c
.attr
.mode
&= ~(
1373 term
.c
.attr
.fg
= defaultfg
;
1374 term
.c
.attr
.bg
= defaultbg
;
1377 term
.c
.attr
.mode
|= ATTR_BOLD
;
1380 term
.c
.attr
.mode
|= ATTR_FAINT
;
1383 term
.c
.attr
.mode
|= ATTR_ITALIC
;
1386 term
.c
.attr
.mode
|= ATTR_UNDERLINE
;
1388 case 5: /* slow blink */
1390 case 6: /* rapid blink */
1391 term
.c
.attr
.mode
|= ATTR_BLINK
;
1394 term
.c
.attr
.mode
|= ATTR_REVERSE
;
1397 term
.c
.attr
.mode
|= ATTR_INVISIBLE
;
1400 term
.c
.attr
.mode
|= ATTR_STRUCK
;
1403 term
.c
.attr
.mode
&= ~(ATTR_BOLD
| ATTR_FAINT
);
1406 term
.c
.attr
.mode
&= ~ATTR_ITALIC
;
1409 term
.c
.attr
.mode
&= ~ATTR_UNDERLINE
;
1412 term
.c
.attr
.mode
&= ~ATTR_BLINK
;
1415 term
.c
.attr
.mode
&= ~ATTR_REVERSE
;
1418 term
.c
.attr
.mode
&= ~ATTR_INVISIBLE
;
1421 term
.c
.attr
.mode
&= ~ATTR_STRUCK
;
1424 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1425 term
.c
.attr
.fg
= idx
;
1428 term
.c
.attr
.fg
= defaultfg
;
1431 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1432 term
.c
.attr
.bg
= idx
;
1435 term
.c
.attr
.bg
= defaultbg
;
1438 if (BETWEEN(attr
[i
], 30, 37)) {
1439 term
.c
.attr
.fg
= attr
[i
] - 30;
1440 } else if (BETWEEN(attr
[i
], 40, 47)) {
1441 term
.c
.attr
.bg
= attr
[i
] - 40;
1442 } else if (BETWEEN(attr
[i
], 90, 97)) {
1443 term
.c
.attr
.fg
= attr
[i
] - 90 + 8;
1444 } else if (BETWEEN(attr
[i
], 100, 107)) {
1445 term
.c
.attr
.bg
= attr
[i
] - 100 + 8;
1448 "erresc(default): gfx attr %d unknown\n",
1458 tsetscroll(int t
, int b
)
1462 LIMIT(t
, 0, term
.row
-1);
1463 LIMIT(b
, 0, term
.row
-1);
1474 tsetmode(int priv
, int set
, const int *args
, int narg
)
1476 int alt
; const int *lim
;
1478 for (lim
= args
+ narg
; args
< lim
; ++args
) {
1481 case 1: /* DECCKM -- Cursor key */
1482 xsetmode(set
, MODE_APPCURSOR
);
1484 case 5: /* DECSCNM -- Reverse video */
1485 xsetmode(set
, MODE_REVERSE
);
1487 case 6: /* DECOM -- Origin */
1488 MODBIT(term
.c
.state
, set
, CURSOR_ORIGIN
);
1491 case 7: /* DECAWM -- Auto wrap */
1492 MODBIT(term
.mode
, set
, MODE_WRAP
);
1494 case 0: /* Error (IGNORED) */
1495 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1496 case 3: /* DECCOLM -- Column (IGNORED) */
1497 case 4: /* DECSCLM -- Scroll (IGNORED) */
1498 case 8: /* DECARM -- Auto repeat (IGNORED) */
1499 case 18: /* DECPFF -- Printer feed (IGNORED) */
1500 case 19: /* DECPEX -- Printer extent (IGNORED) */
1501 case 42: /* DECNRCM -- National characters (IGNORED) */
1502 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1504 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1505 xsetmode(!set
, MODE_HIDE
);
1507 case 9: /* X10 mouse compatibility mode */
1508 xsetpointermotion(0);
1509 xsetmode(0, MODE_MOUSE
);
1510 xsetmode(set
, MODE_MOUSEX10
);
1512 case 1000: /* 1000: report button press */
1513 xsetpointermotion(0);
1514 xsetmode(0, MODE_MOUSE
);
1515 xsetmode(set
, MODE_MOUSEBTN
);
1517 case 1002: /* 1002: report motion on button press */
1518 xsetpointermotion(0);
1519 xsetmode(0, MODE_MOUSE
);
1520 xsetmode(set
, MODE_MOUSEMOTION
);
1522 case 1003: /* 1003: enable all mouse motions */
1523 xsetpointermotion(set
);
1524 xsetmode(0, MODE_MOUSE
);
1525 xsetmode(set
, MODE_MOUSEMANY
);
1527 case 1004: /* 1004: send focus events to tty */
1528 xsetmode(set
, MODE_FOCUS
);
1530 case 1006: /* 1006: extended reporting mode */
1531 xsetmode(set
, MODE_MOUSESGR
);
1534 xsetmode(set
, MODE_8BIT
);
1536 case 1049: /* swap screen & set/restore cursor as xterm */
1537 if (!allowaltscreen
)
1539 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1541 case 47: /* swap screen */
1543 if (!allowaltscreen
)
1545 alt
= IS_SET(MODE_ALTSCREEN
);
1547 tclearregion(0, 0, term
.col
-1,
1550 if (set
^ alt
) /* set is always 1 or 0 */
1556 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1558 case 2004: /* 2004: bracketed paste mode */
1559 xsetmode(set
, MODE_BRCKTPASTE
);
1561 /* Not implemented mouse modes. See comments there. */
1562 case 1001: /* mouse highlight mode; can hang the
1563 terminal by design when implemented. */
1564 case 1005: /* UTF-8 mouse mode; will confuse
1565 applications not supporting UTF-8
1567 case 1015: /* urxvt mangled mouse mode; incompatible
1568 and can be mistaken for other control
1573 "erresc: unknown private set/reset mode %d\n",
1579 case 0: /* Error (IGNORED) */
1582 xsetmode(set
, MODE_KBDLOCK
);
1584 case 4: /* IRM -- Insertion-replacement */
1585 MODBIT(term
.mode
, set
, MODE_INSERT
);
1587 case 12: /* SRM -- Send/Receive */
1588 MODBIT(term
.mode
, !set
, MODE_ECHO
);
1590 case 20: /* LNM -- Linefeed/new line */
1591 MODBIT(term
.mode
, set
, MODE_CRLF
);
1595 "erresc: unknown set/reset mode %d\n",
1609 switch (csiescseq
.mode
[0]) {
1612 fprintf(stderr
, "erresc: unknown csi ");
1616 case '@': /* ICH -- Insert <n> blank char */
1617 DEFAULT(csiescseq
.arg
[0], 1);
1618 tinsertblank(csiescseq
.arg
[0]);
1620 case 'A': /* CUU -- Cursor <n> Up */
1621 DEFAULT(csiescseq
.arg
[0], 1);
1622 tmoveto(term
.c
.x
, term
.c
.y
-csiescseq
.arg
[0]);
1624 case 'B': /* CUD -- Cursor <n> Down */
1625 case 'e': /* VPR --Cursor <n> Down */
1626 DEFAULT(csiescseq
.arg
[0], 1);
1627 tmoveto(term
.c
.x
, term
.c
.y
+csiescseq
.arg
[0]);
1629 case 'i': /* MC -- Media Copy */
1630 switch (csiescseq
.arg
[0]) {
1635 tdumpline(term
.c
.y
);
1641 term
.mode
&= ~MODE_PRINT
;
1644 term
.mode
|= MODE_PRINT
;
1648 case 'c': /* DA -- Device Attributes */
1649 if (csiescseq
.arg
[0] == 0)
1650 ttywrite(vtiden
, strlen(vtiden
), 0);
1652 case 'b': /* REP -- if last char is printable print it <n> more times */
1653 DEFAULT(csiescseq
.arg
[0], 1);
1655 while (csiescseq
.arg
[0]-- > 0)
1658 case 'C': /* CUF -- Cursor <n> Forward */
1659 case 'a': /* HPR -- Cursor <n> Forward */
1660 DEFAULT(csiescseq
.arg
[0], 1);
1661 tmoveto(term
.c
.x
+csiescseq
.arg
[0], term
.c
.y
);
1663 case 'D': /* CUB -- Cursor <n> Backward */
1664 DEFAULT(csiescseq
.arg
[0], 1);
1665 tmoveto(term
.c
.x
-csiescseq
.arg
[0], term
.c
.y
);
1667 case 'E': /* CNL -- Cursor <n> Down and first col */
1668 DEFAULT(csiescseq
.arg
[0], 1);
1669 tmoveto(0, term
.c
.y
+csiescseq
.arg
[0]);
1671 case 'F': /* CPL -- Cursor <n> Up and first col */
1672 DEFAULT(csiescseq
.arg
[0], 1);
1673 tmoveto(0, term
.c
.y
-csiescseq
.arg
[0]);
1675 case 'g': /* TBC -- Tabulation clear */
1676 switch (csiescseq
.arg
[0]) {
1677 case 0: /* clear current tab stop */
1678 term
.tabs
[term
.c
.x
] = 0;
1680 case 3: /* clear all the tabs */
1681 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1687 case 'G': /* CHA -- Move to <col> */
1689 DEFAULT(csiescseq
.arg
[0], 1);
1690 tmoveto(csiescseq
.arg
[0]-1, term
.c
.y
);
1692 case 'H': /* CUP -- Move to <row> <col> */
1694 DEFAULT(csiescseq
.arg
[0], 1);
1695 DEFAULT(csiescseq
.arg
[1], 1);
1696 tmoveato(csiescseq
.arg
[1]-1, csiescseq
.arg
[0]-1);
1698 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1699 DEFAULT(csiescseq
.arg
[0], 1);
1700 tputtab(csiescseq
.arg
[0]);
1702 case 'J': /* ED -- Clear screen */
1703 switch (csiescseq
.arg
[0]) {
1705 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1706 if (term
.c
.y
< term
.row
-1) {
1707 tclearregion(0, term
.c
.y
+1, term
.col
-1,
1713 tclearregion(0, 0, term
.col
-1, term
.c
.y
-1);
1714 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1717 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1723 case 'K': /* EL -- Clear line */
1724 switch (csiescseq
.arg
[0]) {
1726 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1,
1730 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1733 tclearregion(0, term
.c
.y
, term
.col
-1, term
.c
.y
);
1737 case 'S': /* SU -- Scroll <n> line up */
1738 DEFAULT(csiescseq
.arg
[0], 1);
1739 tscrollup(term
.top
, csiescseq
.arg
[0]);
1741 case 'T': /* SD -- Scroll <n> line down */
1742 DEFAULT(csiescseq
.arg
[0], 1);
1743 tscrolldown(term
.top
, csiescseq
.arg
[0]);
1745 case 'L': /* IL -- Insert <n> blank lines */
1746 DEFAULT(csiescseq
.arg
[0], 1);
1747 tinsertblankline(csiescseq
.arg
[0]);
1749 case 'l': /* RM -- Reset Mode */
1750 tsetmode(csiescseq
.priv
, 0, csiescseq
.arg
, csiescseq
.narg
);
1752 case 'M': /* DL -- Delete <n> lines */
1753 DEFAULT(csiescseq
.arg
[0], 1);
1754 tdeleteline(csiescseq
.arg
[0]);
1756 case 'X': /* ECH -- Erase <n> char */
1757 DEFAULT(csiescseq
.arg
[0], 1);
1758 tclearregion(term
.c
.x
, term
.c
.y
,
1759 term
.c
.x
+ csiescseq
.arg
[0] - 1, term
.c
.y
);
1761 case 'P': /* DCH -- Delete <n> char */
1762 DEFAULT(csiescseq
.arg
[0], 1);
1763 tdeletechar(csiescseq
.arg
[0]);
1765 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1766 DEFAULT(csiescseq
.arg
[0], 1);
1767 tputtab(-csiescseq
.arg
[0]);
1769 case 'd': /* VPA -- Move to <row> */
1770 DEFAULT(csiescseq
.arg
[0], 1);
1771 tmoveato(term
.c
.x
, csiescseq
.arg
[0]-1);
1773 case 'h': /* SM -- Set terminal mode */
1774 tsetmode(csiescseq
.priv
, 1, csiescseq
.arg
, csiescseq
.narg
);
1776 case 'm': /* SGR -- Terminal attribute (color) */
1777 tsetattr(csiescseq
.arg
, csiescseq
.narg
);
1779 case 'n': /* DSR – Device Status Report (cursor position) */
1780 if (csiescseq
.arg
[0] == 6) {
1781 len
= snprintf(buf
, sizeof(buf
), "\033[%i;%iR",
1782 term
.c
.y
+1, term
.c
.x
+1);
1783 ttywrite(buf
, len
, 0);
1786 case 'r': /* DECSTBM -- Set Scrolling Region */
1787 if (csiescseq
.priv
) {
1790 DEFAULT(csiescseq
.arg
[0], 1);
1791 DEFAULT(csiescseq
.arg
[1], term
.row
);
1792 tsetscroll(csiescseq
.arg
[0]-1, csiescseq
.arg
[1]-1);
1796 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1797 tcursor(CURSOR_SAVE
);
1799 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1800 tcursor(CURSOR_LOAD
);
1803 switch (csiescseq
.mode
[1]) {
1804 case 'q': /* DECSCUSR -- Set Cursor Style */
1805 if (xsetcursor(csiescseq
.arg
[0]))
1821 fprintf(stderr
, "ESC[");
1822 for (i
= 0; i
< csiescseq
.len
; i
++) {
1823 c
= csiescseq
.buf
[i
] & 0xff;
1826 } else if (c
== '\n') {
1827 fprintf(stderr
, "(\\n)");
1828 } else if (c
== '\r') {
1829 fprintf(stderr
, "(\\r)");
1830 } else if (c
== 0x1b) {
1831 fprintf(stderr
, "(\\e)");
1833 fprintf(stderr
, "(%02x)", c
);
1842 memset(&csiescseq
, 0, sizeof(csiescseq
));
1846 osc4_color_response(int num
)
1850 unsigned char r
, g
, b
;
1852 if (xgetcolor(num
, &r
, &g
, &b
)) {
1853 fprintf(stderr
, "erresc: failed to fetch osc4 color %d\n", num
);
1857 n
= snprintf(buf
, sizeof buf
, "\033]4;%d;rgb:%02x%02x/%02x%02x/%02x%02x\007",
1858 num
, r
, r
, g
, g
, b
, b
);
1860 ttywrite(buf
, n
, 1);
1864 osc_color_response(int index
, int num
)
1868 unsigned char r
, g
, b
;
1870 if (xgetcolor(index
, &r
, &g
, &b
)) {
1871 fprintf(stderr
, "erresc: failed to fetch osc color %d\n", index
);
1875 n
= snprintf(buf
, sizeof buf
, "\033]%d;rgb:%02x%02x/%02x%02x/%02x%02x\007",
1876 num
, r
, r
, g
, g
, b
, b
);
1878 ttywrite(buf
, n
, 1);
1884 char *p
= NULL
, *dec
;
1887 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
1889 par
= (narg
= strescseq
.narg
) ? atoi(strescseq
.args
[0]) : 0;
1891 switch (strescseq
.type
) {
1892 case ']': /* OSC -- Operating System Command */
1896 xsettitle(strescseq
.args
[1]);
1897 xseticontitle(strescseq
.args
[1]);
1902 xseticontitle(strescseq
.args
[1]);
1906 xsettitle(strescseq
.args
[1]);
1909 if (narg
> 2 && allowwindowops
) {
1910 dec
= base64dec(strescseq
.args
[2]);
1915 fprintf(stderr
, "erresc: invalid base64\n");
1923 p
= strescseq
.args
[1];
1925 if (!strcmp(p
, "?"))
1926 osc_color_response(defaultfg
, 10);
1927 else if (xsetcolorname(defaultfg
, p
))
1928 fprintf(stderr
, "erresc: invalid foreground color: %s\n", p
);
1936 p
= strescseq
.args
[1];
1938 if (!strcmp(p
, "?"))
1939 osc_color_response(defaultbg
, 11);
1940 else if (xsetcolorname(defaultbg
, p
))
1941 fprintf(stderr
, "erresc: invalid background color: %s\n", p
);
1949 p
= strescseq
.args
[1];
1951 if (!strcmp(p
, "?"))
1952 osc_color_response(defaultcs
, 12);
1953 else if (xsetcolorname(defaultcs
, p
))
1954 fprintf(stderr
, "erresc: invalid cursor color: %s\n", p
);
1958 case 4: /* color set */
1961 p
= strescseq
.args
[2];
1963 case 104: /* color reset, here p = NULL */
1964 j
= (narg
> 1) ? atoi(strescseq
.args
[1]) : -1;
1966 if (!strcmp(p
, "?"))
1967 osc4_color_response(j
);
1968 else if (xsetcolorname(j
, p
)) {
1969 if (par
== 104 && narg
<= 1)
1970 return; /* color reset without parameter */
1971 fprintf(stderr
, "erresc: invalid color j=%d, p=%s\n",
1972 j
, p
? p
: "(null)");
1975 * TODO if defaultbg color is changed, borders
1983 case 'k': /* old title set compatibility */
1984 xsettitle(strescseq
.args
[0]);
1986 case 'P': /* DCS -- Device Control String */
1987 case '_': /* APC -- Application Program Command */
1988 case '^': /* PM -- Privacy Message */
1992 fprintf(stderr
, "erresc: unknown str ");
2000 char *p
= strescseq
.buf
;
2003 strescseq
.buf
[strescseq
.len
] = '\0';
2008 while (strescseq
.narg
< STR_ARG_SIZ
) {
2009 strescseq
.args
[strescseq
.narg
++] = p
;
2010 while ((c
= *p
) != ';' && c
!= '\0')
2024 fprintf(stderr
, "ESC%c", strescseq
.type
);
2025 for (i
= 0; i
< strescseq
.len
; i
++) {
2026 c
= strescseq
.buf
[i
] & 0xff;
2030 } else if (isprint(c
)) {
2032 } else if (c
== '\n') {
2033 fprintf(stderr
, "(\\n)");
2034 } else if (c
== '\r') {
2035 fprintf(stderr
, "(\\r)");
2036 } else if (c
== 0x1b) {
2037 fprintf(stderr
, "(\\e)");
2039 fprintf(stderr
, "(%02x)", c
);
2042 fprintf(stderr
, "ESC\\\n");
2048 strescseq
= (STREscape
){
2049 .buf
= xrealloc(strescseq
.buf
, STR_BUF_SIZ
),
2055 sendbreak(const Arg
*arg
)
2057 if (tcsendbreak(cmdfd
, 0))
2058 perror("Error sending break");
2062 tprinter(char *s
, size_t len
)
2064 if (iofd
!= -1 && xwrite(iofd
, s
, len
) < 0) {
2065 perror("Error writing to output file");
2072 toggleprinter(const Arg
*arg
)
2074 term
.mode
^= MODE_PRINT
;
2078 printscreen(const Arg
*arg
)
2084 printsel(const Arg
*arg
)
2094 if ((ptr
= getsel())) {
2095 tprinter(ptr
, strlen(ptr
));
2104 const Glyph
*bp
, *end
;
2106 bp
= &term
.line
[n
][0];
2107 end
= &bp
[MIN(tlinelen(n
), term
.col
) - 1];
2108 if (bp
!= end
|| bp
->u
!= ' ') {
2109 for ( ; bp
<= end
; ++bp
)
2110 tprinter(buf
, utf8encode(bp
->u
, buf
));
2120 for (i
= 0; i
< term
.row
; ++i
)
2130 while (x
< term
.col
&& n
--)
2131 for (++x
; x
< term
.col
&& !term
.tabs
[x
]; ++x
)
2134 while (x
> 0 && n
++)
2135 for (--x
; x
> 0 && !term
.tabs
[x
]; --x
)
2138 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
2142 tdefutf8(char ascii
)
2145 term
.mode
|= MODE_UTF8
;
2146 else if (ascii
== '@')
2147 term
.mode
&= ~MODE_UTF8
;
2151 tdeftran(char ascii
)
2153 static char cs
[] = "0B";
2154 static int vcs
[] = {CS_GRAPHIC0
, CS_USA
};
2157 if ((p
= strchr(cs
, ascii
)) == NULL
) {
2158 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
2160 term
.trantbl
[term
.icharset
] = vcs
[p
- cs
];
2169 if (c
== '8') { /* DEC screen alignment test. */
2170 for (x
= 0; x
< term
.col
; ++x
) {
2171 for (y
= 0; y
< term
.row
; ++y
)
2172 tsetchar('E', &term
.c
.attr
, x
, y
);
2178 tstrsequence(uchar c
)
2181 case 0x90: /* DCS -- Device Control String */
2184 case 0x9f: /* APC -- Application Program Command */
2187 case 0x9e: /* PM -- Privacy Message */
2190 case 0x9d: /* OSC -- Operating System Command */
2196 term
.esc
|= ESC_STR
;
2200 tcontrolcode(uchar ascii
)
2207 tmoveto(term
.c
.x
-1, term
.c
.y
);
2210 tmoveto(0, term
.c
.y
);
2215 /* go to first col if the mode is set */
2216 tnewline(IS_SET(MODE_CRLF
));
2218 case '\a': /* BEL */
2219 if (term
.esc
& ESC_STR_END
) {
2220 /* backwards compatibility to xterm */
2226 case '\033': /* ESC */
2228 term
.esc
&= ~(ESC_CSI
|ESC_ALTCHARSET
|ESC_TEST
);
2229 term
.esc
|= ESC_START
;
2231 case '\016': /* SO (LS1 -- Locking shift 1) */
2232 case '\017': /* SI (LS0 -- Locking shift 0) */
2233 term
.charset
= 1 - (ascii
- '\016');
2235 case '\032': /* SUB */
2236 tsetchar('?', &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2238 case '\030': /* CAN */
2241 case '\005': /* ENQ (IGNORED) */
2242 case '\000': /* NUL (IGNORED) */
2243 case '\021': /* XON (IGNORED) */
2244 case '\023': /* XOFF (IGNORED) */
2245 case 0177: /* DEL (IGNORED) */
2247 case 0x80: /* TODO: PAD */
2248 case 0x81: /* TODO: HOP */
2249 case 0x82: /* TODO: BPH */
2250 case 0x83: /* TODO: NBH */
2251 case 0x84: /* TODO: IND */
2253 case 0x85: /* NEL -- Next line */
2254 tnewline(1); /* always go to first col */
2256 case 0x86: /* TODO: SSA */
2257 case 0x87: /* TODO: ESA */
2259 case 0x88: /* HTS -- Horizontal tab stop */
2260 term
.tabs
[term
.c
.x
] = 1;
2262 case 0x89: /* TODO: HTJ */
2263 case 0x8a: /* TODO: VTS */
2264 case 0x8b: /* TODO: PLD */
2265 case 0x8c: /* TODO: PLU */
2266 case 0x8d: /* TODO: RI */
2267 case 0x8e: /* TODO: SS2 */
2268 case 0x8f: /* TODO: SS3 */
2269 case 0x91: /* TODO: PU1 */
2270 case 0x92: /* TODO: PU2 */
2271 case 0x93: /* TODO: STS */
2272 case 0x94: /* TODO: CCH */
2273 case 0x95: /* TODO: MW */
2274 case 0x96: /* TODO: SPA */
2275 case 0x97: /* TODO: EPA */
2276 case 0x98: /* TODO: SOS */
2277 case 0x99: /* TODO: SGCI */
2279 case 0x9a: /* DECID -- Identify Terminal */
2280 ttywrite(vtiden
, strlen(vtiden
), 0);
2282 case 0x9b: /* TODO: CSI */
2283 case 0x9c: /* TODO: ST */
2285 case 0x90: /* DCS -- Device Control String */
2286 case 0x9d: /* OSC -- Operating System Command */
2287 case 0x9e: /* PM -- Privacy Message */
2288 case 0x9f: /* APC -- Application Program Command */
2289 tstrsequence(ascii
);
2292 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2293 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
2297 * returns 1 when the sequence is finished and it hasn't to read
2298 * more characters for this sequence, otherwise 0
2301 eschandle(uchar ascii
)
2305 term
.esc
|= ESC_CSI
;
2308 term
.esc
|= ESC_TEST
;
2311 term
.esc
|= ESC_UTF8
;
2313 case 'P': /* DCS -- Device Control String */
2314 case '_': /* APC -- Application Program Command */
2315 case '^': /* PM -- Privacy Message */
2316 case ']': /* OSC -- Operating System Command */
2317 case 'k': /* old title set compatibility */
2318 tstrsequence(ascii
);
2320 case 'n': /* LS2 -- Locking shift 2 */
2321 case 'o': /* LS3 -- Locking shift 3 */
2322 term
.charset
= 2 + (ascii
- 'n');
2324 case '(': /* GZD4 -- set primary charset G0 */
2325 case ')': /* G1D4 -- set secondary charset G1 */
2326 case '*': /* G2D4 -- set tertiary charset G2 */
2327 case '+': /* G3D4 -- set quaternary charset G3 */
2328 term
.icharset
= ascii
- '(';
2329 term
.esc
|= ESC_ALTCHARSET
;
2331 case 'D': /* IND -- Linefeed */
2332 if (term
.c
.y
== term
.bot
) {
2333 tscrollup(term
.top
, 1);
2335 tmoveto(term
.c
.x
, term
.c
.y
+1);
2338 case 'E': /* NEL -- Next line */
2339 tnewline(1); /* always go to first col */
2341 case 'H': /* HTS -- Horizontal tab stop */
2342 term
.tabs
[term
.c
.x
] = 1;
2344 case 'M': /* RI -- Reverse index */
2345 if (term
.c
.y
== term
.top
) {
2346 tscrolldown(term
.top
, 1);
2348 tmoveto(term
.c
.x
, term
.c
.y
-1);
2351 case 'Z': /* DECID -- Identify Terminal */
2352 ttywrite(vtiden
, strlen(vtiden
), 0);
2354 case 'c': /* RIS -- Reset to initial state */
2359 case '=': /* DECPAM -- Application keypad */
2360 xsetmode(1, MODE_APPKEYPAD
);
2362 case '>': /* DECPNM -- Normal keypad */
2363 xsetmode(0, MODE_APPKEYPAD
);
2365 case '7': /* DECSC -- Save Cursor */
2366 tcursor(CURSOR_SAVE
);
2368 case '8': /* DECRC -- Restore Cursor */
2369 tcursor(CURSOR_LOAD
);
2371 case '\\': /* ST -- String Terminator */
2372 if (term
.esc
& ESC_STR_END
)
2376 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2377 (uchar
) ascii
, isprint(ascii
)? ascii
:'.');
2391 control
= ISCONTROL(u
);
2392 if (u
< 127 || !IS_SET(MODE_UTF8
)) {
2396 len
= utf8encode(u
, c
);
2397 if (!control
&& (width
= wcwidth(u
)) == -1)
2401 if (IS_SET(MODE_PRINT
))
2405 * STR sequence must be checked before anything else
2406 * because it uses all following characters until it
2407 * receives a ESC, a SUB, a ST or any other C1 control
2410 if (term
.esc
& ESC_STR
) {
2411 if (u
== '\a' || u
== 030 || u
== 032 || u
== 033 ||
2413 term
.esc
&= ~(ESC_START
|ESC_STR
);
2414 term
.esc
|= ESC_STR_END
;
2415 goto check_control_code
;
2418 if (strescseq
.len
+len
>= strescseq
.siz
) {
2420 * Here is a bug in terminals. If the user never sends
2421 * some code to stop the str or esc command, then st
2422 * will stop responding. But this is better than
2423 * silently failing with unknown characters. At least
2424 * then users will report back.
2426 * In the case users ever get fixed, here is the code:
2432 if (strescseq
.siz
> (SIZE_MAX
- UTF_SIZ
) / 2)
2435 strescseq
.buf
= xrealloc(strescseq
.buf
, strescseq
.siz
);
2438 memmove(&strescseq
.buf
[strescseq
.len
], c
, len
);
2439 strescseq
.len
+= len
;
2445 * Actions of control codes must be performed as soon they arrive
2446 * because they can be embedded inside a control sequence, and
2447 * they must not cause conflicts with sequences.
2452 * control codes are not shown ever
2457 } else if (term
.esc
& ESC_START
) {
2458 if (term
.esc
& ESC_CSI
) {
2459 csiescseq
.buf
[csiescseq
.len
++] = u
;
2460 if (BETWEEN(u
, 0x40, 0x7E)
2461 || csiescseq
.len
>= \
2462 sizeof(csiescseq
.buf
)-1) {
2468 } else if (term
.esc
& ESC_UTF8
) {
2470 } else if (term
.esc
& ESC_ALTCHARSET
) {
2472 } else if (term
.esc
& ESC_TEST
) {
2477 /* sequence already finished */
2481 * All characters which form part of a sequence are not
2486 if (selected(term
.c
.x
, term
.c
.y
))
2489 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2490 if (IS_SET(MODE_WRAP
) && (term
.c
.state
& CURSOR_WRAPNEXT
)) {
2491 gp
->mode
|= ATTR_WRAP
;
2493 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2496 if (IS_SET(MODE_INSERT
) && term
.c
.x
+width
< term
.col
)
2497 memmove(gp
+width
, gp
, (term
.col
- term
.c
.x
- width
) * sizeof(Glyph
));
2499 if (term
.c
.x
+width
> term
.col
) {
2501 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2504 tsetchar(u
, &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2508 gp
->mode
|= ATTR_WIDE
;
2509 if (term
.c
.x
+1 < term
.col
) {
2511 gp
[1].mode
= ATTR_WDUMMY
;
2514 if (term
.c
.x
+width
< term
.col
) {
2515 tmoveto(term
.c
.x
+width
, term
.c
.y
);
2517 term
.c
.state
|= CURSOR_WRAPNEXT
;
2522 twrite(const char *buf
, int buflen
, int show_ctrl
)
2528 for (n
= 0; n
< buflen
; n
+= charsize
) {
2529 if (IS_SET(MODE_UTF8
)) {
2530 /* process a complete utf8 char */
2531 charsize
= utf8decode(buf
+ n
, &u
, buflen
- n
);
2538 if (show_ctrl
&& ISCONTROL(u
)) {
2543 } else if (u
!= '\n' && u
!= '\r' && u
!= '\t') {
2554 tresize(int col
, int row
)
2557 int minrow
= MIN(row
, term
.row
);
2558 int mincol
= MIN(col
, term
.col
);
2562 if (col
< 1 || row
< 1) {
2564 "tresize: error resizing to %dx%d\n", col
, row
);
2569 * slide screen to keep cursor where we expect it -
2570 * tscrollup would work here, but we can optimize to
2571 * memmove because we're freeing the earlier lines
2573 for (i
= 0; i
<= term
.c
.y
- row
; i
++) {
2577 /* ensure that both src and dst are not NULL */
2579 memmove(term
.line
, term
.line
+ i
, row
* sizeof(Line
));
2580 memmove(term
.alt
, term
.alt
+ i
, row
* sizeof(Line
));
2582 for (i
+= row
; i
< term
.row
; i
++) {
2587 /* resize to new height */
2588 term
.line
= xrealloc(term
.line
, row
* sizeof(Line
));
2589 term
.alt
= xrealloc(term
.alt
, row
* sizeof(Line
));
2590 term
.dirty
= xrealloc(term
.dirty
, row
* sizeof(*term
.dirty
));
2591 term
.tabs
= xrealloc(term
.tabs
, col
* sizeof(*term
.tabs
));
2593 /* resize each row to new width, zero-pad if needed */
2594 for (i
= 0; i
< minrow
; i
++) {
2595 term
.line
[i
] = xrealloc(term
.line
[i
], col
* sizeof(Glyph
));
2596 term
.alt
[i
] = xrealloc(term
.alt
[i
], col
* sizeof(Glyph
));
2599 /* allocate any new rows */
2600 for (/* i = minrow */; i
< row
; i
++) {
2601 term
.line
[i
] = xmalloc(col
* sizeof(Glyph
));
2602 term
.alt
[i
] = xmalloc(col
* sizeof(Glyph
));
2604 if (col
> term
.col
) {
2605 bp
= term
.tabs
+ term
.col
;
2607 memset(bp
, 0, sizeof(*term
.tabs
) * (col
- term
.col
));
2608 while (--bp
> term
.tabs
&& !*bp
)
2610 for (bp
+= tabspaces
; bp
< term
.tabs
+ col
; bp
+= tabspaces
)
2613 /* update terminal size */
2616 /* reset scrolling region */
2617 tsetscroll(0, row
-1);
2618 /* make use of the LIMIT in tmoveto */
2619 tmoveto(term
.c
.x
, term
.c
.y
);
2620 /* Clearing both screens (it makes dirty all lines) */
2622 for (i
= 0; i
< 2; i
++) {
2623 if (mincol
< col
&& 0 < minrow
) {
2624 tclearregion(mincol
, 0, col
- 1, minrow
- 1);
2626 if (0 < col
&& minrow
< row
) {
2627 tclearregion(0, minrow
, col
- 1, row
- 1);
2630 tcursor(CURSOR_LOAD
);
2642 drawregion(int x1
, int y1
, int x2
, int y2
)
2646 for (y
= y1
; y
< y2
; y
++) {
2651 xdrawline(term
.line
[y
], x1
, y
, x2
);
2658 int cx
= term
.c
.x
, ocx
= term
.ocx
, ocy
= term
.ocy
;
2663 /* adjust cursor position */
2664 LIMIT(term
.ocx
, 0, term
.col
-1);
2665 LIMIT(term
.ocy
, 0, term
.row
-1);
2666 if (term
.line
[term
.ocy
][term
.ocx
].mode
& ATTR_WDUMMY
)
2668 if (term
.line
[term
.c
.y
][cx
].mode
& ATTR_WDUMMY
)
2671 drawregion(0, 0, term
.col
, term
.row
);
2672 xdrawcursor(cx
, term
.c
.y
, term
.line
[term
.c
.y
][cx
],
2673 term
.ocx
, term
.ocy
, term
.line
[term
.ocy
][term
.ocx
]);
2675 term
.ocy
= term
.c
.y
;
2677 if (ocx
!= term
.ocx
|| ocy
!= term
.ocy
)
2678 xximspot(term
.ocx
, term
.ocy
);