Xinqi Bao's Git
ea0726cd267532ef878655868c75dbfc4d920e59
1 /* See LICENSE for license details. */
14 #include <sys/ioctl.h>
15 #include <sys/select.h>
18 #include <sys/types.h>
31 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
33 #elif defined(__FreeBSD__) || defined(__DragonFly__)
38 #define UTF_INVALID 0xFFFD
39 #define ESC_BUF_SIZ (128*UTF_SIZ)
40 #define ESC_ARG_SIZ 16
41 #define STR_BUF_SIZ ESC_BUF_SIZ
42 #define STR_ARG_SIZ ESC_ARG_SIZ
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"
54 enum cursor_movement
{
78 ESC_STR
= 4, /* OSC, PM, APC */
80 ESC_STR_END
= 16, /* a final string was encountered */
81 ESC_TEST
= 32, /* Enter in test mode */
86 /* CSI Escape sequence structs */
87 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
89 char buf
[ESC_BUF_SIZ
]; /* raw string */
90 int len
; /* raw string length */
93 int narg
; /* nb of args */
97 /* STR Escape sequence structs */
98 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
100 char type
; /* ESC type ... */
101 char buf
[STR_BUF_SIZ
]; /* raw string */
102 int len
; /* raw string length */
103 char *args
[STR_ARG_SIZ
];
104 int narg
; /* nb of args */
108 static void execsh(char **);
109 static void stty(char **);
110 static void sigchld(int);
112 static void csidump(void);
113 static void csihandle(void);
114 static void csiparse(void);
115 static void csireset(void);
116 static int eschandle(uchar
);
117 static void strdump(void);
118 static void strhandle(void);
119 static void strparse(void);
120 static void strreset(void);
122 static void tprinter(char *, size_t);
123 static void tdumpsel(void);
124 static void tdumpline(int);
125 static void tdump(void);
126 static void tclearregion(int, int, int, int);
127 static void tcursor(int);
128 static void tdeletechar(int);
129 static void tdeleteline(int);
130 static void tinsertblank(int);
131 static void tinsertblankline(int);
132 static int tlinelen(int);
133 static void tmoveto(int, int);
134 static void tmoveato(int, int);
135 static void tnewline(int);
136 static void tputtab(int);
137 static void tputc(Rune
);
138 static void treset(void);
139 static void tscrollup(int, int);
140 static void tscrolldown(int, int);
141 static void tsetattr(int *, int);
142 static void tsetchar(Rune
, Glyph
*, int, int);
143 static void tsetscroll(int, int);
144 static void tswapscreen(void);
145 static void tsetmode(int, int, int *, int);
146 static int twrite(const char *, int, int);
147 static void tfulldirt(void);
148 static void tcontrolcode(uchar
);
149 static void tdectest(char );
150 static void tdefutf8(char);
151 static int32_t tdefcolor(int *, int *, int);
152 static void tdeftran(char);
153 static void tstrsequence(uchar
);
155 static void selscroll(int, int);
156 static void selsnap(int *, int *, int);
158 static Rune
utf8decodebyte(char, size_t *);
159 static char utf8encodebyte(Rune
, size_t);
160 static char *utf8strchr(char *s
, Rune u
);
161 static size_t utf8validate(Rune
*, size_t);
163 static char *base64dec(const char *);
165 static ssize_t
xwrite(int, const char *, size_t);
172 int oldbutton
= 3; /* button event on startup: 3 = release */
174 static CSIEscape csiescseq
;
175 static STREscape strescseq
;
178 static uchar utfbyte
[UTF_SIZ
+ 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
179 static uchar utfmask
[UTF_SIZ
+ 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
180 static Rune utfmin
[UTF_SIZ
+ 1] = { 0, 0, 0x80, 0x800, 0x10000};
181 static Rune utfmax
[UTF_SIZ
+ 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
184 xwrite(int fd
, const char *s
, size_t len
)
190 r
= write(fd
, s
, len
);
203 void *p
= malloc(len
);
206 die("Out of memory\n");
212 xrealloc(void *p
, size_t len
)
214 if ((p
= realloc(p
, len
)) == NULL
)
215 die("Out of memory\n");
223 if ((s
= strdup(s
)) == NULL
)
224 die("Out of memory\n");
230 utf8decode(const char *c
, Rune
*u
, size_t clen
)
232 size_t i
, j
, len
, type
;
238 udecoded
= utf8decodebyte(c
[0], &len
);
239 if (!BETWEEN(len
, 1, UTF_SIZ
))
241 for (i
= 1, j
= 1; i
< clen
&& j
< len
; ++i
, ++j
) {
242 udecoded
= (udecoded
<< 6) | utf8decodebyte(c
[i
], &type
);
249 utf8validate(u
, len
);
255 utf8decodebyte(char c
, size_t *i
)
257 for (*i
= 0; *i
< LEN(utfmask
); ++(*i
))
258 if (((uchar
)c
& utfmask
[*i
]) == utfbyte
[*i
])
259 return (uchar
)c
& ~utfmask
[*i
];
265 utf8encode(Rune u
, char *c
)
269 len
= utf8validate(&u
, 0);
273 for (i
= len
- 1; i
!= 0; --i
) {
274 c
[i
] = utf8encodebyte(u
, 0);
277 c
[0] = utf8encodebyte(u
, len
);
283 utf8encodebyte(Rune u
, size_t i
)
285 return utfbyte
[i
] | (u
& ~utfmask
[i
]);
289 utf8strchr(char *s
, Rune u
)
295 for (i
= 0, j
= 0; i
< len
; i
+= j
) {
296 if (!(j
= utf8decode(&s
[i
], &r
, len
- i
)))
306 utf8validate(Rune
*u
, size_t i
)
308 if (!BETWEEN(*u
, utfmin
[i
], utfmax
[i
]) || BETWEEN(*u
, 0xD800, 0xDFFF))
310 for (i
= 1; *u
> utfmax
[i
]; ++i
)
316 static const char base64_digits
[] = {
317 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
318 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0,
319 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
320 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
321 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
322 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0,
323 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
324 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
325 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
326 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
327 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
328 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
332 base64dec_getc(const char **src
)
334 while (**src
&& !isprint(**src
)) (*src
)++;
339 base64dec(const char *src
)
341 size_t in_len
= strlen(src
);
345 in_len
+= 4 - (in_len
% 4);
346 result
= dst
= xmalloc(in_len
/ 4 * 3 + 1);
348 int a
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
349 int b
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
350 int c
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
351 int d
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
353 *dst
++ = (a
<< 2) | ((b
& 0x30) >> 4);
356 *dst
++ = ((b
& 0x0f) << 4) | ((c
& 0x3c) >> 2);
359 *dst
++ = ((c
& 0x03) << 6) | d
;
378 if (term
.line
[y
][i
- 1].mode
& ATTR_WRAP
)
381 while (i
> 0 && term
.line
[y
][i
- 1].u
== ' ')
392 if (sel
.type
== SEL_REGULAR
&& sel
.ob
.y
!= sel
.oe
.y
) {
393 sel
.nb
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.ob
.x
: sel
.oe
.x
;
394 sel
.ne
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.oe
.x
: sel
.ob
.x
;
396 sel
.nb
.x
= MIN(sel
.ob
.x
, sel
.oe
.x
);
397 sel
.ne
.x
= MAX(sel
.ob
.x
, sel
.oe
.x
);
399 sel
.nb
.y
= MIN(sel
.ob
.y
, sel
.oe
.y
);
400 sel
.ne
.y
= MAX(sel
.ob
.y
, sel
.oe
.y
);
402 selsnap(&sel
.nb
.x
, &sel
.nb
.y
, -1);
403 selsnap(&sel
.ne
.x
, &sel
.ne
.y
, +1);
405 /* expand selection over line breaks */
406 if (sel
.type
== SEL_RECTANGULAR
)
408 i
= tlinelen(sel
.nb
.y
);
411 if (tlinelen(sel
.ne
.y
) <= sel
.ne
.x
)
412 sel
.ne
.x
= term
.col
- 1;
416 selected(int x
, int y
)
418 if (sel
.mode
== SEL_EMPTY
|| sel
.ob
.x
== -1 ||
419 sel
.alt
!= IS_SET(MODE_ALTSCREEN
))
422 if (sel
.type
== SEL_RECTANGULAR
)
423 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
424 && BETWEEN(x
, sel
.nb
.x
, sel
.ne
.x
);
426 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
427 && (y
!= sel
.nb
.y
|| x
>= sel
.nb
.x
)
428 && (y
!= sel
.ne
.y
|| x
<= sel
.ne
.x
);
432 selsnap(int *x
, int *y
, int direction
)
434 int newx
, newy
, xt
, yt
;
435 int delim
, prevdelim
;
441 * Snap around if the word wraps around at the end or
442 * beginning of a line.
444 prevgp
= &term
.line
[*y
][*x
];
445 prevdelim
= ISDELIM(prevgp
->u
);
447 newx
= *x
+ direction
;
449 if (!BETWEEN(newx
, 0, term
.col
- 1)) {
451 newx
= (newx
+ term
.col
) % term
.col
;
452 if (!BETWEEN(newy
, 0, term
.row
- 1))
458 yt
= newy
, xt
= newx
;
459 if (!(term
.line
[yt
][xt
].mode
& ATTR_WRAP
))
463 if (newx
>= tlinelen(newy
))
466 gp
= &term
.line
[newy
][newx
];
467 delim
= ISDELIM(gp
->u
);
468 if (!(gp
->mode
& ATTR_WDUMMY
) && (delim
!= prevdelim
469 || (delim
&& gp
->u
!= prevgp
->u
)))
480 * Snap around if the the previous line or the current one
481 * has set ATTR_WRAP at its end. Then the whole next or
482 * previous line will be selected.
484 *x
= (direction
< 0) ? 0 : term
.col
- 1;
486 for (; *y
> 0; *y
+= direction
) {
487 if (!(term
.line
[*y
-1][term
.col
-1].mode
492 } else if (direction
> 0) {
493 for (; *y
< term
.row
-1; *y
+= direction
) {
494 if (!(term
.line
[*y
][term
.col
-1].mode
508 int y
, bufsize
, lastx
, linelen
;
514 bufsize
= (term
.col
+1) * (sel
.ne
.y
-sel
.nb
.y
+1) * UTF_SIZ
;
515 ptr
= str
= xmalloc(bufsize
);
517 /* append every set & selected glyph to the selection */
518 for (y
= sel
.nb
.y
; y
<= sel
.ne
.y
; y
++) {
519 if ((linelen
= tlinelen(y
)) == 0) {
524 if (sel
.type
== SEL_RECTANGULAR
) {
525 gp
= &term
.line
[y
][sel
.nb
.x
];
528 gp
= &term
.line
[y
][sel
.nb
.y
== y
? sel
.nb
.x
: 0];
529 lastx
= (sel
.ne
.y
== y
) ? sel
.ne
.x
: term
.col
-1;
531 last
= &term
.line
[y
][MIN(lastx
, linelen
-1)];
532 while (last
>= gp
&& last
->u
== ' ')
535 for ( ; gp
<= last
; ++gp
) {
536 if (gp
->mode
& ATTR_WDUMMY
)
539 ptr
+= utf8encode(gp
->u
, ptr
);
543 * Copy and pasting of line endings is inconsistent
544 * in the inconsistent terminal and GUI world.
545 * The best solution seems like to produce '\n' when
546 * something is copied from st and convert '\n' to
547 * '\r', when something to be pasted is received by
549 * FIXME: Fix the computer world.
551 if ((y
< sel
.ne
.y
|| lastx
>= linelen
) && !(last
->mode
& ATTR_WRAP
))
565 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
569 die(const char *errstr
, ...)
573 va_start(ap
, errstr
);
574 vfprintf(stderr
, errstr
, ap
);
583 const struct passwd
*pw
;
586 if ((pw
= getpwuid(getuid())) == NULL
) {
588 die("getpwuid:%s\n", strerror(errno
));
590 die("who are you?\n");
593 if ((sh
= getenv("SHELL")) == NULL
)
594 sh
= (pw
->pw_shell
[0]) ? pw
->pw_shell
: shell
;
602 DEFAULT(args
, ((char *[]) {prog
, NULL
}));
607 setenv("LOGNAME", pw
->pw_name
, 1);
608 setenv("USER", pw
->pw_name
, 1);
609 setenv("SHELL", sh
, 1);
610 setenv("HOME", pw
->pw_dir
, 1);
611 setenv("TERM", termname
, 1);
613 signal(SIGCHLD
, SIG_DFL
);
614 signal(SIGHUP
, SIG_DFL
);
615 signal(SIGINT
, SIG_DFL
);
616 signal(SIGQUIT
, SIG_DFL
);
617 signal(SIGTERM
, SIG_DFL
);
618 signal(SIGALRM
, SIG_DFL
);
630 if ((p
= waitpid(pid
, &stat
, WNOHANG
)) < 0)
631 die("Waiting for pid %hd failed: %s\n", pid
, strerror(errno
));
636 if (!WIFEXITED(stat
) || WEXITSTATUS(stat
))
637 die("child finished with error '%d'\n", stat
);
645 char cmd
[_POSIX_ARG_MAX
], **p
, *q
, *s
;
648 if ((n
= strlen(stty_args
)) > sizeof(cmd
)-1)
649 die("incorrect stty parameters\n");
650 memcpy(cmd
, stty_args
, n
);
652 siz
= sizeof(cmd
) - n
;
653 for (p
= args
; p
&& (s
= *p
); ++p
) {
654 if ((n
= strlen(s
)) > siz
-1)
655 die("stty parameter length too long\n");
662 if (system(cmd
) != 0)
663 perror("Couldn't call stty");
667 ttynew(char *line
, char *out
, char **args
)
672 term
.mode
|= MODE_PRINT
;
673 iofd
= (!strcmp(out
, "-")) ?
674 1 : open(out
, O_WRONLY
| O_CREAT
, 0666);
676 fprintf(stderr
, "Error opening %s:%s\n",
677 out
, strerror(errno
));
682 if ((cmdfd
= open(line
, O_RDWR
)) < 0)
683 die("open line failed: %s\n", strerror(errno
));
689 /* seems to work fine on linux, openbsd and freebsd */
690 if (openpty(&m
, &s
, NULL
, NULL
, NULL
) < 0)
691 die("openpty failed: %s\n", strerror(errno
));
693 switch (pid
= fork()) {
695 die("fork failed\n");
699 setsid(); /* create a new process group */
703 if (ioctl(s
, TIOCSCTTY
, NULL
) < 0)
704 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno
));
712 signal(SIGCHLD
, sigchld
);
720 static char buf
[BUFSIZ
];
721 static int buflen
= 0;
725 /* append read bytes to unprocessed bytes */
726 if ((ret
= read(cmdfd
, buf
+buflen
, LEN(buf
)-buflen
)) < 0)
727 die("Couldn't read from shell: %s\n", strerror(errno
));
730 written
= twrite(buf
, buflen
, 0);
732 /* keep any uncomplete utf8 char for the next call */
734 memmove(buf
, buf
+ written
, buflen
);
740 ttywrite(const char *s
, size_t n
)
747 * Remember that we are using a pty, which might be a modem line.
748 * Writing too much will clog the line. That's why we are doing this
750 * FIXME: Migrate the world to Plan 9.
758 /* Check if we can write. */
759 if (pselect(cmdfd
+1, &rfd
, &wfd
, NULL
, NULL
, NULL
) < 0) {
762 die("select failed: %s\n", strerror(errno
));
764 if (FD_ISSET(cmdfd
, &wfd
)) {
766 * Only write the bytes written by ttywrite() or the
767 * default of 256. This seems to be a reasonable value
768 * for a serial line. Bigger values might clog the I/O.
770 if ((r
= write(cmdfd
, s
, (n
< lim
)? n
: lim
)) < 0)
774 * We weren't able to write out everything.
775 * This means the buffer is getting full
783 /* All bytes have been written. */
787 if (FD_ISSET(cmdfd
, &rfd
))
793 die("write error on tty: %s\n", strerror(errno
));
797 ttysend(char *s
, size_t n
)
800 if (IS_SET(MODE_ECHO
))
805 ttyresize(int tw
, int th
)
813 if (ioctl(cmdfd
, TIOCSWINSZ
, &w
) < 0)
814 fprintf(stderr
, "Couldn't set window size: %s\n", strerror(errno
));
822 for (i
= 0; i
< term
.row
-1; i
++) {
823 for (j
= 0; j
< term
.col
-1; j
++) {
824 if (term
.line
[i
][j
].mode
& attr
)
833 tsetdirt(int top
, int bot
)
837 LIMIT(top
, 0, term
.row
-1);
838 LIMIT(bot
, 0, term
.row
-1);
840 for (i
= top
; i
<= bot
; i
++)
845 tsetdirtattr(int attr
)
849 for (i
= 0; i
< term
.row
-1; i
++) {
850 for (j
= 0; j
< term
.col
-1; j
++) {
851 if (term
.line
[i
][j
].mode
& attr
) {
862 tsetdirt(0, term
.row
-1);
869 int alt
= IS_SET(MODE_ALTSCREEN
);
871 if (mode
== CURSOR_SAVE
) {
873 } else if (mode
== CURSOR_LOAD
) {
875 tmoveto(c
[alt
].x
, c
[alt
].y
);
888 }, .x
= 0, .y
= 0, .state
= CURSOR_DEFAULT
};
890 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
891 for (i
= tabspaces
; i
< term
.col
; i
+= tabspaces
)
894 term
.bot
= term
.row
- 1;
895 term
.mode
= MODE_WRAP
|MODE_UTF8
;
896 memset(term
.trantbl
, CS_USA
, sizeof(term
.trantbl
));
899 for (i
= 0; i
< 2; i
++) {
901 tcursor(CURSOR_SAVE
);
902 tclearregion(0, 0, term
.col
-1, term
.row
-1);
908 tnew(int col
, int row
)
910 term
= (Term
){ .c
= { .attr
= { .fg
= defaultfg
, .bg
= defaultbg
} } };
920 Line
*tmp
= term
.line
;
922 term
.line
= term
.alt
;
924 term
.mode
^= MODE_ALTSCREEN
;
929 tscrolldown(int orig
, int n
)
934 LIMIT(n
, 0, term
.bot
-orig
+1);
936 tsetdirt(orig
, term
.bot
-n
);
937 tclearregion(0, term
.bot
-n
+1, term
.col
-1, term
.bot
);
939 for (i
= term
.bot
; i
>= orig
+n
; i
--) {
941 term
.line
[i
] = term
.line
[i
-n
];
942 term
.line
[i
-n
] = temp
;
949 tscrollup(int orig
, int n
)
954 LIMIT(n
, 0, term
.bot
-orig
+1);
956 tclearregion(0, orig
, term
.col
-1, orig
+n
-1);
957 tsetdirt(orig
+n
, term
.bot
);
959 for (i
= orig
; i
<= term
.bot
-n
; i
++) {
961 term
.line
[i
] = term
.line
[i
+n
];
962 term
.line
[i
+n
] = temp
;
969 selscroll(int orig
, int n
)
974 if (BETWEEN(sel
.ob
.y
, orig
, term
.bot
) || BETWEEN(sel
.oe
.y
, orig
, term
.bot
)) {
975 if ((sel
.ob
.y
+= n
) > term
.bot
|| (sel
.oe
.y
+= n
) < term
.top
) {
979 if (sel
.type
== SEL_RECTANGULAR
) {
980 if (sel
.ob
.y
< term
.top
)
982 if (sel
.oe
.y
> term
.bot
)
985 if (sel
.ob
.y
< term
.top
) {
989 if (sel
.oe
.y
> term
.bot
) {
999 tnewline(int first_col
)
1003 if (y
== term
.bot
) {
1004 tscrollup(term
.top
, 1);
1008 tmoveto(first_col
? 0 : term
.c
.x
, y
);
1014 char *p
= csiescseq
.buf
, *np
;
1023 csiescseq
.buf
[csiescseq
.len
] = '\0';
1024 while (p
< csiescseq
.buf
+csiescseq
.len
) {
1026 v
= strtol(p
, &np
, 10);
1029 if (v
== LONG_MAX
|| v
== LONG_MIN
)
1031 csiescseq
.arg
[csiescseq
.narg
++] = v
;
1033 if (*p
!= ';' || csiescseq
.narg
== ESC_ARG_SIZ
)
1037 csiescseq
.mode
[0] = *p
++;
1038 csiescseq
.mode
[1] = (p
< csiescseq
.buf
+csiescseq
.len
) ? *p
: '\0';
1041 /* for absolute user moves, when decom is set */
1043 tmoveato(int x
, int y
)
1045 tmoveto(x
, y
+ ((term
.c
.state
& CURSOR_ORIGIN
) ? term
.top
: 0));
1049 tmoveto(int x
, int y
)
1053 if (term
.c
.state
& CURSOR_ORIGIN
) {
1058 maxy
= term
.row
- 1;
1060 term
.c
.state
&= ~CURSOR_WRAPNEXT
;
1061 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1062 term
.c
.y
= LIMIT(y
, miny
, maxy
);
1066 tsetchar(Rune u
, Glyph
*attr
, int x
, int y
)
1068 static char *vt100_0
[62] = { /* 0x41 - 0x7e */
1069 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1070 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1071 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1072 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1073 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1074 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1075 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1076 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1080 * The table is proudly stolen from rxvt.
1082 if (term
.trantbl
[term
.charset
] == CS_GRAPHIC0
&&
1083 BETWEEN(u
, 0x41, 0x7e) && vt100_0
[u
- 0x41])
1084 utf8decode(vt100_0
[u
- 0x41], &u
, UTF_SIZ
);
1086 if (term
.line
[y
][x
].mode
& ATTR_WIDE
) {
1087 if (x
+1 < term
.col
) {
1088 term
.line
[y
][x
+1].u
= ' ';
1089 term
.line
[y
][x
+1].mode
&= ~ATTR_WDUMMY
;
1091 } else if (term
.line
[y
][x
].mode
& ATTR_WDUMMY
) {
1092 term
.line
[y
][x
-1].u
= ' ';
1093 term
.line
[y
][x
-1].mode
&= ~ATTR_WIDE
;
1097 term
.line
[y
][x
] = *attr
;
1098 term
.line
[y
][x
].u
= u
;
1102 tclearregion(int x1
, int y1
, int x2
, int y2
)
1108 temp
= x1
, x1
= x2
, x2
= temp
;
1110 temp
= y1
, y1
= y2
, y2
= temp
;
1112 LIMIT(x1
, 0, term
.col
-1);
1113 LIMIT(x2
, 0, term
.col
-1);
1114 LIMIT(y1
, 0, term
.row
-1);
1115 LIMIT(y2
, 0, term
.row
-1);
1117 for (y
= y1
; y
<= y2
; y
++) {
1119 for (x
= x1
; x
<= x2
; x
++) {
1120 gp
= &term
.line
[y
][x
];
1123 gp
->fg
= term
.c
.attr
.fg
;
1124 gp
->bg
= term
.c
.attr
.bg
;
1137 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1141 size
= term
.col
- src
;
1142 line
= term
.line
[term
.c
.y
];
1144 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1145 tclearregion(term
.col
-n
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1154 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1158 size
= term
.col
- dst
;
1159 line
= term
.line
[term
.c
.y
];
1161 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1162 tclearregion(src
, term
.c
.y
, dst
- 1, term
.c
.y
);
1166 tinsertblankline(int n
)
1168 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1169 tscrolldown(term
.c
.y
, n
);
1175 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1176 tscrollup(term
.c
.y
, n
);
1180 tdefcolor(int *attr
, int *npar
, int l
)
1185 switch (attr
[*npar
+ 1]) {
1186 case 2: /* direct color in RGB space */
1187 if (*npar
+ 4 >= l
) {
1189 "erresc(38): Incorrect number of parameters (%d)\n",
1193 r
= attr
[*npar
+ 2];
1194 g
= attr
[*npar
+ 3];
1195 b
= attr
[*npar
+ 4];
1197 if (!BETWEEN(r
, 0, 255) || !BETWEEN(g
, 0, 255) || !BETWEEN(b
, 0, 255))
1198 fprintf(stderr
, "erresc: bad rgb color (%u,%u,%u)\n",
1201 idx
= TRUECOLOR(r
, g
, b
);
1203 case 5: /* indexed color */
1204 if (*npar
+ 2 >= l
) {
1206 "erresc(38): Incorrect number of parameters (%d)\n",
1211 if (!BETWEEN(attr
[*npar
], 0, 255))
1212 fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[*npar
]);
1216 case 0: /* implemented defined (only foreground) */
1217 case 1: /* transparent */
1218 case 3: /* direct color in CMY space */
1219 case 4: /* direct color in CMYK space */
1222 "erresc(38): gfx attr %d unknown\n", attr
[*npar
]);
1230 tsetattr(int *attr
, int l
)
1235 for (i
= 0; i
< l
; i
++) {
1238 term
.c
.attr
.mode
&= ~(
1247 term
.c
.attr
.fg
= defaultfg
;
1248 term
.c
.attr
.bg
= defaultbg
;
1251 term
.c
.attr
.mode
|= ATTR_BOLD
;
1254 term
.c
.attr
.mode
|= ATTR_FAINT
;
1257 term
.c
.attr
.mode
|= ATTR_ITALIC
;
1260 term
.c
.attr
.mode
|= ATTR_UNDERLINE
;
1262 case 5: /* slow blink */
1264 case 6: /* rapid blink */
1265 term
.c
.attr
.mode
|= ATTR_BLINK
;
1268 term
.c
.attr
.mode
|= ATTR_REVERSE
;
1271 term
.c
.attr
.mode
|= ATTR_INVISIBLE
;
1274 term
.c
.attr
.mode
|= ATTR_STRUCK
;
1277 term
.c
.attr
.mode
&= ~(ATTR_BOLD
| ATTR_FAINT
);
1280 term
.c
.attr
.mode
&= ~ATTR_ITALIC
;
1283 term
.c
.attr
.mode
&= ~ATTR_UNDERLINE
;
1286 term
.c
.attr
.mode
&= ~ATTR_BLINK
;
1289 term
.c
.attr
.mode
&= ~ATTR_REVERSE
;
1292 term
.c
.attr
.mode
&= ~ATTR_INVISIBLE
;
1295 term
.c
.attr
.mode
&= ~ATTR_STRUCK
;
1298 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1299 term
.c
.attr
.fg
= idx
;
1302 term
.c
.attr
.fg
= defaultfg
;
1305 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1306 term
.c
.attr
.bg
= idx
;
1309 term
.c
.attr
.bg
= defaultbg
;
1312 if (BETWEEN(attr
[i
], 30, 37)) {
1313 term
.c
.attr
.fg
= attr
[i
] - 30;
1314 } else if (BETWEEN(attr
[i
], 40, 47)) {
1315 term
.c
.attr
.bg
= attr
[i
] - 40;
1316 } else if (BETWEEN(attr
[i
], 90, 97)) {
1317 term
.c
.attr
.fg
= attr
[i
] - 90 + 8;
1318 } else if (BETWEEN(attr
[i
], 100, 107)) {
1319 term
.c
.attr
.bg
= attr
[i
] - 100 + 8;
1322 "erresc(default): gfx attr %d unknown\n",
1323 attr
[i
]), csidump();
1331 tsetscroll(int t
, int b
)
1335 LIMIT(t
, 0, term
.row
-1);
1336 LIMIT(b
, 0, term
.row
-1);
1347 tsetmode(int priv
, int set
, int *args
, int narg
)
1352 for (lim
= args
+ narg
; args
< lim
; ++args
) {
1355 case 1: /* DECCKM -- Cursor key */
1356 MODBIT(term
.mode
, set
, MODE_APPCURSOR
);
1358 case 5: /* DECSCNM -- Reverse video */
1360 MODBIT(term
.mode
, set
, MODE_REVERSE
);
1361 if (mode
!= term
.mode
)
1364 case 6: /* DECOM -- Origin */
1365 MODBIT(term
.c
.state
, set
, CURSOR_ORIGIN
);
1368 case 7: /* DECAWM -- Auto wrap */
1369 MODBIT(term
.mode
, set
, MODE_WRAP
);
1371 case 0: /* Error (IGNORED) */
1372 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1373 case 3: /* DECCOLM -- Column (IGNORED) */
1374 case 4: /* DECSCLM -- Scroll (IGNORED) */
1375 case 8: /* DECARM -- Auto repeat (IGNORED) */
1376 case 18: /* DECPFF -- Printer feed (IGNORED) */
1377 case 19: /* DECPEX -- Printer extent (IGNORED) */
1378 case 42: /* DECNRCM -- National characters (IGNORED) */
1379 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1381 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1382 MODBIT(term
.mode
, !set
, MODE_HIDE
);
1384 case 9: /* X10 mouse compatibility mode */
1385 xsetpointermotion(0);
1386 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1387 MODBIT(term
.mode
, set
, MODE_MOUSEX10
);
1389 case 1000: /* 1000: report button press */
1390 xsetpointermotion(0);
1391 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1392 MODBIT(term
.mode
, set
, MODE_MOUSEBTN
);
1394 case 1002: /* 1002: report motion on button press */
1395 xsetpointermotion(0);
1396 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1397 MODBIT(term
.mode
, set
, MODE_MOUSEMOTION
);
1399 case 1003: /* 1003: enable all mouse motions */
1400 xsetpointermotion(set
);
1401 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1402 MODBIT(term
.mode
, set
, MODE_MOUSEMANY
);
1404 case 1004: /* 1004: send focus events to tty */
1405 MODBIT(term
.mode
, set
, MODE_FOCUS
);
1407 case 1006: /* 1006: extended reporting mode */
1408 MODBIT(term
.mode
, set
, MODE_MOUSESGR
);
1411 MODBIT(term
.mode
, set
, MODE_8BIT
);
1413 case 1049: /* swap screen & set/restore cursor as xterm */
1414 if (!allowaltscreen
)
1416 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1418 case 47: /* swap screen */
1420 if (!allowaltscreen
)
1422 alt
= IS_SET(MODE_ALTSCREEN
);
1424 tclearregion(0, 0, term
.col
-1,
1427 if (set
^ alt
) /* set is always 1 or 0 */
1433 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1435 case 2004: /* 2004: bracketed paste mode */
1436 MODBIT(term
.mode
, set
, MODE_BRCKTPASTE
);
1438 /* Not implemented mouse modes. See comments there. */
1439 case 1001: /* mouse highlight mode; can hang the
1440 terminal by design when implemented. */
1441 case 1005: /* UTF-8 mouse mode; will confuse
1442 applications not supporting UTF-8
1444 case 1015: /* urxvt mangled mouse mode; incompatible
1445 and can be mistaken for other control
1449 "erresc: unknown private set/reset mode %d\n",
1455 case 0: /* Error (IGNORED) */
1457 case 2: /* KAM -- keyboard action */
1458 MODBIT(term
.mode
, set
, MODE_KBDLOCK
);
1460 case 4: /* IRM -- Insertion-replacement */
1461 MODBIT(term
.mode
, set
, MODE_INSERT
);
1463 case 12: /* SRM -- Send/Receive */
1464 MODBIT(term
.mode
, !set
, MODE_ECHO
);
1466 case 20: /* LNM -- Linefeed/new line */
1467 MODBIT(term
.mode
, set
, MODE_CRLF
);
1471 "erresc: unknown set/reset mode %d\n",
1485 switch (csiescseq
.mode
[0]) {
1488 fprintf(stderr
, "erresc: unknown csi ");
1492 case '@': /* ICH -- Insert <n> blank char */
1493 DEFAULT(csiescseq
.arg
[0], 1);
1494 tinsertblank(csiescseq
.arg
[0]);
1496 case 'A': /* CUU -- Cursor <n> Up */
1497 DEFAULT(csiescseq
.arg
[0], 1);
1498 tmoveto(term
.c
.x
, term
.c
.y
-csiescseq
.arg
[0]);
1500 case 'B': /* CUD -- Cursor <n> Down */
1501 case 'e': /* VPR --Cursor <n> Down */
1502 DEFAULT(csiescseq
.arg
[0], 1);
1503 tmoveto(term
.c
.x
, term
.c
.y
+csiescseq
.arg
[0]);
1505 case 'i': /* MC -- Media Copy */
1506 switch (csiescseq
.arg
[0]) {
1511 tdumpline(term
.c
.y
);
1517 term
.mode
&= ~MODE_PRINT
;
1520 term
.mode
|= MODE_PRINT
;
1524 case 'c': /* DA -- Device Attributes */
1525 if (csiescseq
.arg
[0] == 0)
1526 ttywrite(vtiden
, strlen(vtiden
));
1528 case 'C': /* CUF -- Cursor <n> Forward */
1529 case 'a': /* HPR -- Cursor <n> Forward */
1530 DEFAULT(csiescseq
.arg
[0], 1);
1531 tmoveto(term
.c
.x
+csiescseq
.arg
[0], term
.c
.y
);
1533 case 'D': /* CUB -- Cursor <n> Backward */
1534 DEFAULT(csiescseq
.arg
[0], 1);
1535 tmoveto(term
.c
.x
-csiescseq
.arg
[0], term
.c
.y
);
1537 case 'E': /* CNL -- Cursor <n> Down and first col */
1538 DEFAULT(csiescseq
.arg
[0], 1);
1539 tmoveto(0, term
.c
.y
+csiescseq
.arg
[0]);
1541 case 'F': /* CPL -- Cursor <n> Up and first col */
1542 DEFAULT(csiescseq
.arg
[0], 1);
1543 tmoveto(0, term
.c
.y
-csiescseq
.arg
[0]);
1545 case 'g': /* TBC -- Tabulation clear */
1546 switch (csiescseq
.arg
[0]) {
1547 case 0: /* clear current tab stop */
1548 term
.tabs
[term
.c
.x
] = 0;
1550 case 3: /* clear all the tabs */
1551 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1557 case 'G': /* CHA -- Move to <col> */
1559 DEFAULT(csiescseq
.arg
[0], 1);
1560 tmoveto(csiescseq
.arg
[0]-1, term
.c
.y
);
1562 case 'H': /* CUP -- Move to <row> <col> */
1564 DEFAULT(csiescseq
.arg
[0], 1);
1565 DEFAULT(csiescseq
.arg
[1], 1);
1566 tmoveato(csiescseq
.arg
[1]-1, csiescseq
.arg
[0]-1);
1568 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1569 DEFAULT(csiescseq
.arg
[0], 1);
1570 tputtab(csiescseq
.arg
[0]);
1572 case 'J': /* ED -- Clear screen */
1574 switch (csiescseq
.arg
[0]) {
1576 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1577 if (term
.c
.y
< term
.row
-1) {
1578 tclearregion(0, term
.c
.y
+1, term
.col
-1,
1584 tclearregion(0, 0, term
.col
-1, term
.c
.y
-1);
1585 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1588 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1594 case 'K': /* EL -- Clear line */
1595 switch (csiescseq
.arg
[0]) {
1597 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1,
1601 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1604 tclearregion(0, term
.c
.y
, term
.col
-1, term
.c
.y
);
1608 case 'S': /* SU -- Scroll <n> line up */
1609 DEFAULT(csiescseq
.arg
[0], 1);
1610 tscrollup(term
.top
, csiescseq
.arg
[0]);
1612 case 'T': /* SD -- Scroll <n> line down */
1613 DEFAULT(csiescseq
.arg
[0], 1);
1614 tscrolldown(term
.top
, csiescseq
.arg
[0]);
1616 case 'L': /* IL -- Insert <n> blank lines */
1617 DEFAULT(csiescseq
.arg
[0], 1);
1618 tinsertblankline(csiescseq
.arg
[0]);
1620 case 'l': /* RM -- Reset Mode */
1621 tsetmode(csiescseq
.priv
, 0, csiescseq
.arg
, csiescseq
.narg
);
1623 case 'M': /* DL -- Delete <n> lines */
1624 DEFAULT(csiescseq
.arg
[0], 1);
1625 tdeleteline(csiescseq
.arg
[0]);
1627 case 'X': /* ECH -- Erase <n> char */
1628 DEFAULT(csiescseq
.arg
[0], 1);
1629 tclearregion(term
.c
.x
, term
.c
.y
,
1630 term
.c
.x
+ csiescseq
.arg
[0] - 1, term
.c
.y
);
1632 case 'P': /* DCH -- Delete <n> char */
1633 DEFAULT(csiescseq
.arg
[0], 1);
1634 tdeletechar(csiescseq
.arg
[0]);
1636 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1637 DEFAULT(csiescseq
.arg
[0], 1);
1638 tputtab(-csiescseq
.arg
[0]);
1640 case 'd': /* VPA -- Move to <row> */
1641 DEFAULT(csiescseq
.arg
[0], 1);
1642 tmoveato(term
.c
.x
, csiescseq
.arg
[0]-1);
1644 case 'h': /* SM -- Set terminal mode */
1645 tsetmode(csiescseq
.priv
, 1, csiescseq
.arg
, csiescseq
.narg
);
1647 case 'm': /* SGR -- Terminal attribute (color) */
1648 tsetattr(csiescseq
.arg
, csiescseq
.narg
);
1650 case 'n': /* DSR – Device Status Report (cursor position) */
1651 if (csiescseq
.arg
[0] == 6) {
1652 len
= snprintf(buf
, sizeof(buf
),"\033[%i;%iR",
1653 term
.c
.y
+1, term
.c
.x
+1);
1657 case 'r': /* DECSTBM -- Set Scrolling Region */
1658 if (csiescseq
.priv
) {
1661 DEFAULT(csiescseq
.arg
[0], 1);
1662 DEFAULT(csiescseq
.arg
[1], term
.row
);
1663 tsetscroll(csiescseq
.arg
[0]-1, csiescseq
.arg
[1]-1);
1667 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1668 tcursor(CURSOR_SAVE
);
1670 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1671 tcursor(CURSOR_LOAD
);
1674 switch (csiescseq
.mode
[1]) {
1675 case 'q': /* DECSCUSR -- Set Cursor Style */
1676 if (xsetcursor(csiescseq
.arg
[0]))
1692 fprintf(stderr
, "ESC[");
1693 for (i
= 0; i
< csiescseq
.len
; i
++) {
1694 c
= csiescseq
.buf
[i
] & 0xff;
1697 } else if (c
== '\n') {
1698 fprintf(stderr
, "(\\n)");
1699 } else if (c
== '\r') {
1700 fprintf(stderr
, "(\\r)");
1701 } else if (c
== 0x1b) {
1702 fprintf(stderr
, "(\\e)");
1704 fprintf(stderr
, "(%02x)", c
);
1713 memset(&csiescseq
, 0, sizeof(csiescseq
));
1722 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
1724 par
= (narg
= strescseq
.narg
) ? atoi(strescseq
.args
[0]) : 0;
1726 switch (strescseq
.type
) {
1727 case ']': /* OSC -- Operating System Command */
1733 xsettitle(strescseq
.args
[1]);
1739 dec
= base64dec(strescseq
.args
[2]);
1744 fprintf(stderr
, "erresc: invalid base64\n");
1748 case 4: /* color set */
1751 p
= strescseq
.args
[2];
1753 case 104: /* color reset, here p = NULL */
1754 j
= (narg
> 1) ? atoi(strescseq
.args
[1]) : -1;
1755 if (xsetcolorname(j
, p
)) {
1756 fprintf(stderr
, "erresc: invalid color %s\n", p
);
1759 * TODO if defaultbg color is changed, borders
1767 case 'k': /* old title set compatibility */
1768 xsettitle(strescseq
.args
[0]);
1770 case 'P': /* DCS -- Device Control String */
1771 term
.mode
|= ESC_DCS
;
1772 case '_': /* APC -- Application Program Command */
1773 case '^': /* PM -- Privacy Message */
1777 fprintf(stderr
, "erresc: unknown str ");
1785 char *p
= strescseq
.buf
;
1788 strescseq
.buf
[strescseq
.len
] = '\0';
1793 while (strescseq
.narg
< STR_ARG_SIZ
) {
1794 strescseq
.args
[strescseq
.narg
++] = p
;
1795 while ((c
= *p
) != ';' && c
!= '\0')
1809 fprintf(stderr
, "ESC%c", strescseq
.type
);
1810 for (i
= 0; i
< strescseq
.len
; i
++) {
1811 c
= strescseq
.buf
[i
] & 0xff;
1815 } else if (isprint(c
)) {
1817 } else if (c
== '\n') {
1818 fprintf(stderr
, "(\\n)");
1819 } else if (c
== '\r') {
1820 fprintf(stderr
, "(\\r)");
1821 } else if (c
== 0x1b) {
1822 fprintf(stderr
, "(\\e)");
1824 fprintf(stderr
, "(%02x)", c
);
1827 fprintf(stderr
, "ESC\\\n");
1833 memset(&strescseq
, 0, sizeof(strescseq
));
1837 sendbreak(const Arg
*arg
)
1839 if (tcsendbreak(cmdfd
, 0))
1840 perror("Error sending break");
1844 tprinter(char *s
, size_t len
)
1846 if (iofd
!= -1 && xwrite(iofd
, s
, len
) < 0) {
1847 perror("Error writing to output file");
1854 iso14755(const Arg
*arg
)
1857 char *us
, *e
, codepoint
[9], uc
[UTF_SIZ
];
1858 unsigned long utf32
;
1860 if (!(p
= popen(ISO14755CMD
, "r")))
1863 us
= fgets(codepoint
, sizeof(codepoint
), p
);
1866 if (!us
|| *us
== '\0' || *us
== '-' || strlen(us
) > 7)
1868 if ((utf32
= strtoul(us
, &e
, 16)) == ULONG_MAX
||
1869 (*e
!= '\n' && *e
!= '\0'))
1872 ttysend(uc
, utf8encode(utf32
, uc
));
1876 toggleprinter(const Arg
*arg
)
1878 term
.mode
^= MODE_PRINT
;
1882 printscreen(const Arg
*arg
)
1888 printsel(const Arg
*arg
)
1898 if ((ptr
= getsel())) {
1899 tprinter(ptr
, strlen(ptr
));
1910 bp
= &term
.line
[n
][0];
1911 end
= &bp
[MIN(tlinelen(n
), term
.col
) - 1];
1912 if (bp
!= end
|| bp
->u
!= ' ') {
1913 for ( ;bp
<= end
; ++bp
)
1914 tprinter(buf
, utf8encode(bp
->u
, buf
));
1924 for (i
= 0; i
< term
.row
; ++i
)
1934 while (x
< term
.col
&& n
--)
1935 for (++x
; x
< term
.col
&& !term
.tabs
[x
]; ++x
)
1938 while (x
> 0 && n
++)
1939 for (--x
; x
> 0 && !term
.tabs
[x
]; --x
)
1942 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1946 tdefutf8(char ascii
)
1949 term
.mode
|= MODE_UTF8
;
1950 else if (ascii
== '@')
1951 term
.mode
&= ~MODE_UTF8
;
1955 tdeftran(char ascii
)
1957 static char cs
[] = "0B";
1958 static int vcs
[] = {CS_GRAPHIC0
, CS_USA
};
1961 if ((p
= strchr(cs
, ascii
)) == NULL
) {
1962 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
1964 term
.trantbl
[term
.icharset
] = vcs
[p
- cs
];
1973 if (c
== '8') { /* DEC screen alignment test. */
1974 for (x
= 0; x
< term
.col
; ++x
) {
1975 for (y
= 0; y
< term
.row
; ++y
)
1976 tsetchar('E', &term
.c
.attr
, x
, y
);
1982 tstrsequence(uchar c
)
1987 case 0x90: /* DCS -- Device Control String */
1989 term
.esc
|= ESC_DCS
;
1991 case 0x9f: /* APC -- Application Program Command */
1994 case 0x9e: /* PM -- Privacy Message */
1997 case 0x9d: /* OSC -- Operating System Command */
2002 term
.esc
|= ESC_STR
;
2006 tcontrolcode(uchar ascii
)
2013 tmoveto(term
.c
.x
-1, term
.c
.y
);
2016 tmoveto(0, term
.c
.y
);
2021 /* go to first col if the mode is set */
2022 tnewline(IS_SET(MODE_CRLF
));
2024 case '\a': /* BEL */
2025 if (term
.esc
& ESC_STR_END
) {
2026 /* backwards compatibility to xterm */
2032 case '\033': /* ESC */
2034 term
.esc
&= ~(ESC_CSI
|ESC_ALTCHARSET
|ESC_TEST
);
2035 term
.esc
|= ESC_START
;
2037 case '\016': /* SO (LS1 -- Locking shift 1) */
2038 case '\017': /* SI (LS0 -- Locking shift 0) */
2039 term
.charset
= 1 - (ascii
- '\016');
2041 case '\032': /* SUB */
2042 tsetchar('?', &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2043 case '\030': /* CAN */
2046 case '\005': /* ENQ (IGNORED) */
2047 case '\000': /* NUL (IGNORED) */
2048 case '\021': /* XON (IGNORED) */
2049 case '\023': /* XOFF (IGNORED) */
2050 case 0177: /* DEL (IGNORED) */
2052 case 0x80: /* TODO: PAD */
2053 case 0x81: /* TODO: HOP */
2054 case 0x82: /* TODO: BPH */
2055 case 0x83: /* TODO: NBH */
2056 case 0x84: /* TODO: IND */
2058 case 0x85: /* NEL -- Next line */
2059 tnewline(1); /* always go to first col */
2061 case 0x86: /* TODO: SSA */
2062 case 0x87: /* TODO: ESA */
2064 case 0x88: /* HTS -- Horizontal tab stop */
2065 term
.tabs
[term
.c
.x
] = 1;
2067 case 0x89: /* TODO: HTJ */
2068 case 0x8a: /* TODO: VTS */
2069 case 0x8b: /* TODO: PLD */
2070 case 0x8c: /* TODO: PLU */
2071 case 0x8d: /* TODO: RI */
2072 case 0x8e: /* TODO: SS2 */
2073 case 0x8f: /* TODO: SS3 */
2074 case 0x91: /* TODO: PU1 */
2075 case 0x92: /* TODO: PU2 */
2076 case 0x93: /* TODO: STS */
2077 case 0x94: /* TODO: CCH */
2078 case 0x95: /* TODO: MW */
2079 case 0x96: /* TODO: SPA */
2080 case 0x97: /* TODO: EPA */
2081 case 0x98: /* TODO: SOS */
2082 case 0x99: /* TODO: SGCI */
2084 case 0x9a: /* DECID -- Identify Terminal */
2085 ttywrite(vtiden
, strlen(vtiden
));
2087 case 0x9b: /* TODO: CSI */
2088 case 0x9c: /* TODO: ST */
2090 case 0x90: /* DCS -- Device Control String */
2091 case 0x9d: /* OSC -- Operating System Command */
2092 case 0x9e: /* PM -- Privacy Message */
2093 case 0x9f: /* APC -- Application Program Command */
2094 tstrsequence(ascii
);
2097 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2098 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
2102 * returns 1 when the sequence is finished and it hasn't to read
2103 * more characters for this sequence, otherwise 0
2106 eschandle(uchar ascii
)
2110 term
.esc
|= ESC_CSI
;
2113 term
.esc
|= ESC_TEST
;
2116 term
.esc
|= ESC_UTF8
;
2118 case 'P': /* DCS -- Device Control String */
2119 case '_': /* APC -- Application Program Command */
2120 case '^': /* PM -- Privacy Message */
2121 case ']': /* OSC -- Operating System Command */
2122 case 'k': /* old title set compatibility */
2123 tstrsequence(ascii
);
2125 case 'n': /* LS2 -- Locking shift 2 */
2126 case 'o': /* LS3 -- Locking shift 3 */
2127 term
.charset
= 2 + (ascii
- 'n');
2129 case '(': /* GZD4 -- set primary charset G0 */
2130 case ')': /* G1D4 -- set secondary charset G1 */
2131 case '*': /* G2D4 -- set tertiary charset G2 */
2132 case '+': /* G3D4 -- set quaternary charset G3 */
2133 term
.icharset
= ascii
- '(';
2134 term
.esc
|= ESC_ALTCHARSET
;
2136 case 'D': /* IND -- Linefeed */
2137 if (term
.c
.y
== term
.bot
) {
2138 tscrollup(term
.top
, 1);
2140 tmoveto(term
.c
.x
, term
.c
.y
+1);
2143 case 'E': /* NEL -- Next line */
2144 tnewline(1); /* always go to first col */
2146 case 'H': /* HTS -- Horizontal tab stop */
2147 term
.tabs
[term
.c
.x
] = 1;
2149 case 'M': /* RI -- Reverse index */
2150 if (term
.c
.y
== term
.top
) {
2151 tscrolldown(term
.top
, 1);
2153 tmoveto(term
.c
.x
, term
.c
.y
-1);
2156 case 'Z': /* DECID -- Identify Terminal */
2157 ttywrite(vtiden
, strlen(vtiden
));
2159 case 'c': /* RIS -- Reset to inital state */
2164 case '=': /* DECPAM -- Application keypad */
2165 term
.mode
|= MODE_APPKEYPAD
;
2167 case '>': /* DECPNM -- Normal keypad */
2168 term
.mode
&= ~MODE_APPKEYPAD
;
2170 case '7': /* DECSC -- Save Cursor */
2171 tcursor(CURSOR_SAVE
);
2173 case '8': /* DECRC -- Restore Cursor */
2174 tcursor(CURSOR_LOAD
);
2176 case '\\': /* ST -- String Terminator */
2177 if (term
.esc
& ESC_STR_END
)
2181 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2182 (uchar
) ascii
, isprint(ascii
)? ascii
:'.');
2196 control
= ISCONTROL(u
);
2197 if (!IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2201 len
= utf8encode(u
, c
);
2202 if (!control
&& (width
= wcwidth(u
)) == -1) {
2203 memcpy(c
, "\357\277\275", 4); /* UTF_INVALID */
2208 if (IS_SET(MODE_PRINT
))
2212 * STR sequence must be checked before anything else
2213 * because it uses all following characters until it
2214 * receives a ESC, a SUB, a ST or any other C1 control
2217 if (term
.esc
& ESC_STR
) {
2218 if (u
== '\a' || u
== 030 || u
== 032 || u
== 033 ||
2220 term
.esc
&= ~(ESC_START
|ESC_STR
|ESC_DCS
);
2221 if (IS_SET(MODE_SIXEL
)) {
2222 /* TODO: render sixel */;
2223 term
.mode
&= ~MODE_SIXEL
;
2226 term
.esc
|= ESC_STR_END
;
2227 goto check_control_code
;
2231 if (IS_SET(MODE_SIXEL
)) {
2232 /* TODO: implement sixel mode */
2235 if (term
.esc
&ESC_DCS
&& strescseq
.len
== 0 && u
== 'q')
2236 term
.mode
|= MODE_SIXEL
;
2238 if (strescseq
.len
+len
>= sizeof(strescseq
.buf
)-1) {
2240 * Here is a bug in terminals. If the user never sends
2241 * some code to stop the str or esc command, then st
2242 * will stop responding. But this is better than
2243 * silently failing with unknown characters. At least
2244 * then users will report back.
2246 * In the case users ever get fixed, here is the code:
2255 memmove(&strescseq
.buf
[strescseq
.len
], c
, len
);
2256 strescseq
.len
+= len
;
2262 * Actions of control codes must be performed as soon they arrive
2263 * because they can be embedded inside a control sequence, and
2264 * they must not cause conflicts with sequences.
2269 * control codes are not shown ever
2272 } else if (term
.esc
& ESC_START
) {
2273 if (term
.esc
& ESC_CSI
) {
2274 csiescseq
.buf
[csiescseq
.len
++] = u
;
2275 if (BETWEEN(u
, 0x40, 0x7E)
2276 || csiescseq
.len
>= \
2277 sizeof(csiescseq
.buf
)-1) {
2283 } else if (term
.esc
& ESC_UTF8
) {
2285 } else if (term
.esc
& ESC_ALTCHARSET
) {
2287 } else if (term
.esc
& ESC_TEST
) {
2292 /* sequence already finished */
2296 * All characters which form part of a sequence are not
2301 if (sel
.ob
.x
!= -1 && BETWEEN(term
.c
.y
, sel
.ob
.y
, sel
.oe
.y
))
2304 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2305 if (IS_SET(MODE_WRAP
) && (term
.c
.state
& CURSOR_WRAPNEXT
)) {
2306 gp
->mode
|= ATTR_WRAP
;
2308 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2311 if (IS_SET(MODE_INSERT
) && term
.c
.x
+width
< term
.col
)
2312 memmove(gp
+width
, gp
, (term
.col
- term
.c
.x
- width
) * sizeof(Glyph
));
2314 if (term
.c
.x
+width
> term
.col
) {
2316 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2319 tsetchar(u
, &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2322 gp
->mode
|= ATTR_WIDE
;
2323 if (term
.c
.x
+1 < term
.col
) {
2325 gp
[1].mode
= ATTR_WDUMMY
;
2328 if (term
.c
.x
+width
< term
.col
) {
2329 tmoveto(term
.c
.x
+width
, term
.c
.y
);
2331 term
.c
.state
|= CURSOR_WRAPNEXT
;
2336 twrite(const char *buf
, int buflen
, int show_ctrl
)
2342 for (n
= 0; n
< buflen
; n
+= charsize
) {
2343 if (IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2344 /* process a complete utf8 char */
2345 charsize
= utf8decode(buf
+ n
, &u
, buflen
- n
);
2352 if (show_ctrl
&& ISCONTROL(u
)) {
2357 } else if (u
!= '\n' && u
!= '\r' && u
!= '\t') {
2368 tresize(int col
, int row
)
2371 int minrow
= MIN(row
, term
.row
);
2372 int mincol
= MIN(col
, term
.col
);
2376 if (col
< 1 || row
< 1) {
2378 "tresize: error resizing to %dx%d\n", col
, row
);
2383 * slide screen to keep cursor where we expect it -
2384 * tscrollup would work here, but we can optimize to
2385 * memmove because we're freeing the earlier lines
2387 for (i
= 0; i
<= term
.c
.y
- row
; i
++) {
2391 /* ensure that both src and dst are not NULL */
2393 memmove(term
.line
, term
.line
+ i
, row
* sizeof(Line
));
2394 memmove(term
.alt
, term
.alt
+ i
, row
* sizeof(Line
));
2396 for (i
+= row
; i
< term
.row
; i
++) {
2401 /* resize to new height */
2402 term
.line
= xrealloc(term
.line
, row
* sizeof(Line
));
2403 term
.alt
= xrealloc(term
.alt
, row
* sizeof(Line
));
2404 term
.dirty
= xrealloc(term
.dirty
, row
* sizeof(*term
.dirty
));
2405 term
.tabs
= xrealloc(term
.tabs
, col
* sizeof(*term
.tabs
));
2407 /* resize each row to new width, zero-pad if needed */
2408 for (i
= 0; i
< minrow
; i
++) {
2409 term
.line
[i
] = xrealloc(term
.line
[i
], col
* sizeof(Glyph
));
2410 term
.alt
[i
] = xrealloc(term
.alt
[i
], col
* sizeof(Glyph
));
2413 /* allocate any new rows */
2414 for (/* i = minrow */; i
< row
; i
++) {
2415 term
.line
[i
] = xmalloc(col
* sizeof(Glyph
));
2416 term
.alt
[i
] = xmalloc(col
* sizeof(Glyph
));
2418 if (col
> term
.col
) {
2419 bp
= term
.tabs
+ term
.col
;
2421 memset(bp
, 0, sizeof(*term
.tabs
) * (col
- term
.col
));
2422 while (--bp
> term
.tabs
&& !*bp
)
2424 for (bp
+= tabspaces
; bp
< term
.tabs
+ col
; bp
+= tabspaces
)
2427 /* update terminal size */
2430 /* reset scrolling region */
2431 tsetscroll(0, row
-1);
2432 /* make use of the LIMIT in tmoveto */
2433 tmoveto(term
.c
.x
, term
.c
.y
);
2434 /* Clearing both screens (it makes dirty all lines) */
2436 for (i
= 0; i
< 2; i
++) {
2437 if (mincol
< col
&& 0 < minrow
) {
2438 tclearregion(mincol
, 0, col
- 1, minrow
- 1);
2440 if (0 < col
&& minrow
< row
) {
2441 tclearregion(0, minrow
, col
- 1, row
- 1);
2444 tcursor(CURSOR_LOAD
);
2463 numlock(const Arg
*dummy
)