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__)
36 #define UTF_INVALID 0xFFFD
38 #define ESC_BUF_SIZ (128*UTF_SIZ)
39 #define ESC_ARG_SIZ 16
40 #define STR_BUF_SIZ ESC_BUF_SIZ
41 #define STR_ARG_SIZ ESC_ARG_SIZ
44 #define IS_SET(flag) ((term.mode & (flag)) != 0)
45 #define NUMMAXLEN(x) ((int)(sizeof(x) * 2.56 + 0.5) + 1)
46 #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == '\177')
47 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
48 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
49 #define ISDELIM(u) (utf8strchr(worddelimiters, u) != NULL)
52 #define ISO14755CMD "dmenu -w \"$WINDOWID\" -p codepoint: </dev/null"
57 MODE_ALTSCREEN
= 1 << 2,
65 enum cursor_movement
{
89 ESC_STR
= 4, /* OSC, PM, APC */
91 ESC_STR_END
= 16, /* a final string was encountered */
92 ESC_TEST
= 32, /* Enter in test mode */
98 Glyph attr
; /* current char attributes */
109 * Selection variables:
110 * nb – normalized coordinates of the beginning of the selection
111 * ne – normalized coordinates of the end of the selection
112 * ob – original coordinates of the beginning of the selection
113 * oe – original coordinates of the end of the selection
122 /* Internal representation of the screen */
124 int row
; /* nb row */
125 int col
; /* nb col */
126 Line
*line
; /* screen */
127 Line
*alt
; /* alternate screen */
128 int *dirty
; /* dirtyness of lines */
129 TCursor c
; /* cursor */
130 int ocx
; /* old cursor col */
131 int ocy
; /* old cursor row */
132 int top
; /* top scroll limit */
133 int bot
; /* bottom scroll limit */
134 int mode
; /* terminal mode flags */
135 int esc
; /* escape state flags */
136 char trantbl
[4]; /* charset table translation */
137 int charset
; /* current charset */
138 int icharset
; /* selected charset for sequence */
142 /* CSI Escape sequence structs */
143 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
145 char buf
[ESC_BUF_SIZ
]; /* raw string */
146 int len
; /* raw string length */
148 int arg
[ESC_ARG_SIZ
];
149 int narg
; /* nb of args */
153 /* STR Escape sequence structs */
154 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
156 char type
; /* ESC type ... */
157 char buf
[STR_BUF_SIZ
]; /* raw string */
158 int len
; /* raw string length */
159 char *args
[STR_ARG_SIZ
];
160 int narg
; /* nb of args */
163 static void execsh(char *, char **);
164 static void stty(char **);
165 static void sigchld(int);
166 static void ttywriteraw(const char *, size_t);
168 static void csidump(void);
169 static void csihandle(void);
170 static void csiparse(void);
171 static void csireset(void);
172 static int eschandle(uchar
);
173 static void strdump(void);
174 static void strhandle(void);
175 static void strparse(void);
176 static void strreset(void);
178 static void tprinter(char *, size_t);
179 static void tdumpsel(void);
180 static void tdumpline(int);
181 static void tdump(void);
182 static void tclearregion(int, int, int, int);
183 static void tcursor(int);
184 static void tdeletechar(int);
185 static void tdeleteline(int);
186 static void tinsertblank(int);
187 static void tinsertblankline(int);
188 static int tlinelen(int);
189 static void tmoveto(int, int);
190 static void tmoveato(int, int);
191 static void tnewline(int);
192 static void tputtab(int);
193 static void tputc(Rune
);
194 static void treset(void);
195 static void tscrollup(int, int);
196 static void tscrolldown(int, int);
197 static void tsetattr(int *, int);
198 static void tsetchar(Rune
, Glyph
*, int, int);
199 static void tsetdirt(int, int);
200 static void tsetscroll(int, int);
201 static void tswapscreen(void);
202 static void tsetmode(int, int, int *, int);
203 static int twrite(const char *, int, int);
204 static void tfulldirt(void);
205 static void tcontrolcode(uchar
);
206 static void tdectest(char );
207 static void tdefutf8(char);
208 static int32_t tdefcolor(int *, int *, int);
209 static void tdeftran(char);
210 static void tstrsequence(uchar
);
212 static void drawregion(int, int, int, int);
214 static void selnormalize(void);
215 static void selscroll(int, int);
216 static void selsnap(int *, int *, int);
218 static size_t utf8decode(const char *, Rune
*, size_t);
219 static Rune
utf8decodebyte(char, size_t *);
220 static char utf8encodebyte(Rune
, size_t);
221 static char *utf8strchr(char *, Rune
);
222 static size_t utf8validate(Rune
*, size_t);
224 static char *base64dec(const char *);
225 static char base64dec_getc(const char **);
227 static ssize_t
xwrite(int, const char *, size_t);
231 static Selection sel
;
232 static CSIEscape csiescseq
;
233 static STREscape strescseq
;
238 static uchar utfbyte
[UTF_SIZ
+ 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
239 static uchar utfmask
[UTF_SIZ
+ 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
240 static Rune utfmin
[UTF_SIZ
+ 1] = { 0, 0, 0x80, 0x800, 0x10000};
241 static Rune utfmax
[UTF_SIZ
+ 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
244 xwrite(int fd
, const char *s
, size_t len
)
250 r
= write(fd
, s
, len
);
265 if (!(p
= malloc(len
)))
266 die("malloc: %s\n", strerror(errno
));
272 xrealloc(void *p
, size_t len
)
274 if ((p
= realloc(p
, len
)) == NULL
)
275 die("realloc: %s\n", strerror(errno
));
283 if ((s
= strdup(s
)) == NULL
)
284 die("strdup: %s\n", strerror(errno
));
290 utf8decode(const char *c
, Rune
*u
, size_t clen
)
292 size_t i
, j
, len
, type
;
298 udecoded
= utf8decodebyte(c
[0], &len
);
299 if (!BETWEEN(len
, 1, UTF_SIZ
))
301 for (i
= 1, j
= 1; i
< clen
&& j
< len
; ++i
, ++j
) {
302 udecoded
= (udecoded
<< 6) | utf8decodebyte(c
[i
], &type
);
309 utf8validate(u
, len
);
315 utf8decodebyte(char c
, size_t *i
)
317 for (*i
= 0; *i
< LEN(utfmask
); ++(*i
))
318 if (((uchar
)c
& utfmask
[*i
]) == utfbyte
[*i
])
319 return (uchar
)c
& ~utfmask
[*i
];
325 utf8encode(Rune u
, char *c
)
329 len
= utf8validate(&u
, 0);
333 for (i
= len
- 1; i
!= 0; --i
) {
334 c
[i
] = utf8encodebyte(u
, 0);
337 c
[0] = utf8encodebyte(u
, len
);
343 utf8encodebyte(Rune u
, size_t i
)
345 return utfbyte
[i
] | (u
& ~utfmask
[i
]);
349 utf8strchr(char *s
, Rune u
)
355 for (i
= 0, j
= 0; i
< len
; i
+= j
) {
356 if (!(j
= utf8decode(&s
[i
], &r
, len
- i
)))
366 utf8validate(Rune
*u
, size_t i
)
368 if (!BETWEEN(*u
, utfmin
[i
], utfmax
[i
]) || BETWEEN(*u
, 0xD800, 0xDFFF))
370 for (i
= 1; *u
> utfmax
[i
]; ++i
)
376 static const char base64_digits
[] = {
377 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
378 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0,
379 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
380 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
381 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
382 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0,
383 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
384 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
385 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
386 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
387 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
388 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
392 base64dec_getc(const char **src
)
394 while (**src
&& !isprint(**src
)) (*src
)++;
399 base64dec(const char *src
)
401 size_t in_len
= strlen(src
);
405 in_len
+= 4 - (in_len
% 4);
406 result
= dst
= xmalloc(in_len
/ 4 * 3 + 1);
408 int a
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
409 int b
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
410 int c
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
411 int d
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
413 *dst
++ = (a
<< 2) | ((b
& 0x30) >> 4);
416 *dst
++ = ((b
& 0x0f) << 4) | ((c
& 0x3c) >> 2);
419 *dst
++ = ((c
& 0x03) << 6) | d
;
438 if (term
.line
[y
][i
- 1].mode
& ATTR_WRAP
)
441 while (i
> 0 && term
.line
[y
][i
- 1].u
== ' ')
448 selstart(int col
, int row
, int snap
)
451 sel
.mode
= SEL_EMPTY
;
452 sel
.type
= SEL_REGULAR
;
453 sel
.alt
= IS_SET(MODE_ALTSCREEN
);
455 sel
.oe
.x
= sel
.ob
.x
= col
;
456 sel
.oe
.y
= sel
.ob
.y
= row
;
460 sel
.mode
= SEL_READY
;
461 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
465 selextend(int col
, int row
, int type
, int done
)
467 int oldey
, oldex
, oldsby
, oldsey
, oldtype
;
469 if (sel
.mode
== SEL_IDLE
)
471 if (done
&& sel
.mode
== SEL_EMPTY
) {
487 if (oldey
!= sel
.oe
.y
|| oldex
!= sel
.oe
.x
|| oldtype
!= sel
.type
)
488 tsetdirt(MIN(sel
.nb
.y
, oldsby
), MAX(sel
.ne
.y
, oldsey
));
490 sel
.mode
= done
? SEL_IDLE
: SEL_READY
;
498 if (sel
.type
== SEL_REGULAR
&& sel
.ob
.y
!= sel
.oe
.y
) {
499 sel
.nb
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.ob
.x
: sel
.oe
.x
;
500 sel
.ne
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.oe
.x
: sel
.ob
.x
;
502 sel
.nb
.x
= MIN(sel
.ob
.x
, sel
.oe
.x
);
503 sel
.ne
.x
= MAX(sel
.ob
.x
, sel
.oe
.x
);
505 sel
.nb
.y
= MIN(sel
.ob
.y
, sel
.oe
.y
);
506 sel
.ne
.y
= MAX(sel
.ob
.y
, sel
.oe
.y
);
508 selsnap(&sel
.nb
.x
, &sel
.nb
.y
, -1);
509 selsnap(&sel
.ne
.x
, &sel
.ne
.y
, +1);
511 /* expand selection over line breaks */
512 if (sel
.type
== SEL_RECTANGULAR
)
514 i
= tlinelen(sel
.nb
.y
);
517 if (tlinelen(sel
.ne
.y
) <= sel
.ne
.x
)
518 sel
.ne
.x
= term
.col
- 1;
522 selected(int x
, int y
)
524 if (sel
.mode
== SEL_EMPTY
|| sel
.ob
.x
== -1 ||
525 sel
.alt
!= IS_SET(MODE_ALTSCREEN
))
528 if (sel
.type
== SEL_RECTANGULAR
)
529 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
530 && BETWEEN(x
, sel
.nb
.x
, sel
.ne
.x
);
532 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
533 && (y
!= sel
.nb
.y
|| x
>= sel
.nb
.x
)
534 && (y
!= sel
.ne
.y
|| x
<= sel
.ne
.x
);
538 selsnap(int *x
, int *y
, int direction
)
540 int newx
, newy
, xt
, yt
;
541 int delim
, prevdelim
;
547 * Snap around if the word wraps around at the end or
548 * beginning of a line.
550 prevgp
= &term
.line
[*y
][*x
];
551 prevdelim
= ISDELIM(prevgp
->u
);
553 newx
= *x
+ direction
;
555 if (!BETWEEN(newx
, 0, term
.col
- 1)) {
557 newx
= (newx
+ term
.col
) % term
.col
;
558 if (!BETWEEN(newy
, 0, term
.row
- 1))
564 yt
= newy
, xt
= newx
;
565 if (!(term
.line
[yt
][xt
].mode
& ATTR_WRAP
))
569 if (newx
>= tlinelen(newy
))
572 gp
= &term
.line
[newy
][newx
];
573 delim
= ISDELIM(gp
->u
);
574 if (!(gp
->mode
& ATTR_WDUMMY
) && (delim
!= prevdelim
575 || (delim
&& gp
->u
!= prevgp
->u
)))
586 * Snap around if the the previous line or the current one
587 * has set ATTR_WRAP at its end. Then the whole next or
588 * previous line will be selected.
590 *x
= (direction
< 0) ? 0 : term
.col
- 1;
592 for (; *y
> 0; *y
+= direction
) {
593 if (!(term
.line
[*y
-1][term
.col
-1].mode
598 } else if (direction
> 0) {
599 for (; *y
< term
.row
-1; *y
+= direction
) {
600 if (!(term
.line
[*y
][term
.col
-1].mode
614 int y
, bufsize
, lastx
, linelen
;
620 bufsize
= (term
.col
+1) * (sel
.ne
.y
-sel
.nb
.y
+1) * UTF_SIZ
;
621 ptr
= str
= xmalloc(bufsize
);
623 /* append every set & selected glyph to the selection */
624 for (y
= sel
.nb
.y
; y
<= sel
.ne
.y
; y
++) {
625 if ((linelen
= tlinelen(y
)) == 0) {
630 if (sel
.type
== SEL_RECTANGULAR
) {
631 gp
= &term
.line
[y
][sel
.nb
.x
];
634 gp
= &term
.line
[y
][sel
.nb
.y
== y
? sel
.nb
.x
: 0];
635 lastx
= (sel
.ne
.y
== y
) ? sel
.ne
.x
: term
.col
-1;
637 last
= &term
.line
[y
][MIN(lastx
, linelen
-1)];
638 while (last
>= gp
&& last
->u
== ' ')
641 for ( ; gp
<= last
; ++gp
) {
642 if (gp
->mode
& ATTR_WDUMMY
)
645 ptr
+= utf8encode(gp
->u
, ptr
);
649 * Copy and pasting of line endings is inconsistent
650 * in the inconsistent terminal and GUI world.
651 * The best solution seems like to produce '\n' when
652 * something is copied from st and convert '\n' to
653 * '\r', when something to be pasted is received by
655 * FIXME: Fix the computer world.
657 if ((y
< sel
.ne
.y
|| lastx
>= linelen
) && !(last
->mode
& ATTR_WRAP
))
671 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
675 die(const char *errstr
, ...)
679 va_start(ap
, errstr
);
680 vfprintf(stderr
, errstr
, ap
);
686 execsh(char *cmd
, char **args
)
689 const struct passwd
*pw
;
692 if ((pw
= getpwuid(getuid())) == NULL
) {
694 die("getpwuid: %s\n", strerror(errno
));
696 die("who are you?\n");
699 if ((sh
= getenv("SHELL")) == NULL
)
700 sh
= (pw
->pw_shell
[0]) ? pw
->pw_shell
: cmd
;
708 DEFAULT(args
, ((char *[]) {prog
, NULL
}));
713 setenv("LOGNAME", pw
->pw_name
, 1);
714 setenv("USER", pw
->pw_name
, 1);
715 setenv("SHELL", sh
, 1);
716 setenv("HOME", pw
->pw_dir
, 1);
717 setenv("TERM", termname
, 1);
719 signal(SIGCHLD
, SIG_DFL
);
720 signal(SIGHUP
, SIG_DFL
);
721 signal(SIGINT
, SIG_DFL
);
722 signal(SIGQUIT
, SIG_DFL
);
723 signal(SIGTERM
, SIG_DFL
);
724 signal(SIGALRM
, SIG_DFL
);
736 if ((p
= waitpid(pid
, &stat
, WNOHANG
)) < 0)
737 die("waiting for pid %hd failed: %s\n", pid
, strerror(errno
));
742 if (!WIFEXITED(stat
) || WEXITSTATUS(stat
))
743 die("child finished with error '%d'\n", stat
);
750 char cmd
[_POSIX_ARG_MAX
], **p
, *q
, *s
;
753 if ((n
= strlen(stty_args
)) > sizeof(cmd
)-1)
754 die("incorrect stty parameters\n");
755 memcpy(cmd
, stty_args
, n
);
757 siz
= sizeof(cmd
) - n
;
758 for (p
= args
; p
&& (s
= *p
); ++p
) {
759 if ((n
= strlen(s
)) > siz
-1)
760 die("stty parameter length too long\n");
767 if (system(cmd
) != 0)
768 perror("Couldn't call stty");
772 ttynew(char *line
, char *cmd
, char *out
, char **args
)
777 term
.mode
|= MODE_PRINT
;
778 iofd
= (!strcmp(out
, "-")) ?
779 1 : open(out
, O_WRONLY
| O_CREAT
, 0666);
781 fprintf(stderr
, "Error opening %s:%s\n",
782 out
, strerror(errno
));
787 if ((cmdfd
= open(line
, O_RDWR
)) < 0)
788 die("open line '%s' failed: %s\n",
789 line
, strerror(errno
));
795 /* seems to work fine on linux, openbsd and freebsd */
796 if (openpty(&m
, &s
, NULL
, NULL
, NULL
) < 0)
797 die("openpty failed: %s\n", strerror(errno
));
799 switch (pid
= fork()) {
801 die("fork failed: %s\n", strerror(errno
));
805 setsid(); /* create a new process group */
809 if (ioctl(s
, TIOCSCTTY
, NULL
) < 0)
810 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno
));
813 if (pledge("stdio getpw proc exec", NULL
) == -1)
818 if (pledge("stdio rpath tty proc", NULL
) == -1)
822 signal(SIGCHLD
, sigchld
);
831 static char buf
[BUFSIZ
];
832 static int buflen
= 0;
836 /* append read bytes to unprocessed bytes */
837 if ((ret
= read(cmdfd
, buf
+buflen
, LEN(buf
)-buflen
)) < 0)
838 die("couldn't read from shell: %s\n", strerror(errno
));
841 written
= twrite(buf
, buflen
, 0);
843 /* keep any uncomplete utf8 char for the next call */
845 memmove(buf
, buf
+ written
, buflen
);
851 ttywrite(const char *s
, size_t n
, int may_echo
)
855 if (may_echo
&& IS_SET(MODE_ECHO
))
858 if (!IS_SET(MODE_CRLF
)) {
863 /* This is similar to how the kernel handles ONLCR for ttys */
867 ttywriteraw("\r\n", 2);
869 next
= memchr(s
, '\r', n
);
870 DEFAULT(next
, s
+ n
);
871 ttywriteraw(s
, next
- s
);
879 ttywriteraw(const char *s
, size_t n
)
886 * Remember that we are using a pty, which might be a modem line.
887 * Writing too much will clog the line. That's why we are doing this
889 * FIXME: Migrate the world to Plan 9.
897 /* Check if we can write. */
898 if (pselect(cmdfd
+1, &rfd
, &wfd
, NULL
, NULL
, NULL
) < 0) {
901 die("select failed: %s\n", strerror(errno
));
903 if (FD_ISSET(cmdfd
, &wfd
)) {
905 * Only write the bytes written by ttywrite() or the
906 * default of 256. This seems to be a reasonable value
907 * for a serial line. Bigger values might clog the I/O.
909 if ((r
= write(cmdfd
, s
, (n
< lim
)? n
: lim
)) < 0)
913 * We weren't able to write out everything.
914 * This means the buffer is getting full
922 /* All bytes have been written. */
926 if (FD_ISSET(cmdfd
, &rfd
))
932 die("write error on tty: %s\n", strerror(errno
));
936 ttyresize(int tw
, int th
)
944 if (ioctl(cmdfd
, TIOCSWINSZ
, &w
) < 0)
945 fprintf(stderr
, "Couldn't set window size: %s\n", strerror(errno
));
951 /* Send SIGHUP to shell */
960 for (i
= 0; i
< term
.row
-1; i
++) {
961 for (j
= 0; j
< term
.col
-1; j
++) {
962 if (term
.line
[i
][j
].mode
& attr
)
971 tsetdirt(int top
, int bot
)
975 LIMIT(top
, 0, term
.row
-1);
976 LIMIT(bot
, 0, term
.row
-1);
978 for (i
= top
; i
<= bot
; i
++)
983 tsetdirtattr(int attr
)
987 for (i
= 0; i
< term
.row
-1; i
++) {
988 for (j
= 0; j
< term
.col
-1; j
++) {
989 if (term
.line
[i
][j
].mode
& attr
) {
1000 tsetdirt(0, term
.row
-1);
1006 static TCursor c
[2];
1007 int alt
= IS_SET(MODE_ALTSCREEN
);
1009 if (mode
== CURSOR_SAVE
) {
1011 } else if (mode
== CURSOR_LOAD
) {
1013 tmoveto(c
[alt
].x
, c
[alt
].y
);
1022 term
.c
= (TCursor
){{
1026 }, .x
= 0, .y
= 0, .state
= CURSOR_DEFAULT
};
1028 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1029 for (i
= tabspaces
; i
< term
.col
; i
+= tabspaces
)
1032 term
.bot
= term
.row
- 1;
1033 term
.mode
= MODE_WRAP
|MODE_UTF8
;
1034 memset(term
.trantbl
, CS_USA
, sizeof(term
.trantbl
));
1037 for (i
= 0; i
< 2; i
++) {
1039 tcursor(CURSOR_SAVE
);
1040 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1046 tnew(int col
, int row
)
1048 term
= (Term
){ .c
= { .attr
= { .fg
= defaultfg
, .bg
= defaultbg
} } };
1056 Line
*tmp
= term
.line
;
1058 term
.line
= term
.alt
;
1060 term
.mode
^= MODE_ALTSCREEN
;
1065 tscrolldown(int orig
, int n
)
1070 LIMIT(n
, 0, term
.bot
-orig
+1);
1072 tsetdirt(orig
, term
.bot
-n
);
1073 tclearregion(0, term
.bot
-n
+1, term
.col
-1, term
.bot
);
1075 for (i
= term
.bot
; i
>= orig
+n
; i
--) {
1076 temp
= term
.line
[i
];
1077 term
.line
[i
] = term
.line
[i
-n
];
1078 term
.line
[i
-n
] = temp
;
1085 tscrollup(int orig
, int n
)
1090 LIMIT(n
, 0, term
.bot
-orig
+1);
1092 tclearregion(0, orig
, term
.col
-1, orig
+n
-1);
1093 tsetdirt(orig
+n
, term
.bot
);
1095 for (i
= orig
; i
<= term
.bot
-n
; i
++) {
1096 temp
= term
.line
[i
];
1097 term
.line
[i
] = term
.line
[i
+n
];
1098 term
.line
[i
+n
] = temp
;
1101 selscroll(orig
, -n
);
1105 selscroll(int orig
, int n
)
1110 if (BETWEEN(sel
.ob
.y
, orig
, term
.bot
) || BETWEEN(sel
.oe
.y
, orig
, term
.bot
)) {
1111 if ((sel
.ob
.y
+= n
) > term
.bot
|| (sel
.oe
.y
+= n
) < term
.top
) {
1115 if (sel
.type
== SEL_RECTANGULAR
) {
1116 if (sel
.ob
.y
< term
.top
)
1117 sel
.ob
.y
= term
.top
;
1118 if (sel
.oe
.y
> term
.bot
)
1119 sel
.oe
.y
= term
.bot
;
1121 if (sel
.ob
.y
< term
.top
) {
1122 sel
.ob
.y
= term
.top
;
1125 if (sel
.oe
.y
> term
.bot
) {
1126 sel
.oe
.y
= term
.bot
;
1127 sel
.oe
.x
= term
.col
;
1135 tnewline(int first_col
)
1139 if (y
== term
.bot
) {
1140 tscrollup(term
.top
, 1);
1144 tmoveto(first_col
? 0 : term
.c
.x
, y
);
1150 char *p
= csiescseq
.buf
, *np
;
1159 csiescseq
.buf
[csiescseq
.len
] = '\0';
1160 while (p
< csiescseq
.buf
+csiescseq
.len
) {
1162 v
= strtol(p
, &np
, 10);
1165 if (v
== LONG_MAX
|| v
== LONG_MIN
)
1167 csiescseq
.arg
[csiescseq
.narg
++] = v
;
1169 if (*p
!= ';' || csiescseq
.narg
== ESC_ARG_SIZ
)
1173 csiescseq
.mode
[0] = *p
++;
1174 csiescseq
.mode
[1] = (p
< csiescseq
.buf
+csiescseq
.len
) ? *p
: '\0';
1177 /* for absolute user moves, when decom is set */
1179 tmoveato(int x
, int y
)
1181 tmoveto(x
, y
+ ((term
.c
.state
& CURSOR_ORIGIN
) ? term
.top
: 0));
1185 tmoveto(int x
, int y
)
1189 if (term
.c
.state
& CURSOR_ORIGIN
) {
1194 maxy
= term
.row
- 1;
1196 term
.c
.state
&= ~CURSOR_WRAPNEXT
;
1197 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1198 term
.c
.y
= LIMIT(y
, miny
, maxy
);
1202 tsetchar(Rune u
, Glyph
*attr
, int x
, int y
)
1204 static char *vt100_0
[62] = { /* 0x41 - 0x7e */
1205 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1206 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1207 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1208 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1209 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1210 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1211 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1212 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1216 * The table is proudly stolen from rxvt.
1218 if (term
.trantbl
[term
.charset
] == CS_GRAPHIC0
&&
1219 BETWEEN(u
, 0x41, 0x7e) && vt100_0
[u
- 0x41])
1220 utf8decode(vt100_0
[u
- 0x41], &u
, UTF_SIZ
);
1222 if (term
.line
[y
][x
].mode
& ATTR_WIDE
) {
1223 if (x
+1 < term
.col
) {
1224 term
.line
[y
][x
+1].u
= ' ';
1225 term
.line
[y
][x
+1].mode
&= ~ATTR_WDUMMY
;
1227 } else if (term
.line
[y
][x
].mode
& ATTR_WDUMMY
) {
1228 term
.line
[y
][x
-1].u
= ' ';
1229 term
.line
[y
][x
-1].mode
&= ~ATTR_WIDE
;
1233 term
.line
[y
][x
] = *attr
;
1234 term
.line
[y
][x
].u
= u
;
1238 tclearregion(int x1
, int y1
, int x2
, int y2
)
1244 temp
= x1
, x1
= x2
, x2
= temp
;
1246 temp
= y1
, y1
= y2
, y2
= temp
;
1248 LIMIT(x1
, 0, term
.col
-1);
1249 LIMIT(x2
, 0, term
.col
-1);
1250 LIMIT(y1
, 0, term
.row
-1);
1251 LIMIT(y2
, 0, term
.row
-1);
1253 for (y
= y1
; y
<= y2
; y
++) {
1255 for (x
= x1
; x
<= x2
; x
++) {
1256 gp
= &term
.line
[y
][x
];
1259 gp
->fg
= term
.c
.attr
.fg
;
1260 gp
->bg
= term
.c
.attr
.bg
;
1273 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1277 size
= term
.col
- src
;
1278 line
= term
.line
[term
.c
.y
];
1280 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1281 tclearregion(term
.col
-n
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1290 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1294 size
= term
.col
- dst
;
1295 line
= term
.line
[term
.c
.y
];
1297 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1298 tclearregion(src
, term
.c
.y
, dst
- 1, term
.c
.y
);
1302 tinsertblankline(int n
)
1304 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1305 tscrolldown(term
.c
.y
, n
);
1311 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1312 tscrollup(term
.c
.y
, n
);
1316 tdefcolor(int *attr
, int *npar
, int l
)
1321 switch (attr
[*npar
+ 1]) {
1322 case 2: /* direct color in RGB space */
1323 if (*npar
+ 4 >= l
) {
1325 "erresc(38): Incorrect number of parameters (%d)\n",
1329 r
= attr
[*npar
+ 2];
1330 g
= attr
[*npar
+ 3];
1331 b
= attr
[*npar
+ 4];
1333 if (!BETWEEN(r
, 0, 255) || !BETWEEN(g
, 0, 255) || !BETWEEN(b
, 0, 255))
1334 fprintf(stderr
, "erresc: bad rgb color (%u,%u,%u)\n",
1337 idx
= TRUECOLOR(r
, g
, b
);
1339 case 5: /* indexed color */
1340 if (*npar
+ 2 >= l
) {
1342 "erresc(38): Incorrect number of parameters (%d)\n",
1347 if (!BETWEEN(attr
[*npar
], 0, 255))
1348 fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[*npar
]);
1352 case 0: /* implemented defined (only foreground) */
1353 case 1: /* transparent */
1354 case 3: /* direct color in CMY space */
1355 case 4: /* direct color in CMYK space */
1358 "erresc(38): gfx attr %d unknown\n", attr
[*npar
]);
1366 tsetattr(int *attr
, int l
)
1371 for (i
= 0; i
< l
; i
++) {
1374 term
.c
.attr
.mode
&= ~(
1383 term
.c
.attr
.fg
= defaultfg
;
1384 term
.c
.attr
.bg
= defaultbg
;
1387 term
.c
.attr
.mode
|= ATTR_BOLD
;
1390 term
.c
.attr
.mode
|= ATTR_FAINT
;
1393 term
.c
.attr
.mode
|= ATTR_ITALIC
;
1396 term
.c
.attr
.mode
|= ATTR_UNDERLINE
;
1398 case 5: /* slow blink */
1400 case 6: /* rapid blink */
1401 term
.c
.attr
.mode
|= ATTR_BLINK
;
1404 term
.c
.attr
.mode
|= ATTR_REVERSE
;
1407 term
.c
.attr
.mode
|= ATTR_INVISIBLE
;
1410 term
.c
.attr
.mode
|= ATTR_STRUCK
;
1413 term
.c
.attr
.mode
&= ~(ATTR_BOLD
| ATTR_FAINT
);
1416 term
.c
.attr
.mode
&= ~ATTR_ITALIC
;
1419 term
.c
.attr
.mode
&= ~ATTR_UNDERLINE
;
1422 term
.c
.attr
.mode
&= ~ATTR_BLINK
;
1425 term
.c
.attr
.mode
&= ~ATTR_REVERSE
;
1428 term
.c
.attr
.mode
&= ~ATTR_INVISIBLE
;
1431 term
.c
.attr
.mode
&= ~ATTR_STRUCK
;
1434 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1435 term
.c
.attr
.fg
= idx
;
1438 term
.c
.attr
.fg
= defaultfg
;
1441 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1442 term
.c
.attr
.bg
= idx
;
1445 term
.c
.attr
.bg
= defaultbg
;
1448 if (BETWEEN(attr
[i
], 30, 37)) {
1449 term
.c
.attr
.fg
= attr
[i
] - 30;
1450 } else if (BETWEEN(attr
[i
], 40, 47)) {
1451 term
.c
.attr
.bg
= attr
[i
] - 40;
1452 } else if (BETWEEN(attr
[i
], 90, 97)) {
1453 term
.c
.attr
.fg
= attr
[i
] - 90 + 8;
1454 } else if (BETWEEN(attr
[i
], 100, 107)) {
1455 term
.c
.attr
.bg
= attr
[i
] - 100 + 8;
1458 "erresc(default): gfx attr %d unknown\n",
1459 attr
[i
]), csidump();
1467 tsetscroll(int t
, int b
)
1471 LIMIT(t
, 0, term
.row
-1);
1472 LIMIT(b
, 0, term
.row
-1);
1483 tsetmode(int priv
, int set
, int *args
, int narg
)
1487 for (lim
= args
+ narg
; args
< lim
; ++args
) {
1490 case 1: /* DECCKM -- Cursor key */
1491 xsetmode(set
, MODE_APPCURSOR
);
1493 case 5: /* DECSCNM -- Reverse video */
1494 xsetmode(set
, MODE_REVERSE
);
1496 case 6: /* DECOM -- Origin */
1497 MODBIT(term
.c
.state
, set
, CURSOR_ORIGIN
);
1500 case 7: /* DECAWM -- Auto wrap */
1501 MODBIT(term
.mode
, set
, MODE_WRAP
);
1503 case 0: /* Error (IGNORED) */
1504 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1505 case 3: /* DECCOLM -- Column (IGNORED) */
1506 case 4: /* DECSCLM -- Scroll (IGNORED) */
1507 case 8: /* DECARM -- Auto repeat (IGNORED) */
1508 case 18: /* DECPFF -- Printer feed (IGNORED) */
1509 case 19: /* DECPEX -- Printer extent (IGNORED) */
1510 case 42: /* DECNRCM -- National characters (IGNORED) */
1511 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1513 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1514 xsetmode(!set
, MODE_HIDE
);
1516 case 9: /* X10 mouse compatibility mode */
1517 xsetpointermotion(0);
1518 xsetmode(0, MODE_MOUSE
);
1519 xsetmode(set
, MODE_MOUSEX10
);
1521 case 1000: /* 1000: report button press */
1522 xsetpointermotion(0);
1523 xsetmode(0, MODE_MOUSE
);
1524 xsetmode(set
, MODE_MOUSEBTN
);
1526 case 1002: /* 1002: report motion on button press */
1527 xsetpointermotion(0);
1528 xsetmode(0, MODE_MOUSE
);
1529 xsetmode(set
, MODE_MOUSEMOTION
);
1531 case 1003: /* 1003: enable all mouse motions */
1532 xsetpointermotion(set
);
1533 xsetmode(0, MODE_MOUSE
);
1534 xsetmode(set
, MODE_MOUSEMANY
);
1536 case 1004: /* 1004: send focus events to tty */
1537 xsetmode(set
, MODE_FOCUS
);
1539 case 1006: /* 1006: extended reporting mode */
1540 xsetmode(set
, MODE_MOUSESGR
);
1543 xsetmode(set
, MODE_8BIT
);
1545 case 1049: /* swap screen & set/restore cursor as xterm */
1546 if (!allowaltscreen
)
1548 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1550 case 47: /* swap screen */
1552 if (!allowaltscreen
)
1554 alt
= IS_SET(MODE_ALTSCREEN
);
1556 tclearregion(0, 0, term
.col
-1,
1559 if (set
^ alt
) /* set is always 1 or 0 */
1565 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1567 case 2004: /* 2004: bracketed paste mode */
1568 xsetmode(set
, MODE_BRCKTPASTE
);
1570 /* Not implemented mouse modes. See comments there. */
1571 case 1001: /* mouse highlight mode; can hang the
1572 terminal by design when implemented. */
1573 case 1005: /* UTF-8 mouse mode; will confuse
1574 applications not supporting UTF-8
1576 case 1015: /* urxvt mangled mouse mode; incompatible
1577 and can be mistaken for other control
1581 "erresc: unknown private set/reset mode %d\n",
1587 case 0: /* Error (IGNORED) */
1590 xsetmode(set
, MODE_KBDLOCK
);
1592 case 4: /* IRM -- Insertion-replacement */
1593 MODBIT(term
.mode
, set
, MODE_INSERT
);
1595 case 12: /* SRM -- Send/Receive */
1596 MODBIT(term
.mode
, !set
, MODE_ECHO
);
1598 case 20: /* LNM -- Linefeed/new line */
1599 MODBIT(term
.mode
, set
, MODE_CRLF
);
1603 "erresc: unknown set/reset mode %d\n",
1617 switch (csiescseq
.mode
[0]) {
1620 fprintf(stderr
, "erresc: unknown csi ");
1624 case '@': /* ICH -- Insert <n> blank char */
1625 DEFAULT(csiescseq
.arg
[0], 1);
1626 tinsertblank(csiescseq
.arg
[0]);
1628 case 'A': /* CUU -- Cursor <n> Up */
1629 DEFAULT(csiescseq
.arg
[0], 1);
1630 tmoveto(term
.c
.x
, term
.c
.y
-csiescseq
.arg
[0]);
1632 case 'B': /* CUD -- Cursor <n> Down */
1633 case 'e': /* VPR --Cursor <n> Down */
1634 DEFAULT(csiescseq
.arg
[0], 1);
1635 tmoveto(term
.c
.x
, term
.c
.y
+csiescseq
.arg
[0]);
1637 case 'i': /* MC -- Media Copy */
1638 switch (csiescseq
.arg
[0]) {
1643 tdumpline(term
.c
.y
);
1649 term
.mode
&= ~MODE_PRINT
;
1652 term
.mode
|= MODE_PRINT
;
1656 case 'c': /* DA -- Device Attributes */
1657 if (csiescseq
.arg
[0] == 0)
1658 ttywrite(vtiden
, strlen(vtiden
), 0);
1660 case 'C': /* CUF -- Cursor <n> Forward */
1661 case 'a': /* HPR -- Cursor <n> Forward */
1662 DEFAULT(csiescseq
.arg
[0], 1);
1663 tmoveto(term
.c
.x
+csiescseq
.arg
[0], term
.c
.y
);
1665 case 'D': /* CUB -- Cursor <n> Backward */
1666 DEFAULT(csiescseq
.arg
[0], 1);
1667 tmoveto(term
.c
.x
-csiescseq
.arg
[0], term
.c
.y
);
1669 case 'E': /* CNL -- Cursor <n> Down and first col */
1670 DEFAULT(csiescseq
.arg
[0], 1);
1671 tmoveto(0, term
.c
.y
+csiescseq
.arg
[0]);
1673 case 'F': /* CPL -- Cursor <n> Up and first col */
1674 DEFAULT(csiescseq
.arg
[0], 1);
1675 tmoveto(0, term
.c
.y
-csiescseq
.arg
[0]);
1677 case 'g': /* TBC -- Tabulation clear */
1678 switch (csiescseq
.arg
[0]) {
1679 case 0: /* clear current tab stop */
1680 term
.tabs
[term
.c
.x
] = 0;
1682 case 3: /* clear all the tabs */
1683 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1689 case 'G': /* CHA -- Move to <col> */
1691 DEFAULT(csiescseq
.arg
[0], 1);
1692 tmoveto(csiescseq
.arg
[0]-1, term
.c
.y
);
1694 case 'H': /* CUP -- Move to <row> <col> */
1696 DEFAULT(csiescseq
.arg
[0], 1);
1697 DEFAULT(csiescseq
.arg
[1], 1);
1698 tmoveato(csiescseq
.arg
[1]-1, csiescseq
.arg
[0]-1);
1700 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1701 DEFAULT(csiescseq
.arg
[0], 1);
1702 tputtab(csiescseq
.arg
[0]);
1704 case 'J': /* ED -- Clear screen */
1705 switch (csiescseq
.arg
[0]) {
1707 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1708 if (term
.c
.y
< term
.row
-1) {
1709 tclearregion(0, term
.c
.y
+1, term
.col
-1,
1715 tclearregion(0, 0, term
.col
-1, term
.c
.y
-1);
1716 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1719 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1725 case 'K': /* EL -- Clear line */
1726 switch (csiescseq
.arg
[0]) {
1728 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1,
1732 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1735 tclearregion(0, term
.c
.y
, term
.col
-1, term
.c
.y
);
1739 case 'S': /* SU -- Scroll <n> line up */
1740 DEFAULT(csiescseq
.arg
[0], 1);
1741 tscrollup(term
.top
, csiescseq
.arg
[0]);
1743 case 'T': /* SD -- Scroll <n> line down */
1744 DEFAULT(csiescseq
.arg
[0], 1);
1745 tscrolldown(term
.top
, csiescseq
.arg
[0]);
1747 case 'L': /* IL -- Insert <n> blank lines */
1748 DEFAULT(csiescseq
.arg
[0], 1);
1749 tinsertblankline(csiescseq
.arg
[0]);
1751 case 'l': /* RM -- Reset Mode */
1752 tsetmode(csiescseq
.priv
, 0, csiescseq
.arg
, csiescseq
.narg
);
1754 case 'M': /* DL -- Delete <n> lines */
1755 DEFAULT(csiescseq
.arg
[0], 1);
1756 tdeleteline(csiescseq
.arg
[0]);
1758 case 'X': /* ECH -- Erase <n> char */
1759 DEFAULT(csiescseq
.arg
[0], 1);
1760 tclearregion(term
.c
.x
, term
.c
.y
,
1761 term
.c
.x
+ csiescseq
.arg
[0] - 1, term
.c
.y
);
1763 case 'P': /* DCH -- Delete <n> char */
1764 DEFAULT(csiescseq
.arg
[0], 1);
1765 tdeletechar(csiescseq
.arg
[0]);
1767 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1768 DEFAULT(csiescseq
.arg
[0], 1);
1769 tputtab(-csiescseq
.arg
[0]);
1771 case 'd': /* VPA -- Move to <row> */
1772 DEFAULT(csiescseq
.arg
[0], 1);
1773 tmoveato(term
.c
.x
, csiescseq
.arg
[0]-1);
1775 case 'h': /* SM -- Set terminal mode */
1776 tsetmode(csiescseq
.priv
, 1, csiescseq
.arg
, csiescseq
.narg
);
1778 case 'm': /* SGR -- Terminal attribute (color) */
1779 tsetattr(csiescseq
.arg
, csiescseq
.narg
);
1781 case 'n': /* DSR – Device Status Report (cursor position) */
1782 if (csiescseq
.arg
[0] == 6) {
1783 len
= snprintf(buf
, sizeof(buf
),"\033[%i;%iR",
1784 term
.c
.y
+1, term
.c
.x
+1);
1785 ttywrite(buf
, len
, 0);
1788 case 'r': /* DECSTBM -- Set Scrolling Region */
1789 if (csiescseq
.priv
) {
1792 DEFAULT(csiescseq
.arg
[0], 1);
1793 DEFAULT(csiescseq
.arg
[1], term
.row
);
1794 tsetscroll(csiescseq
.arg
[0]-1, csiescseq
.arg
[1]-1);
1798 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1799 tcursor(CURSOR_SAVE
);
1801 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1802 tcursor(CURSOR_LOAD
);
1805 switch (csiescseq
.mode
[1]) {
1806 case 'q': /* DECSCUSR -- Set Cursor Style */
1807 if (xsetcursor(csiescseq
.arg
[0]))
1823 fprintf(stderr
, "ESC[");
1824 for (i
= 0; i
< csiescseq
.len
; i
++) {
1825 c
= csiescseq
.buf
[i
] & 0xff;
1828 } else if (c
== '\n') {
1829 fprintf(stderr
, "(\\n)");
1830 } else if (c
== '\r') {
1831 fprintf(stderr
, "(\\r)");
1832 } else if (c
== 0x1b) {
1833 fprintf(stderr
, "(\\e)");
1835 fprintf(stderr
, "(%02x)", c
);
1844 memset(&csiescseq
, 0, sizeof(csiescseq
));
1853 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
1855 par
= (narg
= strescseq
.narg
) ? atoi(strescseq
.args
[0]) : 0;
1857 switch (strescseq
.type
) {
1858 case ']': /* OSC -- Operating System Command */
1864 xsettitle(strescseq
.args
[1]);
1870 dec
= base64dec(strescseq
.args
[2]);
1875 fprintf(stderr
, "erresc: invalid base64\n");
1879 case 4: /* color set */
1882 p
= strescseq
.args
[2];
1884 case 104: /* color reset, here p = NULL */
1885 j
= (narg
> 1) ? atoi(strescseq
.args
[1]) : -1;
1886 if (xsetcolorname(j
, p
)) {
1887 fprintf(stderr
, "erresc: invalid color %s\n", p
);
1890 * TODO if defaultbg color is changed, borders
1898 case 'k': /* old title set compatibility */
1899 xsettitle(strescseq
.args
[0]);
1901 case 'P': /* DCS -- Device Control String */
1902 term
.mode
|= ESC_DCS
;
1903 case '_': /* APC -- Application Program Command */
1904 case '^': /* PM -- Privacy Message */
1908 fprintf(stderr
, "erresc: unknown str ");
1916 char *p
= strescseq
.buf
;
1919 strescseq
.buf
[strescseq
.len
] = '\0';
1924 while (strescseq
.narg
< STR_ARG_SIZ
) {
1925 strescseq
.args
[strescseq
.narg
++] = p
;
1926 while ((c
= *p
) != ';' && c
!= '\0')
1940 fprintf(stderr
, "ESC%c", strescseq
.type
);
1941 for (i
= 0; i
< strescseq
.len
; i
++) {
1942 c
= strescseq
.buf
[i
] & 0xff;
1946 } else if (isprint(c
)) {
1948 } else if (c
== '\n') {
1949 fprintf(stderr
, "(\\n)");
1950 } else if (c
== '\r') {
1951 fprintf(stderr
, "(\\r)");
1952 } else if (c
== 0x1b) {
1953 fprintf(stderr
, "(\\e)");
1955 fprintf(stderr
, "(%02x)", c
);
1958 fprintf(stderr
, "ESC\\\n");
1964 memset(&strescseq
, 0, sizeof(strescseq
));
1968 sendbreak(const Arg
*arg
)
1970 if (tcsendbreak(cmdfd
, 0))
1971 perror("Error sending break");
1975 tprinter(char *s
, size_t len
)
1977 if (iofd
!= -1 && xwrite(iofd
, s
, len
) < 0) {
1978 perror("Error writing to output file");
1985 iso14755(const Arg
*arg
)
1988 char *us
, *e
, codepoint
[9], uc
[UTF_SIZ
];
1989 unsigned long utf32
;
1991 if (!(p
= popen(ISO14755CMD
, "r")))
1994 us
= fgets(codepoint
, sizeof(codepoint
), p
);
1997 if (!us
|| *us
== '\0' || *us
== '-' || strlen(us
) > 7)
1999 if ((utf32
= strtoul(us
, &e
, 16)) == ULONG_MAX
||
2000 (*e
!= '\n' && *e
!= '\0'))
2003 ttywrite(uc
, utf8encode(utf32
, uc
), 1);
2007 toggleprinter(const Arg
*arg
)
2009 term
.mode
^= MODE_PRINT
;
2013 printscreen(const Arg
*arg
)
2019 printsel(const Arg
*arg
)
2029 if ((ptr
= getsel())) {
2030 tprinter(ptr
, strlen(ptr
));
2041 bp
= &term
.line
[n
][0];
2042 end
= &bp
[MIN(tlinelen(n
), term
.col
) - 1];
2043 if (bp
!= end
|| bp
->u
!= ' ') {
2044 for ( ;bp
<= end
; ++bp
)
2045 tprinter(buf
, utf8encode(bp
->u
, buf
));
2055 for (i
= 0; i
< term
.row
; ++i
)
2065 while (x
< term
.col
&& n
--)
2066 for (++x
; x
< term
.col
&& !term
.tabs
[x
]; ++x
)
2069 while (x
> 0 && n
++)
2070 for (--x
; x
> 0 && !term
.tabs
[x
]; --x
)
2073 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
2077 tdefutf8(char ascii
)
2080 term
.mode
|= MODE_UTF8
;
2081 else if (ascii
== '@')
2082 term
.mode
&= ~MODE_UTF8
;
2086 tdeftran(char ascii
)
2088 static char cs
[] = "0B";
2089 static int vcs
[] = {CS_GRAPHIC0
, CS_USA
};
2092 if ((p
= strchr(cs
, ascii
)) == NULL
) {
2093 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
2095 term
.trantbl
[term
.icharset
] = vcs
[p
- cs
];
2104 if (c
== '8') { /* DEC screen alignment test. */
2105 for (x
= 0; x
< term
.col
; ++x
) {
2106 for (y
= 0; y
< term
.row
; ++y
)
2107 tsetchar('E', &term
.c
.attr
, x
, y
);
2113 tstrsequence(uchar c
)
2118 case 0x90: /* DCS -- Device Control String */
2120 term
.esc
|= ESC_DCS
;
2122 case 0x9f: /* APC -- Application Program Command */
2125 case 0x9e: /* PM -- Privacy Message */
2128 case 0x9d: /* OSC -- Operating System Command */
2133 term
.esc
|= ESC_STR
;
2137 tcontrolcode(uchar ascii
)
2144 tmoveto(term
.c
.x
-1, term
.c
.y
);
2147 tmoveto(0, term
.c
.y
);
2152 /* go to first col if the mode is set */
2153 tnewline(IS_SET(MODE_CRLF
));
2155 case '\a': /* BEL */
2156 if (term
.esc
& ESC_STR_END
) {
2157 /* backwards compatibility to xterm */
2163 case '\033': /* ESC */
2165 term
.esc
&= ~(ESC_CSI
|ESC_ALTCHARSET
|ESC_TEST
);
2166 term
.esc
|= ESC_START
;
2168 case '\016': /* SO (LS1 -- Locking shift 1) */
2169 case '\017': /* SI (LS0 -- Locking shift 0) */
2170 term
.charset
= 1 - (ascii
- '\016');
2172 case '\032': /* SUB */
2173 tsetchar('?', &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2174 case '\030': /* CAN */
2177 case '\005': /* ENQ (IGNORED) */
2178 case '\000': /* NUL (IGNORED) */
2179 case '\021': /* XON (IGNORED) */
2180 case '\023': /* XOFF (IGNORED) */
2181 case 0177: /* DEL (IGNORED) */
2183 case 0x80: /* TODO: PAD */
2184 case 0x81: /* TODO: HOP */
2185 case 0x82: /* TODO: BPH */
2186 case 0x83: /* TODO: NBH */
2187 case 0x84: /* TODO: IND */
2189 case 0x85: /* NEL -- Next line */
2190 tnewline(1); /* always go to first col */
2192 case 0x86: /* TODO: SSA */
2193 case 0x87: /* TODO: ESA */
2195 case 0x88: /* HTS -- Horizontal tab stop */
2196 term
.tabs
[term
.c
.x
] = 1;
2198 case 0x89: /* TODO: HTJ */
2199 case 0x8a: /* TODO: VTS */
2200 case 0x8b: /* TODO: PLD */
2201 case 0x8c: /* TODO: PLU */
2202 case 0x8d: /* TODO: RI */
2203 case 0x8e: /* TODO: SS2 */
2204 case 0x8f: /* TODO: SS3 */
2205 case 0x91: /* TODO: PU1 */
2206 case 0x92: /* TODO: PU2 */
2207 case 0x93: /* TODO: STS */
2208 case 0x94: /* TODO: CCH */
2209 case 0x95: /* TODO: MW */
2210 case 0x96: /* TODO: SPA */
2211 case 0x97: /* TODO: EPA */
2212 case 0x98: /* TODO: SOS */
2213 case 0x99: /* TODO: SGCI */
2215 case 0x9a: /* DECID -- Identify Terminal */
2216 ttywrite(vtiden
, strlen(vtiden
), 0);
2218 case 0x9b: /* TODO: CSI */
2219 case 0x9c: /* TODO: ST */
2221 case 0x90: /* DCS -- Device Control String */
2222 case 0x9d: /* OSC -- Operating System Command */
2223 case 0x9e: /* PM -- Privacy Message */
2224 case 0x9f: /* APC -- Application Program Command */
2225 tstrsequence(ascii
);
2228 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2229 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
2233 * returns 1 when the sequence is finished and it hasn't to read
2234 * more characters for this sequence, otherwise 0
2237 eschandle(uchar ascii
)
2241 term
.esc
|= ESC_CSI
;
2244 term
.esc
|= ESC_TEST
;
2247 term
.esc
|= ESC_UTF8
;
2249 case 'P': /* DCS -- Device Control String */
2250 case '_': /* APC -- Application Program Command */
2251 case '^': /* PM -- Privacy Message */
2252 case ']': /* OSC -- Operating System Command */
2253 case 'k': /* old title set compatibility */
2254 tstrsequence(ascii
);
2256 case 'n': /* LS2 -- Locking shift 2 */
2257 case 'o': /* LS3 -- Locking shift 3 */
2258 term
.charset
= 2 + (ascii
- 'n');
2260 case '(': /* GZD4 -- set primary charset G0 */
2261 case ')': /* G1D4 -- set secondary charset G1 */
2262 case '*': /* G2D4 -- set tertiary charset G2 */
2263 case '+': /* G3D4 -- set quaternary charset G3 */
2264 term
.icharset
= ascii
- '(';
2265 term
.esc
|= ESC_ALTCHARSET
;
2267 case 'D': /* IND -- Linefeed */
2268 if (term
.c
.y
== term
.bot
) {
2269 tscrollup(term
.top
, 1);
2271 tmoveto(term
.c
.x
, term
.c
.y
+1);
2274 case 'E': /* NEL -- Next line */
2275 tnewline(1); /* always go to first col */
2277 case 'H': /* HTS -- Horizontal tab stop */
2278 term
.tabs
[term
.c
.x
] = 1;
2280 case 'M': /* RI -- Reverse index */
2281 if (term
.c
.y
== term
.top
) {
2282 tscrolldown(term
.top
, 1);
2284 tmoveto(term
.c
.x
, term
.c
.y
-1);
2287 case 'Z': /* DECID -- Identify Terminal */
2288 ttywrite(vtiden
, strlen(vtiden
), 0);
2290 case 'c': /* RIS -- Reset to inital state */
2295 case '=': /* DECPAM -- Application keypad */
2296 xsetmode(1, MODE_APPKEYPAD
);
2298 case '>': /* DECPNM -- Normal keypad */
2299 xsetmode(0, MODE_APPKEYPAD
);
2301 case '7': /* DECSC -- Save Cursor */
2302 tcursor(CURSOR_SAVE
);
2304 case '8': /* DECRC -- Restore Cursor */
2305 tcursor(CURSOR_LOAD
);
2307 case '\\': /* ST -- String Terminator */
2308 if (term
.esc
& ESC_STR_END
)
2312 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2313 (uchar
) ascii
, isprint(ascii
)? ascii
:'.');
2327 control
= ISCONTROL(u
);
2328 if (!IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2332 len
= utf8encode(u
, c
);
2333 if (!control
&& (width
= wcwidth(u
)) == -1) {
2334 memcpy(c
, "\357\277\275", 4); /* UTF_INVALID */
2339 if (IS_SET(MODE_PRINT
))
2343 * STR sequence must be checked before anything else
2344 * because it uses all following characters until it
2345 * receives a ESC, a SUB, a ST or any other C1 control
2348 if (term
.esc
& ESC_STR
) {
2349 if (u
== '\a' || u
== 030 || u
== 032 || u
== 033 ||
2351 term
.esc
&= ~(ESC_START
|ESC_STR
|ESC_DCS
);
2352 if (IS_SET(MODE_SIXEL
)) {
2353 /* TODO: render sixel */;
2354 term
.mode
&= ~MODE_SIXEL
;
2357 term
.esc
|= ESC_STR_END
;
2358 goto check_control_code
;
2362 if (IS_SET(MODE_SIXEL
)) {
2363 /* TODO: implement sixel mode */
2366 if (term
.esc
&ESC_DCS
&& strescseq
.len
== 0 && u
== 'q')
2367 term
.mode
|= MODE_SIXEL
;
2369 if (strescseq
.len
+len
>= sizeof(strescseq
.buf
)-1) {
2371 * Here is a bug in terminals. If the user never sends
2372 * some code to stop the str or esc command, then st
2373 * will stop responding. But this is better than
2374 * silently failing with unknown characters. At least
2375 * then users will report back.
2377 * In the case users ever get fixed, here is the code:
2386 memmove(&strescseq
.buf
[strescseq
.len
], c
, len
);
2387 strescseq
.len
+= len
;
2393 * Actions of control codes must be performed as soon they arrive
2394 * because they can be embedded inside a control sequence, and
2395 * they must not cause conflicts with sequences.
2400 * control codes are not shown ever
2403 } else if (term
.esc
& ESC_START
) {
2404 if (term
.esc
& ESC_CSI
) {
2405 csiescseq
.buf
[csiescseq
.len
++] = u
;
2406 if (BETWEEN(u
, 0x40, 0x7E)
2407 || csiescseq
.len
>= \
2408 sizeof(csiescseq
.buf
)-1) {
2414 } else if (term
.esc
& ESC_UTF8
) {
2416 } else if (term
.esc
& ESC_ALTCHARSET
) {
2418 } else if (term
.esc
& ESC_TEST
) {
2423 /* sequence already finished */
2427 * All characters which form part of a sequence are not
2432 if (sel
.ob
.x
!= -1 && BETWEEN(term
.c
.y
, sel
.ob
.y
, sel
.oe
.y
))
2435 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2436 if (IS_SET(MODE_WRAP
) && (term
.c
.state
& CURSOR_WRAPNEXT
)) {
2437 gp
->mode
|= ATTR_WRAP
;
2439 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2442 if (IS_SET(MODE_INSERT
) && term
.c
.x
+width
< term
.col
)
2443 memmove(gp
+width
, gp
, (term
.col
- term
.c
.x
- width
) * sizeof(Glyph
));
2445 if (term
.c
.x
+width
> term
.col
) {
2447 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2450 tsetchar(u
, &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2453 gp
->mode
|= ATTR_WIDE
;
2454 if (term
.c
.x
+1 < term
.col
) {
2456 gp
[1].mode
= ATTR_WDUMMY
;
2459 if (term
.c
.x
+width
< term
.col
) {
2460 tmoveto(term
.c
.x
+width
, term
.c
.y
);
2462 term
.c
.state
|= CURSOR_WRAPNEXT
;
2467 twrite(const char *buf
, int buflen
, int show_ctrl
)
2473 for (n
= 0; n
< buflen
; n
+= charsize
) {
2474 if (IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2475 /* process a complete utf8 char */
2476 charsize
= utf8decode(buf
+ n
, &u
, buflen
- n
);
2483 if (show_ctrl
&& ISCONTROL(u
)) {
2488 } else if (u
!= '\n' && u
!= '\r' && u
!= '\t') {
2499 tresize(int col
, int row
)
2502 int minrow
= MIN(row
, term
.row
);
2503 int mincol
= MIN(col
, term
.col
);
2507 if (col
< 1 || row
< 1) {
2509 "tresize: error resizing to %dx%d\n", col
, row
);
2514 * slide screen to keep cursor where we expect it -
2515 * tscrollup would work here, but we can optimize to
2516 * memmove because we're freeing the earlier lines
2518 for (i
= 0; i
<= term
.c
.y
- row
; i
++) {
2522 /* ensure that both src and dst are not NULL */
2524 memmove(term
.line
, term
.line
+ i
, row
* sizeof(Line
));
2525 memmove(term
.alt
, term
.alt
+ i
, row
* sizeof(Line
));
2527 for (i
+= row
; i
< term
.row
; i
++) {
2532 /* resize to new height */
2533 term
.line
= xrealloc(term
.line
, row
* sizeof(Line
));
2534 term
.alt
= xrealloc(term
.alt
, row
* sizeof(Line
));
2535 term
.dirty
= xrealloc(term
.dirty
, row
* sizeof(*term
.dirty
));
2536 term
.tabs
= xrealloc(term
.tabs
, col
* sizeof(*term
.tabs
));
2538 /* resize each row to new width, zero-pad if needed */
2539 for (i
= 0; i
< minrow
; i
++) {
2540 term
.line
[i
] = xrealloc(term
.line
[i
], col
* sizeof(Glyph
));
2541 term
.alt
[i
] = xrealloc(term
.alt
[i
], col
* sizeof(Glyph
));
2544 /* allocate any new rows */
2545 for (/* i = minrow */; i
< row
; i
++) {
2546 term
.line
[i
] = xmalloc(col
* sizeof(Glyph
));
2547 term
.alt
[i
] = xmalloc(col
* sizeof(Glyph
));
2549 if (col
> term
.col
) {
2550 bp
= term
.tabs
+ term
.col
;
2552 memset(bp
, 0, sizeof(*term
.tabs
) * (col
- term
.col
));
2553 while (--bp
> term
.tabs
&& !*bp
)
2555 for (bp
+= tabspaces
; bp
< term
.tabs
+ col
; bp
+= tabspaces
)
2558 /* update terminal size */
2561 /* reset scrolling region */
2562 tsetscroll(0, row
-1);
2563 /* make use of the LIMIT in tmoveto */
2564 tmoveto(term
.c
.x
, term
.c
.y
);
2565 /* Clearing both screens (it makes dirty all lines) */
2567 for (i
= 0; i
< 2; i
++) {
2568 if (mincol
< col
&& 0 < minrow
) {
2569 tclearregion(mincol
, 0, col
- 1, minrow
- 1);
2571 if (0 < col
&& minrow
< row
) {
2572 tclearregion(0, minrow
, col
- 1, row
- 1);
2575 tcursor(CURSOR_LOAD
);
2587 drawregion(int x1
, int y1
, int x2
, int y2
)
2590 for (y
= y1
; y
< y2
; y
++) {
2595 xdrawline(term
.line
[y
], x1
, y
, x2
);
2607 /* adjust cursor position */
2608 LIMIT(term
.ocx
, 0, term
.col
-1);
2609 LIMIT(term
.ocy
, 0, term
.row
-1);
2610 if (term
.line
[term
.ocy
][term
.ocx
].mode
& ATTR_WDUMMY
)
2612 if (term
.line
[term
.c
.y
][cx
].mode
& ATTR_WDUMMY
)
2615 drawregion(0, 0, term
.col
, term
.row
);
2616 xdrawcursor(cx
, term
.c
.y
, term
.line
[term
.c
.y
][cx
],
2617 term
.ocx
, term
.ocy
, term
.line
[term
.ocy
][term
.ocx
]);
2618 term
.ocx
= cx
, term
.ocy
= term
.c
.y
;