Xinqi Bao's Git
1 /* See LICENSE for license details. */
14 #include <sys/ioctl.h>
15 #include <sys/select.h>
18 #include <sys/types.h>
24 #include <fontconfig/fontconfig.h>
28 #include <X11/cursorfont.h>
29 #include <X11/Xft/Xft.h>
36 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
38 #elif defined(__FreeBSD__) || defined(__DragonFly__)
43 #define UTF_INVALID 0xFFFD
44 #define ESC_BUF_SIZ (128*UTF_SIZ)
45 #define ESC_ARG_SIZ 16
46 #define STR_BUF_SIZ ESC_BUF_SIZ
47 #define STR_ARG_SIZ ESC_ARG_SIZ
50 #define NUMMAXLEN(x) ((int)(sizeof(x) * 2.56 + 0.5) + 1)
51 #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == '\177')
52 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
53 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
54 #define ISDELIM(u) (utf8strchr(worddelimiters, u) != NULL)
57 #define ISO14755CMD "dmenu -w \"$WINDOWID\" -p codepoint: </dev/null"
59 enum cursor_movement
{
83 ESC_STR
= 4, /* OSC, PM, APC */
85 ESC_STR_END
= 16, /* a final string was encountered */
86 ESC_TEST
= 32, /* Enter in test mode */
91 /* CSI Escape sequence structs */
92 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
94 char buf
[ESC_BUF_SIZ
]; /* raw string */
95 int len
; /* raw string length */
98 int narg
; /* nb of args */
102 /* STR Escape sequence structs */
103 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
105 char type
; /* ESC type ... */
106 char buf
[STR_BUF_SIZ
]; /* raw string */
107 int len
; /* raw string length */
108 char *args
[STR_ARG_SIZ
];
109 int narg
; /* nb of args */
113 static void execsh(char **);
114 static void stty(char **);
115 static void sigchld(int);
117 static void csidump(void);
118 static void csihandle(void);
119 static void csiparse(void);
120 static void csireset(void);
121 static int eschandle(uchar
);
122 static void strdump(void);
123 static void strhandle(void);
124 static void strparse(void);
125 static void strreset(void);
127 static void tprinter(char *, size_t);
128 static void tdumpsel(void);
129 static void tdumpline(int);
130 static void tdump(void);
131 static void tclearregion(int, int, int, int);
132 static void tcursor(int);
133 static void tdeletechar(int);
134 static void tdeleteline(int);
135 static void tinsertblank(int);
136 static void tinsertblankline(int);
137 static int tlinelen(int);
138 static void tmoveto(int, int);
139 static void tmoveato(int, int);
140 static void tnewline(int);
141 static void tputtab(int);
142 static void tputc(Rune
);
143 static void treset(void);
144 static void tscrollup(int, int);
145 static void tscrolldown(int, int);
146 static void tsetattr(int *, int);
147 static void tsetchar(Rune
, Glyph
*, int, int);
148 static void tsetscroll(int, int);
149 static void tswapscreen(void);
150 static void tsetmode(int, int, int *, int);
151 static int twrite(const char *, int, int);
152 static void tfulldirt(void);
153 static void tcontrolcode(uchar
);
154 static void tdectest(char );
155 static void tdefutf8(char);
156 static int32_t tdefcolor(int *, int *, int);
157 static void tdeftran(char);
158 static void tstrsequence(uchar
);
160 static void selscroll(int, int);
161 static void selsnap(int *, int *, int);
163 static Rune
utf8decodebyte(char, size_t *);
164 static char utf8encodebyte(Rune
, size_t);
165 static char *utf8strchr(char *s
, Rune u
);
166 static size_t utf8validate(Rune
*, size_t);
168 static char *base64dec(const char *);
170 static ssize_t
xwrite(int, const char *, size_t);
178 int oldbutton
= 3; /* button event on startup: 3 = release */
180 static CSIEscape csiescseq
;
181 static STREscape strescseq
;
184 static uchar utfbyte
[UTF_SIZ
+ 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
185 static uchar utfmask
[UTF_SIZ
+ 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
186 static Rune utfmin
[UTF_SIZ
+ 1] = { 0, 0, 0x80, 0x800, 0x10000};
187 static Rune utfmax
[UTF_SIZ
+ 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
190 xwrite(int fd
, const char *s
, size_t len
)
196 r
= write(fd
, s
, len
);
209 void *p
= malloc(len
);
212 die("Out of memory\n");
218 xrealloc(void *p
, size_t len
)
220 if ((p
= realloc(p
, len
)) == NULL
)
221 die("Out of memory\n");
229 if ((s
= strdup(s
)) == NULL
)
230 die("Out of memory\n");
236 utf8decode(const char *c
, Rune
*u
, size_t clen
)
238 size_t i
, j
, len
, type
;
244 udecoded
= utf8decodebyte(c
[0], &len
);
245 if (!BETWEEN(len
, 1, UTF_SIZ
))
247 for (i
= 1, j
= 1; i
< clen
&& j
< len
; ++i
, ++j
) {
248 udecoded
= (udecoded
<< 6) | utf8decodebyte(c
[i
], &type
);
255 utf8validate(u
, len
);
261 utf8decodebyte(char c
, size_t *i
)
263 for (*i
= 0; *i
< LEN(utfmask
); ++(*i
))
264 if (((uchar
)c
& utfmask
[*i
]) == utfbyte
[*i
])
265 return (uchar
)c
& ~utfmask
[*i
];
271 utf8encode(Rune u
, char *c
)
275 len
= utf8validate(&u
, 0);
279 for (i
= len
- 1; i
!= 0; --i
) {
280 c
[i
] = utf8encodebyte(u
, 0);
283 c
[0] = utf8encodebyte(u
, len
);
289 utf8encodebyte(Rune u
, size_t i
)
291 return utfbyte
[i
] | (u
& ~utfmask
[i
]);
295 utf8strchr(char *s
, Rune u
)
301 for (i
= 0, j
= 0; i
< len
; i
+= j
) {
302 if (!(j
= utf8decode(&s
[i
], &r
, len
- i
)))
312 utf8validate(Rune
*u
, size_t i
)
314 if (!BETWEEN(*u
, utfmin
[i
], utfmax
[i
]) || BETWEEN(*u
, 0xD800, 0xDFFF))
316 for (i
= 1; *u
> utfmax
[i
]; ++i
)
322 static const char base64_digits
[] = {
323 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
324 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0,
325 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
326 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
327 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
328 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0,
329 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
330 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
331 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
332 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
333 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
334 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
338 base64dec_getc(const char **src
)
340 while (**src
&& !isprint(**src
)) (*src
)++;
345 base64dec(const char *src
)
347 size_t in_len
= strlen(src
);
351 in_len
+= 4 - (in_len
% 4);
352 result
= dst
= xmalloc(in_len
/ 4 * 3 + 1);
354 int a
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
355 int b
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
356 int c
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
357 int d
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
359 *dst
++ = (a
<< 2) | ((b
& 0x30) >> 4);
362 *dst
++ = ((b
& 0x0f) << 4) | ((c
& 0x3c) >> 2);
365 *dst
++ = ((c
& 0x03) << 6) | d
;
374 clock_gettime(CLOCK_MONOTONIC
, &sel
.tclick1
);
375 clock_gettime(CLOCK_MONOTONIC
, &sel
.tclick2
);
380 sel
.clipboard
= NULL
;
388 if (term
.line
[y
][i
- 1].mode
& ATTR_WRAP
)
391 while (i
> 0 && term
.line
[y
][i
- 1].u
== ' ')
402 if (sel
.type
== SEL_REGULAR
&& sel
.ob
.y
!= sel
.oe
.y
) {
403 sel
.nb
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.ob
.x
: sel
.oe
.x
;
404 sel
.ne
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.oe
.x
: sel
.ob
.x
;
406 sel
.nb
.x
= MIN(sel
.ob
.x
, sel
.oe
.x
);
407 sel
.ne
.x
= MAX(sel
.ob
.x
, sel
.oe
.x
);
409 sel
.nb
.y
= MIN(sel
.ob
.y
, sel
.oe
.y
);
410 sel
.ne
.y
= MAX(sel
.ob
.y
, sel
.oe
.y
);
412 selsnap(&sel
.nb
.x
, &sel
.nb
.y
, -1);
413 selsnap(&sel
.ne
.x
, &sel
.ne
.y
, +1);
415 /* expand selection over line breaks */
416 if (sel
.type
== SEL_RECTANGULAR
)
418 i
= tlinelen(sel
.nb
.y
);
421 if (tlinelen(sel
.ne
.y
) <= sel
.ne
.x
)
422 sel
.ne
.x
= term
.col
- 1;
426 selected(int x
, int y
)
428 if (sel
.mode
== SEL_EMPTY
)
431 if (sel
.type
== SEL_RECTANGULAR
)
432 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
433 && BETWEEN(x
, sel
.nb
.x
, sel
.ne
.x
);
435 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
436 && (y
!= sel
.nb
.y
|| x
>= sel
.nb
.x
)
437 && (y
!= sel
.ne
.y
|| x
<= sel
.ne
.x
);
441 selsnap(int *x
, int *y
, int direction
)
443 int newx
, newy
, xt
, yt
;
444 int delim
, prevdelim
;
450 * Snap around if the word wraps around at the end or
451 * beginning of a line.
453 prevgp
= &term
.line
[*y
][*x
];
454 prevdelim
= ISDELIM(prevgp
->u
);
456 newx
= *x
+ direction
;
458 if (!BETWEEN(newx
, 0, term
.col
- 1)) {
460 newx
= (newx
+ term
.col
) % term
.col
;
461 if (!BETWEEN(newy
, 0, term
.row
- 1))
467 yt
= newy
, xt
= newx
;
468 if (!(term
.line
[yt
][xt
].mode
& ATTR_WRAP
))
472 if (newx
>= tlinelen(newy
))
475 gp
= &term
.line
[newy
][newx
];
476 delim
= ISDELIM(gp
->u
);
477 if (!(gp
->mode
& ATTR_WDUMMY
) && (delim
!= prevdelim
478 || (delim
&& gp
->u
!= prevgp
->u
)))
489 * Snap around if the the previous line or the current one
490 * has set ATTR_WRAP at its end. Then the whole next or
491 * previous line will be selected.
493 *x
= (direction
< 0) ? 0 : term
.col
- 1;
495 for (; *y
> 0; *y
+= direction
) {
496 if (!(term
.line
[*y
-1][term
.col
-1].mode
501 } else if (direction
> 0) {
502 for (; *y
< term
.row
-1; *y
+= direction
) {
503 if (!(term
.line
[*y
][term
.col
-1].mode
517 int y
, bufsize
, lastx
, linelen
;
523 bufsize
= (term
.col
+1) * (sel
.ne
.y
-sel
.nb
.y
+1) * UTF_SIZ
;
524 ptr
= str
= xmalloc(bufsize
);
526 /* append every set & selected glyph to the selection */
527 for (y
= sel
.nb
.y
; y
<= sel
.ne
.y
; y
++) {
528 if ((linelen
= tlinelen(y
)) == 0) {
533 if (sel
.type
== SEL_RECTANGULAR
) {
534 gp
= &term
.line
[y
][sel
.nb
.x
];
537 gp
= &term
.line
[y
][sel
.nb
.y
== y
? sel
.nb
.x
: 0];
538 lastx
= (sel
.ne
.y
== y
) ? sel
.ne
.x
: term
.col
-1;
540 last
= &term
.line
[y
][MIN(lastx
, linelen
-1)];
541 while (last
>= gp
&& last
->u
== ' ')
544 for ( ; gp
<= last
; ++gp
) {
545 if (gp
->mode
& ATTR_WDUMMY
)
548 ptr
+= utf8encode(gp
->u
, ptr
);
552 * Copy and pasting of line endings is inconsistent
553 * in the inconsistent terminal and GUI world.
554 * The best solution seems like to produce '\n' when
555 * something is copied from st and convert '\n' to
556 * '\r', when something to be pasted is received by
558 * FIXME: Fix the computer world.
560 if ((y
< sel
.ne
.y
|| lastx
>= linelen
) && !(last
->mode
& ATTR_WRAP
))
574 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
578 die(const char *errstr
, ...)
582 va_start(ap
, errstr
);
583 vfprintf(stderr
, errstr
, ap
);
592 const struct passwd
*pw
;
595 if ((pw
= getpwuid(getuid())) == NULL
) {
597 die("getpwuid:%s\n", strerror(errno
));
599 die("who are you?\n");
602 if ((sh
= getenv("SHELL")) == NULL
)
603 sh
= (pw
->pw_shell
[0]) ? pw
->pw_shell
: shell
;
611 DEFAULT(args
, ((char *[]) {prog
, NULL
}));
616 setenv("LOGNAME", pw
->pw_name
, 1);
617 setenv("USER", pw
->pw_name
, 1);
618 setenv("SHELL", sh
, 1);
619 setenv("HOME", pw
->pw_dir
, 1);
620 setenv("TERM", termname
, 1);
622 signal(SIGCHLD
, SIG_DFL
);
623 signal(SIGHUP
, SIG_DFL
);
624 signal(SIGINT
, SIG_DFL
);
625 signal(SIGQUIT
, SIG_DFL
);
626 signal(SIGTERM
, SIG_DFL
);
627 signal(SIGALRM
, SIG_DFL
);
639 if ((p
= waitpid(pid
, &stat
, WNOHANG
)) < 0)
640 die("Waiting for pid %hd failed: %s\n", pid
, strerror(errno
));
645 if (!WIFEXITED(stat
) || WEXITSTATUS(stat
))
646 die("child finished with error '%d'\n", stat
);
654 char cmd
[_POSIX_ARG_MAX
], **p
, *q
, *s
;
657 if ((n
= strlen(stty_args
)) > sizeof(cmd
)-1)
658 die("incorrect stty parameters\n");
659 memcpy(cmd
, stty_args
, n
);
661 siz
= sizeof(cmd
) - n
;
662 for (p
= args
; p
&& (s
= *p
); ++p
) {
663 if ((n
= strlen(s
)) > siz
-1)
664 die("stty parameter length too long\n");
671 if (system(cmd
) != 0)
672 perror("Couldn't call stty");
676 ttynew(char *line
, char *out
, char **args
)
679 struct winsize w
= {term
.row
, term
.col
, 0, 0};
682 term
.mode
|= MODE_PRINT
;
683 iofd
= (!strcmp(out
, "-")) ?
684 1 : open(out
, O_WRONLY
| O_CREAT
, 0666);
686 fprintf(stderr
, "Error opening %s:%s\n",
687 out
, strerror(errno
));
692 if ((cmdfd
= open(line
, O_RDWR
)) < 0)
693 die("open line failed: %s\n", strerror(errno
));
699 /* seems to work fine on linux, openbsd and freebsd */
700 if (openpty(&m
, &s
, NULL
, NULL
, &w
) < 0)
701 die("openpty failed: %s\n", strerror(errno
));
703 switch (pid
= fork()) {
705 die("fork failed\n");
709 setsid(); /* create a new process group */
713 if (ioctl(s
, TIOCSCTTY
, NULL
) < 0)
714 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno
));
722 signal(SIGCHLD
, sigchld
);
730 static char buf
[BUFSIZ
];
731 static int buflen
= 0;
735 /* append read bytes to unprocessed bytes */
736 if ((ret
= read(cmdfd
, buf
+buflen
, LEN(buf
)-buflen
)) < 0)
737 die("Couldn't read from shell: %s\n", strerror(errno
));
740 written
= twrite(buf
, buflen
, 0);
742 /* keep any uncomplete utf8 char for the next call */
744 memmove(buf
, buf
+ written
, buflen
);
750 ttywrite(const char *s
, size_t n
)
757 * Remember that we are using a pty, which might be a modem line.
758 * Writing too much will clog the line. That's why we are doing this
760 * FIXME: Migrate the world to Plan 9.
768 /* Check if we can write. */
769 if (pselect(cmdfd
+1, &rfd
, &wfd
, NULL
, NULL
, NULL
) < 0) {
772 die("select failed: %s\n", strerror(errno
));
774 if (FD_ISSET(cmdfd
, &wfd
)) {
776 * Only write the bytes written by ttywrite() or the
777 * default of 256. This seems to be a reasonable value
778 * for a serial line. Bigger values might clog the I/O.
780 if ((r
= write(cmdfd
, s
, (n
< lim
)? n
: lim
)) < 0)
784 * We weren't able to write out everything.
785 * This means the buffer is getting full
793 /* All bytes have been written. */
797 if (FD_ISSET(cmdfd
, &rfd
))
803 die("write error on tty: %s\n", strerror(errno
));
807 ttysend(char *s
, size_t n
)
810 if (IS_SET(MODE_ECHO
))
815 ttyresize(int tw
, int th
)
823 if (ioctl(cmdfd
, TIOCSWINSZ
, &w
) < 0)
824 fprintf(stderr
, "Couldn't set window size: %s\n", strerror(errno
));
832 for (i
= 0; i
< term
.row
-1; i
++) {
833 for (j
= 0; j
< term
.col
-1; j
++) {
834 if (term
.line
[i
][j
].mode
& attr
)
843 tsetdirt(int top
, int bot
)
847 LIMIT(top
, 0, term
.row
-1);
848 LIMIT(bot
, 0, term
.row
-1);
850 for (i
= top
; i
<= bot
; i
++)
855 tsetdirtattr(int attr
)
859 for (i
= 0; i
< term
.row
-1; i
++) {
860 for (j
= 0; j
< term
.col
-1; j
++) {
861 if (term
.line
[i
][j
].mode
& attr
) {
872 tsetdirt(0, term
.row
-1);
879 int alt
= IS_SET(MODE_ALTSCREEN
);
881 if (mode
== CURSOR_SAVE
) {
883 } else if (mode
== CURSOR_LOAD
) {
885 tmoveto(c
[alt
].x
, c
[alt
].y
);
898 }, .x
= 0, .y
= 0, .state
= CURSOR_DEFAULT
};
900 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
901 for (i
= tabspaces
; i
< term
.col
; i
+= tabspaces
)
904 term
.bot
= term
.row
- 1;
905 term
.mode
= MODE_WRAP
|MODE_UTF8
;
906 memset(term
.trantbl
, CS_USA
, sizeof(term
.trantbl
));
909 for (i
= 0; i
< 2; i
++) {
911 tcursor(CURSOR_SAVE
);
912 tclearregion(0, 0, term
.col
-1, term
.row
-1);
918 tnew(int col
, int row
)
920 term
= (Term
){ .c
= { .attr
= { .fg
= defaultfg
, .bg
= defaultbg
} } };
930 Line
*tmp
= term
.line
;
932 term
.line
= term
.alt
;
934 term
.mode
^= MODE_ALTSCREEN
;
939 tscrolldown(int orig
, int n
)
944 LIMIT(n
, 0, term
.bot
-orig
+1);
946 tsetdirt(orig
, term
.bot
-n
);
947 tclearregion(0, term
.bot
-n
+1, term
.col
-1, term
.bot
);
949 for (i
= term
.bot
; i
>= orig
+n
; i
--) {
951 term
.line
[i
] = term
.line
[i
-n
];
952 term
.line
[i
-n
] = temp
;
959 tscrollup(int orig
, int n
)
964 LIMIT(n
, 0, term
.bot
-orig
+1);
966 tclearregion(0, orig
, term
.col
-1, orig
+n
-1);
967 tsetdirt(orig
+n
, term
.bot
);
969 for (i
= orig
; i
<= term
.bot
-n
; i
++) {
971 term
.line
[i
] = term
.line
[i
+n
];
972 term
.line
[i
+n
] = temp
;
979 selscroll(int orig
, int n
)
984 if (BETWEEN(sel
.ob
.y
, orig
, term
.bot
) || BETWEEN(sel
.oe
.y
, orig
, term
.bot
)) {
985 if ((sel
.ob
.y
+= n
) > term
.bot
|| (sel
.oe
.y
+= n
) < term
.top
) {
989 if (sel
.type
== SEL_RECTANGULAR
) {
990 if (sel
.ob
.y
< term
.top
)
992 if (sel
.oe
.y
> term
.bot
)
995 if (sel
.ob
.y
< term
.top
) {
999 if (sel
.oe
.y
> term
.bot
) {
1000 sel
.oe
.y
= term
.bot
;
1001 sel
.oe
.x
= term
.col
;
1009 tnewline(int first_col
)
1013 if (y
== term
.bot
) {
1014 tscrollup(term
.top
, 1);
1018 tmoveto(first_col
? 0 : term
.c
.x
, y
);
1024 char *p
= csiescseq
.buf
, *np
;
1033 csiescseq
.buf
[csiescseq
.len
] = '\0';
1034 while (p
< csiescseq
.buf
+csiescseq
.len
) {
1036 v
= strtol(p
, &np
, 10);
1039 if (v
== LONG_MAX
|| v
== LONG_MIN
)
1041 csiescseq
.arg
[csiescseq
.narg
++] = v
;
1043 if (*p
!= ';' || csiescseq
.narg
== ESC_ARG_SIZ
)
1047 csiescseq
.mode
[0] = *p
++;
1048 csiescseq
.mode
[1] = (p
< csiescseq
.buf
+csiescseq
.len
) ? *p
: '\0';
1051 /* for absolute user moves, when decom is set */
1053 tmoveato(int x
, int y
)
1055 tmoveto(x
, y
+ ((term
.c
.state
& CURSOR_ORIGIN
) ? term
.top
: 0));
1059 tmoveto(int x
, int y
)
1063 if (term
.c
.state
& CURSOR_ORIGIN
) {
1068 maxy
= term
.row
- 1;
1070 term
.c
.state
&= ~CURSOR_WRAPNEXT
;
1071 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1072 term
.c
.y
= LIMIT(y
, miny
, maxy
);
1076 tsetchar(Rune u
, Glyph
*attr
, int x
, int y
)
1078 static char *vt100_0
[62] = { /* 0x41 - 0x7e */
1079 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1080 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1081 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1082 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1083 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1084 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1085 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1086 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1090 * The table is proudly stolen from rxvt.
1092 if (term
.trantbl
[term
.charset
] == CS_GRAPHIC0
&&
1093 BETWEEN(u
, 0x41, 0x7e) && vt100_0
[u
- 0x41])
1094 utf8decode(vt100_0
[u
- 0x41], &u
, UTF_SIZ
);
1096 if (term
.line
[y
][x
].mode
& ATTR_WIDE
) {
1097 if (x
+1 < term
.col
) {
1098 term
.line
[y
][x
+1].u
= ' ';
1099 term
.line
[y
][x
+1].mode
&= ~ATTR_WDUMMY
;
1101 } else if (term
.line
[y
][x
].mode
& ATTR_WDUMMY
) {
1102 term
.line
[y
][x
-1].u
= ' ';
1103 term
.line
[y
][x
-1].mode
&= ~ATTR_WIDE
;
1107 term
.line
[y
][x
] = *attr
;
1108 term
.line
[y
][x
].u
= u
;
1112 tclearregion(int x1
, int y1
, int x2
, int y2
)
1118 temp
= x1
, x1
= x2
, x2
= temp
;
1120 temp
= y1
, y1
= y2
, y2
= temp
;
1122 LIMIT(x1
, 0, term
.col
-1);
1123 LIMIT(x2
, 0, term
.col
-1);
1124 LIMIT(y1
, 0, term
.row
-1);
1125 LIMIT(y2
, 0, term
.row
-1);
1127 for (y
= y1
; y
<= y2
; y
++) {
1129 for (x
= x1
; x
<= x2
; x
++) {
1130 gp
= &term
.line
[y
][x
];
1133 gp
->fg
= term
.c
.attr
.fg
;
1134 gp
->bg
= term
.c
.attr
.bg
;
1147 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1151 size
= term
.col
- src
;
1152 line
= term
.line
[term
.c
.y
];
1154 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1155 tclearregion(term
.col
-n
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1164 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1168 size
= term
.col
- dst
;
1169 line
= term
.line
[term
.c
.y
];
1171 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1172 tclearregion(src
, term
.c
.y
, dst
- 1, term
.c
.y
);
1176 tinsertblankline(int n
)
1178 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1179 tscrolldown(term
.c
.y
, n
);
1185 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1186 tscrollup(term
.c
.y
, n
);
1190 tdefcolor(int *attr
, int *npar
, int l
)
1195 switch (attr
[*npar
+ 1]) {
1196 case 2: /* direct color in RGB space */
1197 if (*npar
+ 4 >= l
) {
1199 "erresc(38): Incorrect number of parameters (%d)\n",
1203 r
= attr
[*npar
+ 2];
1204 g
= attr
[*npar
+ 3];
1205 b
= attr
[*npar
+ 4];
1207 if (!BETWEEN(r
, 0, 255) || !BETWEEN(g
, 0, 255) || !BETWEEN(b
, 0, 255))
1208 fprintf(stderr
, "erresc: bad rgb color (%u,%u,%u)\n",
1211 idx
= TRUECOLOR(r
, g
, b
);
1213 case 5: /* indexed color */
1214 if (*npar
+ 2 >= l
) {
1216 "erresc(38): Incorrect number of parameters (%d)\n",
1221 if (!BETWEEN(attr
[*npar
], 0, 255))
1222 fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[*npar
]);
1226 case 0: /* implemented defined (only foreground) */
1227 case 1: /* transparent */
1228 case 3: /* direct color in CMY space */
1229 case 4: /* direct color in CMYK space */
1232 "erresc(38): gfx attr %d unknown\n", attr
[*npar
]);
1240 tsetattr(int *attr
, int l
)
1245 for (i
= 0; i
< l
; i
++) {
1248 term
.c
.attr
.mode
&= ~(
1257 term
.c
.attr
.fg
= defaultfg
;
1258 term
.c
.attr
.bg
= defaultbg
;
1261 term
.c
.attr
.mode
|= ATTR_BOLD
;
1264 term
.c
.attr
.mode
|= ATTR_FAINT
;
1267 term
.c
.attr
.mode
|= ATTR_ITALIC
;
1270 term
.c
.attr
.mode
|= ATTR_UNDERLINE
;
1272 case 5: /* slow blink */
1274 case 6: /* rapid blink */
1275 term
.c
.attr
.mode
|= ATTR_BLINK
;
1278 term
.c
.attr
.mode
|= ATTR_REVERSE
;
1281 term
.c
.attr
.mode
|= ATTR_INVISIBLE
;
1284 term
.c
.attr
.mode
|= ATTR_STRUCK
;
1287 term
.c
.attr
.mode
&= ~(ATTR_BOLD
| ATTR_FAINT
);
1290 term
.c
.attr
.mode
&= ~ATTR_ITALIC
;
1293 term
.c
.attr
.mode
&= ~ATTR_UNDERLINE
;
1296 term
.c
.attr
.mode
&= ~ATTR_BLINK
;
1299 term
.c
.attr
.mode
&= ~ATTR_REVERSE
;
1302 term
.c
.attr
.mode
&= ~ATTR_INVISIBLE
;
1305 term
.c
.attr
.mode
&= ~ATTR_STRUCK
;
1308 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1309 term
.c
.attr
.fg
= idx
;
1312 term
.c
.attr
.fg
= defaultfg
;
1315 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1316 term
.c
.attr
.bg
= idx
;
1319 term
.c
.attr
.bg
= defaultbg
;
1322 if (BETWEEN(attr
[i
], 30, 37)) {
1323 term
.c
.attr
.fg
= attr
[i
] - 30;
1324 } else if (BETWEEN(attr
[i
], 40, 47)) {
1325 term
.c
.attr
.bg
= attr
[i
] - 40;
1326 } else if (BETWEEN(attr
[i
], 90, 97)) {
1327 term
.c
.attr
.fg
= attr
[i
] - 90 + 8;
1328 } else if (BETWEEN(attr
[i
], 100, 107)) {
1329 term
.c
.attr
.bg
= attr
[i
] - 100 + 8;
1332 "erresc(default): gfx attr %d unknown\n",
1333 attr
[i
]), csidump();
1341 tsetscroll(int t
, int b
)
1345 LIMIT(t
, 0, term
.row
-1);
1346 LIMIT(b
, 0, term
.row
-1);
1357 tsetmode(int priv
, int set
, int *args
, int narg
)
1362 for (lim
= args
+ narg
; args
< lim
; ++args
) {
1365 case 1: /* DECCKM -- Cursor key */
1366 MODBIT(term
.mode
, set
, MODE_APPCURSOR
);
1368 case 5: /* DECSCNM -- Reverse video */
1370 MODBIT(term
.mode
, set
, MODE_REVERSE
);
1371 if (mode
!= term
.mode
)
1374 case 6: /* DECOM -- Origin */
1375 MODBIT(term
.c
.state
, set
, CURSOR_ORIGIN
);
1378 case 7: /* DECAWM -- Auto wrap */
1379 MODBIT(term
.mode
, set
, MODE_WRAP
);
1381 case 0: /* Error (IGNORED) */
1382 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1383 case 3: /* DECCOLM -- Column (IGNORED) */
1384 case 4: /* DECSCLM -- Scroll (IGNORED) */
1385 case 8: /* DECARM -- Auto repeat (IGNORED) */
1386 case 18: /* DECPFF -- Printer feed (IGNORED) */
1387 case 19: /* DECPEX -- Printer extent (IGNORED) */
1388 case 42: /* DECNRCM -- National characters (IGNORED) */
1389 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1391 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1392 MODBIT(term
.mode
, !set
, MODE_HIDE
);
1394 case 9: /* X10 mouse compatibility mode */
1395 xsetpointermotion(0);
1396 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1397 MODBIT(term
.mode
, set
, MODE_MOUSEX10
);
1399 case 1000: /* 1000: report button press */
1400 xsetpointermotion(0);
1401 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1402 MODBIT(term
.mode
, set
, MODE_MOUSEBTN
);
1404 case 1002: /* 1002: report motion on button press */
1405 xsetpointermotion(0);
1406 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1407 MODBIT(term
.mode
, set
, MODE_MOUSEMOTION
);
1409 case 1003: /* 1003: enable all mouse motions */
1410 xsetpointermotion(set
);
1411 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1412 MODBIT(term
.mode
, set
, MODE_MOUSEMANY
);
1414 case 1004: /* 1004: send focus events to tty */
1415 MODBIT(term
.mode
, set
, MODE_FOCUS
);
1417 case 1006: /* 1006: extended reporting mode */
1418 MODBIT(term
.mode
, set
, MODE_MOUSESGR
);
1421 MODBIT(term
.mode
, set
, MODE_8BIT
);
1423 case 1049: /* swap screen & set/restore cursor as xterm */
1424 if (!allowaltscreen
)
1426 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1428 case 47: /* swap screen */
1430 if (!allowaltscreen
)
1432 alt
= IS_SET(MODE_ALTSCREEN
);
1434 tclearregion(0, 0, term
.col
-1,
1437 if (set
^ alt
) /* set is always 1 or 0 */
1443 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1445 case 2004: /* 2004: bracketed paste mode */
1446 MODBIT(term
.mode
, set
, MODE_BRCKTPASTE
);
1448 /* Not implemented mouse modes. See comments there. */
1449 case 1001: /* mouse highlight mode; can hang the
1450 terminal by design when implemented. */
1451 case 1005: /* UTF-8 mouse mode; will confuse
1452 applications not supporting UTF-8
1454 case 1015: /* urxvt mangled mouse mode; incompatible
1455 and can be mistaken for other control
1459 "erresc: unknown private set/reset mode %d\n",
1465 case 0: /* Error (IGNORED) */
1467 case 2: /* KAM -- keyboard action */
1468 MODBIT(term
.mode
, set
, MODE_KBDLOCK
);
1470 case 4: /* IRM -- Insertion-replacement */
1471 MODBIT(term
.mode
, set
, MODE_INSERT
);
1473 case 12: /* SRM -- Send/Receive */
1474 MODBIT(term
.mode
, !set
, MODE_ECHO
);
1476 case 20: /* LNM -- Linefeed/new line */
1477 MODBIT(term
.mode
, set
, MODE_CRLF
);
1481 "erresc: unknown set/reset mode %d\n",
1495 switch (csiescseq
.mode
[0]) {
1498 fprintf(stderr
, "erresc: unknown csi ");
1502 case '@': /* ICH -- Insert <n> blank char */
1503 DEFAULT(csiescseq
.arg
[0], 1);
1504 tinsertblank(csiescseq
.arg
[0]);
1506 case 'A': /* CUU -- Cursor <n> Up */
1507 DEFAULT(csiescseq
.arg
[0], 1);
1508 tmoveto(term
.c
.x
, term
.c
.y
-csiescseq
.arg
[0]);
1510 case 'B': /* CUD -- Cursor <n> Down */
1511 case 'e': /* VPR --Cursor <n> Down */
1512 DEFAULT(csiescseq
.arg
[0], 1);
1513 tmoveto(term
.c
.x
, term
.c
.y
+csiescseq
.arg
[0]);
1515 case 'i': /* MC -- Media Copy */
1516 switch (csiescseq
.arg
[0]) {
1521 tdumpline(term
.c
.y
);
1527 term
.mode
&= ~MODE_PRINT
;
1530 term
.mode
|= MODE_PRINT
;
1534 case 'c': /* DA -- Device Attributes */
1535 if (csiescseq
.arg
[0] == 0)
1536 ttywrite(vtiden
, strlen(vtiden
));
1538 case 'C': /* CUF -- Cursor <n> Forward */
1539 case 'a': /* HPR -- Cursor <n> Forward */
1540 DEFAULT(csiescseq
.arg
[0], 1);
1541 tmoveto(term
.c
.x
+csiescseq
.arg
[0], term
.c
.y
);
1543 case 'D': /* CUB -- Cursor <n> Backward */
1544 DEFAULT(csiescseq
.arg
[0], 1);
1545 tmoveto(term
.c
.x
-csiescseq
.arg
[0], term
.c
.y
);
1547 case 'E': /* CNL -- Cursor <n> Down and first col */
1548 DEFAULT(csiescseq
.arg
[0], 1);
1549 tmoveto(0, term
.c
.y
+csiescseq
.arg
[0]);
1551 case 'F': /* CPL -- Cursor <n> Up and first col */
1552 DEFAULT(csiescseq
.arg
[0], 1);
1553 tmoveto(0, term
.c
.y
-csiescseq
.arg
[0]);
1555 case 'g': /* TBC -- Tabulation clear */
1556 switch (csiescseq
.arg
[0]) {
1557 case 0: /* clear current tab stop */
1558 term
.tabs
[term
.c
.x
] = 0;
1560 case 3: /* clear all the tabs */
1561 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1567 case 'G': /* CHA -- Move to <col> */
1569 DEFAULT(csiescseq
.arg
[0], 1);
1570 tmoveto(csiescseq
.arg
[0]-1, term
.c
.y
);
1572 case 'H': /* CUP -- Move to <row> <col> */
1574 DEFAULT(csiescseq
.arg
[0], 1);
1575 DEFAULT(csiescseq
.arg
[1], 1);
1576 tmoveato(csiescseq
.arg
[1]-1, csiescseq
.arg
[0]-1);
1578 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1579 DEFAULT(csiescseq
.arg
[0], 1);
1580 tputtab(csiescseq
.arg
[0]);
1582 case 'J': /* ED -- Clear screen */
1584 switch (csiescseq
.arg
[0]) {
1586 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1587 if (term
.c
.y
< term
.row
-1) {
1588 tclearregion(0, term
.c
.y
+1, term
.col
-1,
1594 tclearregion(0, 0, term
.col
-1, term
.c
.y
-1);
1595 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1598 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1604 case 'K': /* EL -- Clear line */
1605 switch (csiescseq
.arg
[0]) {
1607 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1,
1611 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1614 tclearregion(0, term
.c
.y
, term
.col
-1, term
.c
.y
);
1618 case 'S': /* SU -- Scroll <n> line up */
1619 DEFAULT(csiescseq
.arg
[0], 1);
1620 tscrollup(term
.top
, csiescseq
.arg
[0]);
1622 case 'T': /* SD -- Scroll <n> line down */
1623 DEFAULT(csiescseq
.arg
[0], 1);
1624 tscrolldown(term
.top
, csiescseq
.arg
[0]);
1626 case 'L': /* IL -- Insert <n> blank lines */
1627 DEFAULT(csiescseq
.arg
[0], 1);
1628 tinsertblankline(csiescseq
.arg
[0]);
1630 case 'l': /* RM -- Reset Mode */
1631 tsetmode(csiescseq
.priv
, 0, csiescseq
.arg
, csiescseq
.narg
);
1633 case 'M': /* DL -- Delete <n> lines */
1634 DEFAULT(csiescseq
.arg
[0], 1);
1635 tdeleteline(csiescseq
.arg
[0]);
1637 case 'X': /* ECH -- Erase <n> char */
1638 DEFAULT(csiescseq
.arg
[0], 1);
1639 tclearregion(term
.c
.x
, term
.c
.y
,
1640 term
.c
.x
+ csiescseq
.arg
[0] - 1, term
.c
.y
);
1642 case 'P': /* DCH -- Delete <n> char */
1643 DEFAULT(csiescseq
.arg
[0], 1);
1644 tdeletechar(csiescseq
.arg
[0]);
1646 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1647 DEFAULT(csiescseq
.arg
[0], 1);
1648 tputtab(-csiescseq
.arg
[0]);
1650 case 'd': /* VPA -- Move to <row> */
1651 DEFAULT(csiescseq
.arg
[0], 1);
1652 tmoveato(term
.c
.x
, csiescseq
.arg
[0]-1);
1654 case 'h': /* SM -- Set terminal mode */
1655 tsetmode(csiescseq
.priv
, 1, csiescseq
.arg
, csiescseq
.narg
);
1657 case 'm': /* SGR -- Terminal attribute (color) */
1658 tsetattr(csiescseq
.arg
, csiescseq
.narg
);
1660 case 'n': /* DSR – Device Status Report (cursor position) */
1661 if (csiescseq
.arg
[0] == 6) {
1662 len
= snprintf(buf
, sizeof(buf
),"\033[%i;%iR",
1663 term
.c
.y
+1, term
.c
.x
+1);
1667 case 'r': /* DECSTBM -- Set Scrolling Region */
1668 if (csiescseq
.priv
) {
1671 DEFAULT(csiescseq
.arg
[0], 1);
1672 DEFAULT(csiescseq
.arg
[1], term
.row
);
1673 tsetscroll(csiescseq
.arg
[0]-1, csiescseq
.arg
[1]-1);
1677 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1678 tcursor(CURSOR_SAVE
);
1680 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1681 tcursor(CURSOR_LOAD
);
1684 switch (csiescseq
.mode
[1]) {
1685 case 'q': /* DECSCUSR -- Set Cursor Style */
1686 DEFAULT(csiescseq
.arg
[0], 1);
1687 if (!BETWEEN(csiescseq
.arg
[0], 0, 6)) {
1690 win
.cursor
= csiescseq
.arg
[0];
1705 fprintf(stderr
, "ESC[");
1706 for (i
= 0; i
< csiescseq
.len
; i
++) {
1707 c
= csiescseq
.buf
[i
] & 0xff;
1710 } else if (c
== '\n') {
1711 fprintf(stderr
, "(\\n)");
1712 } else if (c
== '\r') {
1713 fprintf(stderr
, "(\\r)");
1714 } else if (c
== 0x1b) {
1715 fprintf(stderr
, "(\\e)");
1717 fprintf(stderr
, "(%02x)", c
);
1726 memset(&csiescseq
, 0, sizeof(csiescseq
));
1735 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
1737 par
= (narg
= strescseq
.narg
) ? atoi(strescseq
.args
[0]) : 0;
1739 switch (strescseq
.type
) {
1740 case ']': /* OSC -- Operating System Command */
1746 xsettitle(strescseq
.args
[1]);
1752 dec
= base64dec(strescseq
.args
[2]);
1754 xsetsel(dec
, CurrentTime
);
1757 fprintf(stderr
, "erresc: invalid base64\n");
1761 case 4: /* color set */
1764 p
= strescseq
.args
[2];
1766 case 104: /* color reset, here p = NULL */
1767 j
= (narg
> 1) ? atoi(strescseq
.args
[1]) : -1;
1768 if (xsetcolorname(j
, p
)) {
1769 fprintf(stderr
, "erresc: invalid color %s\n", p
);
1772 * TODO if defaultbg color is changed, borders
1780 case 'k': /* old title set compatibility */
1781 xsettitle(strescseq
.args
[0]);
1783 case 'P': /* DCS -- Device Control String */
1784 term
.mode
|= ESC_DCS
;
1785 case '_': /* APC -- Application Program Command */
1786 case '^': /* PM -- Privacy Message */
1790 fprintf(stderr
, "erresc: unknown str ");
1798 char *p
= strescseq
.buf
;
1801 strescseq
.buf
[strescseq
.len
] = '\0';
1806 while (strescseq
.narg
< STR_ARG_SIZ
) {
1807 strescseq
.args
[strescseq
.narg
++] = p
;
1808 while ((c
= *p
) != ';' && c
!= '\0')
1822 fprintf(stderr
, "ESC%c", strescseq
.type
);
1823 for (i
= 0; i
< strescseq
.len
; i
++) {
1824 c
= strescseq
.buf
[i
] & 0xff;
1828 } else if (isprint(c
)) {
1830 } else if (c
== '\n') {
1831 fprintf(stderr
, "(\\n)");
1832 } else if (c
== '\r') {
1833 fprintf(stderr
, "(\\r)");
1834 } else if (c
== 0x1b) {
1835 fprintf(stderr
, "(\\e)");
1837 fprintf(stderr
, "(%02x)", c
);
1840 fprintf(stderr
, "ESC\\\n");
1846 memset(&strescseq
, 0, sizeof(strescseq
));
1850 sendbreak(const Arg
*arg
)
1852 if (tcsendbreak(cmdfd
, 0))
1853 perror("Error sending break");
1857 tprinter(char *s
, size_t len
)
1859 if (iofd
!= -1 && xwrite(iofd
, s
, len
) < 0) {
1860 perror("Error writing to output file");
1867 iso14755(const Arg
*arg
)
1870 char *us
, *e
, codepoint
[9], uc
[UTF_SIZ
];
1871 unsigned long utf32
;
1873 if (!(p
= popen(ISO14755CMD
, "r")))
1876 us
= fgets(codepoint
, sizeof(codepoint
), p
);
1879 if (!us
|| *us
== '\0' || *us
== '-' || strlen(us
) > 7)
1881 if ((utf32
= strtoul(us
, &e
, 16)) == ULONG_MAX
||
1882 (*e
!= '\n' && *e
!= '\0'))
1885 ttysend(uc
, utf8encode(utf32
, uc
));
1889 toggleprinter(const Arg
*arg
)
1891 term
.mode
^= MODE_PRINT
;
1895 printscreen(const Arg
*arg
)
1901 printsel(const Arg
*arg
)
1911 if ((ptr
= getsel())) {
1912 tprinter(ptr
, strlen(ptr
));
1923 bp
= &term
.line
[n
][0];
1924 end
= &bp
[MIN(tlinelen(n
), term
.col
) - 1];
1925 if (bp
!= end
|| bp
->u
!= ' ') {
1926 for ( ;bp
<= end
; ++bp
)
1927 tprinter(buf
, utf8encode(bp
->u
, buf
));
1937 for (i
= 0; i
< term
.row
; ++i
)
1947 while (x
< term
.col
&& n
--)
1948 for (++x
; x
< term
.col
&& !term
.tabs
[x
]; ++x
)
1951 while (x
> 0 && n
++)
1952 for (--x
; x
> 0 && !term
.tabs
[x
]; --x
)
1955 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1959 tdefutf8(char ascii
)
1962 term
.mode
|= MODE_UTF8
;
1963 else if (ascii
== '@')
1964 term
.mode
&= ~MODE_UTF8
;
1968 tdeftran(char ascii
)
1970 static char cs
[] = "0B";
1971 static int vcs
[] = {CS_GRAPHIC0
, CS_USA
};
1974 if ((p
= strchr(cs
, ascii
)) == NULL
) {
1975 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
1977 term
.trantbl
[term
.icharset
] = vcs
[p
- cs
];
1986 if (c
== '8') { /* DEC screen alignment test. */
1987 for (x
= 0; x
< term
.col
; ++x
) {
1988 for (y
= 0; y
< term
.row
; ++y
)
1989 tsetchar('E', &term
.c
.attr
, x
, y
);
1995 tstrsequence(uchar c
)
2000 case 0x90: /* DCS -- Device Control String */
2002 term
.esc
|= ESC_DCS
;
2004 case 0x9f: /* APC -- Application Program Command */
2007 case 0x9e: /* PM -- Privacy Message */
2010 case 0x9d: /* OSC -- Operating System Command */
2015 term
.esc
|= ESC_STR
;
2019 tcontrolcode(uchar ascii
)
2026 tmoveto(term
.c
.x
-1, term
.c
.y
);
2029 tmoveto(0, term
.c
.y
);
2034 /* go to first col if the mode is set */
2035 tnewline(IS_SET(MODE_CRLF
));
2037 case '\a': /* BEL */
2038 if (term
.esc
& ESC_STR_END
) {
2039 /* backwards compatibility to xterm */
2045 case '\033': /* ESC */
2047 term
.esc
&= ~(ESC_CSI
|ESC_ALTCHARSET
|ESC_TEST
);
2048 term
.esc
|= ESC_START
;
2050 case '\016': /* SO (LS1 -- Locking shift 1) */
2051 case '\017': /* SI (LS0 -- Locking shift 0) */
2052 term
.charset
= 1 - (ascii
- '\016');
2054 case '\032': /* SUB */
2055 tsetchar('?', &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2056 case '\030': /* CAN */
2059 case '\005': /* ENQ (IGNORED) */
2060 case '\000': /* NUL (IGNORED) */
2061 case '\021': /* XON (IGNORED) */
2062 case '\023': /* XOFF (IGNORED) */
2063 case 0177: /* DEL (IGNORED) */
2065 case 0x80: /* TODO: PAD */
2066 case 0x81: /* TODO: HOP */
2067 case 0x82: /* TODO: BPH */
2068 case 0x83: /* TODO: NBH */
2069 case 0x84: /* TODO: IND */
2071 case 0x85: /* NEL -- Next line */
2072 tnewline(1); /* always go to first col */
2074 case 0x86: /* TODO: SSA */
2075 case 0x87: /* TODO: ESA */
2077 case 0x88: /* HTS -- Horizontal tab stop */
2078 term
.tabs
[term
.c
.x
] = 1;
2080 case 0x89: /* TODO: HTJ */
2081 case 0x8a: /* TODO: VTS */
2082 case 0x8b: /* TODO: PLD */
2083 case 0x8c: /* TODO: PLU */
2084 case 0x8d: /* TODO: RI */
2085 case 0x8e: /* TODO: SS2 */
2086 case 0x8f: /* TODO: SS3 */
2087 case 0x91: /* TODO: PU1 */
2088 case 0x92: /* TODO: PU2 */
2089 case 0x93: /* TODO: STS */
2090 case 0x94: /* TODO: CCH */
2091 case 0x95: /* TODO: MW */
2092 case 0x96: /* TODO: SPA */
2093 case 0x97: /* TODO: EPA */
2094 case 0x98: /* TODO: SOS */
2095 case 0x99: /* TODO: SGCI */
2097 case 0x9a: /* DECID -- Identify Terminal */
2098 ttywrite(vtiden
, strlen(vtiden
));
2100 case 0x9b: /* TODO: CSI */
2101 case 0x9c: /* TODO: ST */
2103 case 0x90: /* DCS -- Device Control String */
2104 case 0x9d: /* OSC -- Operating System Command */
2105 case 0x9e: /* PM -- Privacy Message */
2106 case 0x9f: /* APC -- Application Program Command */
2107 tstrsequence(ascii
);
2110 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2111 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
2115 * returns 1 when the sequence is finished and it hasn't to read
2116 * more characters for this sequence, otherwise 0
2119 eschandle(uchar ascii
)
2123 term
.esc
|= ESC_CSI
;
2126 term
.esc
|= ESC_TEST
;
2129 term
.esc
|= ESC_UTF8
;
2131 case 'P': /* DCS -- Device Control String */
2132 case '_': /* APC -- Application Program Command */
2133 case '^': /* PM -- Privacy Message */
2134 case ']': /* OSC -- Operating System Command */
2135 case 'k': /* old title set compatibility */
2136 tstrsequence(ascii
);
2138 case 'n': /* LS2 -- Locking shift 2 */
2139 case 'o': /* LS3 -- Locking shift 3 */
2140 term
.charset
= 2 + (ascii
- 'n');
2142 case '(': /* GZD4 -- set primary charset G0 */
2143 case ')': /* G1D4 -- set secondary charset G1 */
2144 case '*': /* G2D4 -- set tertiary charset G2 */
2145 case '+': /* G3D4 -- set quaternary charset G3 */
2146 term
.icharset
= ascii
- '(';
2147 term
.esc
|= ESC_ALTCHARSET
;
2149 case 'D': /* IND -- Linefeed */
2150 if (term
.c
.y
== term
.bot
) {
2151 tscrollup(term
.top
, 1);
2153 tmoveto(term
.c
.x
, term
.c
.y
+1);
2156 case 'E': /* NEL -- Next line */
2157 tnewline(1); /* always go to first col */
2159 case 'H': /* HTS -- Horizontal tab stop */
2160 term
.tabs
[term
.c
.x
] = 1;
2162 case 'M': /* RI -- Reverse index */
2163 if (term
.c
.y
== term
.top
) {
2164 tscrolldown(term
.top
, 1);
2166 tmoveto(term
.c
.x
, term
.c
.y
-1);
2169 case 'Z': /* DECID -- Identify Terminal */
2170 ttywrite(vtiden
, strlen(vtiden
));
2172 case 'c': /* RIS -- Reset to inital state */
2177 case '=': /* DECPAM -- Application keypad */
2178 term
.mode
|= MODE_APPKEYPAD
;
2180 case '>': /* DECPNM -- Normal keypad */
2181 term
.mode
&= ~MODE_APPKEYPAD
;
2183 case '7': /* DECSC -- Save Cursor */
2184 tcursor(CURSOR_SAVE
);
2186 case '8': /* DECRC -- Restore Cursor */
2187 tcursor(CURSOR_LOAD
);
2189 case '\\': /* ST -- String Terminator */
2190 if (term
.esc
& ESC_STR_END
)
2194 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2195 (uchar
) ascii
, isprint(ascii
)? ascii
:'.');
2209 control
= ISCONTROL(u
);
2210 if (!IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2214 len
= utf8encode(u
, c
);
2215 if (!control
&& (width
= wcwidth(u
)) == -1) {
2216 memcpy(c
, "\357\277\275", 4); /* UTF_INVALID */
2221 if (IS_SET(MODE_PRINT
))
2225 * STR sequence must be checked before anything else
2226 * because it uses all following characters until it
2227 * receives a ESC, a SUB, a ST or any other C1 control
2230 if (term
.esc
& ESC_STR
) {
2231 if (u
== '\a' || u
== 030 || u
== 032 || u
== 033 ||
2233 term
.esc
&= ~(ESC_START
|ESC_STR
|ESC_DCS
);
2234 if (IS_SET(MODE_SIXEL
)) {
2235 /* TODO: render sixel */;
2236 term
.mode
&= ~MODE_SIXEL
;
2239 term
.esc
|= ESC_STR_END
;
2240 goto check_control_code
;
2244 if (IS_SET(MODE_SIXEL
)) {
2245 /* TODO: implement sixel mode */
2248 if (term
.esc
&ESC_DCS
&& strescseq
.len
== 0 && u
== 'q')
2249 term
.mode
|= MODE_SIXEL
;
2251 if (strescseq
.len
+len
>= sizeof(strescseq
.buf
)-1) {
2253 * Here is a bug in terminals. If the user never sends
2254 * some code to stop the str or esc command, then st
2255 * will stop responding. But this is better than
2256 * silently failing with unknown characters. At least
2257 * then users will report back.
2259 * In the case users ever get fixed, here is the code:
2268 memmove(&strescseq
.buf
[strescseq
.len
], c
, len
);
2269 strescseq
.len
+= len
;
2275 * Actions of control codes must be performed as soon they arrive
2276 * because they can be embedded inside a control sequence, and
2277 * they must not cause conflicts with sequences.
2282 * control codes are not shown ever
2285 } else if (term
.esc
& ESC_START
) {
2286 if (term
.esc
& ESC_CSI
) {
2287 csiescseq
.buf
[csiescseq
.len
++] = u
;
2288 if (BETWEEN(u
, 0x40, 0x7E)
2289 || csiescseq
.len
>= \
2290 sizeof(csiescseq
.buf
)-1) {
2296 } else if (term
.esc
& ESC_UTF8
) {
2298 } else if (term
.esc
& ESC_ALTCHARSET
) {
2300 } else if (term
.esc
& ESC_TEST
) {
2305 /* sequence already finished */
2309 * All characters which form part of a sequence are not
2314 if (sel
.ob
.x
!= -1 && BETWEEN(term
.c
.y
, sel
.ob
.y
, sel
.oe
.y
))
2317 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2318 if (IS_SET(MODE_WRAP
) && (term
.c
.state
& CURSOR_WRAPNEXT
)) {
2319 gp
->mode
|= ATTR_WRAP
;
2321 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2324 if (IS_SET(MODE_INSERT
) && term
.c
.x
+width
< term
.col
)
2325 memmove(gp
+width
, gp
, (term
.col
- term
.c
.x
- width
) * sizeof(Glyph
));
2327 if (term
.c
.x
+width
> term
.col
) {
2329 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2332 tsetchar(u
, &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2335 gp
->mode
|= ATTR_WIDE
;
2336 if (term
.c
.x
+1 < term
.col
) {
2338 gp
[1].mode
= ATTR_WDUMMY
;
2341 if (term
.c
.x
+width
< term
.col
) {
2342 tmoveto(term
.c
.x
+width
, term
.c
.y
);
2344 term
.c
.state
|= CURSOR_WRAPNEXT
;
2349 twrite(const char *buf
, int buflen
, int show_ctrl
)
2355 for (n
= 0; n
< buflen
; n
+= charsize
) {
2356 if (IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2357 /* process a complete utf8 char */
2358 charsize
= utf8decode(buf
+ n
, &u
, buflen
- n
);
2365 if (show_ctrl
&& ISCONTROL(u
)) {
2370 } else if (u
!= '\n' && u
!= '\r' && u
!= '\t') {
2381 tresize(int col
, int row
)
2384 int minrow
= MIN(row
, term
.row
);
2385 int mincol
= MIN(col
, term
.col
);
2389 if (col
< 1 || row
< 1) {
2391 "tresize: error resizing to %dx%d\n", col
, row
);
2396 * slide screen to keep cursor where we expect it -
2397 * tscrollup would work here, but we can optimize to
2398 * memmove because we're freeing the earlier lines
2400 for (i
= 0; i
<= term
.c
.y
- row
; i
++) {
2404 /* ensure that both src and dst are not NULL */
2406 memmove(term
.line
, term
.line
+ i
, row
* sizeof(Line
));
2407 memmove(term
.alt
, term
.alt
+ i
, row
* sizeof(Line
));
2409 for (i
+= row
; i
< term
.row
; i
++) {
2414 /* resize to new height */
2415 term
.line
= xrealloc(term
.line
, row
* sizeof(Line
));
2416 term
.alt
= xrealloc(term
.alt
, row
* sizeof(Line
));
2417 term
.dirty
= xrealloc(term
.dirty
, row
* sizeof(*term
.dirty
));
2418 term
.tabs
= xrealloc(term
.tabs
, col
* sizeof(*term
.tabs
));
2420 /* resize each row to new width, zero-pad if needed */
2421 for (i
= 0; i
< minrow
; i
++) {
2422 term
.line
[i
] = xrealloc(term
.line
[i
], col
* sizeof(Glyph
));
2423 term
.alt
[i
] = xrealloc(term
.alt
[i
], col
* sizeof(Glyph
));
2426 /* allocate any new rows */
2427 for (/* i = minrow */; i
< row
; i
++) {
2428 term
.line
[i
] = xmalloc(col
* sizeof(Glyph
));
2429 term
.alt
[i
] = xmalloc(col
* sizeof(Glyph
));
2431 if (col
> term
.col
) {
2432 bp
= term
.tabs
+ term
.col
;
2434 memset(bp
, 0, sizeof(*term
.tabs
) * (col
- term
.col
));
2435 while (--bp
> term
.tabs
&& !*bp
)
2437 for (bp
+= tabspaces
; bp
< term
.tabs
+ col
; bp
+= tabspaces
)
2440 /* update terminal size */
2443 /* reset scrolling region */
2444 tsetscroll(0, row
-1);
2445 /* make use of the LIMIT in tmoveto */
2446 tmoveto(term
.c
.x
, term
.c
.y
);
2447 /* Clearing both screens (it makes dirty all lines) */
2449 for (i
= 0; i
< 2; i
++) {
2450 if (mincol
< col
&& 0 < minrow
) {
2451 tclearregion(mincol
, 0, col
- 1, minrow
- 1);
2453 if (0 < col
&& minrow
< row
) {
2454 tclearregion(0, minrow
, col
- 1, row
- 1);
2457 tcursor(CURSOR_LOAD
);
2476 numlock(const Arg
*dummy
)