Xinqi Bao's Git
1 /* See LICENSE for license details. */
14 #include <sys/ioctl.h>
15 #include <sys/select.h>
18 #include <sys/types.h>
24 #include <fontconfig/fontconfig.h>
28 #include <X11/cursorfont.h>
29 #include <X11/Xft/Xft.h>
36 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
38 #elif defined(__FreeBSD__) || defined(__DragonFly__)
43 #define UTF_INVALID 0xFFFD
44 #define ESC_BUF_SIZ (128*UTF_SIZ)
45 #define ESC_ARG_SIZ 16
46 #define STR_BUF_SIZ ESC_BUF_SIZ
47 #define STR_ARG_SIZ ESC_ARG_SIZ
50 #define NUMMAXLEN(x) ((int)(sizeof(x) * 2.56 + 0.5) + 1)
51 #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == '\177')
52 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
53 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
54 #define ISDELIM(u) (utf8strchr(worddelimiters, u) != NULL)
57 #define ISO14755CMD "dmenu -w \"$WINDOWID\" -p codepoint: </dev/null"
59 enum cursor_movement
{
83 ESC_STR
= 4, /* OSC, PM, APC */
85 ESC_STR_END
= 16, /* a final string was encountered */
86 ESC_TEST
= 32, /* Enter in test mode */
91 /* CSI Escape sequence structs */
92 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
94 char buf
[ESC_BUF_SIZ
]; /* raw string */
95 int len
; /* raw string length */
98 int narg
; /* nb of args */
102 /* STR Escape sequence structs */
103 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
105 char type
; /* ESC type ... */
106 char buf
[STR_BUF_SIZ
]; /* raw string */
107 int len
; /* raw string length */
108 char *args
[STR_ARG_SIZ
];
109 int narg
; /* nb of args */
113 static void execsh(char **);
114 static void stty(char **);
115 static void sigchld(int);
117 static void csidump(void);
118 static void csihandle(void);
119 static void csiparse(void);
120 static void csireset(void);
121 static int eschandle(uchar
);
122 static void strdump(void);
123 static void strhandle(void);
124 static void strparse(void);
125 static void strreset(void);
127 static void tprinter(char *, size_t);
128 static void tdumpsel(void);
129 static void tdumpline(int);
130 static void tdump(void);
131 static void tclearregion(int, int, int, int);
132 static void tcursor(int);
133 static void tdeletechar(int);
134 static void tdeleteline(int);
135 static void tinsertblank(int);
136 static void tinsertblankline(int);
137 static int tlinelen(int);
138 static void tmoveto(int, int);
139 static void tmoveato(int, int);
140 static void tnewline(int);
141 static void tputtab(int);
142 static void tputc(Rune
);
143 static void treset(void);
144 static void tscrollup(int, int);
145 static void tscrolldown(int, int);
146 static void tsetattr(int *, int);
147 static void tsetchar(Rune
, Glyph
*, int, int);
148 static void tsetscroll(int, int);
149 static void tswapscreen(void);
150 static void tsetmode(int, int, int *, int);
151 static int twrite(const char *, int, int);
152 static void tfulldirt(void);
153 static void tcontrolcode(uchar
);
154 static void tdectest(char );
155 static void tdefutf8(char);
156 static int32_t tdefcolor(int *, int *, int);
157 static void tdeftran(char);
158 static void tstrsequence(uchar
);
160 static void selscroll(int, int);
161 static void selsnap(int *, int *, int);
163 static Rune
utf8decodebyte(char, size_t *);
164 static char utf8encodebyte(Rune
, size_t);
165 static char *utf8strchr(char *s
, Rune u
);
166 static size_t utf8validate(Rune
*, size_t);
168 static char *base64dec(const char *);
170 static ssize_t
xwrite(int, const char *, size_t);
177 int oldbutton
= 3; /* button event on startup: 3 = release */
179 static CSIEscape csiescseq
;
180 static STREscape strescseq
;
183 static uchar utfbyte
[UTF_SIZ
+ 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
184 static uchar utfmask
[UTF_SIZ
+ 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
185 static Rune utfmin
[UTF_SIZ
+ 1] = { 0, 0, 0x80, 0x800, 0x10000};
186 static Rune utfmax
[UTF_SIZ
+ 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
189 xwrite(int fd
, const char *s
, size_t len
)
195 r
= write(fd
, s
, len
);
208 void *p
= malloc(len
);
211 die("Out of memory\n");
217 xrealloc(void *p
, size_t len
)
219 if ((p
= realloc(p
, len
)) == NULL
)
220 die("Out of memory\n");
228 if ((s
= strdup(s
)) == NULL
)
229 die("Out of memory\n");
235 utf8decode(const char *c
, Rune
*u
, size_t clen
)
237 size_t i
, j
, len
, type
;
243 udecoded
= utf8decodebyte(c
[0], &len
);
244 if (!BETWEEN(len
, 1, UTF_SIZ
))
246 for (i
= 1, j
= 1; i
< clen
&& j
< len
; ++i
, ++j
) {
247 udecoded
= (udecoded
<< 6) | utf8decodebyte(c
[i
], &type
);
254 utf8validate(u
, len
);
260 utf8decodebyte(char c
, size_t *i
)
262 for (*i
= 0; *i
< LEN(utfmask
); ++(*i
))
263 if (((uchar
)c
& utfmask
[*i
]) == utfbyte
[*i
])
264 return (uchar
)c
& ~utfmask
[*i
];
270 utf8encode(Rune u
, char *c
)
274 len
= utf8validate(&u
, 0);
278 for (i
= len
- 1; i
!= 0; --i
) {
279 c
[i
] = utf8encodebyte(u
, 0);
282 c
[0] = utf8encodebyte(u
, len
);
288 utf8encodebyte(Rune u
, size_t i
)
290 return utfbyte
[i
] | (u
& ~utfmask
[i
]);
294 utf8strchr(char *s
, Rune u
)
300 for (i
= 0, j
= 0; i
< len
; i
+= j
) {
301 if (!(j
= utf8decode(&s
[i
], &r
, len
- i
)))
311 utf8validate(Rune
*u
, size_t i
)
313 if (!BETWEEN(*u
, utfmin
[i
], utfmax
[i
]) || BETWEEN(*u
, 0xD800, 0xDFFF))
315 for (i
= 1; *u
> utfmax
[i
]; ++i
)
321 static const char base64_digits
[] = {
322 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
323 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0,
324 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
325 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
326 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
327 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0,
328 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
329 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
330 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
331 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
332 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
333 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
337 base64dec_getc(const char **src
)
339 while (**src
&& !isprint(**src
)) (*src
)++;
344 base64dec(const char *src
)
346 size_t in_len
= strlen(src
);
350 in_len
+= 4 - (in_len
% 4);
351 result
= dst
= xmalloc(in_len
/ 4 * 3 + 1);
353 int a
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
354 int b
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
355 int c
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
356 int d
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
358 *dst
++ = (a
<< 2) | ((b
& 0x30) >> 4);
361 *dst
++ = ((b
& 0x0f) << 4) | ((c
& 0x3c) >> 2);
364 *dst
++ = ((c
& 0x03) << 6) | d
;
373 clock_gettime(CLOCK_MONOTONIC
, &sel
.tclick1
);
374 clock_gettime(CLOCK_MONOTONIC
, &sel
.tclick2
);
379 sel
.clipboard
= NULL
;
387 if (term
.line
[y
][i
- 1].mode
& ATTR_WRAP
)
390 while (i
> 0 && term
.line
[y
][i
- 1].u
== ' ')
401 if (sel
.type
== SEL_REGULAR
&& sel
.ob
.y
!= sel
.oe
.y
) {
402 sel
.nb
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.ob
.x
: sel
.oe
.x
;
403 sel
.ne
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.oe
.x
: sel
.ob
.x
;
405 sel
.nb
.x
= MIN(sel
.ob
.x
, sel
.oe
.x
);
406 sel
.ne
.x
= MAX(sel
.ob
.x
, sel
.oe
.x
);
408 sel
.nb
.y
= MIN(sel
.ob
.y
, sel
.oe
.y
);
409 sel
.ne
.y
= MAX(sel
.ob
.y
, sel
.oe
.y
);
411 selsnap(&sel
.nb
.x
, &sel
.nb
.y
, -1);
412 selsnap(&sel
.ne
.x
, &sel
.ne
.y
, +1);
414 /* expand selection over line breaks */
415 if (sel
.type
== SEL_RECTANGULAR
)
417 i
= tlinelen(sel
.nb
.y
);
420 if (tlinelen(sel
.ne
.y
) <= sel
.ne
.x
)
421 sel
.ne
.x
= term
.col
- 1;
425 selected(int x
, int y
)
427 if (sel
.mode
== SEL_EMPTY
)
430 if (sel
.type
== SEL_RECTANGULAR
)
431 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
432 && BETWEEN(x
, sel
.nb
.x
, sel
.ne
.x
);
434 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
435 && (y
!= sel
.nb
.y
|| x
>= sel
.nb
.x
)
436 && (y
!= sel
.ne
.y
|| x
<= sel
.ne
.x
);
440 selsnap(int *x
, int *y
, int direction
)
442 int newx
, newy
, xt
, yt
;
443 int delim
, prevdelim
;
449 * Snap around if the word wraps around at the end or
450 * beginning of a line.
452 prevgp
= &term
.line
[*y
][*x
];
453 prevdelim
= ISDELIM(prevgp
->u
);
455 newx
= *x
+ direction
;
457 if (!BETWEEN(newx
, 0, term
.col
- 1)) {
459 newx
= (newx
+ term
.col
) % term
.col
;
460 if (!BETWEEN(newy
, 0, term
.row
- 1))
466 yt
= newy
, xt
= newx
;
467 if (!(term
.line
[yt
][xt
].mode
& ATTR_WRAP
))
471 if (newx
>= tlinelen(newy
))
474 gp
= &term
.line
[newy
][newx
];
475 delim
= ISDELIM(gp
->u
);
476 if (!(gp
->mode
& ATTR_WDUMMY
) && (delim
!= prevdelim
477 || (delim
&& gp
->u
!= prevgp
->u
)))
488 * Snap around if the the previous line or the current one
489 * has set ATTR_WRAP at its end. Then the whole next or
490 * previous line will be selected.
492 *x
= (direction
< 0) ? 0 : term
.col
- 1;
494 for (; *y
> 0; *y
+= direction
) {
495 if (!(term
.line
[*y
-1][term
.col
-1].mode
500 } else if (direction
> 0) {
501 for (; *y
< term
.row
-1; *y
+= direction
) {
502 if (!(term
.line
[*y
][term
.col
-1].mode
516 int y
, bufsize
, lastx
, linelen
;
522 bufsize
= (term
.col
+1) * (sel
.ne
.y
-sel
.nb
.y
+1) * UTF_SIZ
;
523 ptr
= str
= xmalloc(bufsize
);
525 /* append every set & selected glyph to the selection */
526 for (y
= sel
.nb
.y
; y
<= sel
.ne
.y
; y
++) {
527 if ((linelen
= tlinelen(y
)) == 0) {
532 if (sel
.type
== SEL_RECTANGULAR
) {
533 gp
= &term
.line
[y
][sel
.nb
.x
];
536 gp
= &term
.line
[y
][sel
.nb
.y
== y
? sel
.nb
.x
: 0];
537 lastx
= (sel
.ne
.y
== y
) ? sel
.ne
.x
: term
.col
-1;
539 last
= &term
.line
[y
][MIN(lastx
, linelen
-1)];
540 while (last
>= gp
&& last
->u
== ' ')
543 for ( ; gp
<= last
; ++gp
) {
544 if (gp
->mode
& ATTR_WDUMMY
)
547 ptr
+= utf8encode(gp
->u
, ptr
);
551 * Copy and pasting of line endings is inconsistent
552 * in the inconsistent terminal and GUI world.
553 * The best solution seems like to produce '\n' when
554 * something is copied from st and convert '\n' to
555 * '\r', when something to be pasted is received by
557 * FIXME: Fix the computer world.
559 if ((y
< sel
.ne
.y
|| lastx
>= linelen
) && !(last
->mode
& ATTR_WRAP
))
573 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
577 die(const char *errstr
, ...)
581 va_start(ap
, errstr
);
582 vfprintf(stderr
, errstr
, ap
);
591 const struct passwd
*pw
;
594 if ((pw
= getpwuid(getuid())) == NULL
) {
596 die("getpwuid:%s\n", strerror(errno
));
598 die("who are you?\n");
601 if ((sh
= getenv("SHELL")) == NULL
)
602 sh
= (pw
->pw_shell
[0]) ? pw
->pw_shell
: shell
;
610 DEFAULT(args
, ((char *[]) {prog
, NULL
}));
615 setenv("LOGNAME", pw
->pw_name
, 1);
616 setenv("USER", pw
->pw_name
, 1);
617 setenv("SHELL", sh
, 1);
618 setenv("HOME", pw
->pw_dir
, 1);
619 setenv("TERM", termname
, 1);
621 signal(SIGCHLD
, SIG_DFL
);
622 signal(SIGHUP
, SIG_DFL
);
623 signal(SIGINT
, SIG_DFL
);
624 signal(SIGQUIT
, SIG_DFL
);
625 signal(SIGTERM
, SIG_DFL
);
626 signal(SIGALRM
, SIG_DFL
);
638 if ((p
= waitpid(pid
, &stat
, WNOHANG
)) < 0)
639 die("Waiting for pid %hd failed: %s\n", pid
, strerror(errno
));
644 if (!WIFEXITED(stat
) || WEXITSTATUS(stat
))
645 die("child finished with error '%d'\n", stat
);
653 char cmd
[_POSIX_ARG_MAX
], **p
, *q
, *s
;
656 if ((n
= strlen(stty_args
)) > sizeof(cmd
)-1)
657 die("incorrect stty parameters\n");
658 memcpy(cmd
, stty_args
, n
);
660 siz
= sizeof(cmd
) - n
;
661 for (p
= args
; p
&& (s
= *p
); ++p
) {
662 if ((n
= strlen(s
)) > siz
-1)
663 die("stty parameter length too long\n");
670 if (system(cmd
) != 0)
671 perror("Couldn't call stty");
675 ttynew(char *line
, char *out
, char **args
)
678 struct winsize w
= {term
.row
, term
.col
, 0, 0};
681 term
.mode
|= MODE_PRINT
;
682 iofd
= (!strcmp(out
, "-")) ?
683 1 : open(out
, O_WRONLY
| O_CREAT
, 0666);
685 fprintf(stderr
, "Error opening %s:%s\n",
686 out
, strerror(errno
));
691 if ((cmdfd
= open(line
, O_RDWR
)) < 0)
692 die("open line failed: %s\n", strerror(errno
));
698 /* seems to work fine on linux, openbsd and freebsd */
699 if (openpty(&m
, &s
, NULL
, NULL
, &w
) < 0)
700 die("openpty failed: %s\n", strerror(errno
));
702 switch (pid
= fork()) {
704 die("fork failed\n");
708 setsid(); /* create a new process group */
712 if (ioctl(s
, TIOCSCTTY
, NULL
) < 0)
713 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno
));
721 signal(SIGCHLD
, sigchld
);
729 static char buf
[BUFSIZ
];
730 static int buflen
= 0;
734 /* append read bytes to unprocessed bytes */
735 if ((ret
= read(cmdfd
, buf
+buflen
, LEN(buf
)-buflen
)) < 0)
736 die("Couldn't read from shell: %s\n", strerror(errno
));
739 written
= twrite(buf
, buflen
, 0);
741 /* keep any uncomplete utf8 char for the next call */
743 memmove(buf
, buf
+ written
, buflen
);
749 ttywrite(const char *s
, size_t n
)
756 * Remember that we are using a pty, which might be a modem line.
757 * Writing too much will clog the line. That's why we are doing this
759 * FIXME: Migrate the world to Plan 9.
767 /* Check if we can write. */
768 if (pselect(cmdfd
+1, &rfd
, &wfd
, NULL
, NULL
, NULL
) < 0) {
771 die("select failed: %s\n", strerror(errno
));
773 if (FD_ISSET(cmdfd
, &wfd
)) {
775 * Only write the bytes written by ttywrite() or the
776 * default of 256. This seems to be a reasonable value
777 * for a serial line. Bigger values might clog the I/O.
779 if ((r
= write(cmdfd
, s
, (n
< lim
)? n
: lim
)) < 0)
783 * We weren't able to write out everything.
784 * This means the buffer is getting full
792 /* All bytes have been written. */
796 if (FD_ISSET(cmdfd
, &rfd
))
802 die("write error on tty: %s\n", strerror(errno
));
806 ttysend(char *s
, size_t n
)
809 if (IS_SET(MODE_ECHO
))
814 ttyresize(int tw
, int th
)
822 if (ioctl(cmdfd
, TIOCSWINSZ
, &w
) < 0)
823 fprintf(stderr
, "Couldn't set window size: %s\n", strerror(errno
));
831 for (i
= 0; i
< term
.row
-1; i
++) {
832 for (j
= 0; j
< term
.col
-1; j
++) {
833 if (term
.line
[i
][j
].mode
& attr
)
842 tsetdirt(int top
, int bot
)
846 LIMIT(top
, 0, term
.row
-1);
847 LIMIT(bot
, 0, term
.row
-1);
849 for (i
= top
; i
<= bot
; i
++)
854 tsetdirtattr(int attr
)
858 for (i
= 0; i
< term
.row
-1; i
++) {
859 for (j
= 0; j
< term
.col
-1; j
++) {
860 if (term
.line
[i
][j
].mode
& attr
) {
871 tsetdirt(0, term
.row
-1);
878 int alt
= IS_SET(MODE_ALTSCREEN
);
880 if (mode
== CURSOR_SAVE
) {
882 } else if (mode
== CURSOR_LOAD
) {
884 tmoveto(c
[alt
].x
, c
[alt
].y
);
897 }, .x
= 0, .y
= 0, .state
= CURSOR_DEFAULT
};
899 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
900 for (i
= tabspaces
; i
< term
.col
; i
+= tabspaces
)
903 term
.bot
= term
.row
- 1;
904 term
.mode
= MODE_WRAP
|MODE_UTF8
;
905 memset(term
.trantbl
, CS_USA
, sizeof(term
.trantbl
));
908 for (i
= 0; i
< 2; i
++) {
910 tcursor(CURSOR_SAVE
);
911 tclearregion(0, 0, term
.col
-1, term
.row
-1);
917 tnew(int col
, int row
)
919 term
= (Term
){ .c
= { .attr
= { .fg
= defaultfg
, .bg
= defaultbg
} } };
929 Line
*tmp
= term
.line
;
931 term
.line
= term
.alt
;
933 term
.mode
^= MODE_ALTSCREEN
;
938 tscrolldown(int orig
, int n
)
943 LIMIT(n
, 0, term
.bot
-orig
+1);
945 tsetdirt(orig
, term
.bot
-n
);
946 tclearregion(0, term
.bot
-n
+1, term
.col
-1, term
.bot
);
948 for (i
= term
.bot
; i
>= orig
+n
; i
--) {
950 term
.line
[i
] = term
.line
[i
-n
];
951 term
.line
[i
-n
] = temp
;
958 tscrollup(int orig
, int n
)
963 LIMIT(n
, 0, term
.bot
-orig
+1);
965 tclearregion(0, orig
, term
.col
-1, orig
+n
-1);
966 tsetdirt(orig
+n
, term
.bot
);
968 for (i
= orig
; i
<= term
.bot
-n
; i
++) {
970 term
.line
[i
] = term
.line
[i
+n
];
971 term
.line
[i
+n
] = temp
;
978 selscroll(int orig
, int n
)
983 if (BETWEEN(sel
.ob
.y
, orig
, term
.bot
) || BETWEEN(sel
.oe
.y
, orig
, term
.bot
)) {
984 if ((sel
.ob
.y
+= n
) > term
.bot
|| (sel
.oe
.y
+= n
) < term
.top
) {
988 if (sel
.type
== SEL_RECTANGULAR
) {
989 if (sel
.ob
.y
< term
.top
)
991 if (sel
.oe
.y
> term
.bot
)
994 if (sel
.ob
.y
< term
.top
) {
998 if (sel
.oe
.y
> term
.bot
) {
1000 sel
.oe
.x
= term
.col
;
1008 tnewline(int first_col
)
1012 if (y
== term
.bot
) {
1013 tscrollup(term
.top
, 1);
1017 tmoveto(first_col
? 0 : term
.c
.x
, y
);
1023 char *p
= csiescseq
.buf
, *np
;
1032 csiescseq
.buf
[csiescseq
.len
] = '\0';
1033 while (p
< csiescseq
.buf
+csiescseq
.len
) {
1035 v
= strtol(p
, &np
, 10);
1038 if (v
== LONG_MAX
|| v
== LONG_MIN
)
1040 csiescseq
.arg
[csiescseq
.narg
++] = v
;
1042 if (*p
!= ';' || csiescseq
.narg
== ESC_ARG_SIZ
)
1046 csiescseq
.mode
[0] = *p
++;
1047 csiescseq
.mode
[1] = (p
< csiescseq
.buf
+csiescseq
.len
) ? *p
: '\0';
1050 /* for absolute user moves, when decom is set */
1052 tmoveato(int x
, int y
)
1054 tmoveto(x
, y
+ ((term
.c
.state
& CURSOR_ORIGIN
) ? term
.top
: 0));
1058 tmoveto(int x
, int y
)
1062 if (term
.c
.state
& CURSOR_ORIGIN
) {
1067 maxy
= term
.row
- 1;
1069 term
.c
.state
&= ~CURSOR_WRAPNEXT
;
1070 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1071 term
.c
.y
= LIMIT(y
, miny
, maxy
);
1075 tsetchar(Rune u
, Glyph
*attr
, int x
, int y
)
1077 static char *vt100_0
[62] = { /* 0x41 - 0x7e */
1078 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1079 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1080 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1081 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1082 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1083 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1084 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1085 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1089 * The table is proudly stolen from rxvt.
1091 if (term
.trantbl
[term
.charset
] == CS_GRAPHIC0
&&
1092 BETWEEN(u
, 0x41, 0x7e) && vt100_0
[u
- 0x41])
1093 utf8decode(vt100_0
[u
- 0x41], &u
, UTF_SIZ
);
1095 if (term
.line
[y
][x
].mode
& ATTR_WIDE
) {
1096 if (x
+1 < term
.col
) {
1097 term
.line
[y
][x
+1].u
= ' ';
1098 term
.line
[y
][x
+1].mode
&= ~ATTR_WDUMMY
;
1100 } else if (term
.line
[y
][x
].mode
& ATTR_WDUMMY
) {
1101 term
.line
[y
][x
-1].u
= ' ';
1102 term
.line
[y
][x
-1].mode
&= ~ATTR_WIDE
;
1106 term
.line
[y
][x
] = *attr
;
1107 term
.line
[y
][x
].u
= u
;
1111 tclearregion(int x1
, int y1
, int x2
, int y2
)
1117 temp
= x1
, x1
= x2
, x2
= temp
;
1119 temp
= y1
, y1
= y2
, y2
= temp
;
1121 LIMIT(x1
, 0, term
.col
-1);
1122 LIMIT(x2
, 0, term
.col
-1);
1123 LIMIT(y1
, 0, term
.row
-1);
1124 LIMIT(y2
, 0, term
.row
-1);
1126 for (y
= y1
; y
<= y2
; y
++) {
1128 for (x
= x1
; x
<= x2
; x
++) {
1129 gp
= &term
.line
[y
][x
];
1132 gp
->fg
= term
.c
.attr
.fg
;
1133 gp
->bg
= term
.c
.attr
.bg
;
1146 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1150 size
= term
.col
- src
;
1151 line
= term
.line
[term
.c
.y
];
1153 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1154 tclearregion(term
.col
-n
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1163 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1167 size
= term
.col
- dst
;
1168 line
= term
.line
[term
.c
.y
];
1170 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1171 tclearregion(src
, term
.c
.y
, dst
- 1, term
.c
.y
);
1175 tinsertblankline(int n
)
1177 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1178 tscrolldown(term
.c
.y
, n
);
1184 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1185 tscrollup(term
.c
.y
, n
);
1189 tdefcolor(int *attr
, int *npar
, int l
)
1194 switch (attr
[*npar
+ 1]) {
1195 case 2: /* direct color in RGB space */
1196 if (*npar
+ 4 >= l
) {
1198 "erresc(38): Incorrect number of parameters (%d)\n",
1202 r
= attr
[*npar
+ 2];
1203 g
= attr
[*npar
+ 3];
1204 b
= attr
[*npar
+ 4];
1206 if (!BETWEEN(r
, 0, 255) || !BETWEEN(g
, 0, 255) || !BETWEEN(b
, 0, 255))
1207 fprintf(stderr
, "erresc: bad rgb color (%u,%u,%u)\n",
1210 idx
= TRUECOLOR(r
, g
, b
);
1212 case 5: /* indexed color */
1213 if (*npar
+ 2 >= l
) {
1215 "erresc(38): Incorrect number of parameters (%d)\n",
1220 if (!BETWEEN(attr
[*npar
], 0, 255))
1221 fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[*npar
]);
1225 case 0: /* implemented defined (only foreground) */
1226 case 1: /* transparent */
1227 case 3: /* direct color in CMY space */
1228 case 4: /* direct color in CMYK space */
1231 "erresc(38): gfx attr %d unknown\n", attr
[*npar
]);
1239 tsetattr(int *attr
, int l
)
1244 for (i
= 0; i
< l
; i
++) {
1247 term
.c
.attr
.mode
&= ~(
1256 term
.c
.attr
.fg
= defaultfg
;
1257 term
.c
.attr
.bg
= defaultbg
;
1260 term
.c
.attr
.mode
|= ATTR_BOLD
;
1263 term
.c
.attr
.mode
|= ATTR_FAINT
;
1266 term
.c
.attr
.mode
|= ATTR_ITALIC
;
1269 term
.c
.attr
.mode
|= ATTR_UNDERLINE
;
1271 case 5: /* slow blink */
1273 case 6: /* rapid blink */
1274 term
.c
.attr
.mode
|= ATTR_BLINK
;
1277 term
.c
.attr
.mode
|= ATTR_REVERSE
;
1280 term
.c
.attr
.mode
|= ATTR_INVISIBLE
;
1283 term
.c
.attr
.mode
|= ATTR_STRUCK
;
1286 term
.c
.attr
.mode
&= ~(ATTR_BOLD
| ATTR_FAINT
);
1289 term
.c
.attr
.mode
&= ~ATTR_ITALIC
;
1292 term
.c
.attr
.mode
&= ~ATTR_UNDERLINE
;
1295 term
.c
.attr
.mode
&= ~ATTR_BLINK
;
1298 term
.c
.attr
.mode
&= ~ATTR_REVERSE
;
1301 term
.c
.attr
.mode
&= ~ATTR_INVISIBLE
;
1304 term
.c
.attr
.mode
&= ~ATTR_STRUCK
;
1307 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1308 term
.c
.attr
.fg
= idx
;
1311 term
.c
.attr
.fg
= defaultfg
;
1314 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1315 term
.c
.attr
.bg
= idx
;
1318 term
.c
.attr
.bg
= defaultbg
;
1321 if (BETWEEN(attr
[i
], 30, 37)) {
1322 term
.c
.attr
.fg
= attr
[i
] - 30;
1323 } else if (BETWEEN(attr
[i
], 40, 47)) {
1324 term
.c
.attr
.bg
= attr
[i
] - 40;
1325 } else if (BETWEEN(attr
[i
], 90, 97)) {
1326 term
.c
.attr
.fg
= attr
[i
] - 90 + 8;
1327 } else if (BETWEEN(attr
[i
], 100, 107)) {
1328 term
.c
.attr
.bg
= attr
[i
] - 100 + 8;
1331 "erresc(default): gfx attr %d unknown\n",
1332 attr
[i
]), csidump();
1340 tsetscroll(int t
, int b
)
1344 LIMIT(t
, 0, term
.row
-1);
1345 LIMIT(b
, 0, term
.row
-1);
1356 tsetmode(int priv
, int set
, int *args
, int narg
)
1361 for (lim
= args
+ narg
; args
< lim
; ++args
) {
1364 case 1: /* DECCKM -- Cursor key */
1365 MODBIT(term
.mode
, set
, MODE_APPCURSOR
);
1367 case 5: /* DECSCNM -- Reverse video */
1369 MODBIT(term
.mode
, set
, MODE_REVERSE
);
1370 if (mode
!= term
.mode
)
1373 case 6: /* DECOM -- Origin */
1374 MODBIT(term
.c
.state
, set
, CURSOR_ORIGIN
);
1377 case 7: /* DECAWM -- Auto wrap */
1378 MODBIT(term
.mode
, set
, MODE_WRAP
);
1380 case 0: /* Error (IGNORED) */
1381 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1382 case 3: /* DECCOLM -- Column (IGNORED) */
1383 case 4: /* DECSCLM -- Scroll (IGNORED) */
1384 case 8: /* DECARM -- Auto repeat (IGNORED) */
1385 case 18: /* DECPFF -- Printer feed (IGNORED) */
1386 case 19: /* DECPEX -- Printer extent (IGNORED) */
1387 case 42: /* DECNRCM -- National characters (IGNORED) */
1388 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1390 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1391 MODBIT(term
.mode
, !set
, MODE_HIDE
);
1393 case 9: /* X10 mouse compatibility mode */
1394 xsetpointermotion(0);
1395 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1396 MODBIT(term
.mode
, set
, MODE_MOUSEX10
);
1398 case 1000: /* 1000: report button press */
1399 xsetpointermotion(0);
1400 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1401 MODBIT(term
.mode
, set
, MODE_MOUSEBTN
);
1403 case 1002: /* 1002: report motion on button press */
1404 xsetpointermotion(0);
1405 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1406 MODBIT(term
.mode
, set
, MODE_MOUSEMOTION
);
1408 case 1003: /* 1003: enable all mouse motions */
1409 xsetpointermotion(set
);
1410 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1411 MODBIT(term
.mode
, set
, MODE_MOUSEMANY
);
1413 case 1004: /* 1004: send focus events to tty */
1414 MODBIT(term
.mode
, set
, MODE_FOCUS
);
1416 case 1006: /* 1006: extended reporting mode */
1417 MODBIT(term
.mode
, set
, MODE_MOUSESGR
);
1420 MODBIT(term
.mode
, set
, MODE_8BIT
);
1422 case 1049: /* swap screen & set/restore cursor as xterm */
1423 if (!allowaltscreen
)
1425 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1427 case 47: /* swap screen */
1429 if (!allowaltscreen
)
1431 alt
= IS_SET(MODE_ALTSCREEN
);
1433 tclearregion(0, 0, term
.col
-1,
1436 if (set
^ alt
) /* set is always 1 or 0 */
1442 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1444 case 2004: /* 2004: bracketed paste mode */
1445 MODBIT(term
.mode
, set
, MODE_BRCKTPASTE
);
1447 /* Not implemented mouse modes. See comments there. */
1448 case 1001: /* mouse highlight mode; can hang the
1449 terminal by design when implemented. */
1450 case 1005: /* UTF-8 mouse mode; will confuse
1451 applications not supporting UTF-8
1453 case 1015: /* urxvt mangled mouse mode; incompatible
1454 and can be mistaken for other control
1458 "erresc: unknown private set/reset mode %d\n",
1464 case 0: /* Error (IGNORED) */
1466 case 2: /* KAM -- keyboard action */
1467 MODBIT(term
.mode
, set
, MODE_KBDLOCK
);
1469 case 4: /* IRM -- Insertion-replacement */
1470 MODBIT(term
.mode
, set
, MODE_INSERT
);
1472 case 12: /* SRM -- Send/Receive */
1473 MODBIT(term
.mode
, !set
, MODE_ECHO
);
1475 case 20: /* LNM -- Linefeed/new line */
1476 MODBIT(term
.mode
, set
, MODE_CRLF
);
1480 "erresc: unknown set/reset mode %d\n",
1494 switch (csiescseq
.mode
[0]) {
1497 fprintf(stderr
, "erresc: unknown csi ");
1501 case '@': /* ICH -- Insert <n> blank char */
1502 DEFAULT(csiescseq
.arg
[0], 1);
1503 tinsertblank(csiescseq
.arg
[0]);
1505 case 'A': /* CUU -- Cursor <n> Up */
1506 DEFAULT(csiescseq
.arg
[0], 1);
1507 tmoveto(term
.c
.x
, term
.c
.y
-csiescseq
.arg
[0]);
1509 case 'B': /* CUD -- Cursor <n> Down */
1510 case 'e': /* VPR --Cursor <n> Down */
1511 DEFAULT(csiescseq
.arg
[0], 1);
1512 tmoveto(term
.c
.x
, term
.c
.y
+csiescseq
.arg
[0]);
1514 case 'i': /* MC -- Media Copy */
1515 switch (csiescseq
.arg
[0]) {
1520 tdumpline(term
.c
.y
);
1526 term
.mode
&= ~MODE_PRINT
;
1529 term
.mode
|= MODE_PRINT
;
1533 case 'c': /* DA -- Device Attributes */
1534 if (csiescseq
.arg
[0] == 0)
1535 ttywrite(vtiden
, strlen(vtiden
));
1537 case 'C': /* CUF -- Cursor <n> Forward */
1538 case 'a': /* HPR -- Cursor <n> Forward */
1539 DEFAULT(csiescseq
.arg
[0], 1);
1540 tmoveto(term
.c
.x
+csiescseq
.arg
[0], term
.c
.y
);
1542 case 'D': /* CUB -- Cursor <n> Backward */
1543 DEFAULT(csiescseq
.arg
[0], 1);
1544 tmoveto(term
.c
.x
-csiescseq
.arg
[0], term
.c
.y
);
1546 case 'E': /* CNL -- Cursor <n> Down and first col */
1547 DEFAULT(csiescseq
.arg
[0], 1);
1548 tmoveto(0, term
.c
.y
+csiescseq
.arg
[0]);
1550 case 'F': /* CPL -- Cursor <n> Up and first col */
1551 DEFAULT(csiescseq
.arg
[0], 1);
1552 tmoveto(0, term
.c
.y
-csiescseq
.arg
[0]);
1554 case 'g': /* TBC -- Tabulation clear */
1555 switch (csiescseq
.arg
[0]) {
1556 case 0: /* clear current tab stop */
1557 term
.tabs
[term
.c
.x
] = 0;
1559 case 3: /* clear all the tabs */
1560 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1566 case 'G': /* CHA -- Move to <col> */
1568 DEFAULT(csiescseq
.arg
[0], 1);
1569 tmoveto(csiescseq
.arg
[0]-1, term
.c
.y
);
1571 case 'H': /* CUP -- Move to <row> <col> */
1573 DEFAULT(csiescseq
.arg
[0], 1);
1574 DEFAULT(csiescseq
.arg
[1], 1);
1575 tmoveato(csiescseq
.arg
[1]-1, csiescseq
.arg
[0]-1);
1577 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1578 DEFAULT(csiescseq
.arg
[0], 1);
1579 tputtab(csiescseq
.arg
[0]);
1581 case 'J': /* ED -- Clear screen */
1583 switch (csiescseq
.arg
[0]) {
1585 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1586 if (term
.c
.y
< term
.row
-1) {
1587 tclearregion(0, term
.c
.y
+1, term
.col
-1,
1593 tclearregion(0, 0, term
.col
-1, term
.c
.y
-1);
1594 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1597 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1603 case 'K': /* EL -- Clear line */
1604 switch (csiescseq
.arg
[0]) {
1606 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1,
1610 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1613 tclearregion(0, term
.c
.y
, term
.col
-1, term
.c
.y
);
1617 case 'S': /* SU -- Scroll <n> line up */
1618 DEFAULT(csiescseq
.arg
[0], 1);
1619 tscrollup(term
.top
, csiescseq
.arg
[0]);
1621 case 'T': /* SD -- Scroll <n> line down */
1622 DEFAULT(csiescseq
.arg
[0], 1);
1623 tscrolldown(term
.top
, csiescseq
.arg
[0]);
1625 case 'L': /* IL -- Insert <n> blank lines */
1626 DEFAULT(csiescseq
.arg
[0], 1);
1627 tinsertblankline(csiescseq
.arg
[0]);
1629 case 'l': /* RM -- Reset Mode */
1630 tsetmode(csiescseq
.priv
, 0, csiescseq
.arg
, csiescseq
.narg
);
1632 case 'M': /* DL -- Delete <n> lines */
1633 DEFAULT(csiescseq
.arg
[0], 1);
1634 tdeleteline(csiescseq
.arg
[0]);
1636 case 'X': /* ECH -- Erase <n> char */
1637 DEFAULT(csiescseq
.arg
[0], 1);
1638 tclearregion(term
.c
.x
, term
.c
.y
,
1639 term
.c
.x
+ csiescseq
.arg
[0] - 1, term
.c
.y
);
1641 case 'P': /* DCH -- Delete <n> char */
1642 DEFAULT(csiescseq
.arg
[0], 1);
1643 tdeletechar(csiescseq
.arg
[0]);
1645 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1646 DEFAULT(csiescseq
.arg
[0], 1);
1647 tputtab(-csiescseq
.arg
[0]);
1649 case 'd': /* VPA -- Move to <row> */
1650 DEFAULT(csiescseq
.arg
[0], 1);
1651 tmoveato(term
.c
.x
, csiescseq
.arg
[0]-1);
1653 case 'h': /* SM -- Set terminal mode */
1654 tsetmode(csiescseq
.priv
, 1, csiescseq
.arg
, csiescseq
.narg
);
1656 case 'm': /* SGR -- Terminal attribute (color) */
1657 tsetattr(csiescseq
.arg
, csiescseq
.narg
);
1659 case 'n': /* DSR – Device Status Report (cursor position) */
1660 if (csiescseq
.arg
[0] == 6) {
1661 len
= snprintf(buf
, sizeof(buf
),"\033[%i;%iR",
1662 term
.c
.y
+1, term
.c
.x
+1);
1666 case 'r': /* DECSTBM -- Set Scrolling Region */
1667 if (csiescseq
.priv
) {
1670 DEFAULT(csiescseq
.arg
[0], 1);
1671 DEFAULT(csiescseq
.arg
[1], term
.row
);
1672 tsetscroll(csiescseq
.arg
[0]-1, csiescseq
.arg
[1]-1);
1676 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1677 tcursor(CURSOR_SAVE
);
1679 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1680 tcursor(CURSOR_LOAD
);
1683 switch (csiescseq
.mode
[1]) {
1684 case 'q': /* DECSCUSR -- Set Cursor Style */
1685 if (xsetcursor(csiescseq
.arg
[0]))
1701 fprintf(stderr
, "ESC[");
1702 for (i
= 0; i
< csiescseq
.len
; i
++) {
1703 c
= csiescseq
.buf
[i
] & 0xff;
1706 } else if (c
== '\n') {
1707 fprintf(stderr
, "(\\n)");
1708 } else if (c
== '\r') {
1709 fprintf(stderr
, "(\\r)");
1710 } else if (c
== 0x1b) {
1711 fprintf(stderr
, "(\\e)");
1713 fprintf(stderr
, "(%02x)", c
);
1722 memset(&csiescseq
, 0, sizeof(csiescseq
));
1731 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
1733 par
= (narg
= strescseq
.narg
) ? atoi(strescseq
.args
[0]) : 0;
1735 switch (strescseq
.type
) {
1736 case ']': /* OSC -- Operating System Command */
1742 xsettitle(strescseq
.args
[1]);
1748 dec
= base64dec(strescseq
.args
[2]);
1753 fprintf(stderr
, "erresc: invalid base64\n");
1757 case 4: /* color set */
1760 p
= strescseq
.args
[2];
1762 case 104: /* color reset, here p = NULL */
1763 j
= (narg
> 1) ? atoi(strescseq
.args
[1]) : -1;
1764 if (xsetcolorname(j
, p
)) {
1765 fprintf(stderr
, "erresc: invalid color %s\n", p
);
1768 * TODO if defaultbg color is changed, borders
1776 case 'k': /* old title set compatibility */
1777 xsettitle(strescseq
.args
[0]);
1779 case 'P': /* DCS -- Device Control String */
1780 term
.mode
|= ESC_DCS
;
1781 case '_': /* APC -- Application Program Command */
1782 case '^': /* PM -- Privacy Message */
1786 fprintf(stderr
, "erresc: unknown str ");
1794 char *p
= strescseq
.buf
;
1797 strescseq
.buf
[strescseq
.len
] = '\0';
1802 while (strescseq
.narg
< STR_ARG_SIZ
) {
1803 strescseq
.args
[strescseq
.narg
++] = p
;
1804 while ((c
= *p
) != ';' && c
!= '\0')
1818 fprintf(stderr
, "ESC%c", strescseq
.type
);
1819 for (i
= 0; i
< strescseq
.len
; i
++) {
1820 c
= strescseq
.buf
[i
] & 0xff;
1824 } else if (isprint(c
)) {
1826 } else if (c
== '\n') {
1827 fprintf(stderr
, "(\\n)");
1828 } else if (c
== '\r') {
1829 fprintf(stderr
, "(\\r)");
1830 } else if (c
== 0x1b) {
1831 fprintf(stderr
, "(\\e)");
1833 fprintf(stderr
, "(%02x)", c
);
1836 fprintf(stderr
, "ESC\\\n");
1842 memset(&strescseq
, 0, sizeof(strescseq
));
1846 sendbreak(const Arg
*arg
)
1848 if (tcsendbreak(cmdfd
, 0))
1849 perror("Error sending break");
1853 tprinter(char *s
, size_t len
)
1855 if (iofd
!= -1 && xwrite(iofd
, s
, len
) < 0) {
1856 perror("Error writing to output file");
1863 iso14755(const Arg
*arg
)
1866 char *us
, *e
, codepoint
[9], uc
[UTF_SIZ
];
1867 unsigned long utf32
;
1869 if (!(p
= popen(ISO14755CMD
, "r")))
1872 us
= fgets(codepoint
, sizeof(codepoint
), p
);
1875 if (!us
|| *us
== '\0' || *us
== '-' || strlen(us
) > 7)
1877 if ((utf32
= strtoul(us
, &e
, 16)) == ULONG_MAX
||
1878 (*e
!= '\n' && *e
!= '\0'))
1881 ttysend(uc
, utf8encode(utf32
, uc
));
1885 toggleprinter(const Arg
*arg
)
1887 term
.mode
^= MODE_PRINT
;
1891 printscreen(const Arg
*arg
)
1897 printsel(const Arg
*arg
)
1907 if ((ptr
= getsel())) {
1908 tprinter(ptr
, strlen(ptr
));
1919 bp
= &term
.line
[n
][0];
1920 end
= &bp
[MIN(tlinelen(n
), term
.col
) - 1];
1921 if (bp
!= end
|| bp
->u
!= ' ') {
1922 for ( ;bp
<= end
; ++bp
)
1923 tprinter(buf
, utf8encode(bp
->u
, buf
));
1933 for (i
= 0; i
< term
.row
; ++i
)
1943 while (x
< term
.col
&& n
--)
1944 for (++x
; x
< term
.col
&& !term
.tabs
[x
]; ++x
)
1947 while (x
> 0 && n
++)
1948 for (--x
; x
> 0 && !term
.tabs
[x
]; --x
)
1951 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1955 tdefutf8(char ascii
)
1958 term
.mode
|= MODE_UTF8
;
1959 else if (ascii
== '@')
1960 term
.mode
&= ~MODE_UTF8
;
1964 tdeftran(char ascii
)
1966 static char cs
[] = "0B";
1967 static int vcs
[] = {CS_GRAPHIC0
, CS_USA
};
1970 if ((p
= strchr(cs
, ascii
)) == NULL
) {
1971 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
1973 term
.trantbl
[term
.icharset
] = vcs
[p
- cs
];
1982 if (c
== '8') { /* DEC screen alignment test. */
1983 for (x
= 0; x
< term
.col
; ++x
) {
1984 for (y
= 0; y
< term
.row
; ++y
)
1985 tsetchar('E', &term
.c
.attr
, x
, y
);
1991 tstrsequence(uchar c
)
1996 case 0x90: /* DCS -- Device Control String */
1998 term
.esc
|= ESC_DCS
;
2000 case 0x9f: /* APC -- Application Program Command */
2003 case 0x9e: /* PM -- Privacy Message */
2006 case 0x9d: /* OSC -- Operating System Command */
2011 term
.esc
|= ESC_STR
;
2015 tcontrolcode(uchar ascii
)
2022 tmoveto(term
.c
.x
-1, term
.c
.y
);
2025 tmoveto(0, term
.c
.y
);
2030 /* go to first col if the mode is set */
2031 tnewline(IS_SET(MODE_CRLF
));
2033 case '\a': /* BEL */
2034 if (term
.esc
& ESC_STR_END
) {
2035 /* backwards compatibility to xterm */
2041 case '\033': /* ESC */
2043 term
.esc
&= ~(ESC_CSI
|ESC_ALTCHARSET
|ESC_TEST
);
2044 term
.esc
|= ESC_START
;
2046 case '\016': /* SO (LS1 -- Locking shift 1) */
2047 case '\017': /* SI (LS0 -- Locking shift 0) */
2048 term
.charset
= 1 - (ascii
- '\016');
2050 case '\032': /* SUB */
2051 tsetchar('?', &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2052 case '\030': /* CAN */
2055 case '\005': /* ENQ (IGNORED) */
2056 case '\000': /* NUL (IGNORED) */
2057 case '\021': /* XON (IGNORED) */
2058 case '\023': /* XOFF (IGNORED) */
2059 case 0177: /* DEL (IGNORED) */
2061 case 0x80: /* TODO: PAD */
2062 case 0x81: /* TODO: HOP */
2063 case 0x82: /* TODO: BPH */
2064 case 0x83: /* TODO: NBH */
2065 case 0x84: /* TODO: IND */
2067 case 0x85: /* NEL -- Next line */
2068 tnewline(1); /* always go to first col */
2070 case 0x86: /* TODO: SSA */
2071 case 0x87: /* TODO: ESA */
2073 case 0x88: /* HTS -- Horizontal tab stop */
2074 term
.tabs
[term
.c
.x
] = 1;
2076 case 0x89: /* TODO: HTJ */
2077 case 0x8a: /* TODO: VTS */
2078 case 0x8b: /* TODO: PLD */
2079 case 0x8c: /* TODO: PLU */
2080 case 0x8d: /* TODO: RI */
2081 case 0x8e: /* TODO: SS2 */
2082 case 0x8f: /* TODO: SS3 */
2083 case 0x91: /* TODO: PU1 */
2084 case 0x92: /* TODO: PU2 */
2085 case 0x93: /* TODO: STS */
2086 case 0x94: /* TODO: CCH */
2087 case 0x95: /* TODO: MW */
2088 case 0x96: /* TODO: SPA */
2089 case 0x97: /* TODO: EPA */
2090 case 0x98: /* TODO: SOS */
2091 case 0x99: /* TODO: SGCI */
2093 case 0x9a: /* DECID -- Identify Terminal */
2094 ttywrite(vtiden
, strlen(vtiden
));
2096 case 0x9b: /* TODO: CSI */
2097 case 0x9c: /* TODO: ST */
2099 case 0x90: /* DCS -- Device Control String */
2100 case 0x9d: /* OSC -- Operating System Command */
2101 case 0x9e: /* PM -- Privacy Message */
2102 case 0x9f: /* APC -- Application Program Command */
2103 tstrsequence(ascii
);
2106 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2107 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
2111 * returns 1 when the sequence is finished and it hasn't to read
2112 * more characters for this sequence, otherwise 0
2115 eschandle(uchar ascii
)
2119 term
.esc
|= ESC_CSI
;
2122 term
.esc
|= ESC_TEST
;
2125 term
.esc
|= ESC_UTF8
;
2127 case 'P': /* DCS -- Device Control String */
2128 case '_': /* APC -- Application Program Command */
2129 case '^': /* PM -- Privacy Message */
2130 case ']': /* OSC -- Operating System Command */
2131 case 'k': /* old title set compatibility */
2132 tstrsequence(ascii
);
2134 case 'n': /* LS2 -- Locking shift 2 */
2135 case 'o': /* LS3 -- Locking shift 3 */
2136 term
.charset
= 2 + (ascii
- 'n');
2138 case '(': /* GZD4 -- set primary charset G0 */
2139 case ')': /* G1D4 -- set secondary charset G1 */
2140 case '*': /* G2D4 -- set tertiary charset G2 */
2141 case '+': /* G3D4 -- set quaternary charset G3 */
2142 term
.icharset
= ascii
- '(';
2143 term
.esc
|= ESC_ALTCHARSET
;
2145 case 'D': /* IND -- Linefeed */
2146 if (term
.c
.y
== term
.bot
) {
2147 tscrollup(term
.top
, 1);
2149 tmoveto(term
.c
.x
, term
.c
.y
+1);
2152 case 'E': /* NEL -- Next line */
2153 tnewline(1); /* always go to first col */
2155 case 'H': /* HTS -- Horizontal tab stop */
2156 term
.tabs
[term
.c
.x
] = 1;
2158 case 'M': /* RI -- Reverse index */
2159 if (term
.c
.y
== term
.top
) {
2160 tscrolldown(term
.top
, 1);
2162 tmoveto(term
.c
.x
, term
.c
.y
-1);
2165 case 'Z': /* DECID -- Identify Terminal */
2166 ttywrite(vtiden
, strlen(vtiden
));
2168 case 'c': /* RIS -- Reset to inital state */
2173 case '=': /* DECPAM -- Application keypad */
2174 term
.mode
|= MODE_APPKEYPAD
;
2176 case '>': /* DECPNM -- Normal keypad */
2177 term
.mode
&= ~MODE_APPKEYPAD
;
2179 case '7': /* DECSC -- Save Cursor */
2180 tcursor(CURSOR_SAVE
);
2182 case '8': /* DECRC -- Restore Cursor */
2183 tcursor(CURSOR_LOAD
);
2185 case '\\': /* ST -- String Terminator */
2186 if (term
.esc
& ESC_STR_END
)
2190 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2191 (uchar
) ascii
, isprint(ascii
)? ascii
:'.');
2205 control
= ISCONTROL(u
);
2206 if (!IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2210 len
= utf8encode(u
, c
);
2211 if (!control
&& (width
= wcwidth(u
)) == -1) {
2212 memcpy(c
, "\357\277\275", 4); /* UTF_INVALID */
2217 if (IS_SET(MODE_PRINT
))
2221 * STR sequence must be checked before anything else
2222 * because it uses all following characters until it
2223 * receives a ESC, a SUB, a ST or any other C1 control
2226 if (term
.esc
& ESC_STR
) {
2227 if (u
== '\a' || u
== 030 || u
== 032 || u
== 033 ||
2229 term
.esc
&= ~(ESC_START
|ESC_STR
|ESC_DCS
);
2230 if (IS_SET(MODE_SIXEL
)) {
2231 /* TODO: render sixel */;
2232 term
.mode
&= ~MODE_SIXEL
;
2235 term
.esc
|= ESC_STR_END
;
2236 goto check_control_code
;
2240 if (IS_SET(MODE_SIXEL
)) {
2241 /* TODO: implement sixel mode */
2244 if (term
.esc
&ESC_DCS
&& strescseq
.len
== 0 && u
== 'q')
2245 term
.mode
|= MODE_SIXEL
;
2247 if (strescseq
.len
+len
>= sizeof(strescseq
.buf
)-1) {
2249 * Here is a bug in terminals. If the user never sends
2250 * some code to stop the str or esc command, then st
2251 * will stop responding. But this is better than
2252 * silently failing with unknown characters. At least
2253 * then users will report back.
2255 * In the case users ever get fixed, here is the code:
2264 memmove(&strescseq
.buf
[strescseq
.len
], c
, len
);
2265 strescseq
.len
+= len
;
2271 * Actions of control codes must be performed as soon they arrive
2272 * because they can be embedded inside a control sequence, and
2273 * they must not cause conflicts with sequences.
2278 * control codes are not shown ever
2281 } else if (term
.esc
& ESC_START
) {
2282 if (term
.esc
& ESC_CSI
) {
2283 csiescseq
.buf
[csiescseq
.len
++] = u
;
2284 if (BETWEEN(u
, 0x40, 0x7E)
2285 || csiescseq
.len
>= \
2286 sizeof(csiescseq
.buf
)-1) {
2292 } else if (term
.esc
& ESC_UTF8
) {
2294 } else if (term
.esc
& ESC_ALTCHARSET
) {
2296 } else if (term
.esc
& ESC_TEST
) {
2301 /* sequence already finished */
2305 * All characters which form part of a sequence are not
2310 if (sel
.ob
.x
!= -1 && BETWEEN(term
.c
.y
, sel
.ob
.y
, sel
.oe
.y
))
2313 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2314 if (IS_SET(MODE_WRAP
) && (term
.c
.state
& CURSOR_WRAPNEXT
)) {
2315 gp
->mode
|= ATTR_WRAP
;
2317 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2320 if (IS_SET(MODE_INSERT
) && term
.c
.x
+width
< term
.col
)
2321 memmove(gp
+width
, gp
, (term
.col
- term
.c
.x
- width
) * sizeof(Glyph
));
2323 if (term
.c
.x
+width
> term
.col
) {
2325 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2328 tsetchar(u
, &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2331 gp
->mode
|= ATTR_WIDE
;
2332 if (term
.c
.x
+1 < term
.col
) {
2334 gp
[1].mode
= ATTR_WDUMMY
;
2337 if (term
.c
.x
+width
< term
.col
) {
2338 tmoveto(term
.c
.x
+width
, term
.c
.y
);
2340 term
.c
.state
|= CURSOR_WRAPNEXT
;
2345 twrite(const char *buf
, int buflen
, int show_ctrl
)
2351 for (n
= 0; n
< buflen
; n
+= charsize
) {
2352 if (IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2353 /* process a complete utf8 char */
2354 charsize
= utf8decode(buf
+ n
, &u
, buflen
- n
);
2361 if (show_ctrl
&& ISCONTROL(u
)) {
2366 } else if (u
!= '\n' && u
!= '\r' && u
!= '\t') {
2377 tresize(int col
, int row
)
2380 int minrow
= MIN(row
, term
.row
);
2381 int mincol
= MIN(col
, term
.col
);
2385 if (col
< 1 || row
< 1) {
2387 "tresize: error resizing to %dx%d\n", col
, row
);
2392 * slide screen to keep cursor where we expect it -
2393 * tscrollup would work here, but we can optimize to
2394 * memmove because we're freeing the earlier lines
2396 for (i
= 0; i
<= term
.c
.y
- row
; i
++) {
2400 /* ensure that both src and dst are not NULL */
2402 memmove(term
.line
, term
.line
+ i
, row
* sizeof(Line
));
2403 memmove(term
.alt
, term
.alt
+ i
, row
* sizeof(Line
));
2405 for (i
+= row
; i
< term
.row
; i
++) {
2410 /* resize to new height */
2411 term
.line
= xrealloc(term
.line
, row
* sizeof(Line
));
2412 term
.alt
= xrealloc(term
.alt
, row
* sizeof(Line
));
2413 term
.dirty
= xrealloc(term
.dirty
, row
* sizeof(*term
.dirty
));
2414 term
.tabs
= xrealloc(term
.tabs
, col
* sizeof(*term
.tabs
));
2416 /* resize each row to new width, zero-pad if needed */
2417 for (i
= 0; i
< minrow
; i
++) {
2418 term
.line
[i
] = xrealloc(term
.line
[i
], col
* sizeof(Glyph
));
2419 term
.alt
[i
] = xrealloc(term
.alt
[i
], col
* sizeof(Glyph
));
2422 /* allocate any new rows */
2423 for (/* i = minrow */; i
< row
; i
++) {
2424 term
.line
[i
] = xmalloc(col
* sizeof(Glyph
));
2425 term
.alt
[i
] = xmalloc(col
* sizeof(Glyph
));
2427 if (col
> term
.col
) {
2428 bp
= term
.tabs
+ term
.col
;
2430 memset(bp
, 0, sizeof(*term
.tabs
) * (col
- term
.col
));
2431 while (--bp
> term
.tabs
&& !*bp
)
2433 for (bp
+= tabspaces
; bp
< term
.tabs
+ col
; bp
+= tabspaces
)
2436 /* update terminal size */
2439 /* reset scrolling region */
2440 tsetscroll(0, row
-1);
2441 /* make use of the LIMIT in tmoveto */
2442 tmoveto(term
.c
.x
, term
.c
.y
);
2443 /* Clearing both screens (it makes dirty all lines) */
2445 for (i
= 0; i
< 2; i
++) {
2446 if (mincol
< col
&& 0 < minrow
) {
2447 tclearregion(mincol
, 0, col
- 1, minrow
- 1);
2449 if (0 < col
&& minrow
< row
) {
2450 tclearregion(0, minrow
, col
- 1, row
- 1);
2453 tcursor(CURSOR_LOAD
);
2472 numlock(const Arg
*dummy
)