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 DEFAULT(a, b) (a) = (a) ? (a) : (b)
52 #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == '\177')
53 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
54 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
55 #define ISDELIM(u) (utf8strchr(worddelimiters, u) != NULL)
58 #define ISO14755CMD "dmenu -w \"$WINDOWID\" -p codepoint: </dev/null"
60 enum cursor_movement
{
84 ESC_STR
= 4, /* OSC, PM, APC */
86 ESC_STR_END
= 16, /* a final string was encountered */
87 ESC_TEST
= 32, /* Enter in test mode */
92 /* CSI Escape sequence structs */
93 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
95 char buf
[ESC_BUF_SIZ
]; /* raw string */
96 int len
; /* raw string length */
99 int narg
; /* nb of args */
103 /* STR Escape sequence structs */
104 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
106 char type
; /* ESC type ... */
107 char buf
[STR_BUF_SIZ
]; /* raw string */
108 int len
; /* raw string length */
109 char *args
[STR_ARG_SIZ
];
110 int narg
; /* nb of args */
117 /* three valued logic variables: 0 indifferent, 1 on, -1 off */
118 signed char appkey
; /* application keypad */
119 signed char appcursor
; /* application cursor */
120 signed char crlf
; /* crlf mode */
123 /* function definitions used in config.h */
124 static void clipcopy(const Arg
*);
125 static void clippaste(const Arg
*);
126 static void numlock(const Arg
*);
127 static void selpaste(const Arg
*);
128 static void printsel(const Arg
*);
129 static void printscreen(const Arg
*) ;
130 static void iso14755(const Arg
*);
131 static void toggleprinter(const Arg
*);
132 static void sendbreak(const Arg
*);
134 /* config.h for applying patches and the configuration. */
137 static void execsh(void);
138 static void stty(void);
139 static void sigchld(int);
141 static void csidump(void);
142 static void csihandle(void);
143 static void csiparse(void);
144 static void csireset(void);
145 static int eschandle(uchar
);
146 static void strdump(void);
147 static void strhandle(void);
148 static void strparse(void);
149 static void strreset(void);
151 static void tprinter(char *, size_t);
152 static void tdumpsel(void);
153 static void tdumpline(int);
154 static void tdump(void);
155 static void tclearregion(int, int, int, int);
156 static void tcursor(int);
157 static void tdeletechar(int);
158 static void tdeleteline(int);
159 static void tinsertblank(int);
160 static void tinsertblankline(int);
161 static int tlinelen(int);
162 static void tmoveto(int, int);
163 static void tmoveato(int, int);
164 static void tnewline(int);
165 static void tputtab(int);
166 static void tputc(Rune
);
167 static void treset(void);
168 static void tresize(int, int);
169 static void tscrollup(int, int);
170 static void tscrolldown(int, int);
171 static void tsetattr(int *, int);
172 static void tsetchar(Rune
, Glyph
*, int, int);
173 static void tsetscroll(int, int);
174 static void tswapscreen(void);
175 static void tsetmode(int, int, int *, int);
176 static void tfulldirt(void);
177 static void techo(Rune
);
178 static void tcontrolcode(uchar
);
179 static void tdectest(char );
180 static void tdefutf8(char);
181 static int32_t tdefcolor(int *, int *, int);
182 static void tdeftran(char);
183 static void tstrsequence(uchar
);
185 static void selscroll(int, int);
186 static void selsnap(int *, int *, int);
188 static Rune
utf8decodebyte(char, size_t *);
189 static char utf8encodebyte(Rune
, size_t);
190 static char *utf8strchr(char *s
, Rune u
);
191 static size_t utf8validate(Rune
*, size_t);
193 static char *base64dec(const char *);
195 static ssize_t
xwrite(int, const char *, size_t);
203 char **opt_cmd
= NULL
;
204 char *opt_class
= NULL
;
205 char *opt_embed
= NULL
;
206 char *opt_font
= NULL
;
208 char *opt_line
= NULL
;
209 char *opt_name
= NULL
;
210 char *opt_title
= NULL
;
211 int oldbutton
= 3; /* button event on startup: 3 = release */
213 static CSIEscape csiescseq
;
214 static STREscape strescseq
;
217 static uchar utfbyte
[UTF_SIZ
+ 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
218 static uchar utfmask
[UTF_SIZ
+ 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
219 static Rune utfmin
[UTF_SIZ
+ 1] = { 0, 0, 0x80, 0x800, 0x10000};
220 static Rune utfmax
[UTF_SIZ
+ 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
222 /* config.h array lengths */
223 size_t colornamelen
= LEN(colorname
);
224 size_t mshortcutslen
= LEN(mshortcuts
);
225 size_t shortcutslen
= LEN(shortcuts
);
226 size_t selmaskslen
= LEN(selmasks
);
229 xwrite(int fd
, const char *s
, size_t len
)
235 r
= write(fd
, s
, len
);
248 void *p
= malloc(len
);
251 die("Out of memory\n");
257 xrealloc(void *p
, size_t len
)
259 if ((p
= realloc(p
, len
)) == NULL
)
260 die("Out of memory\n");
268 if ((s
= strdup(s
)) == NULL
)
269 die("Out of memory\n");
275 utf8decode(char *c
, Rune
*u
, size_t clen
)
277 size_t i
, j
, len
, type
;
283 udecoded
= utf8decodebyte(c
[0], &len
);
284 if (!BETWEEN(len
, 1, UTF_SIZ
))
286 for (i
= 1, j
= 1; i
< clen
&& j
< len
; ++i
, ++j
) {
287 udecoded
= (udecoded
<< 6) | utf8decodebyte(c
[i
], &type
);
294 utf8validate(u
, len
);
300 utf8decodebyte(char c
, size_t *i
)
302 for (*i
= 0; *i
< LEN(utfmask
); ++(*i
))
303 if (((uchar
)c
& utfmask
[*i
]) == utfbyte
[*i
])
304 return (uchar
)c
& ~utfmask
[*i
];
310 utf8encode(Rune u
, char *c
)
314 len
= utf8validate(&u
, 0);
318 for (i
= len
- 1; i
!= 0; --i
) {
319 c
[i
] = utf8encodebyte(u
, 0);
322 c
[0] = utf8encodebyte(u
, len
);
328 utf8encodebyte(Rune u
, size_t i
)
330 return utfbyte
[i
] | (u
& ~utfmask
[i
]);
334 utf8strchr(char *s
, Rune u
)
340 for (i
= 0, j
= 0; i
< len
; i
+= j
) {
341 if (!(j
= utf8decode(&s
[i
], &r
, len
- i
)))
351 utf8validate(Rune
*u
, size_t i
)
353 if (!BETWEEN(*u
, utfmin
[i
], utfmax
[i
]) || BETWEEN(*u
, 0xD800, 0xDFFF))
355 for (i
= 1; *u
> utfmax
[i
]; ++i
)
361 static const char base64_digits
[] = {
362 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
363 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0,
364 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
365 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
366 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
367 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0,
368 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
369 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
370 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
371 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
372 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
373 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
377 base64dec_getc(const char **src
)
379 while (**src
&& !isprint(**src
)) (*src
)++;
384 base64dec(const char *src
)
386 size_t in_len
= strlen(src
);
390 in_len
+= 4 - (in_len
% 4);
391 result
= dst
= xmalloc(in_len
/ 4 * 3 + 1);
393 int a
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
394 int b
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
395 int c
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
396 int d
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
398 *dst
++ = (a
<< 2) | ((b
& 0x30) >> 4);
401 *dst
++ = ((b
& 0x0f) << 4) | ((c
& 0x3c) >> 2);
404 *dst
++ = ((c
& 0x03) << 6) | d
;
413 clock_gettime(CLOCK_MONOTONIC
, &sel
.tclick1
);
414 clock_gettime(CLOCK_MONOTONIC
, &sel
.tclick2
);
419 sel
.clipboard
= NULL
;
428 return LIMIT(x
, 0, term
.col
-1);
437 return LIMIT(y
, 0, term
.row
-1);
445 if (term
.line
[y
][i
- 1].mode
& ATTR_WRAP
)
448 while (i
> 0 && term
.line
[y
][i
- 1].u
== ' ')
459 if (sel
.type
== SEL_REGULAR
&& sel
.ob
.y
!= sel
.oe
.y
) {
460 sel
.nb
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.ob
.x
: sel
.oe
.x
;
461 sel
.ne
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.oe
.x
: sel
.ob
.x
;
463 sel
.nb
.x
= MIN(sel
.ob
.x
, sel
.oe
.x
);
464 sel
.ne
.x
= MAX(sel
.ob
.x
, sel
.oe
.x
);
466 sel
.nb
.y
= MIN(sel
.ob
.y
, sel
.oe
.y
);
467 sel
.ne
.y
= MAX(sel
.ob
.y
, sel
.oe
.y
);
469 selsnap(&sel
.nb
.x
, &sel
.nb
.y
, -1);
470 selsnap(&sel
.ne
.x
, &sel
.ne
.y
, +1);
472 /* expand selection over line breaks */
473 if (sel
.type
== SEL_RECTANGULAR
)
475 i
= tlinelen(sel
.nb
.y
);
478 if (tlinelen(sel
.ne
.y
) <= sel
.ne
.x
)
479 sel
.ne
.x
= term
.col
- 1;
483 selected(int x
, int y
)
485 if (sel
.mode
== SEL_EMPTY
)
488 if (sel
.type
== SEL_RECTANGULAR
)
489 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
490 && BETWEEN(x
, sel
.nb
.x
, sel
.ne
.x
);
492 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
493 && (y
!= sel
.nb
.y
|| x
>= sel
.nb
.x
)
494 && (y
!= sel
.ne
.y
|| x
<= sel
.ne
.x
);
498 selsnap(int *x
, int *y
, int direction
)
500 int newx
, newy
, xt
, yt
;
501 int delim
, prevdelim
;
507 * Snap around if the word wraps around at the end or
508 * beginning of a line.
510 prevgp
= &term
.line
[*y
][*x
];
511 prevdelim
= ISDELIM(prevgp
->u
);
513 newx
= *x
+ direction
;
515 if (!BETWEEN(newx
, 0, term
.col
- 1)) {
517 newx
= (newx
+ term
.col
) % term
.col
;
518 if (!BETWEEN(newy
, 0, term
.row
- 1))
524 yt
= newy
, xt
= newx
;
525 if (!(term
.line
[yt
][xt
].mode
& ATTR_WRAP
))
529 if (newx
>= tlinelen(newy
))
532 gp
= &term
.line
[newy
][newx
];
533 delim
= ISDELIM(gp
->u
);
534 if (!(gp
->mode
& ATTR_WDUMMY
) && (delim
!= prevdelim
535 || (delim
&& gp
->u
!= prevgp
->u
)))
546 * Snap around if the the previous line or the current one
547 * has set ATTR_WRAP at its end. Then the whole next or
548 * previous line will be selected.
550 *x
= (direction
< 0) ? 0 : term
.col
- 1;
552 for (; *y
> 0; *y
+= direction
) {
553 if (!(term
.line
[*y
-1][term
.col
-1].mode
558 } else if (direction
> 0) {
559 for (; *y
< term
.row
-1; *y
+= direction
) {
560 if (!(term
.line
[*y
][term
.col
-1].mode
574 int y
, bufsize
, lastx
, linelen
;
580 bufsize
= (term
.col
+1) * (sel
.ne
.y
-sel
.nb
.y
+1) * UTF_SIZ
;
581 ptr
= str
= xmalloc(bufsize
);
583 /* append every set & selected glyph to the selection */
584 for (y
= sel
.nb
.y
; y
<= sel
.ne
.y
; y
++) {
585 if ((linelen
= tlinelen(y
)) == 0) {
590 if (sel
.type
== SEL_RECTANGULAR
) {
591 gp
= &term
.line
[y
][sel
.nb
.x
];
594 gp
= &term
.line
[y
][sel
.nb
.y
== y
? sel
.nb
.x
: 0];
595 lastx
= (sel
.ne
.y
== y
) ? sel
.ne
.x
: term
.col
-1;
597 last
= &term
.line
[y
][MIN(lastx
, linelen
-1)];
598 while (last
>= gp
&& last
->u
== ' ')
601 for ( ; gp
<= last
; ++gp
) {
602 if (gp
->mode
& ATTR_WDUMMY
)
605 ptr
+= utf8encode(gp
->u
, ptr
);
609 * Copy and pasting of line endings is inconsistent
610 * in the inconsistent terminal and GUI world.
611 * The best solution seems like to produce '\n' when
612 * something is copied from st and convert '\n' to
613 * '\r', when something to be pasted is received by
615 * FIXME: Fix the computer world.
617 if ((y
< sel
.ne
.y
|| lastx
>= linelen
) && !(last
->mode
& ATTR_WRAP
))
625 selpaste(const Arg
*dummy
)
631 clipcopy(const Arg
*dummy
)
637 clippaste(const Arg
*dummy
)
649 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
653 die(const char *errstr
, ...)
657 va_start(ap
, errstr
);
658 vfprintf(stderr
, errstr
, ap
);
666 char **args
, *sh
, *prog
;
667 const struct passwd
*pw
;
670 if ((pw
= getpwuid(getuid())) == NULL
) {
672 die("getpwuid:%s\n", strerror(errno
));
674 die("who are you?\n");
677 if ((sh
= getenv("SHELL")) == NULL
)
678 sh
= (pw
->pw_shell
[0]) ? pw
->pw_shell
: shell
;
686 args
= (opt_cmd
) ? opt_cmd
: (char *[]) {prog
, NULL
};
691 setenv("LOGNAME", pw
->pw_name
, 1);
692 setenv("USER", pw
->pw_name
, 1);
693 setenv("SHELL", sh
, 1);
694 setenv("HOME", pw
->pw_dir
, 1);
695 setenv("TERM", termname
, 1);
697 signal(SIGCHLD
, SIG_DFL
);
698 signal(SIGHUP
, SIG_DFL
);
699 signal(SIGINT
, SIG_DFL
);
700 signal(SIGQUIT
, SIG_DFL
);
701 signal(SIGTERM
, SIG_DFL
);
702 signal(SIGALRM
, SIG_DFL
);
714 if ((p
= waitpid(pid
, &stat
, WNOHANG
)) < 0)
715 die("Waiting for pid %hd failed: %s\n", pid
, strerror(errno
));
720 if (!WIFEXITED(stat
) || WEXITSTATUS(stat
))
721 die("child finished with error '%d'\n", stat
);
729 char cmd
[_POSIX_ARG_MAX
], **p
, *q
, *s
;
732 if ((n
= strlen(stty_args
)) > sizeof(cmd
)-1)
733 die("incorrect stty parameters\n");
734 memcpy(cmd
, stty_args
, n
);
736 siz
= sizeof(cmd
) - n
;
737 for (p
= opt_cmd
; p
&& (s
= *p
); ++p
) {
738 if ((n
= strlen(s
)) > siz
-1)
739 die("stty parameter length too long\n");
746 if (system(cmd
) != 0)
747 perror("Couldn't call stty");
754 struct winsize w
= {term
.row
, term
.col
, 0, 0};
757 term
.mode
|= MODE_PRINT
;
758 iofd
= (!strcmp(opt_io
, "-")) ?
759 1 : open(opt_io
, O_WRONLY
| O_CREAT
, 0666);
761 fprintf(stderr
, "Error opening %s:%s\n",
762 opt_io
, strerror(errno
));
767 if ((cmdfd
= open(opt_line
, O_RDWR
)) < 0)
768 die("open line failed: %s\n", strerror(errno
));
774 /* seems to work fine on linux, openbsd and freebsd */
775 if (openpty(&m
, &s
, NULL
, NULL
, &w
) < 0)
776 die("openpty failed: %s\n", strerror(errno
));
778 switch (pid
= fork()) {
780 die("fork failed\n");
784 setsid(); /* create a new process group */
788 if (ioctl(s
, TIOCSCTTY
, NULL
) < 0)
789 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno
));
797 signal(SIGCHLD
, sigchld
);
805 static char buf
[BUFSIZ
];
806 static int buflen
= 0;
808 int charsize
; /* size of utf8 char in bytes */
812 /* append read bytes to unprocessed bytes */
813 if ((ret
= read(cmdfd
, buf
+buflen
, LEN(buf
)-buflen
)) < 0)
814 die("Couldn't read from shell: %s\n", strerror(errno
));
820 if (IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
821 /* process a complete utf8 char */
822 charsize
= utf8decode(ptr
, &unicodep
, buflen
);
832 tputc(*ptr
++ & 0xFF);
836 /* keep any uncomplete utf8 char for the next call */
838 memmove(buf
, ptr
, buflen
);
844 ttywrite(const char *s
, size_t n
)
851 * Remember that we are using a pty, which might be a modem line.
852 * Writing too much will clog the line. That's why we are doing this
854 * FIXME: Migrate the world to Plan 9.
862 /* Check if we can write. */
863 if (pselect(cmdfd
+1, &rfd
, &wfd
, NULL
, NULL
, NULL
) < 0) {
866 die("select failed: %s\n", strerror(errno
));
868 if (FD_ISSET(cmdfd
, &wfd
)) {
870 * Only write the bytes written by ttywrite() or the
871 * default of 256. This seems to be a reasonable value
872 * for a serial line. Bigger values might clog the I/O.
874 if ((r
= write(cmdfd
, s
, (n
< lim
)? n
: lim
)) < 0)
878 * We weren't able to write out everything.
879 * This means the buffer is getting full
887 /* All bytes have been written. */
891 if (FD_ISSET(cmdfd
, &rfd
))
897 die("write error on tty: %s\n", strerror(errno
));
901 ttysend(char *s
, size_t n
)
908 if (!IS_SET(MODE_ECHO
))
912 for (t
= s
; t
< lim
; t
+= len
) {
913 if (IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
914 len
= utf8decode(t
, &u
, n
);
933 w
.ws_xpixel
= win
.tw
;
934 w
.ws_ypixel
= win
.th
;
935 if (ioctl(cmdfd
, TIOCSWINSZ
, &w
) < 0)
936 fprintf(stderr
, "Couldn't set window size: %s\n", strerror(errno
));
944 for (i
= 0; i
< term
.row
-1; i
++) {
945 for (j
= 0; j
< term
.col
-1; j
++) {
946 if (term
.line
[i
][j
].mode
& attr
)
955 tsetdirt(int top
, int bot
)
959 LIMIT(top
, 0, term
.row
-1);
960 LIMIT(bot
, 0, term
.row
-1);
962 for (i
= top
; i
<= bot
; i
++)
967 tsetdirtattr(int attr
)
971 for (i
= 0; i
< term
.row
-1; i
++) {
972 for (j
= 0; j
< term
.col
-1; j
++) {
973 if (term
.line
[i
][j
].mode
& attr
) {
984 tsetdirt(0, term
.row
-1);
991 int alt
= IS_SET(MODE_ALTSCREEN
);
993 if (mode
== CURSOR_SAVE
) {
995 } else if (mode
== CURSOR_LOAD
) {
997 tmoveto(c
[alt
].x
, c
[alt
].y
);
1006 term
.c
= (TCursor
){{
1010 }, .x
= 0, .y
= 0, .state
= CURSOR_DEFAULT
};
1012 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1013 for (i
= tabspaces
; i
< term
.col
; i
+= tabspaces
)
1016 term
.bot
= term
.row
- 1;
1017 term
.mode
= MODE_WRAP
|MODE_UTF8
;
1018 memset(term
.trantbl
, CS_USA
, sizeof(term
.trantbl
));
1021 for (i
= 0; i
< 2; i
++) {
1023 tcursor(CURSOR_SAVE
);
1024 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1030 tnew(int col
, int row
)
1032 term
= (Term
){ .c
= { .attr
= { .fg
= defaultfg
, .bg
= defaultbg
} } };
1042 Line
*tmp
= term
.line
;
1044 term
.line
= term
.alt
;
1046 term
.mode
^= MODE_ALTSCREEN
;
1051 tscrolldown(int orig
, int n
)
1056 LIMIT(n
, 0, term
.bot
-orig
+1);
1058 tsetdirt(orig
, term
.bot
-n
);
1059 tclearregion(0, term
.bot
-n
+1, term
.col
-1, term
.bot
);
1061 for (i
= term
.bot
; i
>= orig
+n
; i
--) {
1062 temp
= term
.line
[i
];
1063 term
.line
[i
] = term
.line
[i
-n
];
1064 term
.line
[i
-n
] = temp
;
1071 tscrollup(int orig
, int n
)
1076 LIMIT(n
, 0, term
.bot
-orig
+1);
1078 tclearregion(0, orig
, term
.col
-1, orig
+n
-1);
1079 tsetdirt(orig
+n
, term
.bot
);
1081 for (i
= orig
; i
<= term
.bot
-n
; i
++) {
1082 temp
= term
.line
[i
];
1083 term
.line
[i
] = term
.line
[i
+n
];
1084 term
.line
[i
+n
] = temp
;
1087 selscroll(orig
, -n
);
1091 selscroll(int orig
, int n
)
1096 if (BETWEEN(sel
.ob
.y
, orig
, term
.bot
) || BETWEEN(sel
.oe
.y
, orig
, term
.bot
)) {
1097 if ((sel
.ob
.y
+= n
) > term
.bot
|| (sel
.oe
.y
+= n
) < term
.top
) {
1101 if (sel
.type
== SEL_RECTANGULAR
) {
1102 if (sel
.ob
.y
< term
.top
)
1103 sel
.ob
.y
= term
.top
;
1104 if (sel
.oe
.y
> term
.bot
)
1105 sel
.oe
.y
= term
.bot
;
1107 if (sel
.ob
.y
< term
.top
) {
1108 sel
.ob
.y
= term
.top
;
1111 if (sel
.oe
.y
> term
.bot
) {
1112 sel
.oe
.y
= term
.bot
;
1113 sel
.oe
.x
= term
.col
;
1121 tnewline(int first_col
)
1125 if (y
== term
.bot
) {
1126 tscrollup(term
.top
, 1);
1130 tmoveto(first_col
? 0 : term
.c
.x
, y
);
1136 char *p
= csiescseq
.buf
, *np
;
1145 csiescseq
.buf
[csiescseq
.len
] = '\0';
1146 while (p
< csiescseq
.buf
+csiescseq
.len
) {
1148 v
= strtol(p
, &np
, 10);
1151 if (v
== LONG_MAX
|| v
== LONG_MIN
)
1153 csiescseq
.arg
[csiescseq
.narg
++] = v
;
1155 if (*p
!= ';' || csiescseq
.narg
== ESC_ARG_SIZ
)
1159 csiescseq
.mode
[0] = *p
++;
1160 csiescseq
.mode
[1] = (p
< csiescseq
.buf
+csiescseq
.len
) ? *p
: '\0';
1163 /* for absolute user moves, when decom is set */
1165 tmoveato(int x
, int y
)
1167 tmoveto(x
, y
+ ((term
.c
.state
& CURSOR_ORIGIN
) ? term
.top
: 0));
1171 tmoveto(int x
, int y
)
1175 if (term
.c
.state
& CURSOR_ORIGIN
) {
1180 maxy
= term
.row
- 1;
1182 term
.c
.state
&= ~CURSOR_WRAPNEXT
;
1183 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1184 term
.c
.y
= LIMIT(y
, miny
, maxy
);
1188 tsetchar(Rune u
, Glyph
*attr
, int x
, int y
)
1190 static char *vt100_0
[62] = { /* 0x41 - 0x7e */
1191 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1192 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1193 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1194 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1195 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1196 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1197 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1198 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1202 * The table is proudly stolen from rxvt.
1204 if (term
.trantbl
[term
.charset
] == CS_GRAPHIC0
&&
1205 BETWEEN(u
, 0x41, 0x7e) && vt100_0
[u
- 0x41])
1206 utf8decode(vt100_0
[u
- 0x41], &u
, UTF_SIZ
);
1208 if (term
.line
[y
][x
].mode
& ATTR_WIDE
) {
1209 if (x
+1 < term
.col
) {
1210 term
.line
[y
][x
+1].u
= ' ';
1211 term
.line
[y
][x
+1].mode
&= ~ATTR_WDUMMY
;
1213 } else if (term
.line
[y
][x
].mode
& ATTR_WDUMMY
) {
1214 term
.line
[y
][x
-1].u
= ' ';
1215 term
.line
[y
][x
-1].mode
&= ~ATTR_WIDE
;
1219 term
.line
[y
][x
] = *attr
;
1220 term
.line
[y
][x
].u
= u
;
1224 tclearregion(int x1
, int y1
, int x2
, int y2
)
1230 temp
= x1
, x1
= x2
, x2
= temp
;
1232 temp
= y1
, y1
= y2
, y2
= temp
;
1234 LIMIT(x1
, 0, term
.col
-1);
1235 LIMIT(x2
, 0, term
.col
-1);
1236 LIMIT(y1
, 0, term
.row
-1);
1237 LIMIT(y2
, 0, term
.row
-1);
1239 for (y
= y1
; y
<= y2
; y
++) {
1241 for (x
= x1
; x
<= x2
; x
++) {
1242 gp
= &term
.line
[y
][x
];
1245 gp
->fg
= term
.c
.attr
.fg
;
1246 gp
->bg
= term
.c
.attr
.bg
;
1259 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1263 size
= term
.col
- src
;
1264 line
= term
.line
[term
.c
.y
];
1266 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1267 tclearregion(term
.col
-n
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1276 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1280 size
= term
.col
- dst
;
1281 line
= term
.line
[term
.c
.y
];
1283 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1284 tclearregion(src
, term
.c
.y
, dst
- 1, term
.c
.y
);
1288 tinsertblankline(int n
)
1290 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1291 tscrolldown(term
.c
.y
, n
);
1297 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1298 tscrollup(term
.c
.y
, n
);
1302 tdefcolor(int *attr
, int *npar
, int l
)
1307 switch (attr
[*npar
+ 1]) {
1308 case 2: /* direct color in RGB space */
1309 if (*npar
+ 4 >= l
) {
1311 "erresc(38): Incorrect number of parameters (%d)\n",
1315 r
= attr
[*npar
+ 2];
1316 g
= attr
[*npar
+ 3];
1317 b
= attr
[*npar
+ 4];
1319 if (!BETWEEN(r
, 0, 255) || !BETWEEN(g
, 0, 255) || !BETWEEN(b
, 0, 255))
1320 fprintf(stderr
, "erresc: bad rgb color (%u,%u,%u)\n",
1323 idx
= TRUECOLOR(r
, g
, b
);
1325 case 5: /* indexed color */
1326 if (*npar
+ 2 >= l
) {
1328 "erresc(38): Incorrect number of parameters (%d)\n",
1333 if (!BETWEEN(attr
[*npar
], 0, 255))
1334 fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[*npar
]);
1338 case 0: /* implemented defined (only foreground) */
1339 case 1: /* transparent */
1340 case 3: /* direct color in CMY space */
1341 case 4: /* direct color in CMYK space */
1344 "erresc(38): gfx attr %d unknown\n", attr
[*npar
]);
1352 tsetattr(int *attr
, int l
)
1357 for (i
= 0; i
< l
; i
++) {
1360 term
.c
.attr
.mode
&= ~(
1369 term
.c
.attr
.fg
= defaultfg
;
1370 term
.c
.attr
.bg
= defaultbg
;
1373 term
.c
.attr
.mode
|= ATTR_BOLD
;
1376 term
.c
.attr
.mode
|= ATTR_FAINT
;
1379 term
.c
.attr
.mode
|= ATTR_ITALIC
;
1382 term
.c
.attr
.mode
|= ATTR_UNDERLINE
;
1384 case 5: /* slow blink */
1386 case 6: /* rapid blink */
1387 term
.c
.attr
.mode
|= ATTR_BLINK
;
1390 term
.c
.attr
.mode
|= ATTR_REVERSE
;
1393 term
.c
.attr
.mode
|= ATTR_INVISIBLE
;
1396 term
.c
.attr
.mode
|= ATTR_STRUCK
;
1399 term
.c
.attr
.mode
&= ~(ATTR_BOLD
| ATTR_FAINT
);
1402 term
.c
.attr
.mode
&= ~ATTR_ITALIC
;
1405 term
.c
.attr
.mode
&= ~ATTR_UNDERLINE
;
1408 term
.c
.attr
.mode
&= ~ATTR_BLINK
;
1411 term
.c
.attr
.mode
&= ~ATTR_REVERSE
;
1414 term
.c
.attr
.mode
&= ~ATTR_INVISIBLE
;
1417 term
.c
.attr
.mode
&= ~ATTR_STRUCK
;
1420 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1421 term
.c
.attr
.fg
= idx
;
1424 term
.c
.attr
.fg
= defaultfg
;
1427 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1428 term
.c
.attr
.bg
= idx
;
1431 term
.c
.attr
.bg
= defaultbg
;
1434 if (BETWEEN(attr
[i
], 30, 37)) {
1435 term
.c
.attr
.fg
= attr
[i
] - 30;
1436 } else if (BETWEEN(attr
[i
], 40, 47)) {
1437 term
.c
.attr
.bg
= attr
[i
] - 40;
1438 } else if (BETWEEN(attr
[i
], 90, 97)) {
1439 term
.c
.attr
.fg
= attr
[i
] - 90 + 8;
1440 } else if (BETWEEN(attr
[i
], 100, 107)) {
1441 term
.c
.attr
.bg
= attr
[i
] - 100 + 8;
1444 "erresc(default): gfx attr %d unknown\n",
1445 attr
[i
]), csidump();
1453 tsetscroll(int t
, int b
)
1457 LIMIT(t
, 0, term
.row
-1);
1458 LIMIT(b
, 0, term
.row
-1);
1469 tsetmode(int priv
, int set
, int *args
, int narg
)
1474 for (lim
= args
+ narg
; args
< lim
; ++args
) {
1477 case 1: /* DECCKM -- Cursor key */
1478 MODBIT(term
.mode
, set
, MODE_APPCURSOR
);
1480 case 5: /* DECSCNM -- Reverse video */
1482 MODBIT(term
.mode
, set
, MODE_REVERSE
);
1483 if (mode
!= term
.mode
)
1486 case 6: /* DECOM -- Origin */
1487 MODBIT(term
.c
.state
, set
, CURSOR_ORIGIN
);
1490 case 7: /* DECAWM -- Auto wrap */
1491 MODBIT(term
.mode
, set
, MODE_WRAP
);
1493 case 0: /* Error (IGNORED) */
1494 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1495 case 3: /* DECCOLM -- Column (IGNORED) */
1496 case 4: /* DECSCLM -- Scroll (IGNORED) */
1497 case 8: /* DECARM -- Auto repeat (IGNORED) */
1498 case 18: /* DECPFF -- Printer feed (IGNORED) */
1499 case 19: /* DECPEX -- Printer extent (IGNORED) */
1500 case 42: /* DECNRCM -- National characters (IGNORED) */
1501 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1503 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1504 MODBIT(term
.mode
, !set
, MODE_HIDE
);
1506 case 9: /* X10 mouse compatibility mode */
1507 xsetpointermotion(0);
1508 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1509 MODBIT(term
.mode
, set
, MODE_MOUSEX10
);
1511 case 1000: /* 1000: report button press */
1512 xsetpointermotion(0);
1513 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1514 MODBIT(term
.mode
, set
, MODE_MOUSEBTN
);
1516 case 1002: /* 1002: report motion on button press */
1517 xsetpointermotion(0);
1518 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1519 MODBIT(term
.mode
, set
, MODE_MOUSEMOTION
);
1521 case 1003: /* 1003: enable all mouse motions */
1522 xsetpointermotion(set
);
1523 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1524 MODBIT(term
.mode
, set
, MODE_MOUSEMANY
);
1526 case 1004: /* 1004: send focus events to tty */
1527 MODBIT(term
.mode
, set
, MODE_FOCUS
);
1529 case 1006: /* 1006: extended reporting mode */
1530 MODBIT(term
.mode
, set
, MODE_MOUSESGR
);
1533 MODBIT(term
.mode
, set
, MODE_8BIT
);
1535 case 1049: /* swap screen & set/restore cursor as xterm */
1536 if (!allowaltscreen
)
1538 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1540 case 47: /* swap screen */
1542 if (!allowaltscreen
)
1544 alt
= IS_SET(MODE_ALTSCREEN
);
1546 tclearregion(0, 0, term
.col
-1,
1549 if (set
^ alt
) /* set is always 1 or 0 */
1555 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1557 case 2004: /* 2004: bracketed paste mode */
1558 MODBIT(term
.mode
, set
, MODE_BRCKTPASTE
);
1560 /* Not implemented mouse modes. See comments there. */
1561 case 1001: /* mouse highlight mode; can hang the
1562 terminal by design when implemented. */
1563 case 1005: /* UTF-8 mouse mode; will confuse
1564 applications not supporting UTF-8
1566 case 1015: /* urxvt mangled mouse mode; incompatible
1567 and can be mistaken for other control
1571 "erresc: unknown private set/reset mode %d\n",
1577 case 0: /* Error (IGNORED) */
1579 case 2: /* KAM -- keyboard action */
1580 MODBIT(term
.mode
, set
, MODE_KBDLOCK
);
1582 case 4: /* IRM -- Insertion-replacement */
1583 MODBIT(term
.mode
, set
, MODE_INSERT
);
1585 case 12: /* SRM -- Send/Receive */
1586 MODBIT(term
.mode
, !set
, MODE_ECHO
);
1588 case 20: /* LNM -- Linefeed/new line */
1589 MODBIT(term
.mode
, set
, MODE_CRLF
);
1593 "erresc: unknown set/reset mode %d\n",
1607 switch (csiescseq
.mode
[0]) {
1610 fprintf(stderr
, "erresc: unknown csi ");
1614 case '@': /* ICH -- Insert <n> blank char */
1615 DEFAULT(csiescseq
.arg
[0], 1);
1616 tinsertblank(csiescseq
.arg
[0]);
1618 case 'A': /* CUU -- Cursor <n> Up */
1619 DEFAULT(csiescseq
.arg
[0], 1);
1620 tmoveto(term
.c
.x
, term
.c
.y
-csiescseq
.arg
[0]);
1622 case 'B': /* CUD -- Cursor <n> Down */
1623 case 'e': /* VPR --Cursor <n> Down */
1624 DEFAULT(csiescseq
.arg
[0], 1);
1625 tmoveto(term
.c
.x
, term
.c
.y
+csiescseq
.arg
[0]);
1627 case 'i': /* MC -- Media Copy */
1628 switch (csiescseq
.arg
[0]) {
1633 tdumpline(term
.c
.y
);
1639 term
.mode
&= ~MODE_PRINT
;
1642 term
.mode
|= MODE_PRINT
;
1646 case 'c': /* DA -- Device Attributes */
1647 if (csiescseq
.arg
[0] == 0)
1648 ttywrite(vtiden
, sizeof(vtiden
) - 1);
1650 case 'C': /* CUF -- Cursor <n> Forward */
1651 case 'a': /* HPR -- Cursor <n> Forward */
1652 DEFAULT(csiescseq
.arg
[0], 1);
1653 tmoveto(term
.c
.x
+csiescseq
.arg
[0], term
.c
.y
);
1655 case 'D': /* CUB -- Cursor <n> Backward */
1656 DEFAULT(csiescseq
.arg
[0], 1);
1657 tmoveto(term
.c
.x
-csiescseq
.arg
[0], term
.c
.y
);
1659 case 'E': /* CNL -- Cursor <n> Down and first col */
1660 DEFAULT(csiescseq
.arg
[0], 1);
1661 tmoveto(0, term
.c
.y
+csiescseq
.arg
[0]);
1663 case 'F': /* CPL -- Cursor <n> Up and first col */
1664 DEFAULT(csiescseq
.arg
[0], 1);
1665 tmoveto(0, term
.c
.y
-csiescseq
.arg
[0]);
1667 case 'g': /* TBC -- Tabulation clear */
1668 switch (csiescseq
.arg
[0]) {
1669 case 0: /* clear current tab stop */
1670 term
.tabs
[term
.c
.x
] = 0;
1672 case 3: /* clear all the tabs */
1673 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1679 case 'G': /* CHA -- Move to <col> */
1681 DEFAULT(csiescseq
.arg
[0], 1);
1682 tmoveto(csiescseq
.arg
[0]-1, term
.c
.y
);
1684 case 'H': /* CUP -- Move to <row> <col> */
1686 DEFAULT(csiescseq
.arg
[0], 1);
1687 DEFAULT(csiescseq
.arg
[1], 1);
1688 tmoveato(csiescseq
.arg
[1]-1, csiescseq
.arg
[0]-1);
1690 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1691 DEFAULT(csiescseq
.arg
[0], 1);
1692 tputtab(csiescseq
.arg
[0]);
1694 case 'J': /* ED -- Clear screen */
1696 switch (csiescseq
.arg
[0]) {
1698 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1699 if (term
.c
.y
< term
.row
-1) {
1700 tclearregion(0, term
.c
.y
+1, term
.col
-1,
1706 tclearregion(0, 0, term
.col
-1, term
.c
.y
-1);
1707 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1710 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1716 case 'K': /* EL -- Clear line */
1717 switch (csiescseq
.arg
[0]) {
1719 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1,
1723 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1726 tclearregion(0, term
.c
.y
, term
.col
-1, term
.c
.y
);
1730 case 'S': /* SU -- Scroll <n> line up */
1731 DEFAULT(csiescseq
.arg
[0], 1);
1732 tscrollup(term
.top
, csiescseq
.arg
[0]);
1734 case 'T': /* SD -- Scroll <n> line down */
1735 DEFAULT(csiescseq
.arg
[0], 1);
1736 tscrolldown(term
.top
, csiescseq
.arg
[0]);
1738 case 'L': /* IL -- Insert <n> blank lines */
1739 DEFAULT(csiescseq
.arg
[0], 1);
1740 tinsertblankline(csiescseq
.arg
[0]);
1742 case 'l': /* RM -- Reset Mode */
1743 tsetmode(csiescseq
.priv
, 0, csiescseq
.arg
, csiescseq
.narg
);
1745 case 'M': /* DL -- Delete <n> lines */
1746 DEFAULT(csiescseq
.arg
[0], 1);
1747 tdeleteline(csiescseq
.arg
[0]);
1749 case 'X': /* ECH -- Erase <n> char */
1750 DEFAULT(csiescseq
.arg
[0], 1);
1751 tclearregion(term
.c
.x
, term
.c
.y
,
1752 term
.c
.x
+ csiescseq
.arg
[0] - 1, term
.c
.y
);
1754 case 'P': /* DCH -- Delete <n> char */
1755 DEFAULT(csiescseq
.arg
[0], 1);
1756 tdeletechar(csiescseq
.arg
[0]);
1758 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1759 DEFAULT(csiescseq
.arg
[0], 1);
1760 tputtab(-csiescseq
.arg
[0]);
1762 case 'd': /* VPA -- Move to <row> */
1763 DEFAULT(csiescseq
.arg
[0], 1);
1764 tmoveato(term
.c
.x
, csiescseq
.arg
[0]-1);
1766 case 'h': /* SM -- Set terminal mode */
1767 tsetmode(csiescseq
.priv
, 1, csiescseq
.arg
, csiescseq
.narg
);
1769 case 'm': /* SGR -- Terminal attribute (color) */
1770 tsetattr(csiescseq
.arg
, csiescseq
.narg
);
1772 case 'n': /* DSR – Device Status Report (cursor position) */
1773 if (csiescseq
.arg
[0] == 6) {
1774 len
= snprintf(buf
, sizeof(buf
),"\033[%i;%iR",
1775 term
.c
.y
+1, term
.c
.x
+1);
1779 case 'r': /* DECSTBM -- Set Scrolling Region */
1780 if (csiescseq
.priv
) {
1783 DEFAULT(csiescseq
.arg
[0], 1);
1784 DEFAULT(csiescseq
.arg
[1], term
.row
);
1785 tsetscroll(csiescseq
.arg
[0]-1, csiescseq
.arg
[1]-1);
1789 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1790 tcursor(CURSOR_SAVE
);
1792 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1793 tcursor(CURSOR_LOAD
);
1796 switch (csiescseq
.mode
[1]) {
1797 case 'q': /* DECSCUSR -- Set Cursor Style */
1798 DEFAULT(csiescseq
.arg
[0], 1);
1799 if (!BETWEEN(csiescseq
.arg
[0], 0, 6)) {
1802 win
.cursor
= csiescseq
.arg
[0];
1817 fprintf(stderr
, "ESC[");
1818 for (i
= 0; i
< csiescseq
.len
; i
++) {
1819 c
= csiescseq
.buf
[i
] & 0xff;
1822 } else if (c
== '\n') {
1823 fprintf(stderr
, "(\\n)");
1824 } else if (c
== '\r') {
1825 fprintf(stderr
, "(\\r)");
1826 } else if (c
== 0x1b) {
1827 fprintf(stderr
, "(\\e)");
1829 fprintf(stderr
, "(%02x)", c
);
1838 memset(&csiescseq
, 0, sizeof(csiescseq
));
1847 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
1849 par
= (narg
= strescseq
.narg
) ? atoi(strescseq
.args
[0]) : 0;
1851 switch (strescseq
.type
) {
1852 case ']': /* OSC -- Operating System Command */
1858 xsettitle(strescseq
.args
[1]);
1864 dec
= base64dec(strescseq
.args
[2]);
1866 xsetsel(dec
, CurrentTime
);
1869 fprintf(stderr
, "erresc: invalid base64\n");
1873 case 4: /* color set */
1876 p
= strescseq
.args
[2];
1878 case 104: /* color reset, here p = NULL */
1879 j
= (narg
> 1) ? atoi(strescseq
.args
[1]) : -1;
1880 if (xsetcolorname(j
, p
)) {
1881 fprintf(stderr
, "erresc: invalid color %s\n", p
);
1884 * TODO if defaultbg color is changed, borders
1892 case 'k': /* old title set compatibility */
1893 xsettitle(strescseq
.args
[0]);
1895 case 'P': /* DCS -- Device Control String */
1896 term
.mode
|= ESC_DCS
;
1897 case '_': /* APC -- Application Program Command */
1898 case '^': /* PM -- Privacy Message */
1902 fprintf(stderr
, "erresc: unknown str ");
1910 char *p
= strescseq
.buf
;
1913 strescseq
.buf
[strescseq
.len
] = '\0';
1918 while (strescseq
.narg
< STR_ARG_SIZ
) {
1919 strescseq
.args
[strescseq
.narg
++] = p
;
1920 while ((c
= *p
) != ';' && c
!= '\0')
1934 fprintf(stderr
, "ESC%c", strescseq
.type
);
1935 for (i
= 0; i
< strescseq
.len
; i
++) {
1936 c
= strescseq
.buf
[i
] & 0xff;
1940 } else if (isprint(c
)) {
1942 } else if (c
== '\n') {
1943 fprintf(stderr
, "(\\n)");
1944 } else if (c
== '\r') {
1945 fprintf(stderr
, "(\\r)");
1946 } else if (c
== 0x1b) {
1947 fprintf(stderr
, "(\\e)");
1949 fprintf(stderr
, "(%02x)", c
);
1952 fprintf(stderr
, "ESC\\\n");
1958 memset(&strescseq
, 0, sizeof(strescseq
));
1962 sendbreak(const Arg
*arg
)
1964 if (tcsendbreak(cmdfd
, 0))
1965 perror("Error sending break");
1969 tprinter(char *s
, size_t len
)
1971 if (iofd
!= -1 && xwrite(iofd
, s
, len
) < 0) {
1972 fprintf(stderr
, "Error writing in %s:%s\n",
1973 opt_io
, strerror(errno
));
1980 iso14755(const Arg
*arg
)
1983 char *us
, *e
, codepoint
[9], uc
[UTF_SIZ
];
1984 unsigned long utf32
;
1986 if (!(p
= popen(ISO14755CMD
, "r")))
1989 us
= fgets(codepoint
, sizeof(codepoint
), p
);
1992 if (!us
|| *us
== '\0' || *us
== '-' || strlen(us
) > 7)
1994 if ((utf32
= strtoul(us
, &e
, 16)) == ULONG_MAX
||
1995 (*e
!= '\n' && *e
!= '\0'))
1998 ttysend(uc
, utf8encode(utf32
, uc
));
2002 toggleprinter(const Arg
*arg
)
2004 term
.mode
^= MODE_PRINT
;
2008 printscreen(const Arg
*arg
)
2014 printsel(const Arg
*arg
)
2024 if ((ptr
= getsel())) {
2025 tprinter(ptr
, strlen(ptr
));
2036 bp
= &term
.line
[n
][0];
2037 end
= &bp
[MIN(tlinelen(n
), term
.col
) - 1];
2038 if (bp
!= end
|| bp
->u
!= ' ') {
2039 for ( ;bp
<= end
; ++bp
)
2040 tprinter(buf
, utf8encode(bp
->u
, buf
));
2050 for (i
= 0; i
< term
.row
; ++i
)
2060 while (x
< term
.col
&& n
--)
2061 for (++x
; x
< term
.col
&& !term
.tabs
[x
]; ++x
)
2064 while (x
> 0 && n
++)
2065 for (--x
; x
> 0 && !term
.tabs
[x
]; --x
)
2068 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
2074 if (ISCONTROL(u
)) { /* control code */
2079 } else if (u
!= '\n' && u
!= '\r' && u
!= '\t') {
2088 tdefutf8(char ascii
)
2091 term
.mode
|= MODE_UTF8
;
2092 else if (ascii
== '@')
2093 term
.mode
&= ~MODE_UTF8
;
2097 tdeftran(char ascii
)
2099 static char cs
[] = "0B";
2100 static int vcs
[] = {CS_GRAPHIC0
, CS_USA
};
2103 if ((p
= strchr(cs
, ascii
)) == NULL
) {
2104 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
2106 term
.trantbl
[term
.icharset
] = vcs
[p
- cs
];
2115 if (c
== '8') { /* DEC screen alignment test. */
2116 for (x
= 0; x
< term
.col
; ++x
) {
2117 for (y
= 0; y
< term
.row
; ++y
)
2118 tsetchar('E', &term
.c
.attr
, x
, y
);
2124 tstrsequence(uchar c
)
2129 case 0x90: /* DCS -- Device Control String */
2131 term
.esc
|= ESC_DCS
;
2133 case 0x9f: /* APC -- Application Program Command */
2136 case 0x9e: /* PM -- Privacy Message */
2139 case 0x9d: /* OSC -- Operating System Command */
2144 term
.esc
|= ESC_STR
;
2148 tcontrolcode(uchar ascii
)
2155 tmoveto(term
.c
.x
-1, term
.c
.y
);
2158 tmoveto(0, term
.c
.y
);
2163 /* go to first col if the mode is set */
2164 tnewline(IS_SET(MODE_CRLF
));
2166 case '\a': /* BEL */
2167 if (term
.esc
& ESC_STR_END
) {
2168 /* backwards compatibility to xterm */
2174 case '\033': /* ESC */
2176 term
.esc
&= ~(ESC_CSI
|ESC_ALTCHARSET
|ESC_TEST
);
2177 term
.esc
|= ESC_START
;
2179 case '\016': /* SO (LS1 -- Locking shift 1) */
2180 case '\017': /* SI (LS0 -- Locking shift 0) */
2181 term
.charset
= 1 - (ascii
- '\016');
2183 case '\032': /* SUB */
2184 tsetchar('?', &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2185 case '\030': /* CAN */
2188 case '\005': /* ENQ (IGNORED) */
2189 case '\000': /* NUL (IGNORED) */
2190 case '\021': /* XON (IGNORED) */
2191 case '\023': /* XOFF (IGNORED) */
2192 case 0177: /* DEL (IGNORED) */
2194 case 0x80: /* TODO: PAD */
2195 case 0x81: /* TODO: HOP */
2196 case 0x82: /* TODO: BPH */
2197 case 0x83: /* TODO: NBH */
2198 case 0x84: /* TODO: IND */
2200 case 0x85: /* NEL -- Next line */
2201 tnewline(1); /* always go to first col */
2203 case 0x86: /* TODO: SSA */
2204 case 0x87: /* TODO: ESA */
2206 case 0x88: /* HTS -- Horizontal tab stop */
2207 term
.tabs
[term
.c
.x
] = 1;
2209 case 0x89: /* TODO: HTJ */
2210 case 0x8a: /* TODO: VTS */
2211 case 0x8b: /* TODO: PLD */
2212 case 0x8c: /* TODO: PLU */
2213 case 0x8d: /* TODO: RI */
2214 case 0x8e: /* TODO: SS2 */
2215 case 0x8f: /* TODO: SS3 */
2216 case 0x91: /* TODO: PU1 */
2217 case 0x92: /* TODO: PU2 */
2218 case 0x93: /* TODO: STS */
2219 case 0x94: /* TODO: CCH */
2220 case 0x95: /* TODO: MW */
2221 case 0x96: /* TODO: SPA */
2222 case 0x97: /* TODO: EPA */
2223 case 0x98: /* TODO: SOS */
2224 case 0x99: /* TODO: SGCI */
2226 case 0x9a: /* DECID -- Identify Terminal */
2227 ttywrite(vtiden
, sizeof(vtiden
) - 1);
2229 case 0x9b: /* TODO: CSI */
2230 case 0x9c: /* TODO: ST */
2232 case 0x90: /* DCS -- Device Control String */
2233 case 0x9d: /* OSC -- Operating System Command */
2234 case 0x9e: /* PM -- Privacy Message */
2235 case 0x9f: /* APC -- Application Program Command */
2236 tstrsequence(ascii
);
2239 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2240 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
2244 * returns 1 when the sequence is finished and it hasn't to read
2245 * more characters for this sequence, otherwise 0
2248 eschandle(uchar ascii
)
2252 term
.esc
|= ESC_CSI
;
2255 term
.esc
|= ESC_TEST
;
2258 term
.esc
|= ESC_UTF8
;
2260 case 'P': /* DCS -- Device Control String */
2261 case '_': /* APC -- Application Program Command */
2262 case '^': /* PM -- Privacy Message */
2263 case ']': /* OSC -- Operating System Command */
2264 case 'k': /* old title set compatibility */
2265 tstrsequence(ascii
);
2267 case 'n': /* LS2 -- Locking shift 2 */
2268 case 'o': /* LS3 -- Locking shift 3 */
2269 term
.charset
= 2 + (ascii
- 'n');
2271 case '(': /* GZD4 -- set primary charset G0 */
2272 case ')': /* G1D4 -- set secondary charset G1 */
2273 case '*': /* G2D4 -- set tertiary charset G2 */
2274 case '+': /* G3D4 -- set quaternary charset G3 */
2275 term
.icharset
= ascii
- '(';
2276 term
.esc
|= ESC_ALTCHARSET
;
2278 case 'D': /* IND -- Linefeed */
2279 if (term
.c
.y
== term
.bot
) {
2280 tscrollup(term
.top
, 1);
2282 tmoveto(term
.c
.x
, term
.c
.y
+1);
2285 case 'E': /* NEL -- Next line */
2286 tnewline(1); /* always go to first col */
2288 case 'H': /* HTS -- Horizontal tab stop */
2289 term
.tabs
[term
.c
.x
] = 1;
2291 case 'M': /* RI -- Reverse index */
2292 if (term
.c
.y
== term
.top
) {
2293 tscrolldown(term
.top
, 1);
2295 tmoveto(term
.c
.x
, term
.c
.y
-1);
2298 case 'Z': /* DECID -- Identify Terminal */
2299 ttywrite(vtiden
, sizeof(vtiden
) - 1);
2301 case 'c': /* RIS -- Reset to inital state */
2306 case '=': /* DECPAM -- Application keypad */
2307 term
.mode
|= MODE_APPKEYPAD
;
2309 case '>': /* DECPNM -- Normal keypad */
2310 term
.mode
&= ~MODE_APPKEYPAD
;
2312 case '7': /* DECSC -- Save Cursor */
2313 tcursor(CURSOR_SAVE
);
2315 case '8': /* DECRC -- Restore Cursor */
2316 tcursor(CURSOR_LOAD
);
2318 case '\\': /* ST -- String Terminator */
2319 if (term
.esc
& ESC_STR_END
)
2323 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2324 (uchar
) ascii
, isprint(ascii
)? ascii
:'.');
2338 control
= ISCONTROL(u
);
2339 if (!IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2343 len
= utf8encode(u
, c
);
2344 if (!control
&& (width
= wcwidth(u
)) == -1) {
2345 memcpy(c
, "\357\277\275", 4); /* UTF_INVALID */
2350 if (IS_SET(MODE_PRINT
))
2354 * STR sequence must be checked before anything else
2355 * because it uses all following characters until it
2356 * receives a ESC, a SUB, a ST or any other C1 control
2359 if (term
.esc
& ESC_STR
) {
2360 if (u
== '\a' || u
== 030 || u
== 032 || u
== 033 ||
2362 term
.esc
&= ~(ESC_START
|ESC_STR
|ESC_DCS
);
2363 if (IS_SET(MODE_SIXEL
)) {
2364 /* TODO: render sixel */;
2365 term
.mode
&= ~MODE_SIXEL
;
2368 term
.esc
|= ESC_STR_END
;
2369 goto check_control_code
;
2373 if (IS_SET(MODE_SIXEL
)) {
2374 /* TODO: implement sixel mode */
2377 if (term
.esc
&ESC_DCS
&& strescseq
.len
== 0 && u
== 'q')
2378 term
.mode
|= MODE_SIXEL
;
2380 if (strescseq
.len
+len
>= sizeof(strescseq
.buf
)-1) {
2382 * Here is a bug in terminals. If the user never sends
2383 * some code to stop the str or esc command, then st
2384 * will stop responding. But this is better than
2385 * silently failing with unknown characters. At least
2386 * then users will report back.
2388 * In the case users ever get fixed, here is the code:
2397 memmove(&strescseq
.buf
[strescseq
.len
], c
, len
);
2398 strescseq
.len
+= len
;
2404 * Actions of control codes must be performed as soon they arrive
2405 * because they can be embedded inside a control sequence, and
2406 * they must not cause conflicts with sequences.
2411 * control codes are not shown ever
2414 } else if (term
.esc
& ESC_START
) {
2415 if (term
.esc
& ESC_CSI
) {
2416 csiescseq
.buf
[csiescseq
.len
++] = u
;
2417 if (BETWEEN(u
, 0x40, 0x7E)
2418 || csiescseq
.len
>= \
2419 sizeof(csiescseq
.buf
)-1) {
2425 } else if (term
.esc
& ESC_UTF8
) {
2427 } else if (term
.esc
& ESC_ALTCHARSET
) {
2429 } else if (term
.esc
& ESC_TEST
) {
2434 /* sequence already finished */
2438 * All characters which form part of a sequence are not
2443 if (sel
.ob
.x
!= -1 && BETWEEN(term
.c
.y
, sel
.ob
.y
, sel
.oe
.y
))
2446 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2447 if (IS_SET(MODE_WRAP
) && (term
.c
.state
& CURSOR_WRAPNEXT
)) {
2448 gp
->mode
|= ATTR_WRAP
;
2450 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2453 if (IS_SET(MODE_INSERT
) && term
.c
.x
+width
< term
.col
)
2454 memmove(gp
+width
, gp
, (term
.col
- term
.c
.x
- width
) * sizeof(Glyph
));
2456 if (term
.c
.x
+width
> term
.col
) {
2458 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2461 tsetchar(u
, &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2464 gp
->mode
|= ATTR_WIDE
;
2465 if (term
.c
.x
+1 < term
.col
) {
2467 gp
[1].mode
= ATTR_WDUMMY
;
2470 if (term
.c
.x
+width
< term
.col
) {
2471 tmoveto(term
.c
.x
+width
, term
.c
.y
);
2473 term
.c
.state
|= CURSOR_WRAPNEXT
;
2478 tresize(int col
, int row
)
2481 int minrow
= MIN(row
, term
.row
);
2482 int mincol
= MIN(col
, term
.col
);
2486 if (col
< 1 || row
< 1) {
2488 "tresize: error resizing to %dx%d\n", col
, row
);
2493 * slide screen to keep cursor where we expect it -
2494 * tscrollup would work here, but we can optimize to
2495 * memmove because we're freeing the earlier lines
2497 for (i
= 0; i
<= term
.c
.y
- row
; i
++) {
2501 /* ensure that both src and dst are not NULL */
2503 memmove(term
.line
, term
.line
+ i
, row
* sizeof(Line
));
2504 memmove(term
.alt
, term
.alt
+ i
, row
* sizeof(Line
));
2506 for (i
+= row
; i
< term
.row
; i
++) {
2511 /* resize to new height */
2512 term
.line
= xrealloc(term
.line
, row
* sizeof(Line
));
2513 term
.alt
= xrealloc(term
.alt
, row
* sizeof(Line
));
2514 term
.dirty
= xrealloc(term
.dirty
, row
* sizeof(*term
.dirty
));
2515 term
.tabs
= xrealloc(term
.tabs
, col
* sizeof(*term
.tabs
));
2517 /* resize each row to new width, zero-pad if needed */
2518 for (i
= 0; i
< minrow
; i
++) {
2519 term
.line
[i
] = xrealloc(term
.line
[i
], col
* sizeof(Glyph
));
2520 term
.alt
[i
] = xrealloc(term
.alt
[i
], col
* sizeof(Glyph
));
2523 /* allocate any new rows */
2524 for (/* i = minrow */; i
< row
; i
++) {
2525 term
.line
[i
] = xmalloc(col
* sizeof(Glyph
));
2526 term
.alt
[i
] = xmalloc(col
* sizeof(Glyph
));
2528 if (col
> term
.col
) {
2529 bp
= term
.tabs
+ term
.col
;
2531 memset(bp
, 0, sizeof(*term
.tabs
) * (col
- term
.col
));
2532 while (--bp
> term
.tabs
&& !*bp
)
2534 for (bp
+= tabspaces
; bp
< term
.tabs
+ col
; bp
+= tabspaces
)
2537 /* update terminal size */
2540 /* reset scrolling region */
2541 tsetscroll(0, row
-1);
2542 /* make use of the LIMIT in tmoveto */
2543 tmoveto(term
.c
.x
, term
.c
.y
);
2544 /* Clearing both screens (it makes dirty all lines) */
2546 for (i
= 0; i
< 2; i
++) {
2547 if (mincol
< col
&& 0 < minrow
) {
2548 tclearregion(mincol
, 0, col
- 1, minrow
- 1);
2550 if (0 < col
&& minrow
< row
) {
2551 tclearregion(0, minrow
, col
- 1, row
- 1);
2554 tcursor(CURSOR_LOAD
);
2562 xsettitle(opt_title
? opt_title
: "st");
2573 match(uint mask
, uint state
)
2575 return mask
== XK_ANY_MOD
|| mask
== (state
& ~ignoremod
);
2579 numlock(const Arg
*dummy
)
2585 kmap(KeySym k
, uint state
)
2590 /* Check for mapped keys out of X11 function keys. */
2591 for (i
= 0; i
< LEN(mappedkeys
); i
++) {
2592 if (mappedkeys
[i
] == k
)
2595 if (i
== LEN(mappedkeys
)) {
2596 if ((k
& 0xFFFF) < 0xFD00)
2600 for (kp
= key
; kp
< key
+ LEN(key
); kp
++) {
2604 if (!match(kp
->mask
, state
))
2607 if (IS_SET(MODE_APPKEYPAD
) ? kp
->appkey
< 0 : kp
->appkey
> 0)
2609 if (term
.numlock
&& kp
->appkey
== 2)
2612 if (IS_SET(MODE_APPCURSOR
) ? kp
->appcursor
< 0 : kp
->appcursor
> 0)
2615 if (IS_SET(MODE_CRLF
) ? kp
->crlf
< 0 : kp
->crlf
> 0)
2625 cresize(int width
, int height
)
2634 col
= (win
.w
- 2 * borderpx
) / win
.cw
;
2635 row
= (win
.h
- 2 * borderpx
) / win
.ch
;