1 /* See LICENSE for license details. */
12 #include <sys/ioctl.h>
13 #include <sys/select.h>
14 #include <sys/types.h>
25 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
27 #elif defined(__FreeBSD__) || defined(__DragonFly__)
32 #define UTF_INVALID 0xFFFD
34 #define ESC_BUF_SIZ (128*UTF_SIZ)
35 #define ESC_ARG_SIZ 16
36 #define STR_BUF_SIZ ESC_BUF_SIZ
37 #define STR_ARG_SIZ ESC_ARG_SIZ
40 #define IS_SET(flag) ((term.mode & (flag)) != 0)
41 #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == '\177')
42 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
43 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
44 #define ISDELIM(u) (u && wcschr(worddelimiters, u))
49 MODE_ALTSCREEN
= 1 << 2,
57 enum cursor_movement
{
81 ESC_STR
= 4, /* OSC, PM, APC */
83 ESC_STR_END
= 16, /* a final string was encountered */
84 ESC_TEST
= 32, /* Enter in test mode */
90 Glyph attr
; /* current char attributes */
101 * Selection variables:
102 * nb – normalized coordinates of the beginning of the selection
103 * ne – normalized coordinates of the end of the selection
104 * ob – original coordinates of the beginning of the selection
105 * oe – original coordinates of the end of the selection
114 /* Internal representation of the screen */
116 int row
; /* nb row */
117 int col
; /* nb col */
118 Line
*line
; /* screen */
119 Line
*alt
; /* alternate screen */
120 int *dirty
; /* dirtyness of lines */
121 TCursor c
; /* cursor */
122 int ocx
; /* old cursor col */
123 int ocy
; /* old cursor row */
124 int top
; /* top scroll limit */
125 int bot
; /* bottom scroll limit */
126 int mode
; /* terminal mode flags */
127 int esc
; /* escape state flags */
128 char trantbl
[4]; /* charset table translation */
129 int charset
; /* current charset */
130 int icharset
; /* selected charset for sequence */
134 /* CSI Escape sequence structs */
135 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
137 char buf
[ESC_BUF_SIZ
]; /* raw string */
138 int len
; /* raw string length */
140 int arg
[ESC_ARG_SIZ
];
141 int narg
; /* nb of args */
145 /* STR Escape sequence structs */
146 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
148 char type
; /* ESC type ... */
149 char buf
[STR_BUF_SIZ
]; /* raw string */
150 int len
; /* raw string length */
151 char *args
[STR_ARG_SIZ
];
152 int narg
; /* nb of args */
155 static void execsh(char *, char **);
156 static void stty(char **);
157 static void sigchld(int);
158 static void ttywriteraw(const char *, size_t);
160 static void csidump(void);
161 static void csihandle(void);
162 static void csiparse(void);
163 static void csireset(void);
164 static int eschandle(uchar
);
165 static void strdump(void);
166 static void strhandle(void);
167 static void strparse(void);
168 static void strreset(void);
170 static void tprinter(char *, size_t);
171 static void tdumpsel(void);
172 static void tdumpline(int);
173 static void tdump(void);
174 static void tclearregion(int, int, int, int);
175 static void tcursor(int);
176 static void tdeletechar(int);
177 static void tdeleteline(int);
178 static void tinsertblank(int);
179 static void tinsertblankline(int);
180 static int tlinelen(int);
181 static void tmoveto(int, int);
182 static void tmoveato(int, int);
183 static void tnewline(int);
184 static void tputtab(int);
185 static void tputc(Rune
);
186 static void treset(void);
187 static void tscrollup(int, int);
188 static void tscrolldown(int, int);
189 static void tsetattr(int *, int);
190 static void tsetchar(Rune
, Glyph
*, int, int);
191 static void tsetdirt(int, int);
192 static void tsetscroll(int, int);
193 static void tswapscreen(void);
194 static void tsetmode(int, int, int *, int);
195 static int twrite(const char *, int, int);
196 static void tfulldirt(void);
197 static void tcontrolcode(uchar
);
198 static void tdectest(char );
199 static void tdefutf8(char);
200 static int32_t tdefcolor(int *, int *, int);
201 static void tdeftran(char);
202 static void tstrsequence(uchar
);
204 static void drawregion(int, int, int, int);
206 static void selnormalize(void);
207 static void selscroll(int, int);
208 static void selsnap(int *, int *, int);
210 static size_t utf8decode(const char *, Rune
*, size_t);
211 static Rune
utf8decodebyte(char, size_t *);
212 static char utf8encodebyte(Rune
, size_t);
213 static 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 uchar utfbyte
[UTF_SIZ
+ 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
230 static uchar utfmask
[UTF_SIZ
+ 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
231 static Rune utfmin
[UTF_SIZ
+ 1] = { 0, 0, 0x80, 0x800, 0x10000};
232 static 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
));
274 if ((s
= strdup(s
)) == NULL
)
275 die("strdup: %s\n", strerror(errno
));
281 utf8decode(const char *c
, Rune
*u
, size_t clen
)
283 size_t i
, j
, len
, type
;
289 udecoded
= utf8decodebyte(c
[0], &len
);
290 if (!BETWEEN(len
, 1, UTF_SIZ
))
292 for (i
= 1, j
= 1; i
< clen
&& j
< len
; ++i
, ++j
) {
293 udecoded
= (udecoded
<< 6) | utf8decodebyte(c
[i
], &type
);
300 utf8validate(u
, len
);
306 utf8decodebyte(char c
, size_t *i
)
308 for (*i
= 0; *i
< LEN(utfmask
); ++(*i
))
309 if (((uchar
)c
& utfmask
[*i
]) == utfbyte
[*i
])
310 return (uchar
)c
& ~utfmask
[*i
];
316 utf8encode(Rune u
, char *c
)
320 len
= utf8validate(&u
, 0);
324 for (i
= len
- 1; i
!= 0; --i
) {
325 c
[i
] = utf8encodebyte(u
, 0);
328 c
[0] = utf8encodebyte(u
, len
);
334 utf8encodebyte(Rune u
, size_t i
)
336 return utfbyte
[i
] | (u
& ~utfmask
[i
]);
340 utf8validate(Rune
*u
, size_t i
)
342 if (!BETWEEN(*u
, utfmin
[i
], utfmax
[i
]) || BETWEEN(*u
, 0xD800, 0xDFFF))
344 for (i
= 1; *u
> utfmax
[i
]; ++i
)
350 static const char base64_digits
[] = {
351 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
352 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0,
353 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
354 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
355 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
356 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0,
357 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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
366 base64dec_getc(const char **src
)
368 while (**src
&& !isprint(**src
)) (*src
)++;
373 base64dec(const char *src
)
375 size_t in_len
= strlen(src
);
379 in_len
+= 4 - (in_len
% 4);
380 result
= dst
= xmalloc(in_len
/ 4 * 3 + 1);
382 int a
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
383 int b
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
384 int c
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
385 int d
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
387 *dst
++ = (a
<< 2) | ((b
& 0x30) >> 4);
390 *dst
++ = ((b
& 0x0f) << 4) | ((c
& 0x3c) >> 2);
393 *dst
++ = ((c
& 0x03) << 6) | d
;
412 if (term
.line
[y
][i
- 1].mode
& ATTR_WRAP
)
415 while (i
> 0 && term
.line
[y
][i
- 1].u
== ' ')
422 selstart(int col
, int row
, int snap
)
425 sel
.mode
= SEL_EMPTY
;
426 sel
.type
= SEL_REGULAR
;
427 sel
.alt
= IS_SET(MODE_ALTSCREEN
);
429 sel
.oe
.x
= sel
.ob
.x
= col
;
430 sel
.oe
.y
= sel
.ob
.y
= row
;
434 sel
.mode
= SEL_READY
;
435 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
439 selextend(int col
, int row
, int type
, int done
)
441 int oldey
, oldex
, oldsby
, oldsey
, oldtype
;
443 if (sel
.mode
== SEL_IDLE
)
445 if (done
&& sel
.mode
== SEL_EMPTY
) {
461 if (oldey
!= sel
.oe
.y
|| oldex
!= sel
.oe
.x
|| oldtype
!= sel
.type
)
462 tsetdirt(MIN(sel
.nb
.y
, oldsby
), MAX(sel
.ne
.y
, oldsey
));
464 sel
.mode
= done
? SEL_IDLE
: SEL_READY
;
472 if (sel
.type
== SEL_REGULAR
&& sel
.ob
.y
!= sel
.oe
.y
) {
473 sel
.nb
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.ob
.x
: sel
.oe
.x
;
474 sel
.ne
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.oe
.x
: sel
.ob
.x
;
476 sel
.nb
.x
= MIN(sel
.ob
.x
, sel
.oe
.x
);
477 sel
.ne
.x
= MAX(sel
.ob
.x
, sel
.oe
.x
);
479 sel
.nb
.y
= MIN(sel
.ob
.y
, sel
.oe
.y
);
480 sel
.ne
.y
= MAX(sel
.ob
.y
, sel
.oe
.y
);
482 selsnap(&sel
.nb
.x
, &sel
.nb
.y
, -1);
483 selsnap(&sel
.ne
.x
, &sel
.ne
.y
, +1);
485 /* expand selection over line breaks */
486 if (sel
.type
== SEL_RECTANGULAR
)
488 i
= tlinelen(sel
.nb
.y
);
491 if (tlinelen(sel
.ne
.y
) <= sel
.ne
.x
)
492 sel
.ne
.x
= term
.col
- 1;
496 selected(int x
, int y
)
498 if (sel
.mode
== SEL_EMPTY
|| sel
.ob
.x
== -1 ||
499 sel
.alt
!= IS_SET(MODE_ALTSCREEN
))
502 if (sel
.type
== SEL_RECTANGULAR
)
503 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
504 && BETWEEN(x
, sel
.nb
.x
, sel
.ne
.x
);
506 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
507 && (y
!= sel
.nb
.y
|| x
>= sel
.nb
.x
)
508 && (y
!= sel
.ne
.y
|| x
<= sel
.ne
.x
);
512 selsnap(int *x
, int *y
, int direction
)
514 int newx
, newy
, xt
, yt
;
515 int delim
, prevdelim
;
521 * Snap around if the word wraps around at the end or
522 * beginning of a line.
524 prevgp
= &term
.line
[*y
][*x
];
525 prevdelim
= ISDELIM(prevgp
->u
);
527 newx
= *x
+ direction
;
529 if (!BETWEEN(newx
, 0, term
.col
- 1)) {
531 newx
= (newx
+ term
.col
) % term
.col
;
532 if (!BETWEEN(newy
, 0, term
.row
- 1))
538 yt
= newy
, xt
= newx
;
539 if (!(term
.line
[yt
][xt
].mode
& ATTR_WRAP
))
543 if (newx
>= tlinelen(newy
))
546 gp
= &term
.line
[newy
][newx
];
547 delim
= ISDELIM(gp
->u
);
548 if (!(gp
->mode
& ATTR_WDUMMY
) && (delim
!= prevdelim
549 || (delim
&& gp
->u
!= prevgp
->u
)))
560 * Snap around if the the previous line or the current one
561 * has set ATTR_WRAP at its end. Then the whole next or
562 * previous line will be selected.
564 *x
= (direction
< 0) ? 0 : term
.col
- 1;
566 for (; *y
> 0; *y
+= direction
) {
567 if (!(term
.line
[*y
-1][term
.col
-1].mode
572 } else if (direction
> 0) {
573 for (; *y
< term
.row
-1; *y
+= direction
) {
574 if (!(term
.line
[*y
][term
.col
-1].mode
588 int y
, bufsize
, lastx
, linelen
;
594 bufsize
= (term
.col
+1) * (sel
.ne
.y
-sel
.nb
.y
+1) * UTF_SIZ
;
595 ptr
= str
= xmalloc(bufsize
);
597 /* append every set & selected glyph to the selection */
598 for (y
= sel
.nb
.y
; y
<= sel
.ne
.y
; y
++) {
599 if ((linelen
= tlinelen(y
)) == 0) {
604 if (sel
.type
== SEL_RECTANGULAR
) {
605 gp
= &term
.line
[y
][sel
.nb
.x
];
608 gp
= &term
.line
[y
][sel
.nb
.y
== y
? sel
.nb
.x
: 0];
609 lastx
= (sel
.ne
.y
== y
) ? sel
.ne
.x
: term
.col
-1;
611 last
= &term
.line
[y
][MIN(lastx
, linelen
-1)];
612 while (last
>= gp
&& last
->u
== ' ')
615 for ( ; gp
<= last
; ++gp
) {
616 if (gp
->mode
& ATTR_WDUMMY
)
619 ptr
+= utf8encode(gp
->u
, ptr
);
623 * Copy and pasting of line endings is inconsistent
624 * in the inconsistent terminal and GUI world.
625 * The best solution seems like to produce '\n' when
626 * something is copied from st and convert '\n' to
627 * '\r', when something to be pasted is received by
629 * FIXME: Fix the computer world.
631 if ((y
< sel
.ne
.y
|| lastx
>= linelen
) && !(last
->mode
& ATTR_WRAP
))
645 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
649 die(const char *errstr
, ...)
653 va_start(ap
, errstr
);
654 vfprintf(stderr
, errstr
, ap
);
660 execsh(char *cmd
, char **args
)
663 const struct passwd
*pw
;
666 if ((pw
= getpwuid(getuid())) == NULL
) {
668 die("getpwuid: %s\n", strerror(errno
));
670 die("who are you?\n");
673 if ((sh
= getenv("SHELL")) == NULL
)
674 sh
= (pw
->pw_shell
[0]) ? pw
->pw_shell
: cmd
;
682 DEFAULT(args
, ((char *[]) {prog
, NULL
}));
687 setenv("LOGNAME", pw
->pw_name
, 1);
688 setenv("USER", pw
->pw_name
, 1);
689 setenv("SHELL", sh
, 1);
690 setenv("HOME", pw
->pw_dir
, 1);
691 setenv("TERM", termname
, 1);
693 signal(SIGCHLD
, SIG_DFL
);
694 signal(SIGHUP
, SIG_DFL
);
695 signal(SIGINT
, SIG_DFL
);
696 signal(SIGQUIT
, SIG_DFL
);
697 signal(SIGTERM
, SIG_DFL
);
698 signal(SIGALRM
, SIG_DFL
);
710 if ((p
= waitpid(pid
, &stat
, WNOHANG
)) < 0)
711 die("waiting for pid %hd failed: %s\n", pid
, strerror(errno
));
716 if (WIFEXITED(stat
) && WEXITSTATUS(stat
))
717 die("child exited with status %d\n", WEXITSTATUS(stat
));
718 else if (WIFSIGNALED(stat
))
719 die("child terminated due to signal %d\n", WTERMSIG(stat
));
726 char cmd
[_POSIX_ARG_MAX
], **p
, *q
, *s
;
729 if ((n
= strlen(stty_args
)) > sizeof(cmd
)-1)
730 die("incorrect stty parameters\n");
731 memcpy(cmd
, stty_args
, n
);
733 siz
= sizeof(cmd
) - n
;
734 for (p
= args
; p
&& (s
= *p
); ++p
) {
735 if ((n
= strlen(s
)) > siz
-1)
736 die("stty parameter length too long\n");
743 if (system(cmd
) != 0)
744 perror("Couldn't call stty");
748 ttynew(char *line
, char *cmd
, char *out
, char **args
)
753 term
.mode
|= MODE_PRINT
;
754 iofd
= (!strcmp(out
, "-")) ?
755 1 : open(out
, O_WRONLY
| O_CREAT
, 0666);
757 fprintf(stderr
, "Error opening %s:%s\n",
758 out
, strerror(errno
));
763 if ((cmdfd
= open(line
, O_RDWR
)) < 0)
764 die("open line '%s' failed: %s\n",
765 line
, strerror(errno
));
771 /* seems to work fine on linux, openbsd and freebsd */
772 if (openpty(&m
, &s
, NULL
, NULL
, NULL
) < 0)
773 die("openpty failed: %s\n", strerror(errno
));
775 switch (pid
= fork()) {
777 die("fork failed: %s\n", strerror(errno
));
781 setsid(); /* create a new process group */
785 if (ioctl(s
, TIOCSCTTY
, NULL
) < 0)
786 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno
));
790 if (pledge("stdio getpw proc exec", NULL
) == -1)
797 if (pledge("stdio rpath tty proc", NULL
) == -1)
802 signal(SIGCHLD
, sigchld
);
811 static char buf
[BUFSIZ
];
812 static int buflen
= 0;
816 /* append read bytes to unprocessed bytes */
817 if ((ret
= read(cmdfd
, buf
+buflen
, LEN(buf
)-buflen
)) < 0)
818 die("couldn't read from shell: %s\n", strerror(errno
));
821 written
= twrite(buf
, buflen
, 0);
823 /* keep any uncomplete utf8 char for the next call */
825 memmove(buf
, buf
+ written
, buflen
);
831 ttywrite(const char *s
, size_t n
, int may_echo
)
835 if (may_echo
&& IS_SET(MODE_ECHO
))
838 if (!IS_SET(MODE_CRLF
)) {
843 /* This is similar to how the kernel handles ONLCR for ttys */
847 ttywriteraw("\r\n", 2);
849 next
= memchr(s
, '\r', n
);
850 DEFAULT(next
, s
+ n
);
851 ttywriteraw(s
, next
- s
);
859 ttywriteraw(const char *s
, size_t n
)
866 * Remember that we are using a pty, which might be a modem line.
867 * Writing too much will clog the line. That's why we are doing this
869 * FIXME: Migrate the world to Plan 9.
877 /* Check if we can write. */
878 if (pselect(cmdfd
+1, &rfd
, &wfd
, NULL
, NULL
, NULL
) < 0) {
881 die("select failed: %s\n", strerror(errno
));
883 if (FD_ISSET(cmdfd
, &wfd
)) {
885 * Only write the bytes written by ttywrite() or the
886 * default of 256. This seems to be a reasonable value
887 * for a serial line. Bigger values might clog the I/O.
889 if ((r
= write(cmdfd
, s
, (n
< lim
)? n
: lim
)) < 0)
893 * We weren't able to write out everything.
894 * This means the buffer is getting full
902 /* All bytes have been written. */
906 if (FD_ISSET(cmdfd
, &rfd
))
912 die("write error on tty: %s\n", strerror(errno
));
916 ttyresize(int tw
, int th
)
924 if (ioctl(cmdfd
, TIOCSWINSZ
, &w
) < 0)
925 fprintf(stderr
, "Couldn't set window size: %s\n", strerror(errno
));
931 /* Send SIGHUP to shell */
940 for (i
= 0; i
< term
.row
-1; i
++) {
941 for (j
= 0; j
< term
.col
-1; j
++) {
942 if (term
.line
[i
][j
].mode
& attr
)
951 tsetdirt(int top
, int bot
)
955 LIMIT(top
, 0, term
.row
-1);
956 LIMIT(bot
, 0, term
.row
-1);
958 for (i
= top
; i
<= bot
; i
++)
963 tsetdirtattr(int attr
)
967 for (i
= 0; i
< term
.row
-1; i
++) {
968 for (j
= 0; j
< term
.col
-1; j
++) {
969 if (term
.line
[i
][j
].mode
& attr
) {
980 tsetdirt(0, term
.row
-1);
987 int alt
= IS_SET(MODE_ALTSCREEN
);
989 if (mode
== CURSOR_SAVE
) {
991 } else if (mode
== CURSOR_LOAD
) {
993 tmoveto(c
[alt
].x
, c
[alt
].y
);
1002 term
.c
= (TCursor
){{
1006 }, .x
= 0, .y
= 0, .state
= CURSOR_DEFAULT
};
1008 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1009 for (i
= tabspaces
; i
< term
.col
; i
+= tabspaces
)
1012 term
.bot
= term
.row
- 1;
1013 term
.mode
= MODE_WRAP
|MODE_UTF8
;
1014 memset(term
.trantbl
, CS_USA
, sizeof(term
.trantbl
));
1017 for (i
= 0; i
< 2; i
++) {
1019 tcursor(CURSOR_SAVE
);
1020 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1026 tnew(int col
, int row
)
1028 term
= (Term
){ .c
= { .attr
= { .fg
= defaultfg
, .bg
= defaultbg
} } };
1036 Line
*tmp
= term
.line
;
1038 term
.line
= term
.alt
;
1040 term
.mode
^= MODE_ALTSCREEN
;
1045 tscrolldown(int orig
, int n
)
1050 LIMIT(n
, 0, term
.bot
-orig
+1);
1052 tsetdirt(orig
, term
.bot
-n
);
1053 tclearregion(0, term
.bot
-n
+1, term
.col
-1, term
.bot
);
1055 for (i
= term
.bot
; i
>= orig
+n
; i
--) {
1056 temp
= term
.line
[i
];
1057 term
.line
[i
] = term
.line
[i
-n
];
1058 term
.line
[i
-n
] = temp
;
1065 tscrollup(int orig
, int n
)
1070 LIMIT(n
, 0, term
.bot
-orig
+1);
1072 tclearregion(0, orig
, term
.col
-1, orig
+n
-1);
1073 tsetdirt(orig
+n
, term
.bot
);
1075 for (i
= orig
; i
<= term
.bot
-n
; i
++) {
1076 temp
= term
.line
[i
];
1077 term
.line
[i
] = term
.line
[i
+n
];
1078 term
.line
[i
+n
] = temp
;
1081 selscroll(orig
, -n
);
1085 selscroll(int orig
, int n
)
1090 if (BETWEEN(sel
.ob
.y
, orig
, term
.bot
) || BETWEEN(sel
.oe
.y
, orig
, term
.bot
)) {
1091 if ((sel
.ob
.y
+= n
) > term
.bot
|| (sel
.oe
.y
+= n
) < term
.top
) {
1095 if (sel
.type
== SEL_RECTANGULAR
) {
1096 if (sel
.ob
.y
< term
.top
)
1097 sel
.ob
.y
= term
.top
;
1098 if (sel
.oe
.y
> term
.bot
)
1099 sel
.oe
.y
= term
.bot
;
1101 if (sel
.ob
.y
< term
.top
) {
1102 sel
.ob
.y
= term
.top
;
1105 if (sel
.oe
.y
> term
.bot
) {
1106 sel
.oe
.y
= term
.bot
;
1107 sel
.oe
.x
= term
.col
;
1115 tnewline(int first_col
)
1119 if (y
== term
.bot
) {
1120 tscrollup(term
.top
, 1);
1124 tmoveto(first_col
? 0 : term
.c
.x
, y
);
1130 char *p
= csiescseq
.buf
, *np
;
1139 csiescseq
.buf
[csiescseq
.len
] = '\0';
1140 while (p
< csiescseq
.buf
+csiescseq
.len
) {
1142 v
= strtol(p
, &np
, 10);
1145 if (v
== LONG_MAX
|| v
== LONG_MIN
)
1147 csiescseq
.arg
[csiescseq
.narg
++] = v
;
1149 if (*p
!= ';' || csiescseq
.narg
== ESC_ARG_SIZ
)
1153 csiescseq
.mode
[0] = *p
++;
1154 csiescseq
.mode
[1] = (p
< csiescseq
.buf
+csiescseq
.len
) ? *p
: '\0';
1157 /* for absolute user moves, when decom is set */
1159 tmoveato(int x
, int y
)
1161 tmoveto(x
, y
+ ((term
.c
.state
& CURSOR_ORIGIN
) ? term
.top
: 0));
1165 tmoveto(int x
, int y
)
1169 if (term
.c
.state
& CURSOR_ORIGIN
) {
1174 maxy
= term
.row
- 1;
1176 term
.c
.state
&= ~CURSOR_WRAPNEXT
;
1177 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1178 term
.c
.y
= LIMIT(y
, miny
, maxy
);
1182 tsetchar(Rune u
, Glyph
*attr
, int x
, int y
)
1184 static char *vt100_0
[62] = { /* 0x41 - 0x7e */
1185 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1186 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1187 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1188 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1189 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1190 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1191 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1192 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1196 * The table is proudly stolen from rxvt.
1198 if (term
.trantbl
[term
.charset
] == CS_GRAPHIC0
&&
1199 BETWEEN(u
, 0x41, 0x7e) && vt100_0
[u
- 0x41])
1200 utf8decode(vt100_0
[u
- 0x41], &u
, UTF_SIZ
);
1202 if (term
.line
[y
][x
].mode
& ATTR_WIDE
) {
1203 if (x
+1 < term
.col
) {
1204 term
.line
[y
][x
+1].u
= ' ';
1205 term
.line
[y
][x
+1].mode
&= ~ATTR_WDUMMY
;
1207 } else if (term
.line
[y
][x
].mode
& ATTR_WDUMMY
) {
1208 term
.line
[y
][x
-1].u
= ' ';
1209 term
.line
[y
][x
-1].mode
&= ~ATTR_WIDE
;
1213 term
.line
[y
][x
] = *attr
;
1214 term
.line
[y
][x
].u
= u
;
1218 tclearregion(int x1
, int y1
, int x2
, int y2
)
1224 temp
= x1
, x1
= x2
, x2
= temp
;
1226 temp
= y1
, y1
= y2
, y2
= temp
;
1228 LIMIT(x1
, 0, term
.col
-1);
1229 LIMIT(x2
, 0, term
.col
-1);
1230 LIMIT(y1
, 0, term
.row
-1);
1231 LIMIT(y2
, 0, term
.row
-1);
1233 for (y
= y1
; y
<= y2
; y
++) {
1235 for (x
= x1
; x
<= x2
; x
++) {
1236 gp
= &term
.line
[y
][x
];
1239 gp
->fg
= term
.c
.attr
.fg
;
1240 gp
->bg
= term
.c
.attr
.bg
;
1253 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1257 size
= term
.col
- src
;
1258 line
= term
.line
[term
.c
.y
];
1260 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1261 tclearregion(term
.col
-n
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1270 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1274 size
= term
.col
- dst
;
1275 line
= term
.line
[term
.c
.y
];
1277 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1278 tclearregion(src
, term
.c
.y
, dst
- 1, term
.c
.y
);
1282 tinsertblankline(int n
)
1284 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1285 tscrolldown(term
.c
.y
, n
);
1291 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1292 tscrollup(term
.c
.y
, n
);
1296 tdefcolor(int *attr
, int *npar
, int l
)
1301 switch (attr
[*npar
+ 1]) {
1302 case 2: /* direct color in RGB space */
1303 if (*npar
+ 4 >= l
) {
1305 "erresc(38): Incorrect number of parameters (%d)\n",
1309 r
= attr
[*npar
+ 2];
1310 g
= attr
[*npar
+ 3];
1311 b
= attr
[*npar
+ 4];
1313 if (!BETWEEN(r
, 0, 255) || !BETWEEN(g
, 0, 255) || !BETWEEN(b
, 0, 255))
1314 fprintf(stderr
, "erresc: bad rgb color (%u,%u,%u)\n",
1317 idx
= TRUECOLOR(r
, g
, b
);
1319 case 5: /* indexed color */
1320 if (*npar
+ 2 >= l
) {
1322 "erresc(38): Incorrect number of parameters (%d)\n",
1327 if (!BETWEEN(attr
[*npar
], 0, 255))
1328 fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[*npar
]);
1332 case 0: /* implemented defined (only foreground) */
1333 case 1: /* transparent */
1334 case 3: /* direct color in CMY space */
1335 case 4: /* direct color in CMYK space */
1338 "erresc(38): gfx attr %d unknown\n", attr
[*npar
]);
1346 tsetattr(int *attr
, int l
)
1351 for (i
= 0; i
< l
; i
++) {
1354 term
.c
.attr
.mode
&= ~(
1363 term
.c
.attr
.fg
= defaultfg
;
1364 term
.c
.attr
.bg
= defaultbg
;
1367 term
.c
.attr
.mode
|= ATTR_BOLD
;
1370 term
.c
.attr
.mode
|= ATTR_FAINT
;
1373 term
.c
.attr
.mode
|= ATTR_ITALIC
;
1376 term
.c
.attr
.mode
|= ATTR_UNDERLINE
;
1378 case 5: /* slow blink */
1380 case 6: /* rapid blink */
1381 term
.c
.attr
.mode
|= ATTR_BLINK
;
1384 term
.c
.attr
.mode
|= ATTR_REVERSE
;
1387 term
.c
.attr
.mode
|= ATTR_INVISIBLE
;
1390 term
.c
.attr
.mode
|= ATTR_STRUCK
;
1393 term
.c
.attr
.mode
&= ~(ATTR_BOLD
| ATTR_FAINT
);
1396 term
.c
.attr
.mode
&= ~ATTR_ITALIC
;
1399 term
.c
.attr
.mode
&= ~ATTR_UNDERLINE
;
1402 term
.c
.attr
.mode
&= ~ATTR_BLINK
;
1405 term
.c
.attr
.mode
&= ~ATTR_REVERSE
;
1408 term
.c
.attr
.mode
&= ~ATTR_INVISIBLE
;
1411 term
.c
.attr
.mode
&= ~ATTR_STRUCK
;
1414 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1415 term
.c
.attr
.fg
= idx
;
1418 term
.c
.attr
.fg
= defaultfg
;
1421 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1422 term
.c
.attr
.bg
= idx
;
1425 term
.c
.attr
.bg
= defaultbg
;
1428 if (BETWEEN(attr
[i
], 30, 37)) {
1429 term
.c
.attr
.fg
= attr
[i
] - 30;
1430 } else if (BETWEEN(attr
[i
], 40, 47)) {
1431 term
.c
.attr
.bg
= attr
[i
] - 40;
1432 } else if (BETWEEN(attr
[i
], 90, 97)) {
1433 term
.c
.attr
.fg
= attr
[i
] - 90 + 8;
1434 } else if (BETWEEN(attr
[i
], 100, 107)) {
1435 term
.c
.attr
.bg
= attr
[i
] - 100 + 8;
1438 "erresc(default): gfx attr %d unknown\n",
1448 tsetscroll(int t
, int b
)
1452 LIMIT(t
, 0, term
.row
-1);
1453 LIMIT(b
, 0, term
.row
-1);
1464 tsetmode(int priv
, int set
, int *args
, int narg
)
1468 for (lim
= args
+ narg
; args
< lim
; ++args
) {
1471 case 1: /* DECCKM -- Cursor key */
1472 xsetmode(set
, MODE_APPCURSOR
);
1474 case 5: /* DECSCNM -- Reverse video */
1475 xsetmode(set
, MODE_REVERSE
);
1477 case 6: /* DECOM -- Origin */
1478 MODBIT(term
.c
.state
, set
, CURSOR_ORIGIN
);
1481 case 7: /* DECAWM -- Auto wrap */
1482 MODBIT(term
.mode
, set
, MODE_WRAP
);
1484 case 0: /* Error (IGNORED) */
1485 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1486 case 3: /* DECCOLM -- Column (IGNORED) */
1487 case 4: /* DECSCLM -- Scroll (IGNORED) */
1488 case 8: /* DECARM -- Auto repeat (IGNORED) */
1489 case 18: /* DECPFF -- Printer feed (IGNORED) */
1490 case 19: /* DECPEX -- Printer extent (IGNORED) */
1491 case 42: /* DECNRCM -- National characters (IGNORED) */
1492 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1494 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1495 xsetmode(!set
, MODE_HIDE
);
1497 case 9: /* X10 mouse compatibility mode */
1498 xsetpointermotion(0);
1499 xsetmode(0, MODE_MOUSE
);
1500 xsetmode(set
, MODE_MOUSEX10
);
1502 case 1000: /* 1000: report button press */
1503 xsetpointermotion(0);
1504 xsetmode(0, MODE_MOUSE
);
1505 xsetmode(set
, MODE_MOUSEBTN
);
1507 case 1002: /* 1002: report motion on button press */
1508 xsetpointermotion(0);
1509 xsetmode(0, MODE_MOUSE
);
1510 xsetmode(set
, MODE_MOUSEMOTION
);
1512 case 1003: /* 1003: enable all mouse motions */
1513 xsetpointermotion(set
);
1514 xsetmode(0, MODE_MOUSE
);
1515 xsetmode(set
, MODE_MOUSEMANY
);
1517 case 1004: /* 1004: send focus events to tty */
1518 xsetmode(set
, MODE_FOCUS
);
1520 case 1006: /* 1006: extended reporting mode */
1521 xsetmode(set
, MODE_MOUSESGR
);
1524 xsetmode(set
, MODE_8BIT
);
1526 case 1049: /* swap screen & set/restore cursor as xterm */
1527 if (!allowaltscreen
)
1529 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1531 case 47: /* swap screen */
1533 if (!allowaltscreen
)
1535 alt
= IS_SET(MODE_ALTSCREEN
);
1537 tclearregion(0, 0, term
.col
-1,
1540 if (set
^ alt
) /* set is always 1 or 0 */
1546 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1548 case 2004: /* 2004: bracketed paste mode */
1549 xsetmode(set
, MODE_BRCKTPASTE
);
1551 /* Not implemented mouse modes. See comments there. */
1552 case 1001: /* mouse highlight mode; can hang the
1553 terminal by design when implemented. */
1554 case 1005: /* UTF-8 mouse mode; will confuse
1555 applications not supporting UTF-8
1557 case 1015: /* urxvt mangled mouse mode; incompatible
1558 and can be mistaken for other control
1563 "erresc: unknown private set/reset mode %d\n",
1569 case 0: /* Error (IGNORED) */
1572 xsetmode(set
, MODE_KBDLOCK
);
1574 case 4: /* IRM -- Insertion-replacement */
1575 MODBIT(term
.mode
, set
, MODE_INSERT
);
1577 case 12: /* SRM -- Send/Receive */
1578 MODBIT(term
.mode
, !set
, MODE_ECHO
);
1580 case 20: /* LNM -- Linefeed/new line */
1581 MODBIT(term
.mode
, set
, MODE_CRLF
);
1585 "erresc: unknown set/reset mode %d\n",
1599 switch (csiescseq
.mode
[0]) {
1602 fprintf(stderr
, "erresc: unknown csi ");
1606 case '@': /* ICH -- Insert <n> blank char */
1607 DEFAULT(csiescseq
.arg
[0], 1);
1608 tinsertblank(csiescseq
.arg
[0]);
1610 case 'A': /* CUU -- Cursor <n> Up */
1611 DEFAULT(csiescseq
.arg
[0], 1);
1612 tmoveto(term
.c
.x
, term
.c
.y
-csiescseq
.arg
[0]);
1614 case 'B': /* CUD -- Cursor <n> Down */
1615 case 'e': /* VPR --Cursor <n> Down */
1616 DEFAULT(csiescseq
.arg
[0], 1);
1617 tmoveto(term
.c
.x
, term
.c
.y
+csiescseq
.arg
[0]);
1619 case 'i': /* MC -- Media Copy */
1620 switch (csiescseq
.arg
[0]) {
1625 tdumpline(term
.c
.y
);
1631 term
.mode
&= ~MODE_PRINT
;
1634 term
.mode
|= MODE_PRINT
;
1638 case 'c': /* DA -- Device Attributes */
1639 if (csiescseq
.arg
[0] == 0)
1640 ttywrite(vtiden
, strlen(vtiden
), 0);
1642 case 'C': /* CUF -- Cursor <n> Forward */
1643 case 'a': /* HPR -- Cursor <n> Forward */
1644 DEFAULT(csiescseq
.arg
[0], 1);
1645 tmoveto(term
.c
.x
+csiescseq
.arg
[0], term
.c
.y
);
1647 case 'D': /* CUB -- Cursor <n> Backward */
1648 DEFAULT(csiescseq
.arg
[0], 1);
1649 tmoveto(term
.c
.x
-csiescseq
.arg
[0], term
.c
.y
);
1651 case 'E': /* CNL -- Cursor <n> Down and first col */
1652 DEFAULT(csiescseq
.arg
[0], 1);
1653 tmoveto(0, term
.c
.y
+csiescseq
.arg
[0]);
1655 case 'F': /* CPL -- Cursor <n> Up and first col */
1656 DEFAULT(csiescseq
.arg
[0], 1);
1657 tmoveto(0, term
.c
.y
-csiescseq
.arg
[0]);
1659 case 'g': /* TBC -- Tabulation clear */
1660 switch (csiescseq
.arg
[0]) {
1661 case 0: /* clear current tab stop */
1662 term
.tabs
[term
.c
.x
] = 0;
1664 case 3: /* clear all the tabs */
1665 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1671 case 'G': /* CHA -- Move to <col> */
1673 DEFAULT(csiescseq
.arg
[0], 1);
1674 tmoveto(csiescseq
.arg
[0]-1, term
.c
.y
);
1676 case 'H': /* CUP -- Move to <row> <col> */
1678 DEFAULT(csiescseq
.arg
[0], 1);
1679 DEFAULT(csiescseq
.arg
[1], 1);
1680 tmoveato(csiescseq
.arg
[1]-1, csiescseq
.arg
[0]-1);
1682 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1683 DEFAULT(csiescseq
.arg
[0], 1);
1684 tputtab(csiescseq
.arg
[0]);
1686 case 'J': /* ED -- Clear screen */
1687 switch (csiescseq
.arg
[0]) {
1689 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1690 if (term
.c
.y
< term
.row
-1) {
1691 tclearregion(0, term
.c
.y
+1, term
.col
-1,
1697 tclearregion(0, 0, term
.col
-1, term
.c
.y
-1);
1698 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1701 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1707 case 'K': /* EL -- Clear line */
1708 switch (csiescseq
.arg
[0]) {
1710 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1,
1714 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1717 tclearregion(0, term
.c
.y
, term
.col
-1, term
.c
.y
);
1721 case 'S': /* SU -- Scroll <n> line up */
1722 DEFAULT(csiescseq
.arg
[0], 1);
1723 tscrollup(term
.top
, csiescseq
.arg
[0]);
1725 case 'T': /* SD -- Scroll <n> line down */
1726 DEFAULT(csiescseq
.arg
[0], 1);
1727 tscrolldown(term
.top
, csiescseq
.arg
[0]);
1729 case 'L': /* IL -- Insert <n> blank lines */
1730 DEFAULT(csiescseq
.arg
[0], 1);
1731 tinsertblankline(csiescseq
.arg
[0]);
1733 case 'l': /* RM -- Reset Mode */
1734 tsetmode(csiescseq
.priv
, 0, csiescseq
.arg
, csiescseq
.narg
);
1736 case 'M': /* DL -- Delete <n> lines */
1737 DEFAULT(csiescseq
.arg
[0], 1);
1738 tdeleteline(csiescseq
.arg
[0]);
1740 case 'X': /* ECH -- Erase <n> char */
1741 DEFAULT(csiescseq
.arg
[0], 1);
1742 tclearregion(term
.c
.x
, term
.c
.y
,
1743 term
.c
.x
+ csiescseq
.arg
[0] - 1, term
.c
.y
);
1745 case 'P': /* DCH -- Delete <n> char */
1746 DEFAULT(csiescseq
.arg
[0], 1);
1747 tdeletechar(csiescseq
.arg
[0]);
1749 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1750 DEFAULT(csiescseq
.arg
[0], 1);
1751 tputtab(-csiescseq
.arg
[0]);
1753 case 'd': /* VPA -- Move to <row> */
1754 DEFAULT(csiescseq
.arg
[0], 1);
1755 tmoveato(term
.c
.x
, csiescseq
.arg
[0]-1);
1757 case 'h': /* SM -- Set terminal mode */
1758 tsetmode(csiescseq
.priv
, 1, csiescseq
.arg
, csiescseq
.narg
);
1760 case 'm': /* SGR -- Terminal attribute (color) */
1761 tsetattr(csiescseq
.arg
, csiescseq
.narg
);
1763 case 'n': /* DSR – Device Status Report (cursor position) */
1764 if (csiescseq
.arg
[0] == 6) {
1765 len
= snprintf(buf
, sizeof(buf
),"\033[%i;%iR",
1766 term
.c
.y
+1, term
.c
.x
+1);
1767 ttywrite(buf
, len
, 0);
1770 case 'r': /* DECSTBM -- Set Scrolling Region */
1771 if (csiescseq
.priv
) {
1774 DEFAULT(csiescseq
.arg
[0], 1);
1775 DEFAULT(csiescseq
.arg
[1], term
.row
);
1776 tsetscroll(csiescseq
.arg
[0]-1, csiescseq
.arg
[1]-1);
1780 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1781 tcursor(CURSOR_SAVE
);
1783 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1784 tcursor(CURSOR_LOAD
);
1787 switch (csiescseq
.mode
[1]) {
1788 case 'q': /* DECSCUSR -- Set Cursor Style */
1789 if (xsetcursor(csiescseq
.arg
[0]))
1805 fprintf(stderr
, "ESC[");
1806 for (i
= 0; i
< csiescseq
.len
; i
++) {
1807 c
= csiescseq
.buf
[i
] & 0xff;
1810 } else if (c
== '\n') {
1811 fprintf(stderr
, "(\\n)");
1812 } else if (c
== '\r') {
1813 fprintf(stderr
, "(\\r)");
1814 } else if (c
== 0x1b) {
1815 fprintf(stderr
, "(\\e)");
1817 fprintf(stderr
, "(%02x)", c
);
1826 memset(&csiescseq
, 0, sizeof(csiescseq
));
1832 char *p
= NULL
, *dec
;
1835 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
1837 par
= (narg
= strescseq
.narg
) ? atoi(strescseq
.args
[0]) : 0;
1839 switch (strescseq
.type
) {
1840 case ']': /* OSC -- Operating System Command */
1846 xsettitle(strescseq
.args
[1]);
1850 dec
= base64dec(strescseq
.args
[2]);
1855 fprintf(stderr
, "erresc: invalid base64\n");
1859 case 4: /* color set */
1862 p
= strescseq
.args
[2];
1864 case 104: /* color reset, here p = NULL */
1865 j
= (narg
> 1) ? atoi(strescseq
.args
[1]) : -1;
1866 if (xsetcolorname(j
, p
)) {
1867 if (par
== 104 && narg
<= 1)
1868 return; /* color reset without parameter */
1869 fprintf(stderr
, "erresc: invalid color j=%d, p=%s\n",
1870 j
, p
? p
: "(null)");
1873 * TODO if defaultbg color is changed, borders
1881 case 'k': /* old title set compatibility */
1882 xsettitle(strescseq
.args
[0]);
1884 case 'P': /* DCS -- Device Control String */
1885 term
.mode
|= ESC_DCS
;
1886 case '_': /* APC -- Application Program Command */
1887 case '^': /* PM -- Privacy Message */
1891 fprintf(stderr
, "erresc: unknown str ");
1899 char *p
= strescseq
.buf
;
1902 strescseq
.buf
[strescseq
.len
] = '\0';
1907 while (strescseq
.narg
< STR_ARG_SIZ
) {
1908 strescseq
.args
[strescseq
.narg
++] = p
;
1909 while ((c
= *p
) != ';' && c
!= '\0')
1923 fprintf(stderr
, "ESC%c", strescseq
.type
);
1924 for (i
= 0; i
< strescseq
.len
; i
++) {
1925 c
= strescseq
.buf
[i
] & 0xff;
1929 } else if (isprint(c
)) {
1931 } else if (c
== '\n') {
1932 fprintf(stderr
, "(\\n)");
1933 } else if (c
== '\r') {
1934 fprintf(stderr
, "(\\r)");
1935 } else if (c
== 0x1b) {
1936 fprintf(stderr
, "(\\e)");
1938 fprintf(stderr
, "(%02x)", c
);
1941 fprintf(stderr
, "ESC\\\n");
1947 memset(&strescseq
, 0, sizeof(strescseq
));
1951 sendbreak(const Arg
*arg
)
1953 if (tcsendbreak(cmdfd
, 0))
1954 perror("Error sending break");
1958 tprinter(char *s
, size_t len
)
1960 if (iofd
!= -1 && xwrite(iofd
, s
, len
) < 0) {
1961 perror("Error writing to output file");
1968 toggleprinter(const Arg
*arg
)
1970 term
.mode
^= MODE_PRINT
;
1974 printscreen(const Arg
*arg
)
1980 printsel(const Arg
*arg
)
1990 if ((ptr
= getsel())) {
1991 tprinter(ptr
, strlen(ptr
));
2002 bp
= &term
.line
[n
][0];
2003 end
= &bp
[MIN(tlinelen(n
), term
.col
) - 1];
2004 if (bp
!= end
|| bp
->u
!= ' ') {
2005 for ( ;bp
<= end
; ++bp
)
2006 tprinter(buf
, utf8encode(bp
->u
, buf
));
2016 for (i
= 0; i
< term
.row
; ++i
)
2026 while (x
< term
.col
&& n
--)
2027 for (++x
; x
< term
.col
&& !term
.tabs
[x
]; ++x
)
2030 while (x
> 0 && n
++)
2031 for (--x
; x
> 0 && !term
.tabs
[x
]; --x
)
2034 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
2038 tdefutf8(char ascii
)
2041 term
.mode
|= MODE_UTF8
;
2042 else if (ascii
== '@')
2043 term
.mode
&= ~MODE_UTF8
;
2047 tdeftran(char ascii
)
2049 static char cs
[] = "0B";
2050 static int vcs
[] = {CS_GRAPHIC0
, CS_USA
};
2053 if ((p
= strchr(cs
, ascii
)) == NULL
) {
2054 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
2056 term
.trantbl
[term
.icharset
] = vcs
[p
- cs
];
2065 if (c
== '8') { /* DEC screen alignment test. */
2066 for (x
= 0; x
< term
.col
; ++x
) {
2067 for (y
= 0; y
< term
.row
; ++y
)
2068 tsetchar('E', &term
.c
.attr
, x
, y
);
2074 tstrsequence(uchar c
)
2079 case 0x90: /* DCS -- Device Control String */
2081 term
.esc
|= ESC_DCS
;
2083 case 0x9f: /* APC -- Application Program Command */
2086 case 0x9e: /* PM -- Privacy Message */
2089 case 0x9d: /* OSC -- Operating System Command */
2094 term
.esc
|= ESC_STR
;
2098 tcontrolcode(uchar ascii
)
2105 tmoveto(term
.c
.x
-1, term
.c
.y
);
2108 tmoveto(0, term
.c
.y
);
2113 /* go to first col if the mode is set */
2114 tnewline(IS_SET(MODE_CRLF
));
2116 case '\a': /* BEL */
2117 if (term
.esc
& ESC_STR_END
) {
2118 /* backwards compatibility to xterm */
2124 case '\033': /* ESC */
2126 term
.esc
&= ~(ESC_CSI
|ESC_ALTCHARSET
|ESC_TEST
);
2127 term
.esc
|= ESC_START
;
2129 case '\016': /* SO (LS1 -- Locking shift 1) */
2130 case '\017': /* SI (LS0 -- Locking shift 0) */
2131 term
.charset
= 1 - (ascii
- '\016');
2133 case '\032': /* SUB */
2134 tsetchar('?', &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2135 case '\030': /* CAN */
2138 case '\005': /* ENQ (IGNORED) */
2139 case '\000': /* NUL (IGNORED) */
2140 case '\021': /* XON (IGNORED) */
2141 case '\023': /* XOFF (IGNORED) */
2142 case 0177: /* DEL (IGNORED) */
2144 case 0x80: /* TODO: PAD */
2145 case 0x81: /* TODO: HOP */
2146 case 0x82: /* TODO: BPH */
2147 case 0x83: /* TODO: NBH */
2148 case 0x84: /* TODO: IND */
2150 case 0x85: /* NEL -- Next line */
2151 tnewline(1); /* always go to first col */
2153 case 0x86: /* TODO: SSA */
2154 case 0x87: /* TODO: ESA */
2156 case 0x88: /* HTS -- Horizontal tab stop */
2157 term
.tabs
[term
.c
.x
] = 1;
2159 case 0x89: /* TODO: HTJ */
2160 case 0x8a: /* TODO: VTS */
2161 case 0x8b: /* TODO: PLD */
2162 case 0x8c: /* TODO: PLU */
2163 case 0x8d: /* TODO: RI */
2164 case 0x8e: /* TODO: SS2 */
2165 case 0x8f: /* TODO: SS3 */
2166 case 0x91: /* TODO: PU1 */
2167 case 0x92: /* TODO: PU2 */
2168 case 0x93: /* TODO: STS */
2169 case 0x94: /* TODO: CCH */
2170 case 0x95: /* TODO: MW */
2171 case 0x96: /* TODO: SPA */
2172 case 0x97: /* TODO: EPA */
2173 case 0x98: /* TODO: SOS */
2174 case 0x99: /* TODO: SGCI */
2176 case 0x9a: /* DECID -- Identify Terminal */
2177 ttywrite(vtiden
, strlen(vtiden
), 0);
2179 case 0x9b: /* TODO: CSI */
2180 case 0x9c: /* TODO: ST */
2182 case 0x90: /* DCS -- Device Control String */
2183 case 0x9d: /* OSC -- Operating System Command */
2184 case 0x9e: /* PM -- Privacy Message */
2185 case 0x9f: /* APC -- Application Program Command */
2186 tstrsequence(ascii
);
2189 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2190 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
2194 * returns 1 when the sequence is finished and it hasn't to read
2195 * more characters for this sequence, otherwise 0
2198 eschandle(uchar ascii
)
2202 term
.esc
|= ESC_CSI
;
2205 term
.esc
|= ESC_TEST
;
2208 term
.esc
|= ESC_UTF8
;
2210 case 'P': /* DCS -- Device Control String */
2211 case '_': /* APC -- Application Program Command */
2212 case '^': /* PM -- Privacy Message */
2213 case ']': /* OSC -- Operating System Command */
2214 case 'k': /* old title set compatibility */
2215 tstrsequence(ascii
);
2217 case 'n': /* LS2 -- Locking shift 2 */
2218 case 'o': /* LS3 -- Locking shift 3 */
2219 term
.charset
= 2 + (ascii
- 'n');
2221 case '(': /* GZD4 -- set primary charset G0 */
2222 case ')': /* G1D4 -- set secondary charset G1 */
2223 case '*': /* G2D4 -- set tertiary charset G2 */
2224 case '+': /* G3D4 -- set quaternary charset G3 */
2225 term
.icharset
= ascii
- '(';
2226 term
.esc
|= ESC_ALTCHARSET
;
2228 case 'D': /* IND -- Linefeed */
2229 if (term
.c
.y
== term
.bot
) {
2230 tscrollup(term
.top
, 1);
2232 tmoveto(term
.c
.x
, term
.c
.y
+1);
2235 case 'E': /* NEL -- Next line */
2236 tnewline(1); /* always go to first col */
2238 case 'H': /* HTS -- Horizontal tab stop */
2239 term
.tabs
[term
.c
.x
] = 1;
2241 case 'M': /* RI -- Reverse index */
2242 if (term
.c
.y
== term
.top
) {
2243 tscrolldown(term
.top
, 1);
2245 tmoveto(term
.c
.x
, term
.c
.y
-1);
2248 case 'Z': /* DECID -- Identify Terminal */
2249 ttywrite(vtiden
, strlen(vtiden
), 0);
2251 case 'c': /* RIS -- Reset to initial state */
2256 case '=': /* DECPAM -- Application keypad */
2257 xsetmode(1, MODE_APPKEYPAD
);
2259 case '>': /* DECPNM -- Normal keypad */
2260 xsetmode(0, MODE_APPKEYPAD
);
2262 case '7': /* DECSC -- Save Cursor */
2263 tcursor(CURSOR_SAVE
);
2265 case '8': /* DECRC -- Restore Cursor */
2266 tcursor(CURSOR_LOAD
);
2268 case '\\': /* ST -- String Terminator */
2269 if (term
.esc
& ESC_STR_END
)
2273 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2274 (uchar
) ascii
, isprint(ascii
)? ascii
:'.');
2288 control
= ISCONTROL(u
);
2289 if (!IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2293 len
= utf8encode(u
, c
);
2294 if (!control
&& (width
= wcwidth(u
)) == -1) {
2295 memcpy(c
, "\357\277\275", 4); /* UTF_INVALID */
2300 if (IS_SET(MODE_PRINT
))
2304 * STR sequence must be checked before anything else
2305 * because it uses all following characters until it
2306 * receives a ESC, a SUB, a ST or any other C1 control
2309 if (term
.esc
& ESC_STR
) {
2310 if (u
== '\a' || u
== 030 || u
== 032 || u
== 033 ||
2312 term
.esc
&= ~(ESC_START
|ESC_STR
|ESC_DCS
);
2313 if (IS_SET(MODE_SIXEL
)) {
2314 /* TODO: render sixel */;
2315 term
.mode
&= ~MODE_SIXEL
;
2318 term
.esc
|= ESC_STR_END
;
2319 goto check_control_code
;
2322 if (IS_SET(MODE_SIXEL
)) {
2323 /* TODO: implement sixel mode */
2326 if (term
.esc
&ESC_DCS
&& strescseq
.len
== 0 && u
== 'q')
2327 term
.mode
|= MODE_SIXEL
;
2329 if (strescseq
.len
+len
>= sizeof(strescseq
.buf
)-1) {
2331 * Here is a bug in terminals. If the user never sends
2332 * some code to stop the str or esc command, then st
2333 * will stop responding. But this is better than
2334 * silently failing with unknown characters. At least
2335 * then users will report back.
2337 * In the case users ever get fixed, here is the code:
2346 memmove(&strescseq
.buf
[strescseq
.len
], c
, len
);
2347 strescseq
.len
+= len
;
2353 * Actions of control codes must be performed as soon they arrive
2354 * because they can be embedded inside a control sequence, and
2355 * they must not cause conflicts with sequences.
2360 * control codes are not shown ever
2363 } else if (term
.esc
& ESC_START
) {
2364 if (term
.esc
& ESC_CSI
) {
2365 csiescseq
.buf
[csiescseq
.len
++] = u
;
2366 if (BETWEEN(u
, 0x40, 0x7E)
2367 || csiescseq
.len
>= \
2368 sizeof(csiescseq
.buf
)-1) {
2374 } else if (term
.esc
& ESC_UTF8
) {
2376 } else if (term
.esc
& ESC_ALTCHARSET
) {
2378 } else if (term
.esc
& ESC_TEST
) {
2383 /* sequence already finished */
2387 * All characters which form part of a sequence are not
2392 if (sel
.ob
.x
!= -1 && BETWEEN(term
.c
.y
, sel
.ob
.y
, sel
.oe
.y
))
2395 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2396 if (IS_SET(MODE_WRAP
) && (term
.c
.state
& CURSOR_WRAPNEXT
)) {
2397 gp
->mode
|= ATTR_WRAP
;
2399 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2402 if (IS_SET(MODE_INSERT
) && term
.c
.x
+width
< term
.col
)
2403 memmove(gp
+width
, gp
, (term
.col
- term
.c
.x
- width
) * sizeof(Glyph
));
2405 if (term
.c
.x
+width
> term
.col
) {
2407 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2410 tsetchar(u
, &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2413 gp
->mode
|= ATTR_WIDE
;
2414 if (term
.c
.x
+1 < term
.col
) {
2416 gp
[1].mode
= ATTR_WDUMMY
;
2419 if (term
.c
.x
+width
< term
.col
) {
2420 tmoveto(term
.c
.x
+width
, term
.c
.y
);
2422 term
.c
.state
|= CURSOR_WRAPNEXT
;
2427 twrite(const char *buf
, int buflen
, int show_ctrl
)
2433 for (n
= 0; n
< buflen
; n
+= charsize
) {
2434 if (IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2435 /* process a complete utf8 char */
2436 charsize
= utf8decode(buf
+ n
, &u
, buflen
- n
);
2443 if (show_ctrl
&& ISCONTROL(u
)) {
2448 } else if (u
!= '\n' && u
!= '\r' && u
!= '\t') {
2459 tresize(int col
, int row
)
2462 int minrow
= MIN(row
, term
.row
);
2463 int mincol
= MIN(col
, term
.col
);
2467 if (col
< 1 || row
< 1) {
2469 "tresize: error resizing to %dx%d\n", col
, row
);
2474 * slide screen to keep cursor where we expect it -
2475 * tscrollup would work here, but we can optimize to
2476 * memmove because we're freeing the earlier lines
2478 for (i
= 0; i
<= term
.c
.y
- row
; i
++) {
2482 /* ensure that both src and dst are not NULL */
2484 memmove(term
.line
, term
.line
+ i
, row
* sizeof(Line
));
2485 memmove(term
.alt
, term
.alt
+ i
, row
* sizeof(Line
));
2487 for (i
+= row
; i
< term
.row
; i
++) {
2492 /* resize to new height */
2493 term
.line
= xrealloc(term
.line
, row
* sizeof(Line
));
2494 term
.alt
= xrealloc(term
.alt
, row
* sizeof(Line
));
2495 term
.dirty
= xrealloc(term
.dirty
, row
* sizeof(*term
.dirty
));
2496 term
.tabs
= xrealloc(term
.tabs
, col
* sizeof(*term
.tabs
));
2498 /* resize each row to new width, zero-pad if needed */
2499 for (i
= 0; i
< minrow
; i
++) {
2500 term
.line
[i
] = xrealloc(term
.line
[i
], col
* sizeof(Glyph
));
2501 term
.alt
[i
] = xrealloc(term
.alt
[i
], col
* sizeof(Glyph
));
2504 /* allocate any new rows */
2505 for (/* i = minrow */; i
< row
; i
++) {
2506 term
.line
[i
] = xmalloc(col
* sizeof(Glyph
));
2507 term
.alt
[i
] = xmalloc(col
* sizeof(Glyph
));
2509 if (col
> term
.col
) {
2510 bp
= term
.tabs
+ term
.col
;
2512 memset(bp
, 0, sizeof(*term
.tabs
) * (col
- term
.col
));
2513 while (--bp
> term
.tabs
&& !*bp
)
2515 for (bp
+= tabspaces
; bp
< term
.tabs
+ col
; bp
+= tabspaces
)
2518 /* update terminal size */
2521 /* reset scrolling region */
2522 tsetscroll(0, row
-1);
2523 /* make use of the LIMIT in tmoveto */
2524 tmoveto(term
.c
.x
, term
.c
.y
);
2525 /* Clearing both screens (it makes dirty all lines) */
2527 for (i
= 0; i
< 2; i
++) {
2528 if (mincol
< col
&& 0 < minrow
) {
2529 tclearregion(mincol
, 0, col
- 1, minrow
- 1);
2531 if (0 < col
&& minrow
< row
) {
2532 tclearregion(0, minrow
, col
- 1, row
- 1);
2535 tcursor(CURSOR_LOAD
);
2547 drawregion(int x1
, int y1
, int x2
, int y2
)
2550 for (y
= y1
; y
< y2
; y
++) {
2555 xdrawline(term
.line
[y
], x1
, y
, x2
);
2567 /* adjust cursor position */
2568 LIMIT(term
.ocx
, 0, term
.col
-1);
2569 LIMIT(term
.ocy
, 0, term
.row
-1);
2570 if (term
.line
[term
.ocy
][term
.ocx
].mode
& ATTR_WDUMMY
)
2572 if (term
.line
[term
.c
.y
][cx
].mode
& ATTR_WDUMMY
)
2575 drawregion(0, 0, term
.col
, term
.row
);
2576 xdrawcursor(cx
, term
.c
.y
, term
.line
[term
.c
.y
][cx
],
2577 term
.ocx
, term
.ocy
, term
.line
[term
.ocy
][term
.ocx
]);
2578 term
.ocx
= cx
, term
.ocy
= term
.c
.y
;
2580 xximspot(term
.ocx
, term
.ocy
);