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 tscrollup(int, int);
169 static void tscrolldown(int, int);
170 static void tsetattr(int *, int);
171 static void tsetchar(Rune
, Glyph
*, int, int);
172 static void tsetscroll(int, int);
173 static void tswapscreen(void);
174 static void tsetmode(int, int, int *, int);
175 static void tfulldirt(void);
176 static void techo(Rune
);
177 static void tcontrolcode(uchar
);
178 static void tdectest(char );
179 static void tdefutf8(char);
180 static int32_t tdefcolor(int *, int *, int);
181 static void tdeftran(char);
182 static void tstrsequence(uchar
);
184 static void selscroll(int, int);
185 static void selsnap(int *, int *, int);
187 static Rune
utf8decodebyte(char, size_t *);
188 static char utf8encodebyte(Rune
, size_t);
189 static char *utf8strchr(char *s
, Rune u
);
190 static size_t utf8validate(Rune
*, size_t);
192 static char *base64dec(const char *);
194 static ssize_t
xwrite(int, const char *, size_t);
202 char **opt_cmd
= NULL
;
203 char *opt_class
= NULL
;
204 char *opt_embed
= NULL
;
205 char *opt_font
= NULL
;
207 char *opt_line
= NULL
;
208 char *opt_name
= NULL
;
209 char *opt_title
= NULL
;
210 int oldbutton
= 3; /* button event on startup: 3 = release */
212 static CSIEscape csiescseq
;
213 static STREscape strescseq
;
216 static uchar utfbyte
[UTF_SIZ
+ 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
217 static uchar utfmask
[UTF_SIZ
+ 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
218 static Rune utfmin
[UTF_SIZ
+ 1] = { 0, 0, 0x80, 0x800, 0x10000};
219 static Rune utfmax
[UTF_SIZ
+ 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
221 /* config.h array lengths */
222 size_t colornamelen
= LEN(colorname
);
223 size_t mshortcutslen
= LEN(mshortcuts
);
224 size_t shortcutslen
= LEN(shortcuts
);
225 size_t selmaskslen
= LEN(selmasks
);
228 xwrite(int fd
, const char *s
, size_t len
)
234 r
= write(fd
, s
, len
);
247 void *p
= malloc(len
);
250 die("Out of memory\n");
256 xrealloc(void *p
, size_t len
)
258 if ((p
= realloc(p
, len
)) == NULL
)
259 die("Out of memory\n");
267 if ((s
= strdup(s
)) == NULL
)
268 die("Out of memory\n");
274 utf8decode(char *c
, Rune
*u
, size_t clen
)
276 size_t i
, j
, len
, type
;
282 udecoded
= utf8decodebyte(c
[0], &len
);
283 if (!BETWEEN(len
, 1, UTF_SIZ
))
285 for (i
= 1, j
= 1; i
< clen
&& j
< len
; ++i
, ++j
) {
286 udecoded
= (udecoded
<< 6) | utf8decodebyte(c
[i
], &type
);
293 utf8validate(u
, len
);
299 utf8decodebyte(char c
, size_t *i
)
301 for (*i
= 0; *i
< LEN(utfmask
); ++(*i
))
302 if (((uchar
)c
& utfmask
[*i
]) == utfbyte
[*i
])
303 return (uchar
)c
& ~utfmask
[*i
];
309 utf8encode(Rune u
, char *c
)
313 len
= utf8validate(&u
, 0);
317 for (i
= len
- 1; i
!= 0; --i
) {
318 c
[i
] = utf8encodebyte(u
, 0);
321 c
[0] = utf8encodebyte(u
, len
);
327 utf8encodebyte(Rune u
, size_t i
)
329 return utfbyte
[i
] | (u
& ~utfmask
[i
]);
333 utf8strchr(char *s
, Rune u
)
339 for (i
= 0, j
= 0; i
< len
; i
+= j
) {
340 if (!(j
= utf8decode(&s
[i
], &r
, len
- i
)))
350 utf8validate(Rune
*u
, size_t i
)
352 if (!BETWEEN(*u
, utfmin
[i
], utfmax
[i
]) || BETWEEN(*u
, 0xD800, 0xDFFF))
354 for (i
= 1; *u
> utfmax
[i
]; ++i
)
360 static const char base64_digits
[] = {
361 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
362 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0,
363 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
364 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
365 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
366 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0,
367 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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
376 base64dec_getc(const char **src
)
378 while (**src
&& !isprint(**src
)) (*src
)++;
383 base64dec(const char *src
)
385 size_t in_len
= strlen(src
);
389 in_len
+= 4 - (in_len
% 4);
390 result
= dst
= xmalloc(in_len
/ 4 * 3 + 1);
392 int a
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
393 int b
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
394 int c
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
395 int d
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
397 *dst
++ = (a
<< 2) | ((b
& 0x30) >> 4);
400 *dst
++ = ((b
& 0x0f) << 4) | ((c
& 0x3c) >> 2);
403 *dst
++ = ((c
& 0x03) << 6) | d
;
412 clock_gettime(CLOCK_MONOTONIC
, &sel
.tclick1
);
413 clock_gettime(CLOCK_MONOTONIC
, &sel
.tclick2
);
418 sel
.clipboard
= NULL
;
426 if (term
.line
[y
][i
- 1].mode
& ATTR_WRAP
)
429 while (i
> 0 && term
.line
[y
][i
- 1].u
== ' ')
440 if (sel
.type
== SEL_REGULAR
&& sel
.ob
.y
!= sel
.oe
.y
) {
441 sel
.nb
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.ob
.x
: sel
.oe
.x
;
442 sel
.ne
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.oe
.x
: sel
.ob
.x
;
444 sel
.nb
.x
= MIN(sel
.ob
.x
, sel
.oe
.x
);
445 sel
.ne
.x
= MAX(sel
.ob
.x
, sel
.oe
.x
);
447 sel
.nb
.y
= MIN(sel
.ob
.y
, sel
.oe
.y
);
448 sel
.ne
.y
= MAX(sel
.ob
.y
, sel
.oe
.y
);
450 selsnap(&sel
.nb
.x
, &sel
.nb
.y
, -1);
451 selsnap(&sel
.ne
.x
, &sel
.ne
.y
, +1);
453 /* expand selection over line breaks */
454 if (sel
.type
== SEL_RECTANGULAR
)
456 i
= tlinelen(sel
.nb
.y
);
459 if (tlinelen(sel
.ne
.y
) <= sel
.ne
.x
)
460 sel
.ne
.x
= term
.col
- 1;
464 selected(int x
, int y
)
466 if (sel
.mode
== SEL_EMPTY
)
469 if (sel
.type
== SEL_RECTANGULAR
)
470 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
471 && BETWEEN(x
, sel
.nb
.x
, sel
.ne
.x
);
473 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
474 && (y
!= sel
.nb
.y
|| x
>= sel
.nb
.x
)
475 && (y
!= sel
.ne
.y
|| x
<= sel
.ne
.x
);
479 selsnap(int *x
, int *y
, int direction
)
481 int newx
, newy
, xt
, yt
;
482 int delim
, prevdelim
;
488 * Snap around if the word wraps around at the end or
489 * beginning of a line.
491 prevgp
= &term
.line
[*y
][*x
];
492 prevdelim
= ISDELIM(prevgp
->u
);
494 newx
= *x
+ direction
;
496 if (!BETWEEN(newx
, 0, term
.col
- 1)) {
498 newx
= (newx
+ term
.col
) % term
.col
;
499 if (!BETWEEN(newy
, 0, term
.row
- 1))
505 yt
= newy
, xt
= newx
;
506 if (!(term
.line
[yt
][xt
].mode
& ATTR_WRAP
))
510 if (newx
>= tlinelen(newy
))
513 gp
= &term
.line
[newy
][newx
];
514 delim
= ISDELIM(gp
->u
);
515 if (!(gp
->mode
& ATTR_WDUMMY
) && (delim
!= prevdelim
516 || (delim
&& gp
->u
!= prevgp
->u
)))
527 * Snap around if the the previous line or the current one
528 * has set ATTR_WRAP at its end. Then the whole next or
529 * previous line will be selected.
531 *x
= (direction
< 0) ? 0 : term
.col
- 1;
533 for (; *y
> 0; *y
+= direction
) {
534 if (!(term
.line
[*y
-1][term
.col
-1].mode
539 } else if (direction
> 0) {
540 for (; *y
< term
.row
-1; *y
+= direction
) {
541 if (!(term
.line
[*y
][term
.col
-1].mode
555 int y
, bufsize
, lastx
, linelen
;
561 bufsize
= (term
.col
+1) * (sel
.ne
.y
-sel
.nb
.y
+1) * UTF_SIZ
;
562 ptr
= str
= xmalloc(bufsize
);
564 /* append every set & selected glyph to the selection */
565 for (y
= sel
.nb
.y
; y
<= sel
.ne
.y
; y
++) {
566 if ((linelen
= tlinelen(y
)) == 0) {
571 if (sel
.type
== SEL_RECTANGULAR
) {
572 gp
= &term
.line
[y
][sel
.nb
.x
];
575 gp
= &term
.line
[y
][sel
.nb
.y
== y
? sel
.nb
.x
: 0];
576 lastx
= (sel
.ne
.y
== y
) ? sel
.ne
.x
: term
.col
-1;
578 last
= &term
.line
[y
][MIN(lastx
, linelen
-1)];
579 while (last
>= gp
&& last
->u
== ' ')
582 for ( ; gp
<= last
; ++gp
) {
583 if (gp
->mode
& ATTR_WDUMMY
)
586 ptr
+= utf8encode(gp
->u
, ptr
);
590 * Copy and pasting of line endings is inconsistent
591 * in the inconsistent terminal and GUI world.
592 * The best solution seems like to produce '\n' when
593 * something is copied from st and convert '\n' to
594 * '\r', when something to be pasted is received by
596 * FIXME: Fix the computer world.
598 if ((y
< sel
.ne
.y
|| lastx
>= linelen
) && !(last
->mode
& ATTR_WRAP
))
606 selpaste(const Arg
*dummy
)
612 clipcopy(const Arg
*dummy
)
618 clippaste(const Arg
*dummy
)
630 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
634 die(const char *errstr
, ...)
638 va_start(ap
, errstr
);
639 vfprintf(stderr
, errstr
, ap
);
647 char **args
, *sh
, *prog
;
648 const struct passwd
*pw
;
651 if ((pw
= getpwuid(getuid())) == NULL
) {
653 die("getpwuid:%s\n", strerror(errno
));
655 die("who are you?\n");
658 if ((sh
= getenv("SHELL")) == NULL
)
659 sh
= (pw
->pw_shell
[0]) ? pw
->pw_shell
: shell
;
667 args
= (opt_cmd
) ? opt_cmd
: (char *[]) {prog
, NULL
};
672 setenv("LOGNAME", pw
->pw_name
, 1);
673 setenv("USER", pw
->pw_name
, 1);
674 setenv("SHELL", sh
, 1);
675 setenv("HOME", pw
->pw_dir
, 1);
676 setenv("TERM", termname
, 1);
678 signal(SIGCHLD
, SIG_DFL
);
679 signal(SIGHUP
, SIG_DFL
);
680 signal(SIGINT
, SIG_DFL
);
681 signal(SIGQUIT
, SIG_DFL
);
682 signal(SIGTERM
, SIG_DFL
);
683 signal(SIGALRM
, SIG_DFL
);
695 if ((p
= waitpid(pid
, &stat
, WNOHANG
)) < 0)
696 die("Waiting for pid %hd failed: %s\n", pid
, strerror(errno
));
701 if (!WIFEXITED(stat
) || WEXITSTATUS(stat
))
702 die("child finished with error '%d'\n", stat
);
710 char cmd
[_POSIX_ARG_MAX
], **p
, *q
, *s
;
713 if ((n
= strlen(stty_args
)) > sizeof(cmd
)-1)
714 die("incorrect stty parameters\n");
715 memcpy(cmd
, stty_args
, n
);
717 siz
= sizeof(cmd
) - n
;
718 for (p
= opt_cmd
; p
&& (s
= *p
); ++p
) {
719 if ((n
= strlen(s
)) > siz
-1)
720 die("stty parameter length too long\n");
727 if (system(cmd
) != 0)
728 perror("Couldn't call stty");
735 struct winsize w
= {term
.row
, term
.col
, 0, 0};
738 term
.mode
|= MODE_PRINT
;
739 iofd
= (!strcmp(opt_io
, "-")) ?
740 1 : open(opt_io
, O_WRONLY
| O_CREAT
, 0666);
742 fprintf(stderr
, "Error opening %s:%s\n",
743 opt_io
, strerror(errno
));
748 if ((cmdfd
= open(opt_line
, O_RDWR
)) < 0)
749 die("open line failed: %s\n", strerror(errno
));
755 /* seems to work fine on linux, openbsd and freebsd */
756 if (openpty(&m
, &s
, NULL
, NULL
, &w
) < 0)
757 die("openpty failed: %s\n", strerror(errno
));
759 switch (pid
= fork()) {
761 die("fork failed\n");
765 setsid(); /* create a new process group */
769 if (ioctl(s
, TIOCSCTTY
, NULL
) < 0)
770 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno
));
778 signal(SIGCHLD
, sigchld
);
786 static char buf
[BUFSIZ
];
787 static int buflen
= 0;
789 int charsize
; /* size of utf8 char in bytes */
793 /* append read bytes to unprocessed bytes */
794 if ((ret
= read(cmdfd
, buf
+buflen
, LEN(buf
)-buflen
)) < 0)
795 die("Couldn't read from shell: %s\n", strerror(errno
));
801 if (IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
802 /* process a complete utf8 char */
803 charsize
= utf8decode(ptr
, &unicodep
, buflen
);
813 tputc(*ptr
++ & 0xFF);
817 /* keep any uncomplete utf8 char for the next call */
819 memmove(buf
, ptr
, buflen
);
825 ttywrite(const char *s
, size_t n
)
832 * Remember that we are using a pty, which might be a modem line.
833 * Writing too much will clog the line. That's why we are doing this
835 * FIXME: Migrate the world to Plan 9.
843 /* Check if we can write. */
844 if (pselect(cmdfd
+1, &rfd
, &wfd
, NULL
, NULL
, NULL
) < 0) {
847 die("select failed: %s\n", strerror(errno
));
849 if (FD_ISSET(cmdfd
, &wfd
)) {
851 * Only write the bytes written by ttywrite() or the
852 * default of 256. This seems to be a reasonable value
853 * for a serial line. Bigger values might clog the I/O.
855 if ((r
= write(cmdfd
, s
, (n
< lim
)? n
: lim
)) < 0)
859 * We weren't able to write out everything.
860 * This means the buffer is getting full
868 /* All bytes have been written. */
872 if (FD_ISSET(cmdfd
, &rfd
))
878 die("write error on tty: %s\n", strerror(errno
));
882 ttysend(char *s
, size_t n
)
889 if (!IS_SET(MODE_ECHO
))
893 for (t
= s
; t
< lim
; t
+= len
) {
894 if (IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
895 len
= utf8decode(t
, &u
, n
);
914 w
.ws_xpixel
= win
.tw
;
915 w
.ws_ypixel
= win
.th
;
916 if (ioctl(cmdfd
, TIOCSWINSZ
, &w
) < 0)
917 fprintf(stderr
, "Couldn't set window size: %s\n", strerror(errno
));
925 for (i
= 0; i
< term
.row
-1; i
++) {
926 for (j
= 0; j
< term
.col
-1; j
++) {
927 if (term
.line
[i
][j
].mode
& attr
)
936 tsetdirt(int top
, int bot
)
940 LIMIT(top
, 0, term
.row
-1);
941 LIMIT(bot
, 0, term
.row
-1);
943 for (i
= top
; i
<= bot
; i
++)
948 tsetdirtattr(int attr
)
952 for (i
= 0; i
< term
.row
-1; i
++) {
953 for (j
= 0; j
< term
.col
-1; j
++) {
954 if (term
.line
[i
][j
].mode
& attr
) {
965 tsetdirt(0, term
.row
-1);
972 int alt
= IS_SET(MODE_ALTSCREEN
);
974 if (mode
== CURSOR_SAVE
) {
976 } else if (mode
== CURSOR_LOAD
) {
978 tmoveto(c
[alt
].x
, c
[alt
].y
);
991 }, .x
= 0, .y
= 0, .state
= CURSOR_DEFAULT
};
993 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
994 for (i
= tabspaces
; i
< term
.col
; i
+= tabspaces
)
997 term
.bot
= term
.row
- 1;
998 term
.mode
= MODE_WRAP
|MODE_UTF8
;
999 memset(term
.trantbl
, CS_USA
, sizeof(term
.trantbl
));
1002 for (i
= 0; i
< 2; i
++) {
1004 tcursor(CURSOR_SAVE
);
1005 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1011 tnew(int col
, int row
)
1013 term
= (Term
){ .c
= { .attr
= { .fg
= defaultfg
, .bg
= defaultbg
} } };
1023 Line
*tmp
= term
.line
;
1025 term
.line
= term
.alt
;
1027 term
.mode
^= MODE_ALTSCREEN
;
1032 tscrolldown(int orig
, int n
)
1037 LIMIT(n
, 0, term
.bot
-orig
+1);
1039 tsetdirt(orig
, term
.bot
-n
);
1040 tclearregion(0, term
.bot
-n
+1, term
.col
-1, term
.bot
);
1042 for (i
= term
.bot
; i
>= orig
+n
; i
--) {
1043 temp
= term
.line
[i
];
1044 term
.line
[i
] = term
.line
[i
-n
];
1045 term
.line
[i
-n
] = temp
;
1052 tscrollup(int orig
, int n
)
1057 LIMIT(n
, 0, term
.bot
-orig
+1);
1059 tclearregion(0, orig
, term
.col
-1, orig
+n
-1);
1060 tsetdirt(orig
+n
, term
.bot
);
1062 for (i
= orig
; i
<= term
.bot
-n
; i
++) {
1063 temp
= term
.line
[i
];
1064 term
.line
[i
] = term
.line
[i
+n
];
1065 term
.line
[i
+n
] = temp
;
1068 selscroll(orig
, -n
);
1072 selscroll(int orig
, int n
)
1077 if (BETWEEN(sel
.ob
.y
, orig
, term
.bot
) || BETWEEN(sel
.oe
.y
, orig
, term
.bot
)) {
1078 if ((sel
.ob
.y
+= n
) > term
.bot
|| (sel
.oe
.y
+= n
) < term
.top
) {
1082 if (sel
.type
== SEL_RECTANGULAR
) {
1083 if (sel
.ob
.y
< term
.top
)
1084 sel
.ob
.y
= term
.top
;
1085 if (sel
.oe
.y
> term
.bot
)
1086 sel
.oe
.y
= term
.bot
;
1088 if (sel
.ob
.y
< term
.top
) {
1089 sel
.ob
.y
= term
.top
;
1092 if (sel
.oe
.y
> term
.bot
) {
1093 sel
.oe
.y
= term
.bot
;
1094 sel
.oe
.x
= term
.col
;
1102 tnewline(int first_col
)
1106 if (y
== term
.bot
) {
1107 tscrollup(term
.top
, 1);
1111 tmoveto(first_col
? 0 : term
.c
.x
, y
);
1117 char *p
= csiescseq
.buf
, *np
;
1126 csiescseq
.buf
[csiescseq
.len
] = '\0';
1127 while (p
< csiescseq
.buf
+csiescseq
.len
) {
1129 v
= strtol(p
, &np
, 10);
1132 if (v
== LONG_MAX
|| v
== LONG_MIN
)
1134 csiescseq
.arg
[csiescseq
.narg
++] = v
;
1136 if (*p
!= ';' || csiescseq
.narg
== ESC_ARG_SIZ
)
1140 csiescseq
.mode
[0] = *p
++;
1141 csiescseq
.mode
[1] = (p
< csiescseq
.buf
+csiescseq
.len
) ? *p
: '\0';
1144 /* for absolute user moves, when decom is set */
1146 tmoveato(int x
, int y
)
1148 tmoveto(x
, y
+ ((term
.c
.state
& CURSOR_ORIGIN
) ? term
.top
: 0));
1152 tmoveto(int x
, int y
)
1156 if (term
.c
.state
& CURSOR_ORIGIN
) {
1161 maxy
= term
.row
- 1;
1163 term
.c
.state
&= ~CURSOR_WRAPNEXT
;
1164 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1165 term
.c
.y
= LIMIT(y
, miny
, maxy
);
1169 tsetchar(Rune u
, Glyph
*attr
, int x
, int y
)
1171 static char *vt100_0
[62] = { /* 0x41 - 0x7e */
1172 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1173 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1174 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1175 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1176 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1177 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1178 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1179 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1183 * The table is proudly stolen from rxvt.
1185 if (term
.trantbl
[term
.charset
] == CS_GRAPHIC0
&&
1186 BETWEEN(u
, 0x41, 0x7e) && vt100_0
[u
- 0x41])
1187 utf8decode(vt100_0
[u
- 0x41], &u
, UTF_SIZ
);
1189 if (term
.line
[y
][x
].mode
& ATTR_WIDE
) {
1190 if (x
+1 < term
.col
) {
1191 term
.line
[y
][x
+1].u
= ' ';
1192 term
.line
[y
][x
+1].mode
&= ~ATTR_WDUMMY
;
1194 } else if (term
.line
[y
][x
].mode
& ATTR_WDUMMY
) {
1195 term
.line
[y
][x
-1].u
= ' ';
1196 term
.line
[y
][x
-1].mode
&= ~ATTR_WIDE
;
1200 term
.line
[y
][x
] = *attr
;
1201 term
.line
[y
][x
].u
= u
;
1205 tclearregion(int x1
, int y1
, int x2
, int y2
)
1211 temp
= x1
, x1
= x2
, x2
= temp
;
1213 temp
= y1
, y1
= y2
, y2
= temp
;
1215 LIMIT(x1
, 0, term
.col
-1);
1216 LIMIT(x2
, 0, term
.col
-1);
1217 LIMIT(y1
, 0, term
.row
-1);
1218 LIMIT(y2
, 0, term
.row
-1);
1220 for (y
= y1
; y
<= y2
; y
++) {
1222 for (x
= x1
; x
<= x2
; x
++) {
1223 gp
= &term
.line
[y
][x
];
1226 gp
->fg
= term
.c
.attr
.fg
;
1227 gp
->bg
= term
.c
.attr
.bg
;
1240 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1244 size
= term
.col
- src
;
1245 line
= term
.line
[term
.c
.y
];
1247 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1248 tclearregion(term
.col
-n
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1257 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1261 size
= term
.col
- dst
;
1262 line
= term
.line
[term
.c
.y
];
1264 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1265 tclearregion(src
, term
.c
.y
, dst
- 1, term
.c
.y
);
1269 tinsertblankline(int n
)
1271 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1272 tscrolldown(term
.c
.y
, n
);
1278 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1279 tscrollup(term
.c
.y
, n
);
1283 tdefcolor(int *attr
, int *npar
, int l
)
1288 switch (attr
[*npar
+ 1]) {
1289 case 2: /* direct color in RGB space */
1290 if (*npar
+ 4 >= l
) {
1292 "erresc(38): Incorrect number of parameters (%d)\n",
1296 r
= attr
[*npar
+ 2];
1297 g
= attr
[*npar
+ 3];
1298 b
= attr
[*npar
+ 4];
1300 if (!BETWEEN(r
, 0, 255) || !BETWEEN(g
, 0, 255) || !BETWEEN(b
, 0, 255))
1301 fprintf(stderr
, "erresc: bad rgb color (%u,%u,%u)\n",
1304 idx
= TRUECOLOR(r
, g
, b
);
1306 case 5: /* indexed color */
1307 if (*npar
+ 2 >= l
) {
1309 "erresc(38): Incorrect number of parameters (%d)\n",
1314 if (!BETWEEN(attr
[*npar
], 0, 255))
1315 fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[*npar
]);
1319 case 0: /* implemented defined (only foreground) */
1320 case 1: /* transparent */
1321 case 3: /* direct color in CMY space */
1322 case 4: /* direct color in CMYK space */
1325 "erresc(38): gfx attr %d unknown\n", attr
[*npar
]);
1333 tsetattr(int *attr
, int l
)
1338 for (i
= 0; i
< l
; i
++) {
1341 term
.c
.attr
.mode
&= ~(
1350 term
.c
.attr
.fg
= defaultfg
;
1351 term
.c
.attr
.bg
= defaultbg
;
1354 term
.c
.attr
.mode
|= ATTR_BOLD
;
1357 term
.c
.attr
.mode
|= ATTR_FAINT
;
1360 term
.c
.attr
.mode
|= ATTR_ITALIC
;
1363 term
.c
.attr
.mode
|= ATTR_UNDERLINE
;
1365 case 5: /* slow blink */
1367 case 6: /* rapid blink */
1368 term
.c
.attr
.mode
|= ATTR_BLINK
;
1371 term
.c
.attr
.mode
|= ATTR_REVERSE
;
1374 term
.c
.attr
.mode
|= ATTR_INVISIBLE
;
1377 term
.c
.attr
.mode
|= ATTR_STRUCK
;
1380 term
.c
.attr
.mode
&= ~(ATTR_BOLD
| ATTR_FAINT
);
1383 term
.c
.attr
.mode
&= ~ATTR_ITALIC
;
1386 term
.c
.attr
.mode
&= ~ATTR_UNDERLINE
;
1389 term
.c
.attr
.mode
&= ~ATTR_BLINK
;
1392 term
.c
.attr
.mode
&= ~ATTR_REVERSE
;
1395 term
.c
.attr
.mode
&= ~ATTR_INVISIBLE
;
1398 term
.c
.attr
.mode
&= ~ATTR_STRUCK
;
1401 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1402 term
.c
.attr
.fg
= idx
;
1405 term
.c
.attr
.fg
= defaultfg
;
1408 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1409 term
.c
.attr
.bg
= idx
;
1412 term
.c
.attr
.bg
= defaultbg
;
1415 if (BETWEEN(attr
[i
], 30, 37)) {
1416 term
.c
.attr
.fg
= attr
[i
] - 30;
1417 } else if (BETWEEN(attr
[i
], 40, 47)) {
1418 term
.c
.attr
.bg
= attr
[i
] - 40;
1419 } else if (BETWEEN(attr
[i
], 90, 97)) {
1420 term
.c
.attr
.fg
= attr
[i
] - 90 + 8;
1421 } else if (BETWEEN(attr
[i
], 100, 107)) {
1422 term
.c
.attr
.bg
= attr
[i
] - 100 + 8;
1425 "erresc(default): gfx attr %d unknown\n",
1426 attr
[i
]), csidump();
1434 tsetscroll(int t
, int b
)
1438 LIMIT(t
, 0, term
.row
-1);
1439 LIMIT(b
, 0, term
.row
-1);
1450 tsetmode(int priv
, int set
, int *args
, int narg
)
1455 for (lim
= args
+ narg
; args
< lim
; ++args
) {
1458 case 1: /* DECCKM -- Cursor key */
1459 MODBIT(term
.mode
, set
, MODE_APPCURSOR
);
1461 case 5: /* DECSCNM -- Reverse video */
1463 MODBIT(term
.mode
, set
, MODE_REVERSE
);
1464 if (mode
!= term
.mode
)
1467 case 6: /* DECOM -- Origin */
1468 MODBIT(term
.c
.state
, set
, CURSOR_ORIGIN
);
1471 case 7: /* DECAWM -- Auto wrap */
1472 MODBIT(term
.mode
, set
, MODE_WRAP
);
1474 case 0: /* Error (IGNORED) */
1475 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1476 case 3: /* DECCOLM -- Column (IGNORED) */
1477 case 4: /* DECSCLM -- Scroll (IGNORED) */
1478 case 8: /* DECARM -- Auto repeat (IGNORED) */
1479 case 18: /* DECPFF -- Printer feed (IGNORED) */
1480 case 19: /* DECPEX -- Printer extent (IGNORED) */
1481 case 42: /* DECNRCM -- National characters (IGNORED) */
1482 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1484 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1485 MODBIT(term
.mode
, !set
, MODE_HIDE
);
1487 case 9: /* X10 mouse compatibility mode */
1488 xsetpointermotion(0);
1489 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1490 MODBIT(term
.mode
, set
, MODE_MOUSEX10
);
1492 case 1000: /* 1000: report button press */
1493 xsetpointermotion(0);
1494 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1495 MODBIT(term
.mode
, set
, MODE_MOUSEBTN
);
1497 case 1002: /* 1002: report motion on button press */
1498 xsetpointermotion(0);
1499 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1500 MODBIT(term
.mode
, set
, MODE_MOUSEMOTION
);
1502 case 1003: /* 1003: enable all mouse motions */
1503 xsetpointermotion(set
);
1504 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1505 MODBIT(term
.mode
, set
, MODE_MOUSEMANY
);
1507 case 1004: /* 1004: send focus events to tty */
1508 MODBIT(term
.mode
, set
, MODE_FOCUS
);
1510 case 1006: /* 1006: extended reporting mode */
1511 MODBIT(term
.mode
, set
, MODE_MOUSESGR
);
1514 MODBIT(term
.mode
, set
, MODE_8BIT
);
1516 case 1049: /* swap screen & set/restore cursor as xterm */
1517 if (!allowaltscreen
)
1519 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1521 case 47: /* swap screen */
1523 if (!allowaltscreen
)
1525 alt
= IS_SET(MODE_ALTSCREEN
);
1527 tclearregion(0, 0, term
.col
-1,
1530 if (set
^ alt
) /* set is always 1 or 0 */
1536 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1538 case 2004: /* 2004: bracketed paste mode */
1539 MODBIT(term
.mode
, set
, MODE_BRCKTPASTE
);
1541 /* Not implemented mouse modes. See comments there. */
1542 case 1001: /* mouse highlight mode; can hang the
1543 terminal by design when implemented. */
1544 case 1005: /* UTF-8 mouse mode; will confuse
1545 applications not supporting UTF-8
1547 case 1015: /* urxvt mangled mouse mode; incompatible
1548 and can be mistaken for other control
1552 "erresc: unknown private set/reset mode %d\n",
1558 case 0: /* Error (IGNORED) */
1560 case 2: /* KAM -- keyboard action */
1561 MODBIT(term
.mode
, set
, MODE_KBDLOCK
);
1563 case 4: /* IRM -- Insertion-replacement */
1564 MODBIT(term
.mode
, set
, MODE_INSERT
);
1566 case 12: /* SRM -- Send/Receive */
1567 MODBIT(term
.mode
, !set
, MODE_ECHO
);
1569 case 20: /* LNM -- Linefeed/new line */
1570 MODBIT(term
.mode
, set
, MODE_CRLF
);
1574 "erresc: unknown set/reset mode %d\n",
1588 switch (csiescseq
.mode
[0]) {
1591 fprintf(stderr
, "erresc: unknown csi ");
1595 case '@': /* ICH -- Insert <n> blank char */
1596 DEFAULT(csiescseq
.arg
[0], 1);
1597 tinsertblank(csiescseq
.arg
[0]);
1599 case 'A': /* CUU -- Cursor <n> Up */
1600 DEFAULT(csiescseq
.arg
[0], 1);
1601 tmoveto(term
.c
.x
, term
.c
.y
-csiescseq
.arg
[0]);
1603 case 'B': /* CUD -- Cursor <n> Down */
1604 case 'e': /* VPR --Cursor <n> Down */
1605 DEFAULT(csiescseq
.arg
[0], 1);
1606 tmoveto(term
.c
.x
, term
.c
.y
+csiescseq
.arg
[0]);
1608 case 'i': /* MC -- Media Copy */
1609 switch (csiescseq
.arg
[0]) {
1614 tdumpline(term
.c
.y
);
1620 term
.mode
&= ~MODE_PRINT
;
1623 term
.mode
|= MODE_PRINT
;
1627 case 'c': /* DA -- Device Attributes */
1628 if (csiescseq
.arg
[0] == 0)
1629 ttywrite(vtiden
, sizeof(vtiden
) - 1);
1631 case 'C': /* CUF -- Cursor <n> Forward */
1632 case 'a': /* HPR -- Cursor <n> Forward */
1633 DEFAULT(csiescseq
.arg
[0], 1);
1634 tmoveto(term
.c
.x
+csiescseq
.arg
[0], term
.c
.y
);
1636 case 'D': /* CUB -- Cursor <n> Backward */
1637 DEFAULT(csiescseq
.arg
[0], 1);
1638 tmoveto(term
.c
.x
-csiescseq
.arg
[0], term
.c
.y
);
1640 case 'E': /* CNL -- Cursor <n> Down and first col */
1641 DEFAULT(csiescseq
.arg
[0], 1);
1642 tmoveto(0, term
.c
.y
+csiescseq
.arg
[0]);
1644 case 'F': /* CPL -- Cursor <n> Up and first col */
1645 DEFAULT(csiescseq
.arg
[0], 1);
1646 tmoveto(0, term
.c
.y
-csiescseq
.arg
[0]);
1648 case 'g': /* TBC -- Tabulation clear */
1649 switch (csiescseq
.arg
[0]) {
1650 case 0: /* clear current tab stop */
1651 term
.tabs
[term
.c
.x
] = 0;
1653 case 3: /* clear all the tabs */
1654 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1660 case 'G': /* CHA -- Move to <col> */
1662 DEFAULT(csiescseq
.arg
[0], 1);
1663 tmoveto(csiescseq
.arg
[0]-1, term
.c
.y
);
1665 case 'H': /* CUP -- Move to <row> <col> */
1667 DEFAULT(csiescseq
.arg
[0], 1);
1668 DEFAULT(csiescseq
.arg
[1], 1);
1669 tmoveato(csiescseq
.arg
[1]-1, csiescseq
.arg
[0]-1);
1671 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1672 DEFAULT(csiescseq
.arg
[0], 1);
1673 tputtab(csiescseq
.arg
[0]);
1675 case 'J': /* ED -- Clear screen */
1677 switch (csiescseq
.arg
[0]) {
1679 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1680 if (term
.c
.y
< term
.row
-1) {
1681 tclearregion(0, term
.c
.y
+1, term
.col
-1,
1687 tclearregion(0, 0, term
.col
-1, term
.c
.y
-1);
1688 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1691 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1697 case 'K': /* EL -- Clear line */
1698 switch (csiescseq
.arg
[0]) {
1700 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1,
1704 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1707 tclearregion(0, term
.c
.y
, term
.col
-1, term
.c
.y
);
1711 case 'S': /* SU -- Scroll <n> line up */
1712 DEFAULT(csiescseq
.arg
[0], 1);
1713 tscrollup(term
.top
, csiescseq
.arg
[0]);
1715 case 'T': /* SD -- Scroll <n> line down */
1716 DEFAULT(csiescseq
.arg
[0], 1);
1717 tscrolldown(term
.top
, csiescseq
.arg
[0]);
1719 case 'L': /* IL -- Insert <n> blank lines */
1720 DEFAULT(csiescseq
.arg
[0], 1);
1721 tinsertblankline(csiescseq
.arg
[0]);
1723 case 'l': /* RM -- Reset Mode */
1724 tsetmode(csiescseq
.priv
, 0, csiescseq
.arg
, csiescseq
.narg
);
1726 case 'M': /* DL -- Delete <n> lines */
1727 DEFAULT(csiescseq
.arg
[0], 1);
1728 tdeleteline(csiescseq
.arg
[0]);
1730 case 'X': /* ECH -- Erase <n> char */
1731 DEFAULT(csiescseq
.arg
[0], 1);
1732 tclearregion(term
.c
.x
, term
.c
.y
,
1733 term
.c
.x
+ csiescseq
.arg
[0] - 1, term
.c
.y
);
1735 case 'P': /* DCH -- Delete <n> char */
1736 DEFAULT(csiescseq
.arg
[0], 1);
1737 tdeletechar(csiescseq
.arg
[0]);
1739 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1740 DEFAULT(csiescseq
.arg
[0], 1);
1741 tputtab(-csiescseq
.arg
[0]);
1743 case 'd': /* VPA -- Move to <row> */
1744 DEFAULT(csiescseq
.arg
[0], 1);
1745 tmoveato(term
.c
.x
, csiescseq
.arg
[0]-1);
1747 case 'h': /* SM -- Set terminal mode */
1748 tsetmode(csiescseq
.priv
, 1, csiescseq
.arg
, csiescseq
.narg
);
1750 case 'm': /* SGR -- Terminal attribute (color) */
1751 tsetattr(csiescseq
.arg
, csiescseq
.narg
);
1753 case 'n': /* DSR – Device Status Report (cursor position) */
1754 if (csiescseq
.arg
[0] == 6) {
1755 len
= snprintf(buf
, sizeof(buf
),"\033[%i;%iR",
1756 term
.c
.y
+1, term
.c
.x
+1);
1760 case 'r': /* DECSTBM -- Set Scrolling Region */
1761 if (csiescseq
.priv
) {
1764 DEFAULT(csiescseq
.arg
[0], 1);
1765 DEFAULT(csiescseq
.arg
[1], term
.row
);
1766 tsetscroll(csiescseq
.arg
[0]-1, csiescseq
.arg
[1]-1);
1770 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1771 tcursor(CURSOR_SAVE
);
1773 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1774 tcursor(CURSOR_LOAD
);
1777 switch (csiescseq
.mode
[1]) {
1778 case 'q': /* DECSCUSR -- Set Cursor Style */
1779 DEFAULT(csiescseq
.arg
[0], 1);
1780 if (!BETWEEN(csiescseq
.arg
[0], 0, 6)) {
1783 win
.cursor
= csiescseq
.arg
[0];
1798 fprintf(stderr
, "ESC[");
1799 for (i
= 0; i
< csiescseq
.len
; i
++) {
1800 c
= csiescseq
.buf
[i
] & 0xff;
1803 } else if (c
== '\n') {
1804 fprintf(stderr
, "(\\n)");
1805 } else if (c
== '\r') {
1806 fprintf(stderr
, "(\\r)");
1807 } else if (c
== 0x1b) {
1808 fprintf(stderr
, "(\\e)");
1810 fprintf(stderr
, "(%02x)", c
);
1819 memset(&csiescseq
, 0, sizeof(csiescseq
));
1828 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
1830 par
= (narg
= strescseq
.narg
) ? atoi(strescseq
.args
[0]) : 0;
1832 switch (strescseq
.type
) {
1833 case ']': /* OSC -- Operating System Command */
1839 xsettitle(strescseq
.args
[1]);
1845 dec
= base64dec(strescseq
.args
[2]);
1847 xsetsel(dec
, CurrentTime
);
1850 fprintf(stderr
, "erresc: invalid base64\n");
1854 case 4: /* color set */
1857 p
= strescseq
.args
[2];
1859 case 104: /* color reset, here p = NULL */
1860 j
= (narg
> 1) ? atoi(strescseq
.args
[1]) : -1;
1861 if (xsetcolorname(j
, p
)) {
1862 fprintf(stderr
, "erresc: invalid color %s\n", p
);
1865 * TODO if defaultbg color is changed, borders
1873 case 'k': /* old title set compatibility */
1874 xsettitle(strescseq
.args
[0]);
1876 case 'P': /* DCS -- Device Control String */
1877 term
.mode
|= ESC_DCS
;
1878 case '_': /* APC -- Application Program Command */
1879 case '^': /* PM -- Privacy Message */
1883 fprintf(stderr
, "erresc: unknown str ");
1891 char *p
= strescseq
.buf
;
1894 strescseq
.buf
[strescseq
.len
] = '\0';
1899 while (strescseq
.narg
< STR_ARG_SIZ
) {
1900 strescseq
.args
[strescseq
.narg
++] = p
;
1901 while ((c
= *p
) != ';' && c
!= '\0')
1915 fprintf(stderr
, "ESC%c", strescseq
.type
);
1916 for (i
= 0; i
< strescseq
.len
; i
++) {
1917 c
= strescseq
.buf
[i
] & 0xff;
1921 } else if (isprint(c
)) {
1923 } else if (c
== '\n') {
1924 fprintf(stderr
, "(\\n)");
1925 } else if (c
== '\r') {
1926 fprintf(stderr
, "(\\r)");
1927 } else if (c
== 0x1b) {
1928 fprintf(stderr
, "(\\e)");
1930 fprintf(stderr
, "(%02x)", c
);
1933 fprintf(stderr
, "ESC\\\n");
1939 memset(&strescseq
, 0, sizeof(strescseq
));
1943 sendbreak(const Arg
*arg
)
1945 if (tcsendbreak(cmdfd
, 0))
1946 perror("Error sending break");
1950 tprinter(char *s
, size_t len
)
1952 if (iofd
!= -1 && xwrite(iofd
, s
, len
) < 0) {
1953 fprintf(stderr
, "Error writing in %s:%s\n",
1954 opt_io
, strerror(errno
));
1961 iso14755(const Arg
*arg
)
1964 char *us
, *e
, codepoint
[9], uc
[UTF_SIZ
];
1965 unsigned long utf32
;
1967 if (!(p
= popen(ISO14755CMD
, "r")))
1970 us
= fgets(codepoint
, sizeof(codepoint
), p
);
1973 if (!us
|| *us
== '\0' || *us
== '-' || strlen(us
) > 7)
1975 if ((utf32
= strtoul(us
, &e
, 16)) == ULONG_MAX
||
1976 (*e
!= '\n' && *e
!= '\0'))
1979 ttysend(uc
, utf8encode(utf32
, uc
));
1983 toggleprinter(const Arg
*arg
)
1985 term
.mode
^= MODE_PRINT
;
1989 printscreen(const Arg
*arg
)
1995 printsel(const Arg
*arg
)
2005 if ((ptr
= getsel())) {
2006 tprinter(ptr
, strlen(ptr
));
2017 bp
= &term
.line
[n
][0];
2018 end
= &bp
[MIN(tlinelen(n
), term
.col
) - 1];
2019 if (bp
!= end
|| bp
->u
!= ' ') {
2020 for ( ;bp
<= end
; ++bp
)
2021 tprinter(buf
, utf8encode(bp
->u
, buf
));
2031 for (i
= 0; i
< term
.row
; ++i
)
2041 while (x
< term
.col
&& n
--)
2042 for (++x
; x
< term
.col
&& !term
.tabs
[x
]; ++x
)
2045 while (x
> 0 && n
++)
2046 for (--x
; x
> 0 && !term
.tabs
[x
]; --x
)
2049 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
2055 if (ISCONTROL(u
)) { /* control code */
2060 } else if (u
!= '\n' && u
!= '\r' && u
!= '\t') {
2069 tdefutf8(char ascii
)
2072 term
.mode
|= MODE_UTF8
;
2073 else if (ascii
== '@')
2074 term
.mode
&= ~MODE_UTF8
;
2078 tdeftran(char ascii
)
2080 static char cs
[] = "0B";
2081 static int vcs
[] = {CS_GRAPHIC0
, CS_USA
};
2084 if ((p
= strchr(cs
, ascii
)) == NULL
) {
2085 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
2087 term
.trantbl
[term
.icharset
] = vcs
[p
- cs
];
2096 if (c
== '8') { /* DEC screen alignment test. */
2097 for (x
= 0; x
< term
.col
; ++x
) {
2098 for (y
= 0; y
< term
.row
; ++y
)
2099 tsetchar('E', &term
.c
.attr
, x
, y
);
2105 tstrsequence(uchar c
)
2110 case 0x90: /* DCS -- Device Control String */
2112 term
.esc
|= ESC_DCS
;
2114 case 0x9f: /* APC -- Application Program Command */
2117 case 0x9e: /* PM -- Privacy Message */
2120 case 0x9d: /* OSC -- Operating System Command */
2125 term
.esc
|= ESC_STR
;
2129 tcontrolcode(uchar ascii
)
2136 tmoveto(term
.c
.x
-1, term
.c
.y
);
2139 tmoveto(0, term
.c
.y
);
2144 /* go to first col if the mode is set */
2145 tnewline(IS_SET(MODE_CRLF
));
2147 case '\a': /* BEL */
2148 if (term
.esc
& ESC_STR_END
) {
2149 /* backwards compatibility to xterm */
2155 case '\033': /* ESC */
2157 term
.esc
&= ~(ESC_CSI
|ESC_ALTCHARSET
|ESC_TEST
);
2158 term
.esc
|= ESC_START
;
2160 case '\016': /* SO (LS1 -- Locking shift 1) */
2161 case '\017': /* SI (LS0 -- Locking shift 0) */
2162 term
.charset
= 1 - (ascii
- '\016');
2164 case '\032': /* SUB */
2165 tsetchar('?', &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2166 case '\030': /* CAN */
2169 case '\005': /* ENQ (IGNORED) */
2170 case '\000': /* NUL (IGNORED) */
2171 case '\021': /* XON (IGNORED) */
2172 case '\023': /* XOFF (IGNORED) */
2173 case 0177: /* DEL (IGNORED) */
2175 case 0x80: /* TODO: PAD */
2176 case 0x81: /* TODO: HOP */
2177 case 0x82: /* TODO: BPH */
2178 case 0x83: /* TODO: NBH */
2179 case 0x84: /* TODO: IND */
2181 case 0x85: /* NEL -- Next line */
2182 tnewline(1); /* always go to first col */
2184 case 0x86: /* TODO: SSA */
2185 case 0x87: /* TODO: ESA */
2187 case 0x88: /* HTS -- Horizontal tab stop */
2188 term
.tabs
[term
.c
.x
] = 1;
2190 case 0x89: /* TODO: HTJ */
2191 case 0x8a: /* TODO: VTS */
2192 case 0x8b: /* TODO: PLD */
2193 case 0x8c: /* TODO: PLU */
2194 case 0x8d: /* TODO: RI */
2195 case 0x8e: /* TODO: SS2 */
2196 case 0x8f: /* TODO: SS3 */
2197 case 0x91: /* TODO: PU1 */
2198 case 0x92: /* TODO: PU2 */
2199 case 0x93: /* TODO: STS */
2200 case 0x94: /* TODO: CCH */
2201 case 0x95: /* TODO: MW */
2202 case 0x96: /* TODO: SPA */
2203 case 0x97: /* TODO: EPA */
2204 case 0x98: /* TODO: SOS */
2205 case 0x99: /* TODO: SGCI */
2207 case 0x9a: /* DECID -- Identify Terminal */
2208 ttywrite(vtiden
, sizeof(vtiden
) - 1);
2210 case 0x9b: /* TODO: CSI */
2211 case 0x9c: /* TODO: ST */
2213 case 0x90: /* DCS -- Device Control String */
2214 case 0x9d: /* OSC -- Operating System Command */
2215 case 0x9e: /* PM -- Privacy Message */
2216 case 0x9f: /* APC -- Application Program Command */
2217 tstrsequence(ascii
);
2220 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2221 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
2225 * returns 1 when the sequence is finished and it hasn't to read
2226 * more characters for this sequence, otherwise 0
2229 eschandle(uchar ascii
)
2233 term
.esc
|= ESC_CSI
;
2236 term
.esc
|= ESC_TEST
;
2239 term
.esc
|= ESC_UTF8
;
2241 case 'P': /* DCS -- Device Control String */
2242 case '_': /* APC -- Application Program Command */
2243 case '^': /* PM -- Privacy Message */
2244 case ']': /* OSC -- Operating System Command */
2245 case 'k': /* old title set compatibility */
2246 tstrsequence(ascii
);
2248 case 'n': /* LS2 -- Locking shift 2 */
2249 case 'o': /* LS3 -- Locking shift 3 */
2250 term
.charset
= 2 + (ascii
- 'n');
2252 case '(': /* GZD4 -- set primary charset G0 */
2253 case ')': /* G1D4 -- set secondary charset G1 */
2254 case '*': /* G2D4 -- set tertiary charset G2 */
2255 case '+': /* G3D4 -- set quaternary charset G3 */
2256 term
.icharset
= ascii
- '(';
2257 term
.esc
|= ESC_ALTCHARSET
;
2259 case 'D': /* IND -- Linefeed */
2260 if (term
.c
.y
== term
.bot
) {
2261 tscrollup(term
.top
, 1);
2263 tmoveto(term
.c
.x
, term
.c
.y
+1);
2266 case 'E': /* NEL -- Next line */
2267 tnewline(1); /* always go to first col */
2269 case 'H': /* HTS -- Horizontal tab stop */
2270 term
.tabs
[term
.c
.x
] = 1;
2272 case 'M': /* RI -- Reverse index */
2273 if (term
.c
.y
== term
.top
) {
2274 tscrolldown(term
.top
, 1);
2276 tmoveto(term
.c
.x
, term
.c
.y
-1);
2279 case 'Z': /* DECID -- Identify Terminal */
2280 ttywrite(vtiden
, sizeof(vtiden
) - 1);
2282 case 'c': /* RIS -- Reset to inital state */
2287 case '=': /* DECPAM -- Application keypad */
2288 term
.mode
|= MODE_APPKEYPAD
;
2290 case '>': /* DECPNM -- Normal keypad */
2291 term
.mode
&= ~MODE_APPKEYPAD
;
2293 case '7': /* DECSC -- Save Cursor */
2294 tcursor(CURSOR_SAVE
);
2296 case '8': /* DECRC -- Restore Cursor */
2297 tcursor(CURSOR_LOAD
);
2299 case '\\': /* ST -- String Terminator */
2300 if (term
.esc
& ESC_STR_END
)
2304 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2305 (uchar
) ascii
, isprint(ascii
)? ascii
:'.');
2319 control
= ISCONTROL(u
);
2320 if (!IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2324 len
= utf8encode(u
, c
);
2325 if (!control
&& (width
= wcwidth(u
)) == -1) {
2326 memcpy(c
, "\357\277\275", 4); /* UTF_INVALID */
2331 if (IS_SET(MODE_PRINT
))
2335 * STR sequence must be checked before anything else
2336 * because it uses all following characters until it
2337 * receives a ESC, a SUB, a ST or any other C1 control
2340 if (term
.esc
& ESC_STR
) {
2341 if (u
== '\a' || u
== 030 || u
== 032 || u
== 033 ||
2343 term
.esc
&= ~(ESC_START
|ESC_STR
|ESC_DCS
);
2344 if (IS_SET(MODE_SIXEL
)) {
2345 /* TODO: render sixel */;
2346 term
.mode
&= ~MODE_SIXEL
;
2349 term
.esc
|= ESC_STR_END
;
2350 goto check_control_code
;
2354 if (IS_SET(MODE_SIXEL
)) {
2355 /* TODO: implement sixel mode */
2358 if (term
.esc
&ESC_DCS
&& strescseq
.len
== 0 && u
== 'q')
2359 term
.mode
|= MODE_SIXEL
;
2361 if (strescseq
.len
+len
>= sizeof(strescseq
.buf
)-1) {
2363 * Here is a bug in terminals. If the user never sends
2364 * some code to stop the str or esc command, then st
2365 * will stop responding. But this is better than
2366 * silently failing with unknown characters. At least
2367 * then users will report back.
2369 * In the case users ever get fixed, here is the code:
2378 memmove(&strescseq
.buf
[strescseq
.len
], c
, len
);
2379 strescseq
.len
+= len
;
2385 * Actions of control codes must be performed as soon they arrive
2386 * because they can be embedded inside a control sequence, and
2387 * they must not cause conflicts with sequences.
2392 * control codes are not shown ever
2395 } else if (term
.esc
& ESC_START
) {
2396 if (term
.esc
& ESC_CSI
) {
2397 csiescseq
.buf
[csiescseq
.len
++] = u
;
2398 if (BETWEEN(u
, 0x40, 0x7E)
2399 || csiescseq
.len
>= \
2400 sizeof(csiescseq
.buf
)-1) {
2406 } else if (term
.esc
& ESC_UTF8
) {
2408 } else if (term
.esc
& ESC_ALTCHARSET
) {
2410 } else if (term
.esc
& ESC_TEST
) {
2415 /* sequence already finished */
2419 * All characters which form part of a sequence are not
2424 if (sel
.ob
.x
!= -1 && BETWEEN(term
.c
.y
, sel
.ob
.y
, sel
.oe
.y
))
2427 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2428 if (IS_SET(MODE_WRAP
) && (term
.c
.state
& CURSOR_WRAPNEXT
)) {
2429 gp
->mode
|= ATTR_WRAP
;
2431 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2434 if (IS_SET(MODE_INSERT
) && term
.c
.x
+width
< term
.col
)
2435 memmove(gp
+width
, gp
, (term
.col
- term
.c
.x
- width
) * sizeof(Glyph
));
2437 if (term
.c
.x
+width
> term
.col
) {
2439 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2442 tsetchar(u
, &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2445 gp
->mode
|= ATTR_WIDE
;
2446 if (term
.c
.x
+1 < term
.col
) {
2448 gp
[1].mode
= ATTR_WDUMMY
;
2451 if (term
.c
.x
+width
< term
.col
) {
2452 tmoveto(term
.c
.x
+width
, term
.c
.y
);
2454 term
.c
.state
|= CURSOR_WRAPNEXT
;
2459 tresize(int col
, int row
)
2462 int minrow
= MIN(row
, term
.row
);
2463 int mincol
= MIN(col
, term
.col
);
2467 if (col
< 1 || row
< 1) {
2469 "tresize: error resizing to %dx%d\n", col
, row
);
2474 * slide screen to keep cursor where we expect it -
2475 * tscrollup would work here, but we can optimize to
2476 * memmove because we're freeing the earlier lines
2478 for (i
= 0; i
<= term
.c
.y
- row
; i
++) {
2482 /* ensure that both src and dst are not NULL */
2484 memmove(term
.line
, term
.line
+ i
, row
* sizeof(Line
));
2485 memmove(term
.alt
, term
.alt
+ i
, row
* sizeof(Line
));
2487 for (i
+= row
; i
< term
.row
; i
++) {
2492 /* resize to new height */
2493 term
.line
= xrealloc(term
.line
, row
* sizeof(Line
));
2494 term
.alt
= xrealloc(term
.alt
, row
* sizeof(Line
));
2495 term
.dirty
= xrealloc(term
.dirty
, row
* sizeof(*term
.dirty
));
2496 term
.tabs
= xrealloc(term
.tabs
, col
* sizeof(*term
.tabs
));
2498 /* resize each row to new width, zero-pad if needed */
2499 for (i
= 0; i
< minrow
; i
++) {
2500 term
.line
[i
] = xrealloc(term
.line
[i
], col
* sizeof(Glyph
));
2501 term
.alt
[i
] = xrealloc(term
.alt
[i
], col
* sizeof(Glyph
));
2504 /* allocate any new rows */
2505 for (/* i = minrow */; i
< row
; i
++) {
2506 term
.line
[i
] = xmalloc(col
* sizeof(Glyph
));
2507 term
.alt
[i
] = xmalloc(col
* sizeof(Glyph
));
2509 if (col
> term
.col
) {
2510 bp
= term
.tabs
+ term
.col
;
2512 memset(bp
, 0, sizeof(*term
.tabs
) * (col
- term
.col
));
2513 while (--bp
> term
.tabs
&& !*bp
)
2515 for (bp
+= tabspaces
; bp
< term
.tabs
+ col
; bp
+= tabspaces
)
2518 /* update terminal size */
2521 /* reset scrolling region */
2522 tsetscroll(0, row
-1);
2523 /* make use of the LIMIT in tmoveto */
2524 tmoveto(term
.c
.x
, term
.c
.y
);
2525 /* Clearing both screens (it makes dirty all lines) */
2527 for (i
= 0; i
< 2; i
++) {
2528 if (mincol
< col
&& 0 < minrow
) {
2529 tclearregion(mincol
, 0, col
- 1, minrow
- 1);
2531 if (0 < col
&& minrow
< row
) {
2532 tclearregion(0, minrow
, col
- 1, row
- 1);
2535 tcursor(CURSOR_LOAD
);
2543 xsettitle(opt_title
? opt_title
: "st");
2554 match(uint mask
, uint state
)
2556 return mask
== XK_ANY_MOD
|| mask
== (state
& ~ignoremod
);
2560 numlock(const Arg
*dummy
)
2566 kmap(KeySym k
, uint state
)
2571 /* Check for mapped keys out of X11 function keys. */
2572 for (i
= 0; i
< LEN(mappedkeys
); i
++) {
2573 if (mappedkeys
[i
] == k
)
2576 if (i
== LEN(mappedkeys
)) {
2577 if ((k
& 0xFFFF) < 0xFD00)
2581 for (kp
= key
; kp
< key
+ LEN(key
); kp
++) {
2585 if (!match(kp
->mask
, state
))
2588 if (IS_SET(MODE_APPKEYPAD
) ? kp
->appkey
< 0 : kp
->appkey
> 0)
2590 if (term
.numlock
&& kp
->appkey
== 2)
2593 if (IS_SET(MODE_APPCURSOR
) ? kp
->appcursor
< 0 : kp
->appcursor
> 0)
2596 if (IS_SET(MODE_CRLF
) ? kp
->crlf
< 0 : kp
->crlf
> 0)