Xinqi Bao's Git
d4dfe6e1fa04a42a45fd4661ff54c93dbbc9aefa
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
;
368 clock_gettime(CLOCK_MONOTONIC
, &sel
.tclick1
);
369 clock_gettime(CLOCK_MONOTONIC
, &sel
.tclick2
);
374 sel
.clipboard
= NULL
;
382 if (term
.line
[y
][i
- 1].mode
& ATTR_WRAP
)
385 while (i
> 0 && term
.line
[y
][i
- 1].u
== ' ')
396 if (sel
.type
== SEL_REGULAR
&& sel
.ob
.y
!= sel
.oe
.y
) {
397 sel
.nb
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.ob
.x
: sel
.oe
.x
;
398 sel
.ne
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.oe
.x
: sel
.ob
.x
;
400 sel
.nb
.x
= MIN(sel
.ob
.x
, sel
.oe
.x
);
401 sel
.ne
.x
= MAX(sel
.ob
.x
, sel
.oe
.x
);
403 sel
.nb
.y
= MIN(sel
.ob
.y
, sel
.oe
.y
);
404 sel
.ne
.y
= MAX(sel
.ob
.y
, sel
.oe
.y
);
406 selsnap(&sel
.nb
.x
, &sel
.nb
.y
, -1);
407 selsnap(&sel
.ne
.x
, &sel
.ne
.y
, +1);
409 /* expand selection over line breaks */
410 if (sel
.type
== SEL_RECTANGULAR
)
412 i
= tlinelen(sel
.nb
.y
);
415 if (tlinelen(sel
.ne
.y
) <= sel
.ne
.x
)
416 sel
.ne
.x
= term
.col
- 1;
420 selected(int x
, int y
)
422 if (sel
.mode
== SEL_EMPTY
|| sel
.ob
.x
== -1 ||
423 sel
.alt
!= IS_SET(MODE_ALTSCREEN
))
426 if (sel
.type
== SEL_RECTANGULAR
)
427 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
428 && BETWEEN(x
, sel
.nb
.x
, sel
.ne
.x
);
430 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
431 && (y
!= sel
.nb
.y
|| x
>= sel
.nb
.x
)
432 && (y
!= sel
.ne
.y
|| x
<= sel
.ne
.x
);
436 selsnap(int *x
, int *y
, int direction
)
438 int newx
, newy
, xt
, yt
;
439 int delim
, prevdelim
;
445 * Snap around if the word wraps around at the end or
446 * beginning of a line.
448 prevgp
= &term
.line
[*y
][*x
];
449 prevdelim
= ISDELIM(prevgp
->u
);
451 newx
= *x
+ direction
;
453 if (!BETWEEN(newx
, 0, term
.col
- 1)) {
455 newx
= (newx
+ term
.col
) % term
.col
;
456 if (!BETWEEN(newy
, 0, term
.row
- 1))
462 yt
= newy
, xt
= newx
;
463 if (!(term
.line
[yt
][xt
].mode
& ATTR_WRAP
))
467 if (newx
>= tlinelen(newy
))
470 gp
= &term
.line
[newy
][newx
];
471 delim
= ISDELIM(gp
->u
);
472 if (!(gp
->mode
& ATTR_WDUMMY
) && (delim
!= prevdelim
473 || (delim
&& gp
->u
!= prevgp
->u
)))
484 * Snap around if the the previous line or the current one
485 * has set ATTR_WRAP at its end. Then the whole next or
486 * previous line will be selected.
488 *x
= (direction
< 0) ? 0 : term
.col
- 1;
490 for (; *y
> 0; *y
+= direction
) {
491 if (!(term
.line
[*y
-1][term
.col
-1].mode
496 } else if (direction
> 0) {
497 for (; *y
< term
.row
-1; *y
+= direction
) {
498 if (!(term
.line
[*y
][term
.col
-1].mode
512 int y
, bufsize
, lastx
, linelen
;
518 bufsize
= (term
.col
+1) * (sel
.ne
.y
-sel
.nb
.y
+1) * UTF_SIZ
;
519 ptr
= str
= xmalloc(bufsize
);
521 /* append every set & selected glyph to the selection */
522 for (y
= sel
.nb
.y
; y
<= sel
.ne
.y
; y
++) {
523 if ((linelen
= tlinelen(y
)) == 0) {
528 if (sel
.type
== SEL_RECTANGULAR
) {
529 gp
= &term
.line
[y
][sel
.nb
.x
];
532 gp
= &term
.line
[y
][sel
.nb
.y
== y
? sel
.nb
.x
: 0];
533 lastx
= (sel
.ne
.y
== y
) ? sel
.ne
.x
: term
.col
-1;
535 last
= &term
.line
[y
][MIN(lastx
, linelen
-1)];
536 while (last
>= gp
&& last
->u
== ' ')
539 for ( ; gp
<= last
; ++gp
) {
540 if (gp
->mode
& ATTR_WDUMMY
)
543 ptr
+= utf8encode(gp
->u
, ptr
);
547 * Copy and pasting of line endings is inconsistent
548 * in the inconsistent terminal and GUI world.
549 * The best solution seems like to produce '\n' when
550 * something is copied from st and convert '\n' to
551 * '\r', when something to be pasted is received by
553 * FIXME: Fix the computer world.
555 if ((y
< sel
.ne
.y
|| lastx
>= linelen
) && !(last
->mode
& ATTR_WRAP
))
569 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
573 die(const char *errstr
, ...)
577 va_start(ap
, errstr
);
578 vfprintf(stderr
, errstr
, ap
);
587 const struct passwd
*pw
;
590 if ((pw
= getpwuid(getuid())) == NULL
) {
592 die("getpwuid:%s\n", strerror(errno
));
594 die("who are you?\n");
597 if ((sh
= getenv("SHELL")) == NULL
)
598 sh
= (pw
->pw_shell
[0]) ? pw
->pw_shell
: shell
;
606 DEFAULT(args
, ((char *[]) {prog
, NULL
}));
611 setenv("LOGNAME", pw
->pw_name
, 1);
612 setenv("USER", pw
->pw_name
, 1);
613 setenv("SHELL", sh
, 1);
614 setenv("HOME", pw
->pw_dir
, 1);
615 setenv("TERM", termname
, 1);
617 signal(SIGCHLD
, SIG_DFL
);
618 signal(SIGHUP
, SIG_DFL
);
619 signal(SIGINT
, SIG_DFL
);
620 signal(SIGQUIT
, SIG_DFL
);
621 signal(SIGTERM
, SIG_DFL
);
622 signal(SIGALRM
, SIG_DFL
);
634 if ((p
= waitpid(pid
, &stat
, WNOHANG
)) < 0)
635 die("Waiting for pid %hd failed: %s\n", pid
, strerror(errno
));
640 if (!WIFEXITED(stat
) || WEXITSTATUS(stat
))
641 die("child finished with error '%d'\n", stat
);
649 char cmd
[_POSIX_ARG_MAX
], **p
, *q
, *s
;
652 if ((n
= strlen(stty_args
)) > sizeof(cmd
)-1)
653 die("incorrect stty parameters\n");
654 memcpy(cmd
, stty_args
, n
);
656 siz
= sizeof(cmd
) - n
;
657 for (p
= args
; p
&& (s
= *p
); ++p
) {
658 if ((n
= strlen(s
)) > siz
-1)
659 die("stty parameter length too long\n");
666 if (system(cmd
) != 0)
667 perror("Couldn't call stty");
671 ttynew(char *line
, char *out
, char **args
)
676 term
.mode
|= MODE_PRINT
;
677 iofd
= (!strcmp(out
, "-")) ?
678 1 : open(out
, O_WRONLY
| O_CREAT
, 0666);
680 fprintf(stderr
, "Error opening %s:%s\n",
681 out
, strerror(errno
));
686 if ((cmdfd
= open(line
, O_RDWR
)) < 0)
687 die("open line failed: %s\n", strerror(errno
));
693 /* seems to work fine on linux, openbsd and freebsd */
694 if (openpty(&m
, &s
, NULL
, NULL
, NULL
) < 0)
695 die("openpty failed: %s\n", strerror(errno
));
697 switch (pid
= fork()) {
699 die("fork failed\n");
703 setsid(); /* create a new process group */
707 if (ioctl(s
, TIOCSCTTY
, NULL
) < 0)
708 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno
));
716 signal(SIGCHLD
, sigchld
);
724 static char buf
[BUFSIZ
];
725 static int buflen
= 0;
729 /* append read bytes to unprocessed bytes */
730 if ((ret
= read(cmdfd
, buf
+buflen
, LEN(buf
)-buflen
)) < 0)
731 die("Couldn't read from shell: %s\n", strerror(errno
));
734 written
= twrite(buf
, buflen
, 0);
736 /* keep any uncomplete utf8 char for the next call */
738 memmove(buf
, buf
+ written
, buflen
);
744 ttywrite(const char *s
, size_t n
)
751 * Remember that we are using a pty, which might be a modem line.
752 * Writing too much will clog the line. That's why we are doing this
754 * FIXME: Migrate the world to Plan 9.
762 /* Check if we can write. */
763 if (pselect(cmdfd
+1, &rfd
, &wfd
, NULL
, NULL
, NULL
) < 0) {
766 die("select failed: %s\n", strerror(errno
));
768 if (FD_ISSET(cmdfd
, &wfd
)) {
770 * Only write the bytes written by ttywrite() or the
771 * default of 256. This seems to be a reasonable value
772 * for a serial line. Bigger values might clog the I/O.
774 if ((r
= write(cmdfd
, s
, (n
< lim
)? n
: lim
)) < 0)
778 * We weren't able to write out everything.
779 * This means the buffer is getting full
787 /* All bytes have been written. */
791 if (FD_ISSET(cmdfd
, &rfd
))
797 die("write error on tty: %s\n", strerror(errno
));
801 ttysend(char *s
, size_t n
)
804 if (IS_SET(MODE_ECHO
))
809 ttyresize(int tw
, int th
)
817 if (ioctl(cmdfd
, TIOCSWINSZ
, &w
) < 0)
818 fprintf(stderr
, "Couldn't set window size: %s\n", strerror(errno
));
826 for (i
= 0; i
< term
.row
-1; i
++) {
827 for (j
= 0; j
< term
.col
-1; j
++) {
828 if (term
.line
[i
][j
].mode
& attr
)
837 tsetdirt(int top
, int bot
)
841 LIMIT(top
, 0, term
.row
-1);
842 LIMIT(bot
, 0, term
.row
-1);
844 for (i
= top
; i
<= bot
; i
++)
849 tsetdirtattr(int attr
)
853 for (i
= 0; i
< term
.row
-1; i
++) {
854 for (j
= 0; j
< term
.col
-1; j
++) {
855 if (term
.line
[i
][j
].mode
& attr
) {
866 tsetdirt(0, term
.row
-1);
873 int alt
= IS_SET(MODE_ALTSCREEN
);
875 if (mode
== CURSOR_SAVE
) {
877 } else if (mode
== CURSOR_LOAD
) {
879 tmoveto(c
[alt
].x
, c
[alt
].y
);
892 }, .x
= 0, .y
= 0, .state
= CURSOR_DEFAULT
};
894 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
895 for (i
= tabspaces
; i
< term
.col
; i
+= tabspaces
)
898 term
.bot
= term
.row
- 1;
899 term
.mode
= MODE_WRAP
|MODE_UTF8
;
900 memset(term
.trantbl
, CS_USA
, sizeof(term
.trantbl
));
903 for (i
= 0; i
< 2; i
++) {
905 tcursor(CURSOR_SAVE
);
906 tclearregion(0, 0, term
.col
-1, term
.row
-1);
912 tnew(int col
, int row
)
914 term
= (Term
){ .c
= { .attr
= { .fg
= defaultfg
, .bg
= defaultbg
} } };
924 Line
*tmp
= term
.line
;
926 term
.line
= term
.alt
;
928 term
.mode
^= MODE_ALTSCREEN
;
933 tscrolldown(int orig
, int n
)
938 LIMIT(n
, 0, term
.bot
-orig
+1);
940 tsetdirt(orig
, term
.bot
-n
);
941 tclearregion(0, term
.bot
-n
+1, term
.col
-1, term
.bot
);
943 for (i
= term
.bot
; i
>= orig
+n
; i
--) {
945 term
.line
[i
] = term
.line
[i
-n
];
946 term
.line
[i
-n
] = temp
;
953 tscrollup(int orig
, int n
)
958 LIMIT(n
, 0, term
.bot
-orig
+1);
960 tclearregion(0, orig
, term
.col
-1, orig
+n
-1);
961 tsetdirt(orig
+n
, term
.bot
);
963 for (i
= orig
; i
<= term
.bot
-n
; i
++) {
965 term
.line
[i
] = term
.line
[i
+n
];
966 term
.line
[i
+n
] = temp
;
973 selscroll(int orig
, int n
)
978 if (BETWEEN(sel
.ob
.y
, orig
, term
.bot
) || BETWEEN(sel
.oe
.y
, orig
, term
.bot
)) {
979 if ((sel
.ob
.y
+= n
) > term
.bot
|| (sel
.oe
.y
+= n
) < term
.top
) {
983 if (sel
.type
== SEL_RECTANGULAR
) {
984 if (sel
.ob
.y
< term
.top
)
986 if (sel
.oe
.y
> term
.bot
)
989 if (sel
.ob
.y
< term
.top
) {
993 if (sel
.oe
.y
> term
.bot
) {
1003 tnewline(int first_col
)
1007 if (y
== term
.bot
) {
1008 tscrollup(term
.top
, 1);
1012 tmoveto(first_col
? 0 : term
.c
.x
, y
);
1018 char *p
= csiescseq
.buf
, *np
;
1027 csiescseq
.buf
[csiescseq
.len
] = '\0';
1028 while (p
< csiescseq
.buf
+csiescseq
.len
) {
1030 v
= strtol(p
, &np
, 10);
1033 if (v
== LONG_MAX
|| v
== LONG_MIN
)
1035 csiescseq
.arg
[csiescseq
.narg
++] = v
;
1037 if (*p
!= ';' || csiescseq
.narg
== ESC_ARG_SIZ
)
1041 csiescseq
.mode
[0] = *p
++;
1042 csiescseq
.mode
[1] = (p
< csiescseq
.buf
+csiescseq
.len
) ? *p
: '\0';
1045 /* for absolute user moves, when decom is set */
1047 tmoveato(int x
, int y
)
1049 tmoveto(x
, y
+ ((term
.c
.state
& CURSOR_ORIGIN
) ? term
.top
: 0));
1053 tmoveto(int x
, int y
)
1057 if (term
.c
.state
& CURSOR_ORIGIN
) {
1062 maxy
= term
.row
- 1;
1064 term
.c
.state
&= ~CURSOR_WRAPNEXT
;
1065 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1066 term
.c
.y
= LIMIT(y
, miny
, maxy
);
1070 tsetchar(Rune u
, Glyph
*attr
, int x
, int y
)
1072 static char *vt100_0
[62] = { /* 0x41 - 0x7e */
1073 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1074 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1075 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1076 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1077 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1078 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1079 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1080 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1084 * The table is proudly stolen from rxvt.
1086 if (term
.trantbl
[term
.charset
] == CS_GRAPHIC0
&&
1087 BETWEEN(u
, 0x41, 0x7e) && vt100_0
[u
- 0x41])
1088 utf8decode(vt100_0
[u
- 0x41], &u
, UTF_SIZ
);
1090 if (term
.line
[y
][x
].mode
& ATTR_WIDE
) {
1091 if (x
+1 < term
.col
) {
1092 term
.line
[y
][x
+1].u
= ' ';
1093 term
.line
[y
][x
+1].mode
&= ~ATTR_WDUMMY
;
1095 } else if (term
.line
[y
][x
].mode
& ATTR_WDUMMY
) {
1096 term
.line
[y
][x
-1].u
= ' ';
1097 term
.line
[y
][x
-1].mode
&= ~ATTR_WIDE
;
1101 term
.line
[y
][x
] = *attr
;
1102 term
.line
[y
][x
].u
= u
;
1106 tclearregion(int x1
, int y1
, int x2
, int y2
)
1112 temp
= x1
, x1
= x2
, x2
= temp
;
1114 temp
= y1
, y1
= y2
, y2
= temp
;
1116 LIMIT(x1
, 0, term
.col
-1);
1117 LIMIT(x2
, 0, term
.col
-1);
1118 LIMIT(y1
, 0, term
.row
-1);
1119 LIMIT(y2
, 0, term
.row
-1);
1121 for (y
= y1
; y
<= y2
; y
++) {
1123 for (x
= x1
; x
<= x2
; x
++) {
1124 gp
= &term
.line
[y
][x
];
1127 gp
->fg
= term
.c
.attr
.fg
;
1128 gp
->bg
= term
.c
.attr
.bg
;
1141 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1145 size
= term
.col
- src
;
1146 line
= term
.line
[term
.c
.y
];
1148 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1149 tclearregion(term
.col
-n
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1158 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1162 size
= term
.col
- dst
;
1163 line
= term
.line
[term
.c
.y
];
1165 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1166 tclearregion(src
, term
.c
.y
, dst
- 1, term
.c
.y
);
1170 tinsertblankline(int n
)
1172 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1173 tscrolldown(term
.c
.y
, n
);
1179 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1180 tscrollup(term
.c
.y
, n
);
1184 tdefcolor(int *attr
, int *npar
, int l
)
1189 switch (attr
[*npar
+ 1]) {
1190 case 2: /* direct color in RGB space */
1191 if (*npar
+ 4 >= l
) {
1193 "erresc(38): Incorrect number of parameters (%d)\n",
1197 r
= attr
[*npar
+ 2];
1198 g
= attr
[*npar
+ 3];
1199 b
= attr
[*npar
+ 4];
1201 if (!BETWEEN(r
, 0, 255) || !BETWEEN(g
, 0, 255) || !BETWEEN(b
, 0, 255))
1202 fprintf(stderr
, "erresc: bad rgb color (%u,%u,%u)\n",
1205 idx
= TRUECOLOR(r
, g
, b
);
1207 case 5: /* indexed color */
1208 if (*npar
+ 2 >= l
) {
1210 "erresc(38): Incorrect number of parameters (%d)\n",
1215 if (!BETWEEN(attr
[*npar
], 0, 255))
1216 fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[*npar
]);
1220 case 0: /* implemented defined (only foreground) */
1221 case 1: /* transparent */
1222 case 3: /* direct color in CMY space */
1223 case 4: /* direct color in CMYK space */
1226 "erresc(38): gfx attr %d unknown\n", attr
[*npar
]);
1234 tsetattr(int *attr
, int l
)
1239 for (i
= 0; i
< l
; i
++) {
1242 term
.c
.attr
.mode
&= ~(
1251 term
.c
.attr
.fg
= defaultfg
;
1252 term
.c
.attr
.bg
= defaultbg
;
1255 term
.c
.attr
.mode
|= ATTR_BOLD
;
1258 term
.c
.attr
.mode
|= ATTR_FAINT
;
1261 term
.c
.attr
.mode
|= ATTR_ITALIC
;
1264 term
.c
.attr
.mode
|= ATTR_UNDERLINE
;
1266 case 5: /* slow blink */
1268 case 6: /* rapid blink */
1269 term
.c
.attr
.mode
|= ATTR_BLINK
;
1272 term
.c
.attr
.mode
|= ATTR_REVERSE
;
1275 term
.c
.attr
.mode
|= ATTR_INVISIBLE
;
1278 term
.c
.attr
.mode
|= ATTR_STRUCK
;
1281 term
.c
.attr
.mode
&= ~(ATTR_BOLD
| ATTR_FAINT
);
1284 term
.c
.attr
.mode
&= ~ATTR_ITALIC
;
1287 term
.c
.attr
.mode
&= ~ATTR_UNDERLINE
;
1290 term
.c
.attr
.mode
&= ~ATTR_BLINK
;
1293 term
.c
.attr
.mode
&= ~ATTR_REVERSE
;
1296 term
.c
.attr
.mode
&= ~ATTR_INVISIBLE
;
1299 term
.c
.attr
.mode
&= ~ATTR_STRUCK
;
1302 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1303 term
.c
.attr
.fg
= idx
;
1306 term
.c
.attr
.fg
= defaultfg
;
1309 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1310 term
.c
.attr
.bg
= idx
;
1313 term
.c
.attr
.bg
= defaultbg
;
1316 if (BETWEEN(attr
[i
], 30, 37)) {
1317 term
.c
.attr
.fg
= attr
[i
] - 30;
1318 } else if (BETWEEN(attr
[i
], 40, 47)) {
1319 term
.c
.attr
.bg
= attr
[i
] - 40;
1320 } else if (BETWEEN(attr
[i
], 90, 97)) {
1321 term
.c
.attr
.fg
= attr
[i
] - 90 + 8;
1322 } else if (BETWEEN(attr
[i
], 100, 107)) {
1323 term
.c
.attr
.bg
= attr
[i
] - 100 + 8;
1326 "erresc(default): gfx attr %d unknown\n",
1327 attr
[i
]), csidump();
1335 tsetscroll(int t
, int b
)
1339 LIMIT(t
, 0, term
.row
-1);
1340 LIMIT(b
, 0, term
.row
-1);
1351 tsetmode(int priv
, int set
, int *args
, int narg
)
1356 for (lim
= args
+ narg
; args
< lim
; ++args
) {
1359 case 1: /* DECCKM -- Cursor key */
1360 MODBIT(term
.mode
, set
, MODE_APPCURSOR
);
1362 case 5: /* DECSCNM -- Reverse video */
1364 MODBIT(term
.mode
, set
, MODE_REVERSE
);
1365 if (mode
!= term
.mode
)
1368 case 6: /* DECOM -- Origin */
1369 MODBIT(term
.c
.state
, set
, CURSOR_ORIGIN
);
1372 case 7: /* DECAWM -- Auto wrap */
1373 MODBIT(term
.mode
, set
, MODE_WRAP
);
1375 case 0: /* Error (IGNORED) */
1376 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1377 case 3: /* DECCOLM -- Column (IGNORED) */
1378 case 4: /* DECSCLM -- Scroll (IGNORED) */
1379 case 8: /* DECARM -- Auto repeat (IGNORED) */
1380 case 18: /* DECPFF -- Printer feed (IGNORED) */
1381 case 19: /* DECPEX -- Printer extent (IGNORED) */
1382 case 42: /* DECNRCM -- National characters (IGNORED) */
1383 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1385 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1386 MODBIT(term
.mode
, !set
, MODE_HIDE
);
1388 case 9: /* X10 mouse compatibility mode */
1389 xsetpointermotion(0);
1390 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1391 MODBIT(term
.mode
, set
, MODE_MOUSEX10
);
1393 case 1000: /* 1000: report button press */
1394 xsetpointermotion(0);
1395 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1396 MODBIT(term
.mode
, set
, MODE_MOUSEBTN
);
1398 case 1002: /* 1002: report motion on button press */
1399 xsetpointermotion(0);
1400 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1401 MODBIT(term
.mode
, set
, MODE_MOUSEMOTION
);
1403 case 1003: /* 1003: enable all mouse motions */
1404 xsetpointermotion(set
);
1405 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1406 MODBIT(term
.mode
, set
, MODE_MOUSEMANY
);
1408 case 1004: /* 1004: send focus events to tty */
1409 MODBIT(term
.mode
, set
, MODE_FOCUS
);
1411 case 1006: /* 1006: extended reporting mode */
1412 MODBIT(term
.mode
, set
, MODE_MOUSESGR
);
1415 MODBIT(term
.mode
, set
, MODE_8BIT
);
1417 case 1049: /* swap screen & set/restore cursor as xterm */
1418 if (!allowaltscreen
)
1420 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1422 case 47: /* swap screen */
1424 if (!allowaltscreen
)
1426 alt
= IS_SET(MODE_ALTSCREEN
);
1428 tclearregion(0, 0, term
.col
-1,
1431 if (set
^ alt
) /* set is always 1 or 0 */
1437 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1439 case 2004: /* 2004: bracketed paste mode */
1440 MODBIT(term
.mode
, set
, MODE_BRCKTPASTE
);
1442 /* Not implemented mouse modes. See comments there. */
1443 case 1001: /* mouse highlight mode; can hang the
1444 terminal by design when implemented. */
1445 case 1005: /* UTF-8 mouse mode; will confuse
1446 applications not supporting UTF-8
1448 case 1015: /* urxvt mangled mouse mode; incompatible
1449 and can be mistaken for other control
1453 "erresc: unknown private set/reset mode %d\n",
1459 case 0: /* Error (IGNORED) */
1461 case 2: /* KAM -- keyboard action */
1462 MODBIT(term
.mode
, set
, MODE_KBDLOCK
);
1464 case 4: /* IRM -- Insertion-replacement */
1465 MODBIT(term
.mode
, set
, MODE_INSERT
);
1467 case 12: /* SRM -- Send/Receive */
1468 MODBIT(term
.mode
, !set
, MODE_ECHO
);
1470 case 20: /* LNM -- Linefeed/new line */
1471 MODBIT(term
.mode
, set
, MODE_CRLF
);
1475 "erresc: unknown set/reset mode %d\n",
1489 switch (csiescseq
.mode
[0]) {
1492 fprintf(stderr
, "erresc: unknown csi ");
1496 case '@': /* ICH -- Insert <n> blank char */
1497 DEFAULT(csiescseq
.arg
[0], 1);
1498 tinsertblank(csiescseq
.arg
[0]);
1500 case 'A': /* CUU -- Cursor <n> Up */
1501 DEFAULT(csiescseq
.arg
[0], 1);
1502 tmoveto(term
.c
.x
, term
.c
.y
-csiescseq
.arg
[0]);
1504 case 'B': /* CUD -- Cursor <n> Down */
1505 case 'e': /* VPR --Cursor <n> Down */
1506 DEFAULT(csiescseq
.arg
[0], 1);
1507 tmoveto(term
.c
.x
, term
.c
.y
+csiescseq
.arg
[0]);
1509 case 'i': /* MC -- Media Copy */
1510 switch (csiescseq
.arg
[0]) {
1515 tdumpline(term
.c
.y
);
1521 term
.mode
&= ~MODE_PRINT
;
1524 term
.mode
|= MODE_PRINT
;
1528 case 'c': /* DA -- Device Attributes */
1529 if (csiescseq
.arg
[0] == 0)
1530 ttywrite(vtiden
, strlen(vtiden
));
1532 case 'C': /* CUF -- Cursor <n> Forward */
1533 case 'a': /* HPR -- Cursor <n> Forward */
1534 DEFAULT(csiescseq
.arg
[0], 1);
1535 tmoveto(term
.c
.x
+csiescseq
.arg
[0], term
.c
.y
);
1537 case 'D': /* CUB -- Cursor <n> Backward */
1538 DEFAULT(csiescseq
.arg
[0], 1);
1539 tmoveto(term
.c
.x
-csiescseq
.arg
[0], term
.c
.y
);
1541 case 'E': /* CNL -- Cursor <n> Down and first col */
1542 DEFAULT(csiescseq
.arg
[0], 1);
1543 tmoveto(0, term
.c
.y
+csiescseq
.arg
[0]);
1545 case 'F': /* CPL -- Cursor <n> Up and first col */
1546 DEFAULT(csiescseq
.arg
[0], 1);
1547 tmoveto(0, term
.c
.y
-csiescseq
.arg
[0]);
1549 case 'g': /* TBC -- Tabulation clear */
1550 switch (csiescseq
.arg
[0]) {
1551 case 0: /* clear current tab stop */
1552 term
.tabs
[term
.c
.x
] = 0;
1554 case 3: /* clear all the tabs */
1555 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1561 case 'G': /* CHA -- Move to <col> */
1563 DEFAULT(csiescseq
.arg
[0], 1);
1564 tmoveto(csiescseq
.arg
[0]-1, term
.c
.y
);
1566 case 'H': /* CUP -- Move to <row> <col> */
1568 DEFAULT(csiescseq
.arg
[0], 1);
1569 DEFAULT(csiescseq
.arg
[1], 1);
1570 tmoveato(csiescseq
.arg
[1]-1, csiescseq
.arg
[0]-1);
1572 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1573 DEFAULT(csiescseq
.arg
[0], 1);
1574 tputtab(csiescseq
.arg
[0]);
1576 case 'J': /* ED -- Clear screen */
1578 switch (csiescseq
.arg
[0]) {
1580 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1581 if (term
.c
.y
< term
.row
-1) {
1582 tclearregion(0, term
.c
.y
+1, term
.col
-1,
1588 tclearregion(0, 0, term
.col
-1, term
.c
.y
-1);
1589 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1592 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1598 case 'K': /* EL -- Clear line */
1599 switch (csiescseq
.arg
[0]) {
1601 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1,
1605 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1608 tclearregion(0, term
.c
.y
, term
.col
-1, term
.c
.y
);
1612 case 'S': /* SU -- Scroll <n> line up */
1613 DEFAULT(csiescseq
.arg
[0], 1);
1614 tscrollup(term
.top
, csiescseq
.arg
[0]);
1616 case 'T': /* SD -- Scroll <n> line down */
1617 DEFAULT(csiescseq
.arg
[0], 1);
1618 tscrolldown(term
.top
, csiescseq
.arg
[0]);
1620 case 'L': /* IL -- Insert <n> blank lines */
1621 DEFAULT(csiescseq
.arg
[0], 1);
1622 tinsertblankline(csiescseq
.arg
[0]);
1624 case 'l': /* RM -- Reset Mode */
1625 tsetmode(csiescseq
.priv
, 0, csiescseq
.arg
, csiescseq
.narg
);
1627 case 'M': /* DL -- Delete <n> lines */
1628 DEFAULT(csiescseq
.arg
[0], 1);
1629 tdeleteline(csiescseq
.arg
[0]);
1631 case 'X': /* ECH -- Erase <n> char */
1632 DEFAULT(csiescseq
.arg
[0], 1);
1633 tclearregion(term
.c
.x
, term
.c
.y
,
1634 term
.c
.x
+ csiescseq
.arg
[0] - 1, term
.c
.y
);
1636 case 'P': /* DCH -- Delete <n> char */
1637 DEFAULT(csiescseq
.arg
[0], 1);
1638 tdeletechar(csiescseq
.arg
[0]);
1640 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1641 DEFAULT(csiescseq
.arg
[0], 1);
1642 tputtab(-csiescseq
.arg
[0]);
1644 case 'd': /* VPA -- Move to <row> */
1645 DEFAULT(csiescseq
.arg
[0], 1);
1646 tmoveato(term
.c
.x
, csiescseq
.arg
[0]-1);
1648 case 'h': /* SM -- Set terminal mode */
1649 tsetmode(csiescseq
.priv
, 1, csiescseq
.arg
, csiescseq
.narg
);
1651 case 'm': /* SGR -- Terminal attribute (color) */
1652 tsetattr(csiescseq
.arg
, csiescseq
.narg
);
1654 case 'n': /* DSR – Device Status Report (cursor position) */
1655 if (csiescseq
.arg
[0] == 6) {
1656 len
= snprintf(buf
, sizeof(buf
),"\033[%i;%iR",
1657 term
.c
.y
+1, term
.c
.x
+1);
1661 case 'r': /* DECSTBM -- Set Scrolling Region */
1662 if (csiescseq
.priv
) {
1665 DEFAULT(csiescseq
.arg
[0], 1);
1666 DEFAULT(csiescseq
.arg
[1], term
.row
);
1667 tsetscroll(csiescseq
.arg
[0]-1, csiescseq
.arg
[1]-1);
1671 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1672 tcursor(CURSOR_SAVE
);
1674 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1675 tcursor(CURSOR_LOAD
);
1678 switch (csiescseq
.mode
[1]) {
1679 case 'q': /* DECSCUSR -- Set Cursor Style */
1680 if (xsetcursor(csiescseq
.arg
[0]))
1696 fprintf(stderr
, "ESC[");
1697 for (i
= 0; i
< csiescseq
.len
; i
++) {
1698 c
= csiescseq
.buf
[i
] & 0xff;
1701 } else if (c
== '\n') {
1702 fprintf(stderr
, "(\\n)");
1703 } else if (c
== '\r') {
1704 fprintf(stderr
, "(\\r)");
1705 } else if (c
== 0x1b) {
1706 fprintf(stderr
, "(\\e)");
1708 fprintf(stderr
, "(%02x)", c
);
1717 memset(&csiescseq
, 0, sizeof(csiescseq
));
1726 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
1728 par
= (narg
= strescseq
.narg
) ? atoi(strescseq
.args
[0]) : 0;
1730 switch (strescseq
.type
) {
1731 case ']': /* OSC -- Operating System Command */
1737 xsettitle(strescseq
.args
[1]);
1743 dec
= base64dec(strescseq
.args
[2]);
1748 fprintf(stderr
, "erresc: invalid base64\n");
1752 case 4: /* color set */
1755 p
= strescseq
.args
[2];
1757 case 104: /* color reset, here p = NULL */
1758 j
= (narg
> 1) ? atoi(strescseq
.args
[1]) : -1;
1759 if (xsetcolorname(j
, p
)) {
1760 fprintf(stderr
, "erresc: invalid color %s\n", p
);
1763 * TODO if defaultbg color is changed, borders
1771 case 'k': /* old title set compatibility */
1772 xsettitle(strescseq
.args
[0]);
1774 case 'P': /* DCS -- Device Control String */
1775 term
.mode
|= ESC_DCS
;
1776 case '_': /* APC -- Application Program Command */
1777 case '^': /* PM -- Privacy Message */
1781 fprintf(stderr
, "erresc: unknown str ");
1789 char *p
= strescseq
.buf
;
1792 strescseq
.buf
[strescseq
.len
] = '\0';
1797 while (strescseq
.narg
< STR_ARG_SIZ
) {
1798 strescseq
.args
[strescseq
.narg
++] = p
;
1799 while ((c
= *p
) != ';' && c
!= '\0')
1813 fprintf(stderr
, "ESC%c", strescseq
.type
);
1814 for (i
= 0; i
< strescseq
.len
; i
++) {
1815 c
= strescseq
.buf
[i
] & 0xff;
1819 } else if (isprint(c
)) {
1821 } else if (c
== '\n') {
1822 fprintf(stderr
, "(\\n)");
1823 } else if (c
== '\r') {
1824 fprintf(stderr
, "(\\r)");
1825 } else if (c
== 0x1b) {
1826 fprintf(stderr
, "(\\e)");
1828 fprintf(stderr
, "(%02x)", c
);
1831 fprintf(stderr
, "ESC\\\n");
1837 memset(&strescseq
, 0, sizeof(strescseq
));
1841 sendbreak(const Arg
*arg
)
1843 if (tcsendbreak(cmdfd
, 0))
1844 perror("Error sending break");
1848 tprinter(char *s
, size_t len
)
1850 if (iofd
!= -1 && xwrite(iofd
, s
, len
) < 0) {
1851 perror("Error writing to output file");
1858 iso14755(const Arg
*arg
)
1861 char *us
, *e
, codepoint
[9], uc
[UTF_SIZ
];
1862 unsigned long utf32
;
1864 if (!(p
= popen(ISO14755CMD
, "r")))
1867 us
= fgets(codepoint
, sizeof(codepoint
), p
);
1870 if (!us
|| *us
== '\0' || *us
== '-' || strlen(us
) > 7)
1872 if ((utf32
= strtoul(us
, &e
, 16)) == ULONG_MAX
||
1873 (*e
!= '\n' && *e
!= '\0'))
1876 ttysend(uc
, utf8encode(utf32
, uc
));
1880 toggleprinter(const Arg
*arg
)
1882 term
.mode
^= MODE_PRINT
;
1886 printscreen(const Arg
*arg
)
1892 printsel(const Arg
*arg
)
1902 if ((ptr
= getsel())) {
1903 tprinter(ptr
, strlen(ptr
));
1914 bp
= &term
.line
[n
][0];
1915 end
= &bp
[MIN(tlinelen(n
), term
.col
) - 1];
1916 if (bp
!= end
|| bp
->u
!= ' ') {
1917 for ( ;bp
<= end
; ++bp
)
1918 tprinter(buf
, utf8encode(bp
->u
, buf
));
1928 for (i
= 0; i
< term
.row
; ++i
)
1938 while (x
< term
.col
&& n
--)
1939 for (++x
; x
< term
.col
&& !term
.tabs
[x
]; ++x
)
1942 while (x
> 0 && n
++)
1943 for (--x
; x
> 0 && !term
.tabs
[x
]; --x
)
1946 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1950 tdefutf8(char ascii
)
1953 term
.mode
|= MODE_UTF8
;
1954 else if (ascii
== '@')
1955 term
.mode
&= ~MODE_UTF8
;
1959 tdeftran(char ascii
)
1961 static char cs
[] = "0B";
1962 static int vcs
[] = {CS_GRAPHIC0
, CS_USA
};
1965 if ((p
= strchr(cs
, ascii
)) == NULL
) {
1966 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
1968 term
.trantbl
[term
.icharset
] = vcs
[p
- cs
];
1977 if (c
== '8') { /* DEC screen alignment test. */
1978 for (x
= 0; x
< term
.col
; ++x
) {
1979 for (y
= 0; y
< term
.row
; ++y
)
1980 tsetchar('E', &term
.c
.attr
, x
, y
);
1986 tstrsequence(uchar c
)
1991 case 0x90: /* DCS -- Device Control String */
1993 term
.esc
|= ESC_DCS
;
1995 case 0x9f: /* APC -- Application Program Command */
1998 case 0x9e: /* PM -- Privacy Message */
2001 case 0x9d: /* OSC -- Operating System Command */
2006 term
.esc
|= ESC_STR
;
2010 tcontrolcode(uchar ascii
)
2017 tmoveto(term
.c
.x
-1, term
.c
.y
);
2020 tmoveto(0, term
.c
.y
);
2025 /* go to first col if the mode is set */
2026 tnewline(IS_SET(MODE_CRLF
));
2028 case '\a': /* BEL */
2029 if (term
.esc
& ESC_STR_END
) {
2030 /* backwards compatibility to xterm */
2036 case '\033': /* ESC */
2038 term
.esc
&= ~(ESC_CSI
|ESC_ALTCHARSET
|ESC_TEST
);
2039 term
.esc
|= ESC_START
;
2041 case '\016': /* SO (LS1 -- Locking shift 1) */
2042 case '\017': /* SI (LS0 -- Locking shift 0) */
2043 term
.charset
= 1 - (ascii
- '\016');
2045 case '\032': /* SUB */
2046 tsetchar('?', &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2047 case '\030': /* CAN */
2050 case '\005': /* ENQ (IGNORED) */
2051 case '\000': /* NUL (IGNORED) */
2052 case '\021': /* XON (IGNORED) */
2053 case '\023': /* XOFF (IGNORED) */
2054 case 0177: /* DEL (IGNORED) */
2056 case 0x80: /* TODO: PAD */
2057 case 0x81: /* TODO: HOP */
2058 case 0x82: /* TODO: BPH */
2059 case 0x83: /* TODO: NBH */
2060 case 0x84: /* TODO: IND */
2062 case 0x85: /* NEL -- Next line */
2063 tnewline(1); /* always go to first col */
2065 case 0x86: /* TODO: SSA */
2066 case 0x87: /* TODO: ESA */
2068 case 0x88: /* HTS -- Horizontal tab stop */
2069 term
.tabs
[term
.c
.x
] = 1;
2071 case 0x89: /* TODO: HTJ */
2072 case 0x8a: /* TODO: VTS */
2073 case 0x8b: /* TODO: PLD */
2074 case 0x8c: /* TODO: PLU */
2075 case 0x8d: /* TODO: RI */
2076 case 0x8e: /* TODO: SS2 */
2077 case 0x8f: /* TODO: SS3 */
2078 case 0x91: /* TODO: PU1 */
2079 case 0x92: /* TODO: PU2 */
2080 case 0x93: /* TODO: STS */
2081 case 0x94: /* TODO: CCH */
2082 case 0x95: /* TODO: MW */
2083 case 0x96: /* TODO: SPA */
2084 case 0x97: /* TODO: EPA */
2085 case 0x98: /* TODO: SOS */
2086 case 0x99: /* TODO: SGCI */
2088 case 0x9a: /* DECID -- Identify Terminal */
2089 ttywrite(vtiden
, strlen(vtiden
));
2091 case 0x9b: /* TODO: CSI */
2092 case 0x9c: /* TODO: ST */
2094 case 0x90: /* DCS -- Device Control String */
2095 case 0x9d: /* OSC -- Operating System Command */
2096 case 0x9e: /* PM -- Privacy Message */
2097 case 0x9f: /* APC -- Application Program Command */
2098 tstrsequence(ascii
);
2101 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2102 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
2106 * returns 1 when the sequence is finished and it hasn't to read
2107 * more characters for this sequence, otherwise 0
2110 eschandle(uchar ascii
)
2114 term
.esc
|= ESC_CSI
;
2117 term
.esc
|= ESC_TEST
;
2120 term
.esc
|= ESC_UTF8
;
2122 case 'P': /* DCS -- Device Control String */
2123 case '_': /* APC -- Application Program Command */
2124 case '^': /* PM -- Privacy Message */
2125 case ']': /* OSC -- Operating System Command */
2126 case 'k': /* old title set compatibility */
2127 tstrsequence(ascii
);
2129 case 'n': /* LS2 -- Locking shift 2 */
2130 case 'o': /* LS3 -- Locking shift 3 */
2131 term
.charset
= 2 + (ascii
- 'n');
2133 case '(': /* GZD4 -- set primary charset G0 */
2134 case ')': /* G1D4 -- set secondary charset G1 */
2135 case '*': /* G2D4 -- set tertiary charset G2 */
2136 case '+': /* G3D4 -- set quaternary charset G3 */
2137 term
.icharset
= ascii
- '(';
2138 term
.esc
|= ESC_ALTCHARSET
;
2140 case 'D': /* IND -- Linefeed */
2141 if (term
.c
.y
== term
.bot
) {
2142 tscrollup(term
.top
, 1);
2144 tmoveto(term
.c
.x
, term
.c
.y
+1);
2147 case 'E': /* NEL -- Next line */
2148 tnewline(1); /* always go to first col */
2150 case 'H': /* HTS -- Horizontal tab stop */
2151 term
.tabs
[term
.c
.x
] = 1;
2153 case 'M': /* RI -- Reverse index */
2154 if (term
.c
.y
== term
.top
) {
2155 tscrolldown(term
.top
, 1);
2157 tmoveto(term
.c
.x
, term
.c
.y
-1);
2160 case 'Z': /* DECID -- Identify Terminal */
2161 ttywrite(vtiden
, strlen(vtiden
));
2163 case 'c': /* RIS -- Reset to inital state */
2168 case '=': /* DECPAM -- Application keypad */
2169 term
.mode
|= MODE_APPKEYPAD
;
2171 case '>': /* DECPNM -- Normal keypad */
2172 term
.mode
&= ~MODE_APPKEYPAD
;
2174 case '7': /* DECSC -- Save Cursor */
2175 tcursor(CURSOR_SAVE
);
2177 case '8': /* DECRC -- Restore Cursor */
2178 tcursor(CURSOR_LOAD
);
2180 case '\\': /* ST -- String Terminator */
2181 if (term
.esc
& ESC_STR_END
)
2185 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2186 (uchar
) ascii
, isprint(ascii
)? ascii
:'.');
2200 control
= ISCONTROL(u
);
2201 if (!IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2205 len
= utf8encode(u
, c
);
2206 if (!control
&& (width
= wcwidth(u
)) == -1) {
2207 memcpy(c
, "\357\277\275", 4); /* UTF_INVALID */
2212 if (IS_SET(MODE_PRINT
))
2216 * STR sequence must be checked before anything else
2217 * because it uses all following characters until it
2218 * receives a ESC, a SUB, a ST or any other C1 control
2221 if (term
.esc
& ESC_STR
) {
2222 if (u
== '\a' || u
== 030 || u
== 032 || u
== 033 ||
2224 term
.esc
&= ~(ESC_START
|ESC_STR
|ESC_DCS
);
2225 if (IS_SET(MODE_SIXEL
)) {
2226 /* TODO: render sixel */;
2227 term
.mode
&= ~MODE_SIXEL
;
2230 term
.esc
|= ESC_STR_END
;
2231 goto check_control_code
;
2235 if (IS_SET(MODE_SIXEL
)) {
2236 /* TODO: implement sixel mode */
2239 if (term
.esc
&ESC_DCS
&& strescseq
.len
== 0 && u
== 'q')
2240 term
.mode
|= MODE_SIXEL
;
2242 if (strescseq
.len
+len
>= sizeof(strescseq
.buf
)-1) {
2244 * Here is a bug in terminals. If the user never sends
2245 * some code to stop the str or esc command, then st
2246 * will stop responding. But this is better than
2247 * silently failing with unknown characters. At least
2248 * then users will report back.
2250 * In the case users ever get fixed, here is the code:
2259 memmove(&strescseq
.buf
[strescseq
.len
], c
, len
);
2260 strescseq
.len
+= len
;
2266 * Actions of control codes must be performed as soon they arrive
2267 * because they can be embedded inside a control sequence, and
2268 * they must not cause conflicts with sequences.
2273 * control codes are not shown ever
2276 } else if (term
.esc
& ESC_START
) {
2277 if (term
.esc
& ESC_CSI
) {
2278 csiescseq
.buf
[csiescseq
.len
++] = u
;
2279 if (BETWEEN(u
, 0x40, 0x7E)
2280 || csiescseq
.len
>= \
2281 sizeof(csiescseq
.buf
)-1) {
2287 } else if (term
.esc
& ESC_UTF8
) {
2289 } else if (term
.esc
& ESC_ALTCHARSET
) {
2291 } else if (term
.esc
& ESC_TEST
) {
2296 /* sequence already finished */
2300 * All characters which form part of a sequence are not
2305 if (sel
.ob
.x
!= -1 && BETWEEN(term
.c
.y
, sel
.ob
.y
, sel
.oe
.y
))
2308 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2309 if (IS_SET(MODE_WRAP
) && (term
.c
.state
& CURSOR_WRAPNEXT
)) {
2310 gp
->mode
|= ATTR_WRAP
;
2312 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2315 if (IS_SET(MODE_INSERT
) && term
.c
.x
+width
< term
.col
)
2316 memmove(gp
+width
, gp
, (term
.col
- term
.c
.x
- width
) * sizeof(Glyph
));
2318 if (term
.c
.x
+width
> term
.col
) {
2320 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2323 tsetchar(u
, &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2326 gp
->mode
|= ATTR_WIDE
;
2327 if (term
.c
.x
+1 < term
.col
) {
2329 gp
[1].mode
= ATTR_WDUMMY
;
2332 if (term
.c
.x
+width
< term
.col
) {
2333 tmoveto(term
.c
.x
+width
, term
.c
.y
);
2335 term
.c
.state
|= CURSOR_WRAPNEXT
;
2340 twrite(const char *buf
, int buflen
, int show_ctrl
)
2346 for (n
= 0; n
< buflen
; n
+= charsize
) {
2347 if (IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2348 /* process a complete utf8 char */
2349 charsize
= utf8decode(buf
+ n
, &u
, buflen
- n
);
2356 if (show_ctrl
&& ISCONTROL(u
)) {
2361 } else if (u
!= '\n' && u
!= '\r' && u
!= '\t') {
2372 tresize(int col
, int row
)
2375 int minrow
= MIN(row
, term
.row
);
2376 int mincol
= MIN(col
, term
.col
);
2380 if (col
< 1 || row
< 1) {
2382 "tresize: error resizing to %dx%d\n", col
, row
);
2387 * slide screen to keep cursor where we expect it -
2388 * tscrollup would work here, but we can optimize to
2389 * memmove because we're freeing the earlier lines
2391 for (i
= 0; i
<= term
.c
.y
- row
; i
++) {
2395 /* ensure that both src and dst are not NULL */
2397 memmove(term
.line
, term
.line
+ i
, row
* sizeof(Line
));
2398 memmove(term
.alt
, term
.alt
+ i
, row
* sizeof(Line
));
2400 for (i
+= row
; i
< term
.row
; i
++) {
2405 /* resize to new height */
2406 term
.line
= xrealloc(term
.line
, row
* sizeof(Line
));
2407 term
.alt
= xrealloc(term
.alt
, row
* sizeof(Line
));
2408 term
.dirty
= xrealloc(term
.dirty
, row
* sizeof(*term
.dirty
));
2409 term
.tabs
= xrealloc(term
.tabs
, col
* sizeof(*term
.tabs
));
2411 /* resize each row to new width, zero-pad if needed */
2412 for (i
= 0; i
< minrow
; i
++) {
2413 term
.line
[i
] = xrealloc(term
.line
[i
], col
* sizeof(Glyph
));
2414 term
.alt
[i
] = xrealloc(term
.alt
[i
], col
* sizeof(Glyph
));
2417 /* allocate any new rows */
2418 for (/* i = minrow */; i
< row
; i
++) {
2419 term
.line
[i
] = xmalloc(col
* sizeof(Glyph
));
2420 term
.alt
[i
] = xmalloc(col
* sizeof(Glyph
));
2422 if (col
> term
.col
) {
2423 bp
= term
.tabs
+ term
.col
;
2425 memset(bp
, 0, sizeof(*term
.tabs
) * (col
- term
.col
));
2426 while (--bp
> term
.tabs
&& !*bp
)
2428 for (bp
+= tabspaces
; bp
< term
.tabs
+ col
; bp
+= tabspaces
)
2431 /* update terminal size */
2434 /* reset scrolling region */
2435 tsetscroll(0, row
-1);
2436 /* make use of the LIMIT in tmoveto */
2437 tmoveto(term
.c
.x
, term
.c
.y
);
2438 /* Clearing both screens (it makes dirty all lines) */
2440 for (i
= 0; i
< 2; i
++) {
2441 if (mincol
< col
&& 0 < minrow
) {
2442 tclearregion(mincol
, 0, col
- 1, minrow
- 1);
2444 if (0 < col
&& minrow
< row
) {
2445 tclearregion(0, minrow
, col
- 1, row
- 1);
2448 tcursor(CURSOR_LOAD
);
2467 numlock(const Arg
*dummy
)