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>
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 if (par
== 104 && narg
<= 1)
1869 return; /* color reset without parameter */
1870 fprintf(stderr
, "erresc: invalid color j=%d, p=%s\n",
1871 j
, p
? p
: "(null)");
1874 * TODO if defaultbg color is changed, borders
1882 case 'k': /* old title set compatibility */
1883 xsettitle(strescseq
.args
[0]);
1885 case 'P': /* DCS -- Device Control String */
1886 term
.mode
|= ESC_DCS
;
1887 case '_': /* APC -- Application Program Command */
1888 case '^': /* PM -- Privacy Message */
1892 fprintf(stderr
, "erresc: unknown str ");
1900 char *p
= strescseq
.buf
;
1903 strescseq
.buf
[strescseq
.len
] = '\0';
1908 while (strescseq
.narg
< STR_ARG_SIZ
) {
1909 strescseq
.args
[strescseq
.narg
++] = p
;
1910 while ((c
= *p
) != ';' && c
!= '\0')
1924 fprintf(stderr
, "ESC%c", strescseq
.type
);
1925 for (i
= 0; i
< strescseq
.len
; i
++) {
1926 c
= strescseq
.buf
[i
] & 0xff;
1930 } else if (isprint(c
)) {
1932 } else if (c
== '\n') {
1933 fprintf(stderr
, "(\\n)");
1934 } else if (c
== '\r') {
1935 fprintf(stderr
, "(\\r)");
1936 } else if (c
== 0x1b) {
1937 fprintf(stderr
, "(\\e)");
1939 fprintf(stderr
, "(%02x)", c
);
1942 fprintf(stderr
, "ESC\\\n");
1948 memset(&strescseq
, 0, sizeof(strescseq
));
1952 sendbreak(const Arg
*arg
)
1954 if (tcsendbreak(cmdfd
, 0))
1955 perror("Error sending break");
1959 tprinter(char *s
, size_t len
)
1961 if (iofd
!= -1 && xwrite(iofd
, s
, len
) < 0) {
1962 perror("Error writing to output file");
1969 toggleprinter(const Arg
*arg
)
1971 term
.mode
^= MODE_PRINT
;
1975 printscreen(const Arg
*arg
)
1981 printsel(const Arg
*arg
)
1991 if ((ptr
= getsel())) {
1992 tprinter(ptr
, strlen(ptr
));
2003 bp
= &term
.line
[n
][0];
2004 end
= &bp
[MIN(tlinelen(n
), term
.col
) - 1];
2005 if (bp
!= end
|| bp
->u
!= ' ') {
2006 for ( ;bp
<= end
; ++bp
)
2007 tprinter(buf
, utf8encode(bp
->u
, buf
));
2017 for (i
= 0; i
< term
.row
; ++i
)
2027 while (x
< term
.col
&& n
--)
2028 for (++x
; x
< term
.col
&& !term
.tabs
[x
]; ++x
)
2031 while (x
> 0 && n
++)
2032 for (--x
; x
> 0 && !term
.tabs
[x
]; --x
)
2035 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
2039 tdefutf8(char ascii
)
2042 term
.mode
|= MODE_UTF8
;
2043 else if (ascii
== '@')
2044 term
.mode
&= ~MODE_UTF8
;
2048 tdeftran(char ascii
)
2050 static char cs
[] = "0B";
2051 static int vcs
[] = {CS_GRAPHIC0
, CS_USA
};
2054 if ((p
= strchr(cs
, ascii
)) == NULL
) {
2055 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
2057 term
.trantbl
[term
.icharset
] = vcs
[p
- cs
];
2066 if (c
== '8') { /* DEC screen alignment test. */
2067 for (x
= 0; x
< term
.col
; ++x
) {
2068 for (y
= 0; y
< term
.row
; ++y
)
2069 tsetchar('E', &term
.c
.attr
, x
, y
);
2075 tstrsequence(uchar c
)
2080 case 0x90: /* DCS -- Device Control String */
2082 term
.esc
|= ESC_DCS
;
2084 case 0x9f: /* APC -- Application Program Command */
2087 case 0x9e: /* PM -- Privacy Message */
2090 case 0x9d: /* OSC -- Operating System Command */
2095 term
.esc
|= ESC_STR
;
2099 tcontrolcode(uchar ascii
)
2106 tmoveto(term
.c
.x
-1, term
.c
.y
);
2109 tmoveto(0, term
.c
.y
);
2114 /* go to first col if the mode is set */
2115 tnewline(IS_SET(MODE_CRLF
));
2117 case '\a': /* BEL */
2118 if (term
.esc
& ESC_STR_END
) {
2119 /* backwards compatibility to xterm */
2125 case '\033': /* ESC */
2127 term
.esc
&= ~(ESC_CSI
|ESC_ALTCHARSET
|ESC_TEST
);
2128 term
.esc
|= ESC_START
;
2130 case '\016': /* SO (LS1 -- Locking shift 1) */
2131 case '\017': /* SI (LS0 -- Locking shift 0) */
2132 term
.charset
= 1 - (ascii
- '\016');
2134 case '\032': /* SUB */
2135 tsetchar('?', &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2136 case '\030': /* CAN */
2139 case '\005': /* ENQ (IGNORED) */
2140 case '\000': /* NUL (IGNORED) */
2141 case '\021': /* XON (IGNORED) */
2142 case '\023': /* XOFF (IGNORED) */
2143 case 0177: /* DEL (IGNORED) */
2145 case 0x80: /* TODO: PAD */
2146 case 0x81: /* TODO: HOP */
2147 case 0x82: /* TODO: BPH */
2148 case 0x83: /* TODO: NBH */
2149 case 0x84: /* TODO: IND */
2151 case 0x85: /* NEL -- Next line */
2152 tnewline(1); /* always go to first col */
2154 case 0x86: /* TODO: SSA */
2155 case 0x87: /* TODO: ESA */
2157 case 0x88: /* HTS -- Horizontal tab stop */
2158 term
.tabs
[term
.c
.x
] = 1;
2160 case 0x89: /* TODO: HTJ */
2161 case 0x8a: /* TODO: VTS */
2162 case 0x8b: /* TODO: PLD */
2163 case 0x8c: /* TODO: PLU */
2164 case 0x8d: /* TODO: RI */
2165 case 0x8e: /* TODO: SS2 */
2166 case 0x8f: /* TODO: SS3 */
2167 case 0x91: /* TODO: PU1 */
2168 case 0x92: /* TODO: PU2 */
2169 case 0x93: /* TODO: STS */
2170 case 0x94: /* TODO: CCH */
2171 case 0x95: /* TODO: MW */
2172 case 0x96: /* TODO: SPA */
2173 case 0x97: /* TODO: EPA */
2174 case 0x98: /* TODO: SOS */
2175 case 0x99: /* TODO: SGCI */
2177 case 0x9a: /* DECID -- Identify Terminal */
2178 ttywrite(vtiden
, strlen(vtiden
), 0);
2180 case 0x9b: /* TODO: CSI */
2181 case 0x9c: /* TODO: ST */
2183 case 0x90: /* DCS -- Device Control String */
2184 case 0x9d: /* OSC -- Operating System Command */
2185 case 0x9e: /* PM -- Privacy Message */
2186 case 0x9f: /* APC -- Application Program Command */
2187 tstrsequence(ascii
);
2190 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2191 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
2195 * returns 1 when the sequence is finished and it hasn't to read
2196 * more characters for this sequence, otherwise 0
2199 eschandle(uchar ascii
)
2203 term
.esc
|= ESC_CSI
;
2206 term
.esc
|= ESC_TEST
;
2209 term
.esc
|= ESC_UTF8
;
2211 case 'P': /* DCS -- Device Control String */
2212 case '_': /* APC -- Application Program Command */
2213 case '^': /* PM -- Privacy Message */
2214 case ']': /* OSC -- Operating System Command */
2215 case 'k': /* old title set compatibility */
2216 tstrsequence(ascii
);
2218 case 'n': /* LS2 -- Locking shift 2 */
2219 case 'o': /* LS3 -- Locking shift 3 */
2220 term
.charset
= 2 + (ascii
- 'n');
2222 case '(': /* GZD4 -- set primary charset G0 */
2223 case ')': /* G1D4 -- set secondary charset G1 */
2224 case '*': /* G2D4 -- set tertiary charset G2 */
2225 case '+': /* G3D4 -- set quaternary charset G3 */
2226 term
.icharset
= ascii
- '(';
2227 term
.esc
|= ESC_ALTCHARSET
;
2229 case 'D': /* IND -- Linefeed */
2230 if (term
.c
.y
== term
.bot
) {
2231 tscrollup(term
.top
, 1);
2233 tmoveto(term
.c
.x
, term
.c
.y
+1);
2236 case 'E': /* NEL -- Next line */
2237 tnewline(1); /* always go to first col */
2239 case 'H': /* HTS -- Horizontal tab stop */
2240 term
.tabs
[term
.c
.x
] = 1;
2242 case 'M': /* RI -- Reverse index */
2243 if (term
.c
.y
== term
.top
) {
2244 tscrolldown(term
.top
, 1);
2246 tmoveto(term
.c
.x
, term
.c
.y
-1);
2249 case 'Z': /* DECID -- Identify Terminal */
2250 ttywrite(vtiden
, strlen(vtiden
), 0);
2252 case 'c': /* RIS -- Reset to initial state */
2257 case '=': /* DECPAM -- Application keypad */
2258 xsetmode(1, MODE_APPKEYPAD
);
2260 case '>': /* DECPNM -- Normal keypad */
2261 xsetmode(0, MODE_APPKEYPAD
);
2263 case '7': /* DECSC -- Save Cursor */
2264 tcursor(CURSOR_SAVE
);
2266 case '8': /* DECRC -- Restore Cursor */
2267 tcursor(CURSOR_LOAD
);
2269 case '\\': /* ST -- String Terminator */
2270 if (term
.esc
& ESC_STR_END
)
2274 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2275 (uchar
) ascii
, isprint(ascii
)? ascii
:'.');
2289 control
= ISCONTROL(u
);
2290 if (!IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2294 len
= utf8encode(u
, c
);
2295 if (!control
&& (width
= wcwidth(u
)) == -1) {
2296 memcpy(c
, "\357\277\275", 4); /* UTF_INVALID */
2301 if (IS_SET(MODE_PRINT
))
2305 * STR sequence must be checked before anything else
2306 * because it uses all following characters until it
2307 * receives a ESC, a SUB, a ST or any other C1 control
2310 if (term
.esc
& ESC_STR
) {
2311 if (u
== '\a' || u
== 030 || u
== 032 || u
== 033 ||
2313 term
.esc
&= ~(ESC_START
|ESC_STR
|ESC_DCS
);
2314 if (IS_SET(MODE_SIXEL
)) {
2315 /* TODO: render sixel */;
2316 term
.mode
&= ~MODE_SIXEL
;
2319 term
.esc
|= ESC_STR_END
;
2320 goto check_control_code
;
2323 if (IS_SET(MODE_SIXEL
)) {
2324 /* TODO: implement sixel mode */
2327 if (term
.esc
&ESC_DCS
&& strescseq
.len
== 0 && u
== 'q')
2328 term
.mode
|= MODE_SIXEL
;
2330 if (strescseq
.len
+len
>= sizeof(strescseq
.buf
)-1) {
2332 * Here is a bug in terminals. If the user never sends
2333 * some code to stop the str or esc command, then st
2334 * will stop responding. But this is better than
2335 * silently failing with unknown characters. At least
2336 * then users will report back.
2338 * In the case users ever get fixed, here is the code:
2347 memmove(&strescseq
.buf
[strescseq
.len
], c
, len
);
2348 strescseq
.len
+= len
;
2354 * Actions of control codes must be performed as soon they arrive
2355 * because they can be embedded inside a control sequence, and
2356 * they must not cause conflicts with sequences.
2361 * control codes are not shown ever
2364 } else if (term
.esc
& ESC_START
) {
2365 if (term
.esc
& ESC_CSI
) {
2366 csiescseq
.buf
[csiescseq
.len
++] = u
;
2367 if (BETWEEN(u
, 0x40, 0x7E)
2368 || csiescseq
.len
>= \
2369 sizeof(csiescseq
.buf
)-1) {
2375 } else if (term
.esc
& ESC_UTF8
) {
2377 } else if (term
.esc
& ESC_ALTCHARSET
) {
2379 } else if (term
.esc
& ESC_TEST
) {
2384 /* sequence already finished */
2388 * All characters which form part of a sequence are not
2393 if (sel
.ob
.x
!= -1 && BETWEEN(term
.c
.y
, sel
.ob
.y
, sel
.oe
.y
))
2396 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2397 if (IS_SET(MODE_WRAP
) && (term
.c
.state
& CURSOR_WRAPNEXT
)) {
2398 gp
->mode
|= ATTR_WRAP
;
2400 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2403 if (IS_SET(MODE_INSERT
) && term
.c
.x
+width
< term
.col
)
2404 memmove(gp
+width
, gp
, (term
.col
- term
.c
.x
- width
) * sizeof(Glyph
));
2406 if (term
.c
.x
+width
> term
.col
) {
2408 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2411 tsetchar(u
, &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2414 gp
->mode
|= ATTR_WIDE
;
2415 if (term
.c
.x
+1 < term
.col
) {
2417 gp
[1].mode
= ATTR_WDUMMY
;
2420 if (term
.c
.x
+width
< term
.col
) {
2421 tmoveto(term
.c
.x
+width
, term
.c
.y
);
2423 term
.c
.state
|= CURSOR_WRAPNEXT
;
2428 twrite(const char *buf
, int buflen
, int show_ctrl
)
2434 for (n
= 0; n
< buflen
; n
+= charsize
) {
2435 if (IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2436 /* process a complete utf8 char */
2437 charsize
= utf8decode(buf
+ n
, &u
, buflen
- n
);
2444 if (show_ctrl
&& ISCONTROL(u
)) {
2449 } else if (u
!= '\n' && u
!= '\r' && u
!= '\t') {
2460 tresize(int col
, int row
)
2463 int minrow
= MIN(row
, term
.row
);
2464 int mincol
= MIN(col
, term
.col
);
2468 if (col
< 1 || row
< 1) {
2470 "tresize: error resizing to %dx%d\n", col
, row
);
2475 * slide screen to keep cursor where we expect it -
2476 * tscrollup would work here, but we can optimize to
2477 * memmove because we're freeing the earlier lines
2479 for (i
= 0; i
<= term
.c
.y
- row
; i
++) {
2483 /* ensure that both src and dst are not NULL */
2485 memmove(term
.line
, term
.line
+ i
, row
* sizeof(Line
));
2486 memmove(term
.alt
, term
.alt
+ i
, row
* sizeof(Line
));
2488 for (i
+= row
; i
< term
.row
; i
++) {
2493 /* resize to new height */
2494 term
.line
= xrealloc(term
.line
, row
* sizeof(Line
));
2495 term
.alt
= xrealloc(term
.alt
, row
* sizeof(Line
));
2496 term
.dirty
= xrealloc(term
.dirty
, row
* sizeof(*term
.dirty
));
2497 term
.tabs
= xrealloc(term
.tabs
, col
* sizeof(*term
.tabs
));
2499 /* resize each row to new width, zero-pad if needed */
2500 for (i
= 0; i
< minrow
; i
++) {
2501 term
.line
[i
] = xrealloc(term
.line
[i
], col
* sizeof(Glyph
));
2502 term
.alt
[i
] = xrealloc(term
.alt
[i
], col
* sizeof(Glyph
));
2505 /* allocate any new rows */
2506 for (/* i = minrow */; i
< row
; i
++) {
2507 term
.line
[i
] = xmalloc(col
* sizeof(Glyph
));
2508 term
.alt
[i
] = xmalloc(col
* sizeof(Glyph
));
2510 if (col
> term
.col
) {
2511 bp
= term
.tabs
+ term
.col
;
2513 memset(bp
, 0, sizeof(*term
.tabs
) * (col
- term
.col
));
2514 while (--bp
> term
.tabs
&& !*bp
)
2516 for (bp
+= tabspaces
; bp
< term
.tabs
+ col
; bp
+= tabspaces
)
2519 /* update terminal size */
2522 /* reset scrolling region */
2523 tsetscroll(0, row
-1);
2524 /* make use of the LIMIT in tmoveto */
2525 tmoveto(term
.c
.x
, term
.c
.y
);
2526 /* Clearing both screens (it makes dirty all lines) */
2528 for (i
= 0; i
< 2; i
++) {
2529 if (mincol
< col
&& 0 < minrow
) {
2530 tclearregion(mincol
, 0, col
- 1, minrow
- 1);
2532 if (0 < col
&& minrow
< row
) {
2533 tclearregion(0, minrow
, col
- 1, row
- 1);
2536 tcursor(CURSOR_LOAD
);
2548 drawregion(int x1
, int y1
, int x2
, int y2
)
2551 for (y
= y1
; y
< y2
; y
++) {
2556 xdrawline(term
.line
[y
], x1
, y
, x2
);
2568 /* adjust cursor position */
2569 LIMIT(term
.ocx
, 0, term
.col
-1);
2570 LIMIT(term
.ocy
, 0, term
.row
-1);
2571 if (term
.line
[term
.ocy
][term
.ocx
].mode
& ATTR_WDUMMY
)
2573 if (term
.line
[term
.c
.y
][cx
].mode
& ATTR_WDUMMY
)
2576 drawregion(0, 0, term
.col
, term
.row
);
2577 xdrawcursor(cx
, term
.c
.y
, term
.line
[term
.c
.y
][cx
],
2578 term
.ocx
, term
.ocy
, term
.line
[term
.ocy
][term
.ocx
]);
2579 term
.ocx
= cx
, term
.ocy
= term
.c
.y
;
2581 xximspot(term
.ocx
, term
.ocy
);