Xinqi Bao's Git
1 /* See LICENSE for license details. */
12 #include <sys/ioctl.h>
13 #include <sys/select.h>
14 #include <sys/types.h>
25 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
27 #elif defined(__FreeBSD__) || defined(__DragonFly__)
32 #define UTF_INVALID 0xFFFD
34 #define ESC_BUF_SIZ (128*UTF_SIZ)
35 #define ESC_ARG_SIZ 16
36 #define STR_BUF_SIZ ESC_BUF_SIZ
37 #define STR_ARG_SIZ ESC_ARG_SIZ
40 #define IS_SET(flag) ((term.mode & (flag)) != 0)
41 #define NUMMAXLEN(x) ((int)(sizeof(x) * 2.56 + 0.5) + 1)
42 #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == '\177')
43 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
44 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
45 #define ISDELIM(u) (utf8strchr(worddelimiters, u) != NULL)
48 #define ISO14755CMD "dmenu -w \"$WINDOWID\" -p codepoint: </dev/null"
53 MODE_ALTSCREEN
= 1 << 2,
61 enum cursor_movement
{
85 ESC_STR
= 4, /* OSC, PM, APC */
87 ESC_STR_END
= 16, /* a final string was encountered */
88 ESC_TEST
= 32, /* Enter in test mode */
94 Glyph attr
; /* current char attributes */
105 * Selection variables:
106 * nb – normalized coordinates of the beginning of the selection
107 * ne – normalized coordinates of the end of the selection
108 * ob – original coordinates of the beginning of the selection
109 * oe – original coordinates of the end of the selection
118 /* Internal representation of the screen */
120 int row
; /* nb row */
121 int col
; /* nb col */
122 Line
*line
; /* screen */
123 Line
*alt
; /* alternate screen */
124 int *dirty
; /* dirtyness of lines */
125 TCursor c
; /* cursor */
126 int ocx
; /* old cursor col */
127 int ocy
; /* old cursor row */
128 int top
; /* top scroll limit */
129 int bot
; /* bottom scroll limit */
130 int mode
; /* terminal mode flags */
131 int esc
; /* escape state flags */
132 char trantbl
[4]; /* charset table translation */
133 int charset
; /* current charset */
134 int icharset
; /* selected charset for sequence */
138 /* CSI Escape sequence structs */
139 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
141 char buf
[ESC_BUF_SIZ
]; /* raw string */
142 int len
; /* raw string length */
144 int arg
[ESC_ARG_SIZ
];
145 int narg
; /* nb of args */
149 /* STR Escape sequence structs */
150 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
152 char type
; /* ESC type ... */
153 char buf
[STR_BUF_SIZ
]; /* raw string */
154 int len
; /* raw string length */
155 char *args
[STR_ARG_SIZ
];
156 int narg
; /* nb of args */
159 static void execsh(char *, char **);
160 static void stty(char **);
161 static void sigchld(int);
162 static void ttywriteraw(const char *, size_t);
164 static void csidump(void);
165 static void csihandle(void);
166 static void csiparse(void);
167 static void csireset(void);
168 static int eschandle(uchar
);
169 static void strdump(void);
170 static void strhandle(void);
171 static void strparse(void);
172 static void strreset(void);
174 static void tprinter(char *, size_t);
175 static void tdumpsel(void);
176 static void tdumpline(int);
177 static void tdump(void);
178 static void tclearregion(int, int, int, int);
179 static void tcursor(int);
180 static void tdeletechar(int);
181 static void tdeleteline(int);
182 static void tinsertblank(int);
183 static void tinsertblankline(int);
184 static int tlinelen(int);
185 static void tmoveto(int, int);
186 static void tmoveato(int, int);
187 static void tnewline(int);
188 static void tputtab(int);
189 static void tputc(Rune
);
190 static void treset(void);
191 static void tscrollup(int, int);
192 static void tscrolldown(int, int);
193 static void tsetattr(int *, int);
194 static void tsetchar(Rune
, Glyph
*, int, int);
195 static void tsetdirt(int, int);
196 static void tsetscroll(int, int);
197 static void tswapscreen(void);
198 static void tsetmode(int, int, int *, int);
199 static int twrite(const char *, int, int);
200 static void tfulldirt(void);
201 static void tcontrolcode(uchar
);
202 static void tdectest(char );
203 static void tdefutf8(char);
204 static int32_t tdefcolor(int *, int *, int);
205 static void tdeftran(char);
206 static void tstrsequence(uchar
);
208 static void drawregion(int, int, int, int);
210 static void selnormalize(void);
211 static void selscroll(int, int);
212 static void selsnap(int *, int *, int);
214 static size_t utf8decode(const char *, Rune
*, size_t);
215 static Rune
utf8decodebyte(char, size_t *);
216 static char utf8encodebyte(Rune
, size_t);
217 static char *utf8strchr(char *, Rune
);
218 static size_t utf8validate(Rune
*, size_t);
220 static char *base64dec(const char *);
221 static char base64dec_getc(const char **);
223 static ssize_t
xwrite(int, const char *, size_t);
227 static Selection sel
;
228 static CSIEscape csiescseq
;
229 static STREscape strescseq
;
234 static uchar utfbyte
[UTF_SIZ
+ 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
235 static uchar utfmask
[UTF_SIZ
+ 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
236 static Rune utfmin
[UTF_SIZ
+ 1] = { 0, 0, 0x80, 0x800, 0x10000};
237 static Rune utfmax
[UTF_SIZ
+ 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
240 xwrite(int fd
, const char *s
, size_t len
)
246 r
= write(fd
, s
, len
);
261 if (!(p
= malloc(len
)))
262 die("malloc: %s\n", strerror(errno
));
268 xrealloc(void *p
, size_t len
)
270 if ((p
= realloc(p
, len
)) == NULL
)
271 die("realloc: %s\n", strerror(errno
));
279 if ((s
= strdup(s
)) == NULL
)
280 die("strdup: %s\n", strerror(errno
));
286 utf8decode(const char *c
, Rune
*u
, size_t clen
)
288 size_t i
, j
, len
, type
;
294 udecoded
= utf8decodebyte(c
[0], &len
);
295 if (!BETWEEN(len
, 1, UTF_SIZ
))
297 for (i
= 1, j
= 1; i
< clen
&& j
< len
; ++i
, ++j
) {
298 udecoded
= (udecoded
<< 6) | utf8decodebyte(c
[i
], &type
);
305 utf8validate(u
, len
);
311 utf8decodebyte(char c
, size_t *i
)
313 for (*i
= 0; *i
< LEN(utfmask
); ++(*i
))
314 if (((uchar
)c
& utfmask
[*i
]) == utfbyte
[*i
])
315 return (uchar
)c
& ~utfmask
[*i
];
321 utf8encode(Rune u
, char *c
)
325 len
= utf8validate(&u
, 0);
329 for (i
= len
- 1; i
!= 0; --i
) {
330 c
[i
] = utf8encodebyte(u
, 0);
333 c
[0] = utf8encodebyte(u
, len
);
339 utf8encodebyte(Rune u
, size_t i
)
341 return utfbyte
[i
] | (u
& ~utfmask
[i
]);
345 utf8strchr(char *s
, Rune u
)
351 for (i
= 0, j
= 0; i
< len
; i
+= j
) {
352 if (!(j
= utf8decode(&s
[i
], &r
, len
- i
)))
362 utf8validate(Rune
*u
, size_t i
)
364 if (!BETWEEN(*u
, utfmin
[i
], utfmax
[i
]) || BETWEEN(*u
, 0xD800, 0xDFFF))
366 for (i
= 1; *u
> utfmax
[i
]; ++i
)
372 static const char base64_digits
[] = {
373 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
374 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0,
375 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
376 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
377 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
378 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0,
379 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
380 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
381 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
382 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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
388 base64dec_getc(const char **src
)
390 while (**src
&& !isprint(**src
)) (*src
)++;
395 base64dec(const char *src
)
397 size_t in_len
= strlen(src
);
401 in_len
+= 4 - (in_len
% 4);
402 result
= dst
= xmalloc(in_len
/ 4 * 3 + 1);
404 int a
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
405 int b
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
406 int c
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
407 int d
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
409 *dst
++ = (a
<< 2) | ((b
& 0x30) >> 4);
412 *dst
++ = ((b
& 0x0f) << 4) | ((c
& 0x3c) >> 2);
415 *dst
++ = ((c
& 0x03) << 6) | d
;
434 if (term
.line
[y
][i
- 1].mode
& ATTR_WRAP
)
437 while (i
> 0 && term
.line
[y
][i
- 1].u
== ' ')
444 selstart(int col
, int row
, int snap
)
447 sel
.mode
= SEL_EMPTY
;
448 sel
.type
= SEL_REGULAR
;
449 sel
.alt
= IS_SET(MODE_ALTSCREEN
);
451 sel
.oe
.x
= sel
.ob
.x
= col
;
452 sel
.oe
.y
= sel
.ob
.y
= row
;
456 sel
.mode
= SEL_READY
;
457 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
461 selextend(int col
, int row
, int type
, int done
)
463 int oldey
, oldex
, oldsby
, oldsey
, oldtype
;
465 if (sel
.mode
== SEL_IDLE
)
467 if (done
&& sel
.mode
== SEL_EMPTY
) {
483 if (oldey
!= sel
.oe
.y
|| oldex
!= sel
.oe
.x
|| oldtype
!= sel
.type
)
484 tsetdirt(MIN(sel
.nb
.y
, oldsby
), MAX(sel
.ne
.y
, oldsey
));
486 sel
.mode
= done
? SEL_IDLE
: SEL_READY
;
494 if (sel
.type
== SEL_REGULAR
&& sel
.ob
.y
!= sel
.oe
.y
) {
495 sel
.nb
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.ob
.x
: sel
.oe
.x
;
496 sel
.ne
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.oe
.x
: sel
.ob
.x
;
498 sel
.nb
.x
= MIN(sel
.ob
.x
, sel
.oe
.x
);
499 sel
.ne
.x
= MAX(sel
.ob
.x
, sel
.oe
.x
);
501 sel
.nb
.y
= MIN(sel
.ob
.y
, sel
.oe
.y
);
502 sel
.ne
.y
= MAX(sel
.ob
.y
, sel
.oe
.y
);
504 selsnap(&sel
.nb
.x
, &sel
.nb
.y
, -1);
505 selsnap(&sel
.ne
.x
, &sel
.ne
.y
, +1);
507 /* expand selection over line breaks */
508 if (sel
.type
== SEL_RECTANGULAR
)
510 i
= tlinelen(sel
.nb
.y
);
513 if (tlinelen(sel
.ne
.y
) <= sel
.ne
.x
)
514 sel
.ne
.x
= term
.col
- 1;
518 selected(int x
, int y
)
520 if (sel
.mode
== SEL_EMPTY
|| sel
.ob
.x
== -1 ||
521 sel
.alt
!= IS_SET(MODE_ALTSCREEN
))
524 if (sel
.type
== SEL_RECTANGULAR
)
525 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
526 && BETWEEN(x
, sel
.nb
.x
, sel
.ne
.x
);
528 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
529 && (y
!= sel
.nb
.y
|| x
>= sel
.nb
.x
)
530 && (y
!= sel
.ne
.y
|| x
<= sel
.ne
.x
);
534 selsnap(int *x
, int *y
, int direction
)
536 int newx
, newy
, xt
, yt
;
537 int delim
, prevdelim
;
543 * Snap around if the word wraps around at the end or
544 * beginning of a line.
546 prevgp
= &term
.line
[*y
][*x
];
547 prevdelim
= ISDELIM(prevgp
->u
);
549 newx
= *x
+ direction
;
551 if (!BETWEEN(newx
, 0, term
.col
- 1)) {
553 newx
= (newx
+ term
.col
) % term
.col
;
554 if (!BETWEEN(newy
, 0, term
.row
- 1))
560 yt
= newy
, xt
= newx
;
561 if (!(term
.line
[yt
][xt
].mode
& ATTR_WRAP
))
565 if (newx
>= tlinelen(newy
))
568 gp
= &term
.line
[newy
][newx
];
569 delim
= ISDELIM(gp
->u
);
570 if (!(gp
->mode
& ATTR_WDUMMY
) && (delim
!= prevdelim
571 || (delim
&& gp
->u
!= prevgp
->u
)))
582 * Snap around if the the previous line or the current one
583 * has set ATTR_WRAP at its end. Then the whole next or
584 * previous line will be selected.
586 *x
= (direction
< 0) ? 0 : term
.col
- 1;
588 for (; *y
> 0; *y
+= direction
) {
589 if (!(term
.line
[*y
-1][term
.col
-1].mode
594 } else if (direction
> 0) {
595 for (; *y
< term
.row
-1; *y
+= direction
) {
596 if (!(term
.line
[*y
][term
.col
-1].mode
610 int y
, bufsize
, lastx
, linelen
;
616 bufsize
= (term
.col
+1) * (sel
.ne
.y
-sel
.nb
.y
+1) * UTF_SIZ
;
617 ptr
= str
= xmalloc(bufsize
);
619 /* append every set & selected glyph to the selection */
620 for (y
= sel
.nb
.y
; y
<= sel
.ne
.y
; y
++) {
621 if ((linelen
= tlinelen(y
)) == 0) {
626 if (sel
.type
== SEL_RECTANGULAR
) {
627 gp
= &term
.line
[y
][sel
.nb
.x
];
630 gp
= &term
.line
[y
][sel
.nb
.y
== y
? sel
.nb
.x
: 0];
631 lastx
= (sel
.ne
.y
== y
) ? sel
.ne
.x
: term
.col
-1;
633 last
= &term
.line
[y
][MIN(lastx
, linelen
-1)];
634 while (last
>= gp
&& last
->u
== ' ')
637 for ( ; gp
<= last
; ++gp
) {
638 if (gp
->mode
& ATTR_WDUMMY
)
641 ptr
+= utf8encode(gp
->u
, ptr
);
645 * Copy and pasting of line endings is inconsistent
646 * in the inconsistent terminal and GUI world.
647 * The best solution seems like to produce '\n' when
648 * something is copied from st and convert '\n' to
649 * '\r', when something to be pasted is received by
651 * FIXME: Fix the computer world.
653 if ((y
< sel
.ne
.y
|| lastx
>= linelen
) && !(last
->mode
& ATTR_WRAP
))
667 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
671 die(const char *errstr
, ...)
675 va_start(ap
, errstr
);
676 vfprintf(stderr
, errstr
, ap
);
682 execsh(char *cmd
, char **args
)
685 const struct passwd
*pw
;
688 if ((pw
= getpwuid(getuid())) == NULL
) {
690 die("getpwuid: %s\n", strerror(errno
));
692 die("who are you?\n");
695 if ((sh
= getenv("SHELL")) == NULL
)
696 sh
= (pw
->pw_shell
[0]) ? pw
->pw_shell
: cmd
;
704 DEFAULT(args
, ((char *[]) {prog
, NULL
}));
709 setenv("LOGNAME", pw
->pw_name
, 1);
710 setenv("USER", pw
->pw_name
, 1);
711 setenv("SHELL", sh
, 1);
712 setenv("HOME", pw
->pw_dir
, 1);
713 setenv("TERM", termname
, 1);
715 signal(SIGCHLD
, SIG_DFL
);
716 signal(SIGHUP
, SIG_DFL
);
717 signal(SIGINT
, SIG_DFL
);
718 signal(SIGQUIT
, SIG_DFL
);
719 signal(SIGTERM
, SIG_DFL
);
720 signal(SIGALRM
, SIG_DFL
);
732 if ((p
= waitpid(pid
, &stat
, WNOHANG
)) < 0)
733 die("waiting for pid %hd failed: %s\n", pid
, strerror(errno
));
738 if (!WIFEXITED(stat
) || WEXITSTATUS(stat
))
739 die("child finished with error '%d'\n", stat
);
746 char cmd
[_POSIX_ARG_MAX
], **p
, *q
, *s
;
749 if ((n
= strlen(stty_args
)) > sizeof(cmd
)-1)
750 die("incorrect stty parameters\n");
751 memcpy(cmd
, stty_args
, n
);
753 siz
= sizeof(cmd
) - n
;
754 for (p
= args
; p
&& (s
= *p
); ++p
) {
755 if ((n
= strlen(s
)) > siz
-1)
756 die("stty parameter length too long\n");
763 if (system(cmd
) != 0)
764 perror("Couldn't call stty");
768 ttynew(char *line
, char *cmd
, char *out
, char **args
)
773 term
.mode
|= MODE_PRINT
;
774 iofd
= (!strcmp(out
, "-")) ?
775 1 : open(out
, O_WRONLY
| O_CREAT
, 0666);
777 fprintf(stderr
, "Error opening %s:%s\n",
778 out
, strerror(errno
));
783 if ((cmdfd
= open(line
, O_RDWR
)) < 0)
784 die("open line '%s' failed: %s\n",
785 line
, strerror(errno
));
791 /* seems to work fine on linux, openbsd and freebsd */
792 if (openpty(&m
, &s
, NULL
, NULL
, NULL
) < 0)
793 die("openpty failed: %s\n", strerror(errno
));
795 switch (pid
= fork()) {
797 die("fork failed: %s\n", strerror(errno
));
801 setsid(); /* create a new process group */
805 if (ioctl(s
, TIOCSCTTY
, NULL
) < 0)
806 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno
));
814 signal(SIGCHLD
, sigchld
);
823 static char buf
[BUFSIZ
];
824 static int buflen
= 0;
828 /* append read bytes to unprocessed bytes */
829 if ((ret
= read(cmdfd
, buf
+buflen
, LEN(buf
)-buflen
)) < 0)
830 die("couldn't read from shell: %s\n", strerror(errno
));
833 written
= twrite(buf
, buflen
, 0);
835 /* keep any uncomplete utf8 char for the next call */
837 memmove(buf
, buf
+ written
, buflen
);
843 ttywrite(const char *s
, size_t n
, int may_echo
)
847 if (may_echo
&& IS_SET(MODE_ECHO
))
850 if (!IS_SET(MODE_CRLF
)) {
855 /* This is similar to how the kernel handles ONLCR for ttys */
859 ttywriteraw("\r\n", 2);
861 next
= memchr(s
, '\r', n
);
862 DEFAULT(next
, s
+ n
);
863 ttywriteraw(s
, next
- s
);
871 ttywriteraw(const char *s
, size_t n
)
878 * Remember that we are using a pty, which might be a modem line.
879 * Writing too much will clog the line. That's why we are doing this
881 * FIXME: Migrate the world to Plan 9.
889 /* Check if we can write. */
890 if (pselect(cmdfd
+1, &rfd
, &wfd
, NULL
, NULL
, NULL
) < 0) {
893 die("select failed: %s\n", strerror(errno
));
895 if (FD_ISSET(cmdfd
, &wfd
)) {
897 * Only write the bytes written by ttywrite() or the
898 * default of 256. This seems to be a reasonable value
899 * for a serial line. Bigger values might clog the I/O.
901 if ((r
= write(cmdfd
, s
, (n
< lim
)? n
: lim
)) < 0)
905 * We weren't able to write out everything.
906 * This means the buffer is getting full
914 /* All bytes have been written. */
918 if (FD_ISSET(cmdfd
, &rfd
))
924 die("write error on tty: %s\n", strerror(errno
));
928 ttyresize(int tw
, int th
)
936 if (ioctl(cmdfd
, TIOCSWINSZ
, &w
) < 0)
937 fprintf(stderr
, "Couldn't set window size: %s\n", strerror(errno
));
943 /* Send SIGHUP to shell */
952 for (i
= 0; i
< term
.row
-1; i
++) {
953 for (j
= 0; j
< term
.col
-1; j
++) {
954 if (term
.line
[i
][j
].mode
& attr
)
963 tsetdirt(int top
, int bot
)
967 LIMIT(top
, 0, term
.row
-1);
968 LIMIT(bot
, 0, term
.row
-1);
970 for (i
= top
; i
<= bot
; i
++)
975 tsetdirtattr(int attr
)
979 for (i
= 0; i
< term
.row
-1; i
++) {
980 for (j
= 0; j
< term
.col
-1; j
++) {
981 if (term
.line
[i
][j
].mode
& attr
) {
992 tsetdirt(0, term
.row
-1);
999 int alt
= IS_SET(MODE_ALTSCREEN
);
1001 if (mode
== CURSOR_SAVE
) {
1003 } else if (mode
== CURSOR_LOAD
) {
1005 tmoveto(c
[alt
].x
, c
[alt
].y
);
1014 term
.c
= (TCursor
){{
1018 }, .x
= 0, .y
= 0, .state
= CURSOR_DEFAULT
};
1020 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1021 for (i
= tabspaces
; i
< term
.col
; i
+= tabspaces
)
1024 term
.bot
= term
.row
- 1;
1025 term
.mode
= MODE_WRAP
|MODE_UTF8
;
1026 memset(term
.trantbl
, CS_USA
, sizeof(term
.trantbl
));
1029 for (i
= 0; i
< 2; i
++) {
1031 tcursor(CURSOR_SAVE
);
1032 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1038 tnew(int col
, int row
)
1040 term
= (Term
){ .c
= { .attr
= { .fg
= defaultfg
, .bg
= defaultbg
} } };
1048 Line
*tmp
= term
.line
;
1050 term
.line
= term
.alt
;
1052 term
.mode
^= MODE_ALTSCREEN
;
1057 tscrolldown(int orig
, int n
)
1062 LIMIT(n
, 0, term
.bot
-orig
+1);
1064 tsetdirt(orig
, term
.bot
-n
);
1065 tclearregion(0, term
.bot
-n
+1, term
.col
-1, term
.bot
);
1067 for (i
= term
.bot
; i
>= orig
+n
; i
--) {
1068 temp
= term
.line
[i
];
1069 term
.line
[i
] = term
.line
[i
-n
];
1070 term
.line
[i
-n
] = temp
;
1077 tscrollup(int orig
, int n
)
1082 LIMIT(n
, 0, term
.bot
-orig
+1);
1084 tclearregion(0, orig
, term
.col
-1, orig
+n
-1);
1085 tsetdirt(orig
+n
, term
.bot
);
1087 for (i
= orig
; i
<= term
.bot
-n
; i
++) {
1088 temp
= term
.line
[i
];
1089 term
.line
[i
] = term
.line
[i
+n
];
1090 term
.line
[i
+n
] = temp
;
1093 selscroll(orig
, -n
);
1097 selscroll(int orig
, int n
)
1102 if (BETWEEN(sel
.ob
.y
, orig
, term
.bot
) || BETWEEN(sel
.oe
.y
, orig
, term
.bot
)) {
1103 if ((sel
.ob
.y
+= n
) > term
.bot
|| (sel
.oe
.y
+= n
) < term
.top
) {
1107 if (sel
.type
== SEL_RECTANGULAR
) {
1108 if (sel
.ob
.y
< term
.top
)
1109 sel
.ob
.y
= term
.top
;
1110 if (sel
.oe
.y
> term
.bot
)
1111 sel
.oe
.y
= term
.bot
;
1113 if (sel
.ob
.y
< term
.top
) {
1114 sel
.ob
.y
= term
.top
;
1117 if (sel
.oe
.y
> term
.bot
) {
1118 sel
.oe
.y
= term
.bot
;
1119 sel
.oe
.x
= term
.col
;
1127 tnewline(int first_col
)
1131 if (y
== term
.bot
) {
1132 tscrollup(term
.top
, 1);
1136 tmoveto(first_col
? 0 : term
.c
.x
, y
);
1142 char *p
= csiescseq
.buf
, *np
;
1151 csiescseq
.buf
[csiescseq
.len
] = '\0';
1152 while (p
< csiescseq
.buf
+csiescseq
.len
) {
1154 v
= strtol(p
, &np
, 10);
1157 if (v
== LONG_MAX
|| v
== LONG_MIN
)
1159 csiescseq
.arg
[csiescseq
.narg
++] = v
;
1161 if (*p
!= ';' || csiescseq
.narg
== ESC_ARG_SIZ
)
1165 csiescseq
.mode
[0] = *p
++;
1166 csiescseq
.mode
[1] = (p
< csiescseq
.buf
+csiescseq
.len
) ? *p
: '\0';
1169 /* for absolute user moves, when decom is set */
1171 tmoveato(int x
, int y
)
1173 tmoveto(x
, y
+ ((term
.c
.state
& CURSOR_ORIGIN
) ? term
.top
: 0));
1177 tmoveto(int x
, int y
)
1181 if (term
.c
.state
& CURSOR_ORIGIN
) {
1186 maxy
= term
.row
- 1;
1188 term
.c
.state
&= ~CURSOR_WRAPNEXT
;
1189 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1190 term
.c
.y
= LIMIT(y
, miny
, maxy
);
1194 tsetchar(Rune u
, Glyph
*attr
, int x
, int y
)
1196 static char *vt100_0
[62] = { /* 0x41 - 0x7e */
1197 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1198 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1199 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1200 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1201 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1202 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1203 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1204 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1208 * The table is proudly stolen from rxvt.
1210 if (term
.trantbl
[term
.charset
] == CS_GRAPHIC0
&&
1211 BETWEEN(u
, 0x41, 0x7e) && vt100_0
[u
- 0x41])
1212 utf8decode(vt100_0
[u
- 0x41], &u
, UTF_SIZ
);
1214 if (term
.line
[y
][x
].mode
& ATTR_WIDE
) {
1215 if (x
+1 < term
.col
) {
1216 term
.line
[y
][x
+1].u
= ' ';
1217 term
.line
[y
][x
+1].mode
&= ~ATTR_WDUMMY
;
1219 } else if (term
.line
[y
][x
].mode
& ATTR_WDUMMY
) {
1220 term
.line
[y
][x
-1].u
= ' ';
1221 term
.line
[y
][x
-1].mode
&= ~ATTR_WIDE
;
1225 term
.line
[y
][x
] = *attr
;
1226 term
.line
[y
][x
].u
= u
;
1230 tclearregion(int x1
, int y1
, int x2
, int y2
)
1236 temp
= x1
, x1
= x2
, x2
= temp
;
1238 temp
= y1
, y1
= y2
, y2
= temp
;
1240 LIMIT(x1
, 0, term
.col
-1);
1241 LIMIT(x2
, 0, term
.col
-1);
1242 LIMIT(y1
, 0, term
.row
-1);
1243 LIMIT(y2
, 0, term
.row
-1);
1245 for (y
= y1
; y
<= y2
; y
++) {
1247 for (x
= x1
; x
<= x2
; x
++) {
1248 gp
= &term
.line
[y
][x
];
1251 gp
->fg
= term
.c
.attr
.fg
;
1252 gp
->bg
= term
.c
.attr
.bg
;
1265 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1269 size
= term
.col
- src
;
1270 line
= term
.line
[term
.c
.y
];
1272 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1273 tclearregion(term
.col
-n
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1282 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1286 size
= term
.col
- dst
;
1287 line
= term
.line
[term
.c
.y
];
1289 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1290 tclearregion(src
, term
.c
.y
, dst
- 1, term
.c
.y
);
1294 tinsertblankline(int n
)
1296 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1297 tscrolldown(term
.c
.y
, n
);
1303 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1304 tscrollup(term
.c
.y
, n
);
1308 tdefcolor(int *attr
, int *npar
, int l
)
1313 switch (attr
[*npar
+ 1]) {
1314 case 2: /* direct color in RGB space */
1315 if (*npar
+ 4 >= l
) {
1317 "erresc(38): Incorrect number of parameters (%d)\n",
1321 r
= attr
[*npar
+ 2];
1322 g
= attr
[*npar
+ 3];
1323 b
= attr
[*npar
+ 4];
1325 if (!BETWEEN(r
, 0, 255) || !BETWEEN(g
, 0, 255) || !BETWEEN(b
, 0, 255))
1326 fprintf(stderr
, "erresc: bad rgb color (%u,%u,%u)\n",
1329 idx
= TRUECOLOR(r
, g
, b
);
1331 case 5: /* indexed color */
1332 if (*npar
+ 2 >= l
) {
1334 "erresc(38): Incorrect number of parameters (%d)\n",
1339 if (!BETWEEN(attr
[*npar
], 0, 255))
1340 fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[*npar
]);
1344 case 0: /* implemented defined (only foreground) */
1345 case 1: /* transparent */
1346 case 3: /* direct color in CMY space */
1347 case 4: /* direct color in CMYK space */
1350 "erresc(38): gfx attr %d unknown\n", attr
[*npar
]);
1358 tsetattr(int *attr
, int l
)
1363 for (i
= 0; i
< l
; i
++) {
1366 term
.c
.attr
.mode
&= ~(
1375 term
.c
.attr
.fg
= defaultfg
;
1376 term
.c
.attr
.bg
= defaultbg
;
1379 term
.c
.attr
.mode
|= ATTR_BOLD
;
1382 term
.c
.attr
.mode
|= ATTR_FAINT
;
1385 term
.c
.attr
.mode
|= ATTR_ITALIC
;
1388 term
.c
.attr
.mode
|= ATTR_UNDERLINE
;
1390 case 5: /* slow blink */
1392 case 6: /* rapid blink */
1393 term
.c
.attr
.mode
|= ATTR_BLINK
;
1396 term
.c
.attr
.mode
|= ATTR_REVERSE
;
1399 term
.c
.attr
.mode
|= ATTR_INVISIBLE
;
1402 term
.c
.attr
.mode
|= ATTR_STRUCK
;
1405 term
.c
.attr
.mode
&= ~(ATTR_BOLD
| ATTR_FAINT
);
1408 term
.c
.attr
.mode
&= ~ATTR_ITALIC
;
1411 term
.c
.attr
.mode
&= ~ATTR_UNDERLINE
;
1414 term
.c
.attr
.mode
&= ~ATTR_BLINK
;
1417 term
.c
.attr
.mode
&= ~ATTR_REVERSE
;
1420 term
.c
.attr
.mode
&= ~ATTR_INVISIBLE
;
1423 term
.c
.attr
.mode
&= ~ATTR_STRUCK
;
1426 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1427 term
.c
.attr
.fg
= idx
;
1430 term
.c
.attr
.fg
= defaultfg
;
1433 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1434 term
.c
.attr
.bg
= idx
;
1437 term
.c
.attr
.bg
= defaultbg
;
1440 if (BETWEEN(attr
[i
], 30, 37)) {
1441 term
.c
.attr
.fg
= attr
[i
] - 30;
1442 } else if (BETWEEN(attr
[i
], 40, 47)) {
1443 term
.c
.attr
.bg
= attr
[i
] - 40;
1444 } else if (BETWEEN(attr
[i
], 90, 97)) {
1445 term
.c
.attr
.fg
= attr
[i
] - 90 + 8;
1446 } else if (BETWEEN(attr
[i
], 100, 107)) {
1447 term
.c
.attr
.bg
= attr
[i
] - 100 + 8;
1450 "erresc(default): gfx attr %d unknown\n",
1451 attr
[i
]), csidump();
1459 tsetscroll(int t
, int b
)
1463 LIMIT(t
, 0, term
.row
-1);
1464 LIMIT(b
, 0, term
.row
-1);
1475 tsetmode(int priv
, int set
, int *args
, int narg
)
1479 for (lim
= args
+ narg
; args
< lim
; ++args
) {
1482 case 1: /* DECCKM -- Cursor key */
1483 xsetmode(set
, MODE_APPCURSOR
);
1485 case 5: /* DECSCNM -- Reverse video */
1486 xsetmode(set
, MODE_REVERSE
);
1488 case 6: /* DECOM -- Origin */
1489 MODBIT(term
.c
.state
, set
, CURSOR_ORIGIN
);
1492 case 7: /* DECAWM -- Auto wrap */
1493 MODBIT(term
.mode
, set
, MODE_WRAP
);
1495 case 0: /* Error (IGNORED) */
1496 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1497 case 3: /* DECCOLM -- Column (IGNORED) */
1498 case 4: /* DECSCLM -- Scroll (IGNORED) */
1499 case 8: /* DECARM -- Auto repeat (IGNORED) */
1500 case 18: /* DECPFF -- Printer feed (IGNORED) */
1501 case 19: /* DECPEX -- Printer extent (IGNORED) */
1502 case 42: /* DECNRCM -- National characters (IGNORED) */
1503 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1505 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1506 xsetmode(!set
, MODE_HIDE
);
1508 case 9: /* X10 mouse compatibility mode */
1509 xsetpointermotion(0);
1510 xsetmode(0, MODE_MOUSE
);
1511 xsetmode(set
, MODE_MOUSEX10
);
1513 case 1000: /* 1000: report button press */
1514 xsetpointermotion(0);
1515 xsetmode(0, MODE_MOUSE
);
1516 xsetmode(set
, MODE_MOUSEBTN
);
1518 case 1002: /* 1002: report motion on button press */
1519 xsetpointermotion(0);
1520 xsetmode(0, MODE_MOUSE
);
1521 xsetmode(set
, MODE_MOUSEMOTION
);
1523 case 1003: /* 1003: enable all mouse motions */
1524 xsetpointermotion(set
);
1525 xsetmode(0, MODE_MOUSE
);
1526 xsetmode(set
, MODE_MOUSEMANY
);
1528 case 1004: /* 1004: send focus events to tty */
1529 xsetmode(set
, MODE_FOCUS
);
1531 case 1006: /* 1006: extended reporting mode */
1532 xsetmode(set
, MODE_MOUSESGR
);
1535 xsetmode(set
, MODE_8BIT
);
1537 case 1049: /* swap screen & set/restore cursor as xterm */
1538 if (!allowaltscreen
)
1540 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1542 case 47: /* swap screen */
1544 if (!allowaltscreen
)
1546 alt
= IS_SET(MODE_ALTSCREEN
);
1548 tclearregion(0, 0, term
.col
-1,
1551 if (set
^ alt
) /* set is always 1 or 0 */
1557 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1559 case 2004: /* 2004: bracketed paste mode */
1560 xsetmode(set
, MODE_BRCKTPASTE
);
1562 /* Not implemented mouse modes. See comments there. */
1563 case 1001: /* mouse highlight mode; can hang the
1564 terminal by design when implemented. */
1565 case 1005: /* UTF-8 mouse mode; will confuse
1566 applications not supporting UTF-8
1568 case 1015: /* urxvt mangled mouse mode; incompatible
1569 and can be mistaken for other control
1573 "erresc: unknown private set/reset mode %d\n",
1579 case 0: /* Error (IGNORED) */
1582 xsetmode(set
, MODE_KBDLOCK
);
1584 case 4: /* IRM -- Insertion-replacement */
1585 MODBIT(term
.mode
, set
, MODE_INSERT
);
1587 case 12: /* SRM -- Send/Receive */
1588 MODBIT(term
.mode
, !set
, MODE_ECHO
);
1590 case 20: /* LNM -- Linefeed/new line */
1591 MODBIT(term
.mode
, set
, MODE_CRLF
);
1595 "erresc: unknown set/reset mode %d\n",
1609 switch (csiescseq
.mode
[0]) {
1612 fprintf(stderr
, "erresc: unknown csi ");
1616 case '@': /* ICH -- Insert <n> blank char */
1617 DEFAULT(csiescseq
.arg
[0], 1);
1618 tinsertblank(csiescseq
.arg
[0]);
1620 case 'A': /* CUU -- Cursor <n> Up */
1621 DEFAULT(csiescseq
.arg
[0], 1);
1622 tmoveto(term
.c
.x
, term
.c
.y
-csiescseq
.arg
[0]);
1624 case 'B': /* CUD -- Cursor <n> Down */
1625 case 'e': /* VPR --Cursor <n> Down */
1626 DEFAULT(csiescseq
.arg
[0], 1);
1627 tmoveto(term
.c
.x
, term
.c
.y
+csiescseq
.arg
[0]);
1629 case 'i': /* MC -- Media Copy */
1630 switch (csiescseq
.arg
[0]) {
1635 tdumpline(term
.c
.y
);
1641 term
.mode
&= ~MODE_PRINT
;
1644 term
.mode
|= MODE_PRINT
;
1648 case 'c': /* DA -- Device Attributes */
1649 if (csiescseq
.arg
[0] == 0)
1650 ttywrite(vtiden
, strlen(vtiden
), 0);
1652 case 'C': /* CUF -- Cursor <n> Forward */
1653 case 'a': /* HPR -- Cursor <n> Forward */
1654 DEFAULT(csiescseq
.arg
[0], 1);
1655 tmoveto(term
.c
.x
+csiescseq
.arg
[0], term
.c
.y
);
1657 case 'D': /* CUB -- Cursor <n> Backward */
1658 DEFAULT(csiescseq
.arg
[0], 1);
1659 tmoveto(term
.c
.x
-csiescseq
.arg
[0], term
.c
.y
);
1661 case 'E': /* CNL -- Cursor <n> Down and first col */
1662 DEFAULT(csiescseq
.arg
[0], 1);
1663 tmoveto(0, term
.c
.y
+csiescseq
.arg
[0]);
1665 case 'F': /* CPL -- Cursor <n> Up and first col */
1666 DEFAULT(csiescseq
.arg
[0], 1);
1667 tmoveto(0, term
.c
.y
-csiescseq
.arg
[0]);
1669 case 'g': /* TBC -- Tabulation clear */
1670 switch (csiescseq
.arg
[0]) {
1671 case 0: /* clear current tab stop */
1672 term
.tabs
[term
.c
.x
] = 0;
1674 case 3: /* clear all the tabs */
1675 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1681 case 'G': /* CHA -- Move to <col> */
1683 DEFAULT(csiescseq
.arg
[0], 1);
1684 tmoveto(csiescseq
.arg
[0]-1, term
.c
.y
);
1686 case 'H': /* CUP -- Move to <row> <col> */
1688 DEFAULT(csiescseq
.arg
[0], 1);
1689 DEFAULT(csiescseq
.arg
[1], 1);
1690 tmoveato(csiescseq
.arg
[1]-1, csiescseq
.arg
[0]-1);
1692 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1693 DEFAULT(csiescseq
.arg
[0], 1);
1694 tputtab(csiescseq
.arg
[0]);
1696 case 'J': /* ED -- Clear screen */
1697 switch (csiescseq
.arg
[0]) {
1699 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1700 if (term
.c
.y
< term
.row
-1) {
1701 tclearregion(0, term
.c
.y
+1, term
.col
-1,
1707 tclearregion(0, 0, term
.col
-1, term
.c
.y
-1);
1708 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1711 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1717 case 'K': /* EL -- Clear line */
1718 switch (csiescseq
.arg
[0]) {
1720 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1,
1724 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1727 tclearregion(0, term
.c
.y
, term
.col
-1, term
.c
.y
);
1731 case 'S': /* SU -- Scroll <n> line up */
1732 DEFAULT(csiescseq
.arg
[0], 1);
1733 tscrollup(term
.top
, csiescseq
.arg
[0]);
1735 case 'T': /* SD -- Scroll <n> line down */
1736 DEFAULT(csiescseq
.arg
[0], 1);
1737 tscrolldown(term
.top
, csiescseq
.arg
[0]);
1739 case 'L': /* IL -- Insert <n> blank lines */
1740 DEFAULT(csiescseq
.arg
[0], 1);
1741 tinsertblankline(csiescseq
.arg
[0]);
1743 case 'l': /* RM -- Reset Mode */
1744 tsetmode(csiescseq
.priv
, 0, csiescseq
.arg
, csiescseq
.narg
);
1746 case 'M': /* DL -- Delete <n> lines */
1747 DEFAULT(csiescseq
.arg
[0], 1);
1748 tdeleteline(csiescseq
.arg
[0]);
1750 case 'X': /* ECH -- Erase <n> char */
1751 DEFAULT(csiescseq
.arg
[0], 1);
1752 tclearregion(term
.c
.x
, term
.c
.y
,
1753 term
.c
.x
+ csiescseq
.arg
[0] - 1, term
.c
.y
);
1755 case 'P': /* DCH -- Delete <n> char */
1756 DEFAULT(csiescseq
.arg
[0], 1);
1757 tdeletechar(csiescseq
.arg
[0]);
1759 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1760 DEFAULT(csiescseq
.arg
[0], 1);
1761 tputtab(-csiescseq
.arg
[0]);
1763 case 'd': /* VPA -- Move to <row> */
1764 DEFAULT(csiescseq
.arg
[0], 1);
1765 tmoveato(term
.c
.x
, csiescseq
.arg
[0]-1);
1767 case 'h': /* SM -- Set terminal mode */
1768 tsetmode(csiescseq
.priv
, 1, csiescseq
.arg
, csiescseq
.narg
);
1770 case 'm': /* SGR -- Terminal attribute (color) */
1771 tsetattr(csiescseq
.arg
, csiescseq
.narg
);
1773 case 'n': /* DSR – Device Status Report (cursor position) */
1774 if (csiescseq
.arg
[0] == 6) {
1775 len
= snprintf(buf
, sizeof(buf
),"\033[%i;%iR",
1776 term
.c
.y
+1, term
.c
.x
+1);
1777 ttywrite(buf
, len
, 0);
1780 case 'r': /* DECSTBM -- Set Scrolling Region */
1781 if (csiescseq
.priv
) {
1784 DEFAULT(csiescseq
.arg
[0], 1);
1785 DEFAULT(csiescseq
.arg
[1], term
.row
);
1786 tsetscroll(csiescseq
.arg
[0]-1, csiescseq
.arg
[1]-1);
1790 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1791 tcursor(CURSOR_SAVE
);
1793 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1794 tcursor(CURSOR_LOAD
);
1797 switch (csiescseq
.mode
[1]) {
1798 case 'q': /* DECSCUSR -- Set Cursor Style */
1799 if (xsetcursor(csiescseq
.arg
[0]))
1815 fprintf(stderr
, "ESC[");
1816 for (i
= 0; i
< csiescseq
.len
; i
++) {
1817 c
= csiescseq
.buf
[i
] & 0xff;
1820 } else if (c
== '\n') {
1821 fprintf(stderr
, "(\\n)");
1822 } else if (c
== '\r') {
1823 fprintf(stderr
, "(\\r)");
1824 } else if (c
== 0x1b) {
1825 fprintf(stderr
, "(\\e)");
1827 fprintf(stderr
, "(%02x)", c
);
1836 memset(&csiescseq
, 0, sizeof(csiescseq
));
1845 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
1847 par
= (narg
= strescseq
.narg
) ? atoi(strescseq
.args
[0]) : 0;
1849 switch (strescseq
.type
) {
1850 case ']': /* OSC -- Operating System Command */
1856 xsettitle(strescseq
.args
[1]);
1862 dec
= base64dec(strescseq
.args
[2]);
1867 fprintf(stderr
, "erresc: invalid base64\n");
1871 case 4: /* color set */
1874 p
= strescseq
.args
[2];
1876 case 104: /* color reset, here p = NULL */
1877 j
= (narg
> 1) ? atoi(strescseq
.args
[1]) : -1;
1878 if (xsetcolorname(j
, p
)) {
1879 fprintf(stderr
, "erresc: invalid color %s\n", p
);
1882 * TODO if defaultbg color is changed, borders
1890 case 'k': /* old title set compatibility */
1891 xsettitle(strescseq
.args
[0]);
1893 case 'P': /* DCS -- Device Control String */
1894 term
.mode
|= ESC_DCS
;
1895 case '_': /* APC -- Application Program Command */
1896 case '^': /* PM -- Privacy Message */
1900 fprintf(stderr
, "erresc: unknown str ");
1908 char *p
= strescseq
.buf
;
1911 strescseq
.buf
[strescseq
.len
] = '\0';
1916 while (strescseq
.narg
< STR_ARG_SIZ
) {
1917 strescseq
.args
[strescseq
.narg
++] = p
;
1918 while ((c
= *p
) != ';' && c
!= '\0')
1932 fprintf(stderr
, "ESC%c", strescseq
.type
);
1933 for (i
= 0; i
< strescseq
.len
; i
++) {
1934 c
= strescseq
.buf
[i
] & 0xff;
1938 } else if (isprint(c
)) {
1940 } else if (c
== '\n') {
1941 fprintf(stderr
, "(\\n)");
1942 } else if (c
== '\r') {
1943 fprintf(stderr
, "(\\r)");
1944 } else if (c
== 0x1b) {
1945 fprintf(stderr
, "(\\e)");
1947 fprintf(stderr
, "(%02x)", c
);
1950 fprintf(stderr
, "ESC\\\n");
1956 memset(&strescseq
, 0, sizeof(strescseq
));
1960 sendbreak(const Arg
*arg
)
1962 if (tcsendbreak(cmdfd
, 0))
1963 perror("Error sending break");
1967 tprinter(char *s
, size_t len
)
1969 if (iofd
!= -1 && xwrite(iofd
, s
, len
) < 0) {
1970 perror("Error writing to output file");
1977 iso14755(const Arg
*arg
)
1980 char *us
, *e
, codepoint
[9], uc
[UTF_SIZ
];
1981 unsigned long utf32
;
1983 if (!(p
= popen(ISO14755CMD
, "r")))
1986 us
= fgets(codepoint
, sizeof(codepoint
), p
);
1989 if (!us
|| *us
== '\0' || *us
== '-' || strlen(us
) > 7)
1991 if ((utf32
= strtoul(us
, &e
, 16)) == ULONG_MAX
||
1992 (*e
!= '\n' && *e
!= '\0'))
1995 ttywrite(uc
, utf8encode(utf32
, uc
), 1);
1999 toggleprinter(const Arg
*arg
)
2001 term
.mode
^= MODE_PRINT
;
2005 printscreen(const Arg
*arg
)
2011 printsel(const Arg
*arg
)
2021 if ((ptr
= getsel())) {
2022 tprinter(ptr
, strlen(ptr
));
2033 bp
= &term
.line
[n
][0];
2034 end
= &bp
[MIN(tlinelen(n
), term
.col
) - 1];
2035 if (bp
!= end
|| bp
->u
!= ' ') {
2036 for ( ;bp
<= end
; ++bp
)
2037 tprinter(buf
, utf8encode(bp
->u
, buf
));
2047 for (i
= 0; i
< term
.row
; ++i
)
2057 while (x
< term
.col
&& n
--)
2058 for (++x
; x
< term
.col
&& !term
.tabs
[x
]; ++x
)
2061 while (x
> 0 && n
++)
2062 for (--x
; x
> 0 && !term
.tabs
[x
]; --x
)
2065 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
2069 tdefutf8(char ascii
)
2072 term
.mode
|= MODE_UTF8
;
2073 else if (ascii
== '@')
2074 term
.mode
&= ~MODE_UTF8
;
2078 tdeftran(char ascii
)
2080 static char cs
[] = "0B";
2081 static int vcs
[] = {CS_GRAPHIC0
, CS_USA
};
2084 if ((p
= strchr(cs
, ascii
)) == NULL
) {
2085 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
2087 term
.trantbl
[term
.icharset
] = vcs
[p
- cs
];
2096 if (c
== '8') { /* DEC screen alignment test. */
2097 for (x
= 0; x
< term
.col
; ++x
) {
2098 for (y
= 0; y
< term
.row
; ++y
)
2099 tsetchar('E', &term
.c
.attr
, x
, y
);
2105 tstrsequence(uchar c
)
2110 case 0x90: /* DCS -- Device Control String */
2112 term
.esc
|= ESC_DCS
;
2114 case 0x9f: /* APC -- Application Program Command */
2117 case 0x9e: /* PM -- Privacy Message */
2120 case 0x9d: /* OSC -- Operating System Command */
2125 term
.esc
|= ESC_STR
;
2129 tcontrolcode(uchar ascii
)
2136 tmoveto(term
.c
.x
-1, term
.c
.y
);
2139 tmoveto(0, term
.c
.y
);
2144 /* go to first col if the mode is set */
2145 tnewline(IS_SET(MODE_CRLF
));
2147 case '\a': /* BEL */
2148 if (term
.esc
& ESC_STR_END
) {
2149 /* backwards compatibility to xterm */
2155 case '\033': /* ESC */
2157 term
.esc
&= ~(ESC_CSI
|ESC_ALTCHARSET
|ESC_TEST
);
2158 term
.esc
|= ESC_START
;
2160 case '\016': /* SO (LS1 -- Locking shift 1) */
2161 case '\017': /* SI (LS0 -- Locking shift 0) */
2162 term
.charset
= 1 - (ascii
- '\016');
2164 case '\032': /* SUB */
2165 tsetchar('?', &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2166 case '\030': /* CAN */
2169 case '\005': /* ENQ (IGNORED) */
2170 case '\000': /* NUL (IGNORED) */
2171 case '\021': /* XON (IGNORED) */
2172 case '\023': /* XOFF (IGNORED) */
2173 case 0177: /* DEL (IGNORED) */
2175 case 0x80: /* TODO: PAD */
2176 case 0x81: /* TODO: HOP */
2177 case 0x82: /* TODO: BPH */
2178 case 0x83: /* TODO: NBH */
2179 case 0x84: /* TODO: IND */
2181 case 0x85: /* NEL -- Next line */
2182 tnewline(1); /* always go to first col */
2184 case 0x86: /* TODO: SSA */
2185 case 0x87: /* TODO: ESA */
2187 case 0x88: /* HTS -- Horizontal tab stop */
2188 term
.tabs
[term
.c
.x
] = 1;
2190 case 0x89: /* TODO: HTJ */
2191 case 0x8a: /* TODO: VTS */
2192 case 0x8b: /* TODO: PLD */
2193 case 0x8c: /* TODO: PLU */
2194 case 0x8d: /* TODO: RI */
2195 case 0x8e: /* TODO: SS2 */
2196 case 0x8f: /* TODO: SS3 */
2197 case 0x91: /* TODO: PU1 */
2198 case 0x92: /* TODO: PU2 */
2199 case 0x93: /* TODO: STS */
2200 case 0x94: /* TODO: CCH */
2201 case 0x95: /* TODO: MW */
2202 case 0x96: /* TODO: SPA */
2203 case 0x97: /* TODO: EPA */
2204 case 0x98: /* TODO: SOS */
2205 case 0x99: /* TODO: SGCI */
2207 case 0x9a: /* DECID -- Identify Terminal */
2208 ttywrite(vtiden
, strlen(vtiden
), 0);
2210 case 0x9b: /* TODO: CSI */
2211 case 0x9c: /* TODO: ST */
2213 case 0x90: /* DCS -- Device Control String */
2214 case 0x9d: /* OSC -- Operating System Command */
2215 case 0x9e: /* PM -- Privacy Message */
2216 case 0x9f: /* APC -- Application Program Command */
2217 tstrsequence(ascii
);
2220 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2221 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
2225 * returns 1 when the sequence is finished and it hasn't to read
2226 * more characters for this sequence, otherwise 0
2229 eschandle(uchar ascii
)
2233 term
.esc
|= ESC_CSI
;
2236 term
.esc
|= ESC_TEST
;
2239 term
.esc
|= ESC_UTF8
;
2241 case 'P': /* DCS -- Device Control String */
2242 case '_': /* APC -- Application Program Command */
2243 case '^': /* PM -- Privacy Message */
2244 case ']': /* OSC -- Operating System Command */
2245 case 'k': /* old title set compatibility */
2246 tstrsequence(ascii
);
2248 case 'n': /* LS2 -- Locking shift 2 */
2249 case 'o': /* LS3 -- Locking shift 3 */
2250 term
.charset
= 2 + (ascii
- 'n');
2252 case '(': /* GZD4 -- set primary charset G0 */
2253 case ')': /* G1D4 -- set secondary charset G1 */
2254 case '*': /* G2D4 -- set tertiary charset G2 */
2255 case '+': /* G3D4 -- set quaternary charset G3 */
2256 term
.icharset
= ascii
- '(';
2257 term
.esc
|= ESC_ALTCHARSET
;
2259 case 'D': /* IND -- Linefeed */
2260 if (term
.c
.y
== term
.bot
) {
2261 tscrollup(term
.top
, 1);
2263 tmoveto(term
.c
.x
, term
.c
.y
+1);
2266 case 'E': /* NEL -- Next line */
2267 tnewline(1); /* always go to first col */
2269 case 'H': /* HTS -- Horizontal tab stop */
2270 term
.tabs
[term
.c
.x
] = 1;
2272 case 'M': /* RI -- Reverse index */
2273 if (term
.c
.y
== term
.top
) {
2274 tscrolldown(term
.top
, 1);
2276 tmoveto(term
.c
.x
, term
.c
.y
-1);
2279 case 'Z': /* DECID -- Identify Terminal */
2280 ttywrite(vtiden
, strlen(vtiden
), 0);
2282 case 'c': /* RIS -- Reset to inital state */
2287 case '=': /* DECPAM -- Application keypad */
2288 xsetmode(1, MODE_APPKEYPAD
);
2290 case '>': /* DECPNM -- Normal keypad */
2291 xsetmode(0, MODE_APPKEYPAD
);
2293 case '7': /* DECSC -- Save Cursor */
2294 tcursor(CURSOR_SAVE
);
2296 case '8': /* DECRC -- Restore Cursor */
2297 tcursor(CURSOR_LOAD
);
2299 case '\\': /* ST -- String Terminator */
2300 if (term
.esc
& ESC_STR_END
)
2304 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2305 (uchar
) ascii
, isprint(ascii
)? ascii
:'.');
2319 control
= ISCONTROL(u
);
2320 if (!IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2324 len
= utf8encode(u
, c
);
2325 if (!control
&& (width
= wcwidth(u
)) == -1) {
2326 memcpy(c
, "\357\277\275", 4); /* UTF_INVALID */
2331 if (IS_SET(MODE_PRINT
))
2335 * STR sequence must be checked before anything else
2336 * because it uses all following characters until it
2337 * receives a ESC, a SUB, a ST or any other C1 control
2340 if (term
.esc
& ESC_STR
) {
2341 if (u
== '\a' || u
== 030 || u
== 032 || u
== 033 ||
2343 term
.esc
&= ~(ESC_START
|ESC_STR
|ESC_DCS
);
2344 if (IS_SET(MODE_SIXEL
)) {
2345 /* TODO: render sixel */;
2346 term
.mode
&= ~MODE_SIXEL
;
2349 term
.esc
|= ESC_STR_END
;
2350 goto check_control_code
;
2354 if (IS_SET(MODE_SIXEL
)) {
2355 /* TODO: implement sixel mode */
2358 if (term
.esc
&ESC_DCS
&& strescseq
.len
== 0 && u
== 'q')
2359 term
.mode
|= MODE_SIXEL
;
2361 if (strescseq
.len
+len
>= sizeof(strescseq
.buf
)-1) {
2363 * Here is a bug in terminals. If the user never sends
2364 * some code to stop the str or esc command, then st
2365 * will stop responding. But this is better than
2366 * silently failing with unknown characters. At least
2367 * then users will report back.
2369 * In the case users ever get fixed, here is the code:
2378 memmove(&strescseq
.buf
[strescseq
.len
], c
, len
);
2379 strescseq
.len
+= len
;
2385 * Actions of control codes must be performed as soon they arrive
2386 * because they can be embedded inside a control sequence, and
2387 * they must not cause conflicts with sequences.
2392 * control codes are not shown ever
2395 } else if (term
.esc
& ESC_START
) {
2396 if (term
.esc
& ESC_CSI
) {
2397 csiescseq
.buf
[csiescseq
.len
++] = u
;
2398 if (BETWEEN(u
, 0x40, 0x7E)
2399 || csiescseq
.len
>= \
2400 sizeof(csiescseq
.buf
)-1) {
2406 } else if (term
.esc
& ESC_UTF8
) {
2408 } else if (term
.esc
& ESC_ALTCHARSET
) {
2410 } else if (term
.esc
& ESC_TEST
) {
2415 /* sequence already finished */
2419 * All characters which form part of a sequence are not
2424 if (sel
.ob
.x
!= -1 && BETWEEN(term
.c
.y
, sel
.ob
.y
, sel
.oe
.y
))
2427 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2428 if (IS_SET(MODE_WRAP
) && (term
.c
.state
& CURSOR_WRAPNEXT
)) {
2429 gp
->mode
|= ATTR_WRAP
;
2431 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2434 if (IS_SET(MODE_INSERT
) && term
.c
.x
+width
< term
.col
)
2435 memmove(gp
+width
, gp
, (term
.col
- term
.c
.x
- width
) * sizeof(Glyph
));
2437 if (term
.c
.x
+width
> term
.col
) {
2439 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2442 tsetchar(u
, &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2445 gp
->mode
|= ATTR_WIDE
;
2446 if (term
.c
.x
+1 < term
.col
) {
2448 gp
[1].mode
= ATTR_WDUMMY
;
2451 if (term
.c
.x
+width
< term
.col
) {
2452 tmoveto(term
.c
.x
+width
, term
.c
.y
);
2454 term
.c
.state
|= CURSOR_WRAPNEXT
;
2459 twrite(const char *buf
, int buflen
, int show_ctrl
)
2465 for (n
= 0; n
< buflen
; n
+= charsize
) {
2466 if (IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2467 /* process a complete utf8 char */
2468 charsize
= utf8decode(buf
+ n
, &u
, buflen
- n
);
2475 if (show_ctrl
&& ISCONTROL(u
)) {
2480 } else if (u
!= '\n' && u
!= '\r' && u
!= '\t') {
2491 tresize(int col
, int row
)
2494 int minrow
= MIN(row
, term
.row
);
2495 int mincol
= MIN(col
, term
.col
);
2499 if (col
< 1 || row
< 1) {
2501 "tresize: error resizing to %dx%d\n", col
, row
);
2506 * slide screen to keep cursor where we expect it -
2507 * tscrollup would work here, but we can optimize to
2508 * memmove because we're freeing the earlier lines
2510 for (i
= 0; i
<= term
.c
.y
- row
; i
++) {
2514 /* ensure that both src and dst are not NULL */
2516 memmove(term
.line
, term
.line
+ i
, row
* sizeof(Line
));
2517 memmove(term
.alt
, term
.alt
+ i
, row
* sizeof(Line
));
2519 for (i
+= row
; i
< term
.row
; i
++) {
2524 /* resize to new height */
2525 term
.line
= xrealloc(term
.line
, row
* sizeof(Line
));
2526 term
.alt
= xrealloc(term
.alt
, row
* sizeof(Line
));
2527 term
.dirty
= xrealloc(term
.dirty
, row
* sizeof(*term
.dirty
));
2528 term
.tabs
= xrealloc(term
.tabs
, col
* sizeof(*term
.tabs
));
2530 /* resize each row to new width, zero-pad if needed */
2531 for (i
= 0; i
< minrow
; i
++) {
2532 term
.line
[i
] = xrealloc(term
.line
[i
], col
* sizeof(Glyph
));
2533 term
.alt
[i
] = xrealloc(term
.alt
[i
], col
* sizeof(Glyph
));
2536 /* allocate any new rows */
2537 for (/* i = minrow */; i
< row
; i
++) {
2538 term
.line
[i
] = xmalloc(col
* sizeof(Glyph
));
2539 term
.alt
[i
] = xmalloc(col
* sizeof(Glyph
));
2541 if (col
> term
.col
) {
2542 bp
= term
.tabs
+ term
.col
;
2544 memset(bp
, 0, sizeof(*term
.tabs
) * (col
- term
.col
));
2545 while (--bp
> term
.tabs
&& !*bp
)
2547 for (bp
+= tabspaces
; bp
< term
.tabs
+ col
; bp
+= tabspaces
)
2550 /* update terminal size */
2553 /* reset scrolling region */
2554 tsetscroll(0, row
-1);
2555 /* make use of the LIMIT in tmoveto */
2556 tmoveto(term
.c
.x
, term
.c
.y
);
2557 /* Clearing both screens (it makes dirty all lines) */
2559 for (i
= 0; i
< 2; i
++) {
2560 if (mincol
< col
&& 0 < minrow
) {
2561 tclearregion(mincol
, 0, col
- 1, minrow
- 1);
2563 if (0 < col
&& minrow
< row
) {
2564 tclearregion(0, minrow
, col
- 1, row
- 1);
2567 tcursor(CURSOR_LOAD
);
2579 drawregion(int x1
, int y1
, int x2
, int y2
)
2582 for (y
= y1
; y
< y2
; y
++) {
2587 xdrawline(term
.line
[y
], x1
, y
, x2
);
2599 /* adjust cursor position */
2600 LIMIT(term
.ocx
, 0, term
.col
-1);
2601 LIMIT(term
.ocy
, 0, term
.row
-1);
2602 if (term
.line
[term
.ocy
][term
.ocx
].mode
& ATTR_WDUMMY
)
2604 if (term
.line
[term
.c
.y
][cx
].mode
& ATTR_WDUMMY
)
2607 drawregion(0, 0, term
.col
, term
.row
);
2608 xdrawcursor(cx
, term
.c
.y
, term
.line
[term
.c
.y
][cx
],
2609 term
.ocx
, term
.ocy
, term
.line
[term
.ocy
][term
.ocx
]);
2610 term
.ocx
= cx
, term
.ocy
= term
.c
.y
;