Xinqi Bao's Git
b7e215e7841ab5de18726ce8b2ca3c1309143773
1 /* See LICENSE for license details. */
14 #include <sys/ioctl.h>
15 #include <sys/select.h>
18 #include <sys/types.h>
31 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
33 #elif defined(__FreeBSD__) || defined(__DragonFly__)
38 #define UTF_INVALID 0xFFFD
39 #define ESC_BUF_SIZ (128*UTF_SIZ)
40 #define ESC_ARG_SIZ 16
41 #define STR_BUF_SIZ ESC_BUF_SIZ
42 #define STR_ARG_SIZ ESC_ARG_SIZ
45 #define NUMMAXLEN(x) ((int)(sizeof(x) * 2.56 + 0.5) + 1)
46 #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == '\177')
47 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
48 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
49 #define ISDELIM(u) (utf8strchr(worddelimiters, u) != NULL)
52 #define ISO14755CMD "dmenu -w \"$WINDOWID\" -p codepoint: </dev/null"
54 enum cursor_movement
{
78 ESC_STR
= 4, /* OSC, PM, APC */
80 ESC_STR_END
= 16, /* a final string was encountered */
81 ESC_TEST
= 32, /* Enter in test mode */
86 /* CSI Escape sequence structs */
87 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
89 char buf
[ESC_BUF_SIZ
]; /* raw string */
90 int len
; /* raw string length */
93 int narg
; /* nb of args */
97 /* STR Escape sequence structs */
98 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
100 char type
; /* ESC type ... */
101 char buf
[STR_BUF_SIZ
]; /* raw string */
102 int len
; /* raw string length */
103 char *args
[STR_ARG_SIZ
];
104 int narg
; /* nb of args */
108 static void execsh(char **);
109 static void stty(char **);
110 static void sigchld(int);
112 static void csidump(void);
113 static void csihandle(void);
114 static void csiparse(void);
115 static void csireset(void);
116 static int eschandle(uchar
);
117 static void strdump(void);
118 static void strhandle(void);
119 static void strparse(void);
120 static void strreset(void);
122 static void tprinter(char *, size_t);
123 static void tdumpsel(void);
124 static void tdumpline(int);
125 static void tdump(void);
126 static void tclearregion(int, int, int, int);
127 static void tcursor(int);
128 static void tdeletechar(int);
129 static void tdeleteline(int);
130 static void tinsertblank(int);
131 static void tinsertblankline(int);
132 static int tlinelen(int);
133 static void tmoveto(int, int);
134 static void tmoveato(int, int);
135 static void tnewline(int);
136 static void tputtab(int);
137 static void tputc(Rune
);
138 static void treset(void);
139 static void tscrollup(int, int);
140 static void tscrolldown(int, int);
141 static void tsetattr(int *, int);
142 static void tsetchar(Rune
, Glyph
*, int, int);
143 static void tsetdirt(int, int);
144 static void tsetscroll(int, int);
145 static void tswapscreen(void);
146 static void tsetmode(int, int, int *, int);
147 static int twrite(const char *, int, int);
148 static void tfulldirt(void);
149 static void tcontrolcode(uchar
);
150 static void tdectest(char );
151 static void tdefutf8(char);
152 static int32_t tdefcolor(int *, int *, int);
153 static void tdeftran(char);
154 static void tstrsequence(uchar
);
156 static void selscroll(int, int);
157 static void selsnap(int *, int *, int);
159 static Rune
utf8decodebyte(char, size_t *);
160 static char utf8encodebyte(Rune
, size_t);
161 static char *utf8strchr(char *s
, Rune u
);
162 static size_t utf8validate(Rune
*, size_t);
164 static char *base64dec(const char *);
166 static ssize_t
xwrite(int, const char *, size_t);
172 int oldbutton
= 3; /* button event on startup: 3 = release */
174 static Selection sel
;
175 static CSIEscape csiescseq
;
176 static STREscape strescseq
;
179 static uchar utfbyte
[UTF_SIZ
+ 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
180 static uchar utfmask
[UTF_SIZ
+ 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
181 static Rune utfmin
[UTF_SIZ
+ 1] = { 0, 0, 0x80, 0x800, 0x10000};
182 static Rune utfmax
[UTF_SIZ
+ 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
185 xwrite(int fd
, const char *s
, size_t len
)
191 r
= write(fd
, s
, len
);
204 void *p
= malloc(len
);
207 die("Out of memory\n");
213 xrealloc(void *p
, size_t len
)
215 if ((p
= realloc(p
, len
)) == NULL
)
216 die("Out of memory\n");
224 if ((s
= strdup(s
)) == NULL
)
225 die("Out of memory\n");
231 utf8decode(const char *c
, Rune
*u
, size_t clen
)
233 size_t i
, j
, len
, type
;
239 udecoded
= utf8decodebyte(c
[0], &len
);
240 if (!BETWEEN(len
, 1, UTF_SIZ
))
242 for (i
= 1, j
= 1; i
< clen
&& j
< len
; ++i
, ++j
) {
243 udecoded
= (udecoded
<< 6) | utf8decodebyte(c
[i
], &type
);
250 utf8validate(u
, len
);
256 utf8decodebyte(char c
, size_t *i
)
258 for (*i
= 0; *i
< LEN(utfmask
); ++(*i
))
259 if (((uchar
)c
& utfmask
[*i
]) == utfbyte
[*i
])
260 return (uchar
)c
& ~utfmask
[*i
];
266 utf8encode(Rune u
, char *c
)
270 len
= utf8validate(&u
, 0);
274 for (i
= len
- 1; i
!= 0; --i
) {
275 c
[i
] = utf8encodebyte(u
, 0);
278 c
[0] = utf8encodebyte(u
, len
);
284 utf8encodebyte(Rune u
, size_t i
)
286 return utfbyte
[i
] | (u
& ~utfmask
[i
]);
290 utf8strchr(char *s
, Rune u
)
296 for (i
= 0, j
= 0; i
< len
; i
+= j
) {
297 if (!(j
= utf8decode(&s
[i
], &r
, len
- i
)))
307 utf8validate(Rune
*u
, size_t i
)
309 if (!BETWEEN(*u
, utfmin
[i
], utfmax
[i
]) || BETWEEN(*u
, 0xD800, 0xDFFF))
311 for (i
= 1; *u
> utfmax
[i
]; ++i
)
317 static const char base64_digits
[] = {
318 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
319 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0,
320 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
321 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
322 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
323 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0,
324 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
325 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
326 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
327 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
328 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
329 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
333 base64dec_getc(const char **src
)
335 while (**src
&& !isprint(**src
)) (*src
)++;
340 base64dec(const char *src
)
342 size_t in_len
= strlen(src
);
346 in_len
+= 4 - (in_len
% 4);
347 result
= dst
= xmalloc(in_len
/ 4 * 3 + 1);
349 int a
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
350 int b
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
351 int c
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
352 int d
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
354 *dst
++ = (a
<< 2) | ((b
& 0x30) >> 4);
357 *dst
++ = ((b
& 0x0f) << 4) | ((c
& 0x3c) >> 2);
360 *dst
++ = ((c
& 0x03) << 6) | d
;
379 if (term
.line
[y
][i
- 1].mode
& ATTR_WRAP
)
382 while (i
> 0 && term
.line
[y
][i
- 1].u
== ' ')
389 selstart(int col
, int row
, int snap
)
392 sel
.mode
= SEL_EMPTY
;
393 sel
.type
= SEL_REGULAR
;
395 sel
.oe
.x
= sel
.ob
.x
= col
;
396 sel
.oe
.y
= sel
.ob
.y
= row
;
400 sel
.mode
= SEL_READY
;
401 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
405 selextend(int col
, int row
, int type
, int done
)
407 int oldey
, oldex
, oldsby
, oldsey
, oldtype
;
411 if (done
&& sel
.mode
== SEL_EMPTY
) {
422 sel
.alt
= IS_SET(MODE_ALTSCREEN
);
428 if (oldey
!= sel
.oe
.y
|| oldex
!= sel
.oe
.x
|| oldtype
!= sel
.type
)
429 tsetdirt(MIN(sel
.nb
.y
, oldsby
), MAX(sel
.ne
.y
, oldsey
));
431 sel
.mode
= done
? SEL_IDLE
: SEL_READY
;
439 if (sel
.type
== SEL_REGULAR
&& sel
.ob
.y
!= sel
.oe
.y
) {
440 sel
.nb
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.ob
.x
: sel
.oe
.x
;
441 sel
.ne
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.oe
.x
: sel
.ob
.x
;
443 sel
.nb
.x
= MIN(sel
.ob
.x
, sel
.oe
.x
);
444 sel
.ne
.x
= MAX(sel
.ob
.x
, sel
.oe
.x
);
446 sel
.nb
.y
= MIN(sel
.ob
.y
, sel
.oe
.y
);
447 sel
.ne
.y
= MAX(sel
.ob
.y
, sel
.oe
.y
);
449 selsnap(&sel
.nb
.x
, &sel
.nb
.y
, -1);
450 selsnap(&sel
.ne
.x
, &sel
.ne
.y
, +1);
452 /* expand selection over line breaks */
453 if (sel
.type
== SEL_RECTANGULAR
)
455 i
= tlinelen(sel
.nb
.y
);
458 if (tlinelen(sel
.ne
.y
) <= sel
.ne
.x
)
459 sel
.ne
.x
= term
.col
- 1;
463 selected(int x
, int y
)
465 if (sel
.mode
== SEL_EMPTY
|| sel
.ob
.x
== -1 ||
466 sel
.alt
!= IS_SET(MODE_ALTSCREEN
))
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
))
612 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
616 die(const char *errstr
, ...)
620 va_start(ap
, errstr
);
621 vfprintf(stderr
, errstr
, ap
);
630 const struct passwd
*pw
;
633 if ((pw
= getpwuid(getuid())) == NULL
) {
635 die("getpwuid:%s\n", strerror(errno
));
637 die("who are you?\n");
640 if ((sh
= getenv("SHELL")) == NULL
)
641 sh
= (pw
->pw_shell
[0]) ? pw
->pw_shell
: shell
;
649 DEFAULT(args
, ((char *[]) {prog
, NULL
}));
654 setenv("LOGNAME", pw
->pw_name
, 1);
655 setenv("USER", pw
->pw_name
, 1);
656 setenv("SHELL", sh
, 1);
657 setenv("HOME", pw
->pw_dir
, 1);
658 setenv("TERM", termname
, 1);
660 signal(SIGCHLD
, SIG_DFL
);
661 signal(SIGHUP
, SIG_DFL
);
662 signal(SIGINT
, SIG_DFL
);
663 signal(SIGQUIT
, SIG_DFL
);
664 signal(SIGTERM
, SIG_DFL
);
665 signal(SIGALRM
, SIG_DFL
);
677 if ((p
= waitpid(pid
, &stat
, WNOHANG
)) < 0)
678 die("Waiting for pid %hd failed: %s\n", pid
, strerror(errno
));
683 if (!WIFEXITED(stat
) || WEXITSTATUS(stat
))
684 die("child finished with error '%d'\n", stat
);
692 char cmd
[_POSIX_ARG_MAX
], **p
, *q
, *s
;
695 if ((n
= strlen(stty_args
)) > sizeof(cmd
)-1)
696 die("incorrect stty parameters\n");
697 memcpy(cmd
, stty_args
, n
);
699 siz
= sizeof(cmd
) - n
;
700 for (p
= args
; p
&& (s
= *p
); ++p
) {
701 if ((n
= strlen(s
)) > siz
-1)
702 die("stty parameter length too long\n");
709 if (system(cmd
) != 0)
710 perror("Couldn't call stty");
714 ttynew(char *line
, char *out
, char **args
)
719 term
.mode
|= MODE_PRINT
;
720 iofd
= (!strcmp(out
, "-")) ?
721 1 : open(out
, O_WRONLY
| O_CREAT
, 0666);
723 fprintf(stderr
, "Error opening %s:%s\n",
724 out
, strerror(errno
));
729 if ((cmdfd
= open(line
, O_RDWR
)) < 0)
730 die("open line failed: %s\n", strerror(errno
));
736 /* seems to work fine on linux, openbsd and freebsd */
737 if (openpty(&m
, &s
, NULL
, NULL
, NULL
) < 0)
738 die("openpty failed: %s\n", strerror(errno
));
740 switch (pid
= fork()) {
742 die("fork failed\n");
746 setsid(); /* create a new process group */
750 if (ioctl(s
, TIOCSCTTY
, NULL
) < 0)
751 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno
));
759 signal(SIGCHLD
, sigchld
);
767 static char buf
[BUFSIZ
];
768 static int buflen
= 0;
772 /* append read bytes to unprocessed bytes */
773 if ((ret
= read(cmdfd
, buf
+buflen
, LEN(buf
)-buflen
)) < 0)
774 die("Couldn't read from shell: %s\n", strerror(errno
));
777 written
= twrite(buf
, buflen
, 0);
779 /* keep any uncomplete utf8 char for the next call */
781 memmove(buf
, buf
+ written
, buflen
);
787 ttywrite(const char *s
, size_t n
)
794 * Remember that we are using a pty, which might be a modem line.
795 * Writing too much will clog the line. That's why we are doing this
797 * FIXME: Migrate the world to Plan 9.
805 /* Check if we can write. */
806 if (pselect(cmdfd
+1, &rfd
, &wfd
, NULL
, NULL
, NULL
) < 0) {
809 die("select failed: %s\n", strerror(errno
));
811 if (FD_ISSET(cmdfd
, &wfd
)) {
813 * Only write the bytes written by ttywrite() or the
814 * default of 256. This seems to be a reasonable value
815 * for a serial line. Bigger values might clog the I/O.
817 if ((r
= write(cmdfd
, s
, (n
< lim
)? n
: lim
)) < 0)
821 * We weren't able to write out everything.
822 * This means the buffer is getting full
830 /* All bytes have been written. */
834 if (FD_ISSET(cmdfd
, &rfd
))
840 die("write error on tty: %s\n", strerror(errno
));
844 ttysend(char *s
, size_t n
)
847 if (IS_SET(MODE_ECHO
))
852 ttyresize(int tw
, int th
)
860 if (ioctl(cmdfd
, TIOCSWINSZ
, &w
) < 0)
861 fprintf(stderr
, "Couldn't set window size: %s\n", strerror(errno
));
869 for (i
= 0; i
< term
.row
-1; i
++) {
870 for (j
= 0; j
< term
.col
-1; j
++) {
871 if (term
.line
[i
][j
].mode
& attr
)
880 tsetdirt(int top
, int bot
)
884 LIMIT(top
, 0, term
.row
-1);
885 LIMIT(bot
, 0, term
.row
-1);
887 for (i
= top
; i
<= bot
; i
++)
892 tsetdirtattr(int attr
)
896 for (i
= 0; i
< term
.row
-1; i
++) {
897 for (j
= 0; j
< term
.col
-1; j
++) {
898 if (term
.line
[i
][j
].mode
& attr
) {
909 tsetdirt(0, term
.row
-1);
916 int alt
= IS_SET(MODE_ALTSCREEN
);
918 if (mode
== CURSOR_SAVE
) {
920 } else if (mode
== CURSOR_LOAD
) {
922 tmoveto(c
[alt
].x
, c
[alt
].y
);
935 }, .x
= 0, .y
= 0, .state
= CURSOR_DEFAULT
};
937 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
938 for (i
= tabspaces
; i
< term
.col
; i
+= tabspaces
)
941 term
.bot
= term
.row
- 1;
942 term
.mode
= MODE_WRAP
|MODE_UTF8
;
943 memset(term
.trantbl
, CS_USA
, sizeof(term
.trantbl
));
946 for (i
= 0; i
< 2; i
++) {
948 tcursor(CURSOR_SAVE
);
949 tclearregion(0, 0, term
.col
-1, term
.row
-1);
955 tnew(int col
, int row
)
957 term
= (Term
){ .c
= { .attr
= { .fg
= defaultfg
, .bg
= defaultbg
} } };
967 Line
*tmp
= term
.line
;
969 term
.line
= term
.alt
;
971 term
.mode
^= MODE_ALTSCREEN
;
976 tscrolldown(int orig
, int n
)
981 LIMIT(n
, 0, term
.bot
-orig
+1);
983 tsetdirt(orig
, term
.bot
-n
);
984 tclearregion(0, term
.bot
-n
+1, term
.col
-1, term
.bot
);
986 for (i
= term
.bot
; i
>= orig
+n
; i
--) {
988 term
.line
[i
] = term
.line
[i
-n
];
989 term
.line
[i
-n
] = temp
;
996 tscrollup(int orig
, int n
)
1001 LIMIT(n
, 0, term
.bot
-orig
+1);
1003 tclearregion(0, orig
, term
.col
-1, orig
+n
-1);
1004 tsetdirt(orig
+n
, term
.bot
);
1006 for (i
= orig
; i
<= term
.bot
-n
; i
++) {
1007 temp
= term
.line
[i
];
1008 term
.line
[i
] = term
.line
[i
+n
];
1009 term
.line
[i
+n
] = temp
;
1012 selscroll(orig
, -n
);
1016 selscroll(int orig
, int n
)
1021 if (BETWEEN(sel
.ob
.y
, orig
, term
.bot
) || BETWEEN(sel
.oe
.y
, orig
, term
.bot
)) {
1022 if ((sel
.ob
.y
+= n
) > term
.bot
|| (sel
.oe
.y
+= n
) < term
.top
) {
1026 if (sel
.type
== SEL_RECTANGULAR
) {
1027 if (sel
.ob
.y
< term
.top
)
1028 sel
.ob
.y
= term
.top
;
1029 if (sel
.oe
.y
> term
.bot
)
1030 sel
.oe
.y
= term
.bot
;
1032 if (sel
.ob
.y
< term
.top
) {
1033 sel
.ob
.y
= term
.top
;
1036 if (sel
.oe
.y
> term
.bot
) {
1037 sel
.oe
.y
= term
.bot
;
1038 sel
.oe
.x
= term
.col
;
1046 tnewline(int first_col
)
1050 if (y
== term
.bot
) {
1051 tscrollup(term
.top
, 1);
1055 tmoveto(first_col
? 0 : term
.c
.x
, y
);
1061 char *p
= csiescseq
.buf
, *np
;
1070 csiescseq
.buf
[csiescseq
.len
] = '\0';
1071 while (p
< csiescseq
.buf
+csiescseq
.len
) {
1073 v
= strtol(p
, &np
, 10);
1076 if (v
== LONG_MAX
|| v
== LONG_MIN
)
1078 csiescseq
.arg
[csiescseq
.narg
++] = v
;
1080 if (*p
!= ';' || csiescseq
.narg
== ESC_ARG_SIZ
)
1084 csiescseq
.mode
[0] = *p
++;
1085 csiescseq
.mode
[1] = (p
< csiescseq
.buf
+csiescseq
.len
) ? *p
: '\0';
1088 /* for absolute user moves, when decom is set */
1090 tmoveato(int x
, int y
)
1092 tmoveto(x
, y
+ ((term
.c
.state
& CURSOR_ORIGIN
) ? term
.top
: 0));
1096 tmoveto(int x
, int y
)
1100 if (term
.c
.state
& CURSOR_ORIGIN
) {
1105 maxy
= term
.row
- 1;
1107 term
.c
.state
&= ~CURSOR_WRAPNEXT
;
1108 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1109 term
.c
.y
= LIMIT(y
, miny
, maxy
);
1113 tsetchar(Rune u
, Glyph
*attr
, int x
, int y
)
1115 static char *vt100_0
[62] = { /* 0x41 - 0x7e */
1116 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1117 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1118 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1119 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1120 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1121 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1122 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1123 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1127 * The table is proudly stolen from rxvt.
1129 if (term
.trantbl
[term
.charset
] == CS_GRAPHIC0
&&
1130 BETWEEN(u
, 0x41, 0x7e) && vt100_0
[u
- 0x41])
1131 utf8decode(vt100_0
[u
- 0x41], &u
, UTF_SIZ
);
1133 if (term
.line
[y
][x
].mode
& ATTR_WIDE
) {
1134 if (x
+1 < term
.col
) {
1135 term
.line
[y
][x
+1].u
= ' ';
1136 term
.line
[y
][x
+1].mode
&= ~ATTR_WDUMMY
;
1138 } else if (term
.line
[y
][x
].mode
& ATTR_WDUMMY
) {
1139 term
.line
[y
][x
-1].u
= ' ';
1140 term
.line
[y
][x
-1].mode
&= ~ATTR_WIDE
;
1144 term
.line
[y
][x
] = *attr
;
1145 term
.line
[y
][x
].u
= u
;
1149 tclearregion(int x1
, int y1
, int x2
, int y2
)
1155 temp
= x1
, x1
= x2
, x2
= temp
;
1157 temp
= y1
, y1
= y2
, y2
= temp
;
1159 LIMIT(x1
, 0, term
.col
-1);
1160 LIMIT(x2
, 0, term
.col
-1);
1161 LIMIT(y1
, 0, term
.row
-1);
1162 LIMIT(y2
, 0, term
.row
-1);
1164 for (y
= y1
; y
<= y2
; y
++) {
1166 for (x
= x1
; x
<= x2
; x
++) {
1167 gp
= &term
.line
[y
][x
];
1170 gp
->fg
= term
.c
.attr
.fg
;
1171 gp
->bg
= term
.c
.attr
.bg
;
1184 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1188 size
= term
.col
- src
;
1189 line
= term
.line
[term
.c
.y
];
1191 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1192 tclearregion(term
.col
-n
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1201 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1205 size
= term
.col
- dst
;
1206 line
= term
.line
[term
.c
.y
];
1208 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1209 tclearregion(src
, term
.c
.y
, dst
- 1, term
.c
.y
);
1213 tinsertblankline(int n
)
1215 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1216 tscrolldown(term
.c
.y
, n
);
1222 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1223 tscrollup(term
.c
.y
, n
);
1227 tdefcolor(int *attr
, int *npar
, int l
)
1232 switch (attr
[*npar
+ 1]) {
1233 case 2: /* direct color in RGB space */
1234 if (*npar
+ 4 >= l
) {
1236 "erresc(38): Incorrect number of parameters (%d)\n",
1240 r
= attr
[*npar
+ 2];
1241 g
= attr
[*npar
+ 3];
1242 b
= attr
[*npar
+ 4];
1244 if (!BETWEEN(r
, 0, 255) || !BETWEEN(g
, 0, 255) || !BETWEEN(b
, 0, 255))
1245 fprintf(stderr
, "erresc: bad rgb color (%u,%u,%u)\n",
1248 idx
= TRUECOLOR(r
, g
, b
);
1250 case 5: /* indexed color */
1251 if (*npar
+ 2 >= l
) {
1253 "erresc(38): Incorrect number of parameters (%d)\n",
1258 if (!BETWEEN(attr
[*npar
], 0, 255))
1259 fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[*npar
]);
1263 case 0: /* implemented defined (only foreground) */
1264 case 1: /* transparent */
1265 case 3: /* direct color in CMY space */
1266 case 4: /* direct color in CMYK space */
1269 "erresc(38): gfx attr %d unknown\n", attr
[*npar
]);
1277 tsetattr(int *attr
, int l
)
1282 for (i
= 0; i
< l
; i
++) {
1285 term
.c
.attr
.mode
&= ~(
1294 term
.c
.attr
.fg
= defaultfg
;
1295 term
.c
.attr
.bg
= defaultbg
;
1298 term
.c
.attr
.mode
|= ATTR_BOLD
;
1301 term
.c
.attr
.mode
|= ATTR_FAINT
;
1304 term
.c
.attr
.mode
|= ATTR_ITALIC
;
1307 term
.c
.attr
.mode
|= ATTR_UNDERLINE
;
1309 case 5: /* slow blink */
1311 case 6: /* rapid blink */
1312 term
.c
.attr
.mode
|= ATTR_BLINK
;
1315 term
.c
.attr
.mode
|= ATTR_REVERSE
;
1318 term
.c
.attr
.mode
|= ATTR_INVISIBLE
;
1321 term
.c
.attr
.mode
|= ATTR_STRUCK
;
1324 term
.c
.attr
.mode
&= ~(ATTR_BOLD
| ATTR_FAINT
);
1327 term
.c
.attr
.mode
&= ~ATTR_ITALIC
;
1330 term
.c
.attr
.mode
&= ~ATTR_UNDERLINE
;
1333 term
.c
.attr
.mode
&= ~ATTR_BLINK
;
1336 term
.c
.attr
.mode
&= ~ATTR_REVERSE
;
1339 term
.c
.attr
.mode
&= ~ATTR_INVISIBLE
;
1342 term
.c
.attr
.mode
&= ~ATTR_STRUCK
;
1345 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1346 term
.c
.attr
.fg
= idx
;
1349 term
.c
.attr
.fg
= defaultfg
;
1352 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1353 term
.c
.attr
.bg
= idx
;
1356 term
.c
.attr
.bg
= defaultbg
;
1359 if (BETWEEN(attr
[i
], 30, 37)) {
1360 term
.c
.attr
.fg
= attr
[i
] - 30;
1361 } else if (BETWEEN(attr
[i
], 40, 47)) {
1362 term
.c
.attr
.bg
= attr
[i
] - 40;
1363 } else if (BETWEEN(attr
[i
], 90, 97)) {
1364 term
.c
.attr
.fg
= attr
[i
] - 90 + 8;
1365 } else if (BETWEEN(attr
[i
], 100, 107)) {
1366 term
.c
.attr
.bg
= attr
[i
] - 100 + 8;
1369 "erresc(default): gfx attr %d unknown\n",
1370 attr
[i
]), csidump();
1378 tsetscroll(int t
, int b
)
1382 LIMIT(t
, 0, term
.row
-1);
1383 LIMIT(b
, 0, term
.row
-1);
1394 tsetmode(int priv
, int set
, int *args
, int narg
)
1399 for (lim
= args
+ narg
; args
< lim
; ++args
) {
1402 case 1: /* DECCKM -- Cursor key */
1403 MODBIT(term
.mode
, set
, MODE_APPCURSOR
);
1405 case 5: /* DECSCNM -- Reverse video */
1407 MODBIT(term
.mode
, set
, MODE_REVERSE
);
1408 if (mode
!= term
.mode
)
1411 case 6: /* DECOM -- Origin */
1412 MODBIT(term
.c
.state
, set
, CURSOR_ORIGIN
);
1415 case 7: /* DECAWM -- Auto wrap */
1416 MODBIT(term
.mode
, set
, MODE_WRAP
);
1418 case 0: /* Error (IGNORED) */
1419 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1420 case 3: /* DECCOLM -- Column (IGNORED) */
1421 case 4: /* DECSCLM -- Scroll (IGNORED) */
1422 case 8: /* DECARM -- Auto repeat (IGNORED) */
1423 case 18: /* DECPFF -- Printer feed (IGNORED) */
1424 case 19: /* DECPEX -- Printer extent (IGNORED) */
1425 case 42: /* DECNRCM -- National characters (IGNORED) */
1426 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1428 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1429 MODBIT(term
.mode
, !set
, MODE_HIDE
);
1431 case 9: /* X10 mouse compatibility mode */
1432 xsetpointermotion(0);
1433 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1434 MODBIT(term
.mode
, set
, MODE_MOUSEX10
);
1436 case 1000: /* 1000: report button press */
1437 xsetpointermotion(0);
1438 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1439 MODBIT(term
.mode
, set
, MODE_MOUSEBTN
);
1441 case 1002: /* 1002: report motion on button press */
1442 xsetpointermotion(0);
1443 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1444 MODBIT(term
.mode
, set
, MODE_MOUSEMOTION
);
1446 case 1003: /* 1003: enable all mouse motions */
1447 xsetpointermotion(set
);
1448 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1449 MODBIT(term
.mode
, set
, MODE_MOUSEMANY
);
1451 case 1004: /* 1004: send focus events to tty */
1452 MODBIT(term
.mode
, set
, MODE_FOCUS
);
1454 case 1006: /* 1006: extended reporting mode */
1455 MODBIT(term
.mode
, set
, MODE_MOUSESGR
);
1458 MODBIT(term
.mode
, set
, MODE_8BIT
);
1460 case 1049: /* swap screen & set/restore cursor as xterm */
1461 if (!allowaltscreen
)
1463 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1465 case 47: /* swap screen */
1467 if (!allowaltscreen
)
1469 alt
= IS_SET(MODE_ALTSCREEN
);
1471 tclearregion(0, 0, term
.col
-1,
1474 if (set
^ alt
) /* set is always 1 or 0 */
1480 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1482 case 2004: /* 2004: bracketed paste mode */
1483 MODBIT(term
.mode
, set
, MODE_BRCKTPASTE
);
1485 /* Not implemented mouse modes. See comments there. */
1486 case 1001: /* mouse highlight mode; can hang the
1487 terminal by design when implemented. */
1488 case 1005: /* UTF-8 mouse mode; will confuse
1489 applications not supporting UTF-8
1491 case 1015: /* urxvt mangled mouse mode; incompatible
1492 and can be mistaken for other control
1496 "erresc: unknown private set/reset mode %d\n",
1502 case 0: /* Error (IGNORED) */
1504 case 2: /* KAM -- keyboard action */
1505 MODBIT(term
.mode
, set
, MODE_KBDLOCK
);
1507 case 4: /* IRM -- Insertion-replacement */
1508 MODBIT(term
.mode
, set
, MODE_INSERT
);
1510 case 12: /* SRM -- Send/Receive */
1511 MODBIT(term
.mode
, !set
, MODE_ECHO
);
1513 case 20: /* LNM -- Linefeed/new line */
1514 MODBIT(term
.mode
, set
, MODE_CRLF
);
1518 "erresc: unknown set/reset mode %d\n",
1532 switch (csiescseq
.mode
[0]) {
1535 fprintf(stderr
, "erresc: unknown csi ");
1539 case '@': /* ICH -- Insert <n> blank char */
1540 DEFAULT(csiescseq
.arg
[0], 1);
1541 tinsertblank(csiescseq
.arg
[0]);
1543 case 'A': /* CUU -- Cursor <n> Up */
1544 DEFAULT(csiescseq
.arg
[0], 1);
1545 tmoveto(term
.c
.x
, term
.c
.y
-csiescseq
.arg
[0]);
1547 case 'B': /* CUD -- Cursor <n> Down */
1548 case 'e': /* VPR --Cursor <n> Down */
1549 DEFAULT(csiescseq
.arg
[0], 1);
1550 tmoveto(term
.c
.x
, term
.c
.y
+csiescseq
.arg
[0]);
1552 case 'i': /* MC -- Media Copy */
1553 switch (csiescseq
.arg
[0]) {
1558 tdumpline(term
.c
.y
);
1564 term
.mode
&= ~MODE_PRINT
;
1567 term
.mode
|= MODE_PRINT
;
1571 case 'c': /* DA -- Device Attributes */
1572 if (csiescseq
.arg
[0] == 0)
1573 ttywrite(vtiden
, strlen(vtiden
));
1575 case 'C': /* CUF -- Cursor <n> Forward */
1576 case 'a': /* HPR -- Cursor <n> Forward */
1577 DEFAULT(csiescseq
.arg
[0], 1);
1578 tmoveto(term
.c
.x
+csiescseq
.arg
[0], term
.c
.y
);
1580 case 'D': /* CUB -- Cursor <n> Backward */
1581 DEFAULT(csiescseq
.arg
[0], 1);
1582 tmoveto(term
.c
.x
-csiescseq
.arg
[0], term
.c
.y
);
1584 case 'E': /* CNL -- Cursor <n> Down and first col */
1585 DEFAULT(csiescseq
.arg
[0], 1);
1586 tmoveto(0, term
.c
.y
+csiescseq
.arg
[0]);
1588 case 'F': /* CPL -- Cursor <n> Up and first col */
1589 DEFAULT(csiescseq
.arg
[0], 1);
1590 tmoveto(0, term
.c
.y
-csiescseq
.arg
[0]);
1592 case 'g': /* TBC -- Tabulation clear */
1593 switch (csiescseq
.arg
[0]) {
1594 case 0: /* clear current tab stop */
1595 term
.tabs
[term
.c
.x
] = 0;
1597 case 3: /* clear all the tabs */
1598 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1604 case 'G': /* CHA -- Move to <col> */
1606 DEFAULT(csiescseq
.arg
[0], 1);
1607 tmoveto(csiescseq
.arg
[0]-1, term
.c
.y
);
1609 case 'H': /* CUP -- Move to <row> <col> */
1611 DEFAULT(csiescseq
.arg
[0], 1);
1612 DEFAULT(csiescseq
.arg
[1], 1);
1613 tmoveato(csiescseq
.arg
[1]-1, csiescseq
.arg
[0]-1);
1615 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1616 DEFAULT(csiescseq
.arg
[0], 1);
1617 tputtab(csiescseq
.arg
[0]);
1619 case 'J': /* ED -- Clear screen */
1621 switch (csiescseq
.arg
[0]) {
1623 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1624 if (term
.c
.y
< term
.row
-1) {
1625 tclearregion(0, term
.c
.y
+1, term
.col
-1,
1631 tclearregion(0, 0, term
.col
-1, term
.c
.y
-1);
1632 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1635 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1641 case 'K': /* EL -- Clear line */
1642 switch (csiescseq
.arg
[0]) {
1644 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1,
1648 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1651 tclearregion(0, term
.c
.y
, term
.col
-1, term
.c
.y
);
1655 case 'S': /* SU -- Scroll <n> line up */
1656 DEFAULT(csiescseq
.arg
[0], 1);
1657 tscrollup(term
.top
, csiescseq
.arg
[0]);
1659 case 'T': /* SD -- Scroll <n> line down */
1660 DEFAULT(csiescseq
.arg
[0], 1);
1661 tscrolldown(term
.top
, csiescseq
.arg
[0]);
1663 case 'L': /* IL -- Insert <n> blank lines */
1664 DEFAULT(csiescseq
.arg
[0], 1);
1665 tinsertblankline(csiescseq
.arg
[0]);
1667 case 'l': /* RM -- Reset Mode */
1668 tsetmode(csiescseq
.priv
, 0, csiescseq
.arg
, csiescseq
.narg
);
1670 case 'M': /* DL -- Delete <n> lines */
1671 DEFAULT(csiescseq
.arg
[0], 1);
1672 tdeleteline(csiescseq
.arg
[0]);
1674 case 'X': /* ECH -- Erase <n> char */
1675 DEFAULT(csiescseq
.arg
[0], 1);
1676 tclearregion(term
.c
.x
, term
.c
.y
,
1677 term
.c
.x
+ csiescseq
.arg
[0] - 1, term
.c
.y
);
1679 case 'P': /* DCH -- Delete <n> char */
1680 DEFAULT(csiescseq
.arg
[0], 1);
1681 tdeletechar(csiescseq
.arg
[0]);
1683 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1684 DEFAULT(csiescseq
.arg
[0], 1);
1685 tputtab(-csiescseq
.arg
[0]);
1687 case 'd': /* VPA -- Move to <row> */
1688 DEFAULT(csiescseq
.arg
[0], 1);
1689 tmoveato(term
.c
.x
, csiescseq
.arg
[0]-1);
1691 case 'h': /* SM -- Set terminal mode */
1692 tsetmode(csiescseq
.priv
, 1, csiescseq
.arg
, csiescseq
.narg
);
1694 case 'm': /* SGR -- Terminal attribute (color) */
1695 tsetattr(csiescseq
.arg
, csiescseq
.narg
);
1697 case 'n': /* DSR – Device Status Report (cursor position) */
1698 if (csiescseq
.arg
[0] == 6) {
1699 len
= snprintf(buf
, sizeof(buf
),"\033[%i;%iR",
1700 term
.c
.y
+1, term
.c
.x
+1);
1704 case 'r': /* DECSTBM -- Set Scrolling Region */
1705 if (csiescseq
.priv
) {
1708 DEFAULT(csiescseq
.arg
[0], 1);
1709 DEFAULT(csiescseq
.arg
[1], term
.row
);
1710 tsetscroll(csiescseq
.arg
[0]-1, csiescseq
.arg
[1]-1);
1714 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1715 tcursor(CURSOR_SAVE
);
1717 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1718 tcursor(CURSOR_LOAD
);
1721 switch (csiescseq
.mode
[1]) {
1722 case 'q': /* DECSCUSR -- Set Cursor Style */
1723 if (xsetcursor(csiescseq
.arg
[0]))
1739 fprintf(stderr
, "ESC[");
1740 for (i
= 0; i
< csiescseq
.len
; i
++) {
1741 c
= csiescseq
.buf
[i
] & 0xff;
1744 } else if (c
== '\n') {
1745 fprintf(stderr
, "(\\n)");
1746 } else if (c
== '\r') {
1747 fprintf(stderr
, "(\\r)");
1748 } else if (c
== 0x1b) {
1749 fprintf(stderr
, "(\\e)");
1751 fprintf(stderr
, "(%02x)", c
);
1760 memset(&csiescseq
, 0, sizeof(csiescseq
));
1769 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
1771 par
= (narg
= strescseq
.narg
) ? atoi(strescseq
.args
[0]) : 0;
1773 switch (strescseq
.type
) {
1774 case ']': /* OSC -- Operating System Command */
1780 xsettitle(strescseq
.args
[1]);
1786 dec
= base64dec(strescseq
.args
[2]);
1791 fprintf(stderr
, "erresc: invalid base64\n");
1795 case 4: /* color set */
1798 p
= strescseq
.args
[2];
1800 case 104: /* color reset, here p = NULL */
1801 j
= (narg
> 1) ? atoi(strescseq
.args
[1]) : -1;
1802 if (xsetcolorname(j
, p
)) {
1803 fprintf(stderr
, "erresc: invalid color %s\n", p
);
1806 * TODO if defaultbg color is changed, borders
1814 case 'k': /* old title set compatibility */
1815 xsettitle(strescseq
.args
[0]);
1817 case 'P': /* DCS -- Device Control String */
1818 term
.mode
|= ESC_DCS
;
1819 case '_': /* APC -- Application Program Command */
1820 case '^': /* PM -- Privacy Message */
1824 fprintf(stderr
, "erresc: unknown str ");
1832 char *p
= strescseq
.buf
;
1835 strescseq
.buf
[strescseq
.len
] = '\0';
1840 while (strescseq
.narg
< STR_ARG_SIZ
) {
1841 strescseq
.args
[strescseq
.narg
++] = p
;
1842 while ((c
= *p
) != ';' && c
!= '\0')
1856 fprintf(stderr
, "ESC%c", strescseq
.type
);
1857 for (i
= 0; i
< strescseq
.len
; i
++) {
1858 c
= strescseq
.buf
[i
] & 0xff;
1862 } else if (isprint(c
)) {
1864 } else if (c
== '\n') {
1865 fprintf(stderr
, "(\\n)");
1866 } else if (c
== '\r') {
1867 fprintf(stderr
, "(\\r)");
1868 } else if (c
== 0x1b) {
1869 fprintf(stderr
, "(\\e)");
1871 fprintf(stderr
, "(%02x)", c
);
1874 fprintf(stderr
, "ESC\\\n");
1880 memset(&strescseq
, 0, sizeof(strescseq
));
1884 sendbreak(const Arg
*arg
)
1886 if (tcsendbreak(cmdfd
, 0))
1887 perror("Error sending break");
1891 tprinter(char *s
, size_t len
)
1893 if (iofd
!= -1 && xwrite(iofd
, s
, len
) < 0) {
1894 perror("Error writing to output file");
1901 iso14755(const Arg
*arg
)
1904 char *us
, *e
, codepoint
[9], uc
[UTF_SIZ
];
1905 unsigned long utf32
;
1907 if (!(p
= popen(ISO14755CMD
, "r")))
1910 us
= fgets(codepoint
, sizeof(codepoint
), p
);
1913 if (!us
|| *us
== '\0' || *us
== '-' || strlen(us
) > 7)
1915 if ((utf32
= strtoul(us
, &e
, 16)) == ULONG_MAX
||
1916 (*e
!= '\n' && *e
!= '\0'))
1919 ttysend(uc
, utf8encode(utf32
, uc
));
1923 toggleprinter(const Arg
*arg
)
1925 term
.mode
^= MODE_PRINT
;
1929 printscreen(const Arg
*arg
)
1935 printsel(const Arg
*arg
)
1945 if ((ptr
= getsel())) {
1946 tprinter(ptr
, strlen(ptr
));
1957 bp
= &term
.line
[n
][0];
1958 end
= &bp
[MIN(tlinelen(n
), term
.col
) - 1];
1959 if (bp
!= end
|| bp
->u
!= ' ') {
1960 for ( ;bp
<= end
; ++bp
)
1961 tprinter(buf
, utf8encode(bp
->u
, buf
));
1971 for (i
= 0; i
< term
.row
; ++i
)
1981 while (x
< term
.col
&& n
--)
1982 for (++x
; x
< term
.col
&& !term
.tabs
[x
]; ++x
)
1985 while (x
> 0 && n
++)
1986 for (--x
; x
> 0 && !term
.tabs
[x
]; --x
)
1989 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1993 tdefutf8(char ascii
)
1996 term
.mode
|= MODE_UTF8
;
1997 else if (ascii
== '@')
1998 term
.mode
&= ~MODE_UTF8
;
2002 tdeftran(char ascii
)
2004 static char cs
[] = "0B";
2005 static int vcs
[] = {CS_GRAPHIC0
, CS_USA
};
2008 if ((p
= strchr(cs
, ascii
)) == NULL
) {
2009 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
2011 term
.trantbl
[term
.icharset
] = vcs
[p
- cs
];
2020 if (c
== '8') { /* DEC screen alignment test. */
2021 for (x
= 0; x
< term
.col
; ++x
) {
2022 for (y
= 0; y
< term
.row
; ++y
)
2023 tsetchar('E', &term
.c
.attr
, x
, y
);
2029 tstrsequence(uchar c
)
2034 case 0x90: /* DCS -- Device Control String */
2036 term
.esc
|= ESC_DCS
;
2038 case 0x9f: /* APC -- Application Program Command */
2041 case 0x9e: /* PM -- Privacy Message */
2044 case 0x9d: /* OSC -- Operating System Command */
2049 term
.esc
|= ESC_STR
;
2053 tcontrolcode(uchar ascii
)
2060 tmoveto(term
.c
.x
-1, term
.c
.y
);
2063 tmoveto(0, term
.c
.y
);
2068 /* go to first col if the mode is set */
2069 tnewline(IS_SET(MODE_CRLF
));
2071 case '\a': /* BEL */
2072 if (term
.esc
& ESC_STR_END
) {
2073 /* backwards compatibility to xterm */
2079 case '\033': /* ESC */
2081 term
.esc
&= ~(ESC_CSI
|ESC_ALTCHARSET
|ESC_TEST
);
2082 term
.esc
|= ESC_START
;
2084 case '\016': /* SO (LS1 -- Locking shift 1) */
2085 case '\017': /* SI (LS0 -- Locking shift 0) */
2086 term
.charset
= 1 - (ascii
- '\016');
2088 case '\032': /* SUB */
2089 tsetchar('?', &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2090 case '\030': /* CAN */
2093 case '\005': /* ENQ (IGNORED) */
2094 case '\000': /* NUL (IGNORED) */
2095 case '\021': /* XON (IGNORED) */
2096 case '\023': /* XOFF (IGNORED) */
2097 case 0177: /* DEL (IGNORED) */
2099 case 0x80: /* TODO: PAD */
2100 case 0x81: /* TODO: HOP */
2101 case 0x82: /* TODO: BPH */
2102 case 0x83: /* TODO: NBH */
2103 case 0x84: /* TODO: IND */
2105 case 0x85: /* NEL -- Next line */
2106 tnewline(1); /* always go to first col */
2108 case 0x86: /* TODO: SSA */
2109 case 0x87: /* TODO: ESA */
2111 case 0x88: /* HTS -- Horizontal tab stop */
2112 term
.tabs
[term
.c
.x
] = 1;
2114 case 0x89: /* TODO: HTJ */
2115 case 0x8a: /* TODO: VTS */
2116 case 0x8b: /* TODO: PLD */
2117 case 0x8c: /* TODO: PLU */
2118 case 0x8d: /* TODO: RI */
2119 case 0x8e: /* TODO: SS2 */
2120 case 0x8f: /* TODO: SS3 */
2121 case 0x91: /* TODO: PU1 */
2122 case 0x92: /* TODO: PU2 */
2123 case 0x93: /* TODO: STS */
2124 case 0x94: /* TODO: CCH */
2125 case 0x95: /* TODO: MW */
2126 case 0x96: /* TODO: SPA */
2127 case 0x97: /* TODO: EPA */
2128 case 0x98: /* TODO: SOS */
2129 case 0x99: /* TODO: SGCI */
2131 case 0x9a: /* DECID -- Identify Terminal */
2132 ttywrite(vtiden
, strlen(vtiden
));
2134 case 0x9b: /* TODO: CSI */
2135 case 0x9c: /* TODO: ST */
2137 case 0x90: /* DCS -- Device Control String */
2138 case 0x9d: /* OSC -- Operating System Command */
2139 case 0x9e: /* PM -- Privacy Message */
2140 case 0x9f: /* APC -- Application Program Command */
2141 tstrsequence(ascii
);
2144 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2145 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
2149 * returns 1 when the sequence is finished and it hasn't to read
2150 * more characters for this sequence, otherwise 0
2153 eschandle(uchar ascii
)
2157 term
.esc
|= ESC_CSI
;
2160 term
.esc
|= ESC_TEST
;
2163 term
.esc
|= ESC_UTF8
;
2165 case 'P': /* DCS -- Device Control String */
2166 case '_': /* APC -- Application Program Command */
2167 case '^': /* PM -- Privacy Message */
2168 case ']': /* OSC -- Operating System Command */
2169 case 'k': /* old title set compatibility */
2170 tstrsequence(ascii
);
2172 case 'n': /* LS2 -- Locking shift 2 */
2173 case 'o': /* LS3 -- Locking shift 3 */
2174 term
.charset
= 2 + (ascii
- 'n');
2176 case '(': /* GZD4 -- set primary charset G0 */
2177 case ')': /* G1D4 -- set secondary charset G1 */
2178 case '*': /* G2D4 -- set tertiary charset G2 */
2179 case '+': /* G3D4 -- set quaternary charset G3 */
2180 term
.icharset
= ascii
- '(';
2181 term
.esc
|= ESC_ALTCHARSET
;
2183 case 'D': /* IND -- Linefeed */
2184 if (term
.c
.y
== term
.bot
) {
2185 tscrollup(term
.top
, 1);
2187 tmoveto(term
.c
.x
, term
.c
.y
+1);
2190 case 'E': /* NEL -- Next line */
2191 tnewline(1); /* always go to first col */
2193 case 'H': /* HTS -- Horizontal tab stop */
2194 term
.tabs
[term
.c
.x
] = 1;
2196 case 'M': /* RI -- Reverse index */
2197 if (term
.c
.y
== term
.top
) {
2198 tscrolldown(term
.top
, 1);
2200 tmoveto(term
.c
.x
, term
.c
.y
-1);
2203 case 'Z': /* DECID -- Identify Terminal */
2204 ttywrite(vtiden
, strlen(vtiden
));
2206 case 'c': /* RIS -- Reset to inital state */
2211 case '=': /* DECPAM -- Application keypad */
2212 term
.mode
|= MODE_APPKEYPAD
;
2214 case '>': /* DECPNM -- Normal keypad */
2215 term
.mode
&= ~MODE_APPKEYPAD
;
2217 case '7': /* DECSC -- Save Cursor */
2218 tcursor(CURSOR_SAVE
);
2220 case '8': /* DECRC -- Restore Cursor */
2221 tcursor(CURSOR_LOAD
);
2223 case '\\': /* ST -- String Terminator */
2224 if (term
.esc
& ESC_STR_END
)
2228 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2229 (uchar
) ascii
, isprint(ascii
)? ascii
:'.');
2243 control
= ISCONTROL(u
);
2244 if (!IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2248 len
= utf8encode(u
, c
);
2249 if (!control
&& (width
= wcwidth(u
)) == -1) {
2250 memcpy(c
, "\357\277\275", 4); /* UTF_INVALID */
2255 if (IS_SET(MODE_PRINT
))
2259 * STR sequence must be checked before anything else
2260 * because it uses all following characters until it
2261 * receives a ESC, a SUB, a ST or any other C1 control
2264 if (term
.esc
& ESC_STR
) {
2265 if (u
== '\a' || u
== 030 || u
== 032 || u
== 033 ||
2267 term
.esc
&= ~(ESC_START
|ESC_STR
|ESC_DCS
);
2268 if (IS_SET(MODE_SIXEL
)) {
2269 /* TODO: render sixel */;
2270 term
.mode
&= ~MODE_SIXEL
;
2273 term
.esc
|= ESC_STR_END
;
2274 goto check_control_code
;
2278 if (IS_SET(MODE_SIXEL
)) {
2279 /* TODO: implement sixel mode */
2282 if (term
.esc
&ESC_DCS
&& strescseq
.len
== 0 && u
== 'q')
2283 term
.mode
|= MODE_SIXEL
;
2285 if (strescseq
.len
+len
>= sizeof(strescseq
.buf
)-1) {
2287 * Here is a bug in terminals. If the user never sends
2288 * some code to stop the str or esc command, then st
2289 * will stop responding. But this is better than
2290 * silently failing with unknown characters. At least
2291 * then users will report back.
2293 * In the case users ever get fixed, here is the code:
2302 memmove(&strescseq
.buf
[strescseq
.len
], c
, len
);
2303 strescseq
.len
+= len
;
2309 * Actions of control codes must be performed as soon they arrive
2310 * because they can be embedded inside a control sequence, and
2311 * they must not cause conflicts with sequences.
2316 * control codes are not shown ever
2319 } else if (term
.esc
& ESC_START
) {
2320 if (term
.esc
& ESC_CSI
) {
2321 csiescseq
.buf
[csiescseq
.len
++] = u
;
2322 if (BETWEEN(u
, 0x40, 0x7E)
2323 || csiescseq
.len
>= \
2324 sizeof(csiescseq
.buf
)-1) {
2330 } else if (term
.esc
& ESC_UTF8
) {
2332 } else if (term
.esc
& ESC_ALTCHARSET
) {
2334 } else if (term
.esc
& ESC_TEST
) {
2339 /* sequence already finished */
2343 * All characters which form part of a sequence are not
2348 if (sel
.ob
.x
!= -1 && BETWEEN(term
.c
.y
, sel
.ob
.y
, sel
.oe
.y
))
2351 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2352 if (IS_SET(MODE_WRAP
) && (term
.c
.state
& CURSOR_WRAPNEXT
)) {
2353 gp
->mode
|= ATTR_WRAP
;
2355 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2358 if (IS_SET(MODE_INSERT
) && term
.c
.x
+width
< term
.col
)
2359 memmove(gp
+width
, gp
, (term
.col
- term
.c
.x
- width
) * sizeof(Glyph
));
2361 if (term
.c
.x
+width
> term
.col
) {
2363 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2366 tsetchar(u
, &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2369 gp
->mode
|= ATTR_WIDE
;
2370 if (term
.c
.x
+1 < term
.col
) {
2372 gp
[1].mode
= ATTR_WDUMMY
;
2375 if (term
.c
.x
+width
< term
.col
) {
2376 tmoveto(term
.c
.x
+width
, term
.c
.y
);
2378 term
.c
.state
|= CURSOR_WRAPNEXT
;
2383 twrite(const char *buf
, int buflen
, int show_ctrl
)
2389 for (n
= 0; n
< buflen
; n
+= charsize
) {
2390 if (IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2391 /* process a complete utf8 char */
2392 charsize
= utf8decode(buf
+ n
, &u
, buflen
- n
);
2399 if (show_ctrl
&& ISCONTROL(u
)) {
2404 } else if (u
!= '\n' && u
!= '\r' && u
!= '\t') {
2415 tresize(int col
, int row
)
2418 int minrow
= MIN(row
, term
.row
);
2419 int mincol
= MIN(col
, term
.col
);
2423 if (col
< 1 || row
< 1) {
2425 "tresize: error resizing to %dx%d\n", col
, row
);
2430 * slide screen to keep cursor where we expect it -
2431 * tscrollup would work here, but we can optimize to
2432 * memmove because we're freeing the earlier lines
2434 for (i
= 0; i
<= term
.c
.y
- row
; i
++) {
2438 /* ensure that both src and dst are not NULL */
2440 memmove(term
.line
, term
.line
+ i
, row
* sizeof(Line
));
2441 memmove(term
.alt
, term
.alt
+ i
, row
* sizeof(Line
));
2443 for (i
+= row
; i
< term
.row
; i
++) {
2448 /* resize to new height */
2449 term
.line
= xrealloc(term
.line
, row
* sizeof(Line
));
2450 term
.alt
= xrealloc(term
.alt
, row
* sizeof(Line
));
2451 term
.dirty
= xrealloc(term
.dirty
, row
* sizeof(*term
.dirty
));
2452 term
.tabs
= xrealloc(term
.tabs
, col
* sizeof(*term
.tabs
));
2454 /* resize each row to new width, zero-pad if needed */
2455 for (i
= 0; i
< minrow
; i
++) {
2456 term
.line
[i
] = xrealloc(term
.line
[i
], col
* sizeof(Glyph
));
2457 term
.alt
[i
] = xrealloc(term
.alt
[i
], col
* sizeof(Glyph
));
2460 /* allocate any new rows */
2461 for (/* i = minrow */; i
< row
; i
++) {
2462 term
.line
[i
] = xmalloc(col
* sizeof(Glyph
));
2463 term
.alt
[i
] = xmalloc(col
* sizeof(Glyph
));
2465 if (col
> term
.col
) {
2466 bp
= term
.tabs
+ term
.col
;
2468 memset(bp
, 0, sizeof(*term
.tabs
) * (col
- term
.col
));
2469 while (--bp
> term
.tabs
&& !*bp
)
2471 for (bp
+= tabspaces
; bp
< term
.tabs
+ col
; bp
+= tabspaces
)
2474 /* update terminal size */
2477 /* reset scrolling region */
2478 tsetscroll(0, row
-1);
2479 /* make use of the LIMIT in tmoveto */
2480 tmoveto(term
.c
.x
, term
.c
.y
);
2481 /* Clearing both screens (it makes dirty all lines) */
2483 for (i
= 0; i
< 2; i
++) {
2484 if (mincol
< col
&& 0 < minrow
) {
2485 tclearregion(mincol
, 0, col
- 1, minrow
- 1);
2487 if (0 < col
&& minrow
< row
) {
2488 tclearregion(0, minrow
, col
- 1, row
- 1);
2491 tcursor(CURSOR_LOAD
);
2510 numlock(const Arg
*dummy
)