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>
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 IS_SET(flag) ((term.mode & (flag)) != 0)
46 #define NUMMAXLEN(x) ((int)(sizeof(x) * 2.56 + 0.5) + 1)
47 #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == '\177')
48 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
49 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
50 #define ISDELIM(u) (utf8strchr(worddelimiters, u) != NULL)
53 #define ISO14755CMD "dmenu -w \"$WINDOWID\" -p codepoint: </dev/null"
58 MODE_ALTSCREEN
= 1 << 2,
66 enum cursor_movement
{
90 ESC_STR
= 4, /* OSC, PM, APC */
92 ESC_STR_END
= 16, /* a final string was encountered */
93 ESC_TEST
= 32, /* Enter in test mode */
98 /* CSI Escape sequence structs */
99 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
101 char buf
[ESC_BUF_SIZ
]; /* raw string */
102 int len
; /* raw string length */
104 int arg
[ESC_ARG_SIZ
];
105 int narg
; /* nb of args */
109 /* STR Escape sequence structs */
110 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
112 char type
; /* ESC type ... */
113 char buf
[STR_BUF_SIZ
]; /* raw string */
114 int len
; /* raw string length */
115 char *args
[STR_ARG_SIZ
];
116 int narg
; /* nb of args */
120 static void execsh(char **);
121 static void stty(char **);
122 static void sigchld(int);
123 static void ttywriteraw(const char *, size_t);
125 static void csidump(void);
126 static void csihandle(void);
127 static void csiparse(void);
128 static void csireset(void);
129 static int eschandle(uchar
);
130 static void strdump(void);
131 static void strhandle(void);
132 static void strparse(void);
133 static void strreset(void);
135 static void tprinter(char *, size_t);
136 static void tdumpsel(void);
137 static void tdumpline(int);
138 static void tdump(void);
139 static void tclearregion(int, int, int, int);
140 static void tcursor(int);
141 static void tdeletechar(int);
142 static void tdeleteline(int);
143 static void tinsertblank(int);
144 static void tinsertblankline(int);
145 static int tlinelen(int);
146 static void tmoveto(int, int);
147 static void tmoveato(int, int);
148 static void tnewline(int);
149 static void tputtab(int);
150 static void tputc(Rune
);
151 static void treset(void);
152 static void tscrollup(int, int);
153 static void tscrolldown(int, int);
154 static void tsetattr(int *, int);
155 static void tsetchar(Rune
, Glyph
*, int, int);
156 static void tsetdirt(int, int);
157 static void tsetscroll(int, int);
158 static void tswapscreen(void);
159 static void tsetmode(int, int, int *, int);
160 static int twrite(const char *, int, int);
161 static void tfulldirt(void);
162 static void tcontrolcode(uchar
);
163 static void tdectest(char );
164 static void tdefutf8(char);
165 static int32_t tdefcolor(int *, int *, int);
166 static void tdeftran(char);
167 static void tstrsequence(uchar
);
169 static void selscroll(int, int);
170 static void selsnap(int *, int *, int);
172 static Rune
utf8decodebyte(char, size_t *);
173 static char utf8encodebyte(Rune
, size_t);
174 static char *utf8strchr(char *s
, Rune u
);
175 static size_t utf8validate(Rune
*, size_t);
177 static char *base64dec(const char *);
179 static ssize_t
xwrite(int, const char *, size_t);
185 int oldbutton
= 3; /* button event on startup: 3 = release */
187 static Selection sel
;
188 static CSIEscape csiescseq
;
189 static STREscape strescseq
;
192 static uchar utfbyte
[UTF_SIZ
+ 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
193 static uchar utfmask
[UTF_SIZ
+ 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
194 static Rune utfmin
[UTF_SIZ
+ 1] = { 0, 0, 0x80, 0x800, 0x10000};
195 static Rune utfmax
[UTF_SIZ
+ 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
198 xwrite(int fd
, const char *s
, size_t len
)
204 r
= write(fd
, s
, len
);
217 void *p
= malloc(len
);
220 die("Out of memory\n");
226 xrealloc(void *p
, size_t len
)
228 if ((p
= realloc(p
, len
)) == NULL
)
229 die("Out of memory\n");
237 if ((s
= strdup(s
)) == NULL
)
238 die("Out of memory\n");
244 utf8decode(const char *c
, Rune
*u
, size_t clen
)
246 size_t i
, j
, len
, type
;
252 udecoded
= utf8decodebyte(c
[0], &len
);
253 if (!BETWEEN(len
, 1, UTF_SIZ
))
255 for (i
= 1, j
= 1; i
< clen
&& j
< len
; ++i
, ++j
) {
256 udecoded
= (udecoded
<< 6) | utf8decodebyte(c
[i
], &type
);
263 utf8validate(u
, len
);
269 utf8decodebyte(char c
, size_t *i
)
271 for (*i
= 0; *i
< LEN(utfmask
); ++(*i
))
272 if (((uchar
)c
& utfmask
[*i
]) == utfbyte
[*i
])
273 return (uchar
)c
& ~utfmask
[*i
];
279 utf8encode(Rune u
, char *c
)
283 len
= utf8validate(&u
, 0);
287 for (i
= len
- 1; i
!= 0; --i
) {
288 c
[i
] = utf8encodebyte(u
, 0);
291 c
[0] = utf8encodebyte(u
, len
);
297 utf8encodebyte(Rune u
, size_t i
)
299 return utfbyte
[i
] | (u
& ~utfmask
[i
]);
303 utf8strchr(char *s
, Rune u
)
309 for (i
= 0, j
= 0; i
< len
; i
+= j
) {
310 if (!(j
= utf8decode(&s
[i
], &r
, len
- i
)))
320 utf8validate(Rune
*u
, size_t i
)
322 if (!BETWEEN(*u
, utfmin
[i
], utfmax
[i
]) || BETWEEN(*u
, 0xD800, 0xDFFF))
324 for (i
= 1; *u
> utfmax
[i
]; ++i
)
330 static const char base64_digits
[] = {
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, 62, 0, 0, 0,
333 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
334 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
335 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
336 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0,
337 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
338 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
339 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
340 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
341 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
342 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
346 base64dec_getc(const char **src
)
348 while (**src
&& !isprint(**src
)) (*src
)++;
353 base64dec(const char *src
)
355 size_t in_len
= strlen(src
);
359 in_len
+= 4 - (in_len
% 4);
360 result
= dst
= xmalloc(in_len
/ 4 * 3 + 1);
362 int a
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
363 int b
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
364 int c
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
365 int d
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
367 *dst
++ = (a
<< 2) | ((b
& 0x30) >> 4);
370 *dst
++ = ((b
& 0x0f) << 4) | ((c
& 0x3c) >> 2);
373 *dst
++ = ((c
& 0x03) << 6) | d
;
392 if (term
.line
[y
][i
- 1].mode
& ATTR_WRAP
)
395 while (i
> 0 && term
.line
[y
][i
- 1].u
== ' ')
402 selstart(int col
, int row
, int snap
)
405 sel
.mode
= SEL_EMPTY
;
406 sel
.type
= SEL_REGULAR
;
408 sel
.oe
.x
= sel
.ob
.x
= col
;
409 sel
.oe
.y
= sel
.ob
.y
= row
;
413 sel
.mode
= SEL_READY
;
414 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
418 selextend(int col
, int row
, int type
, int done
)
420 int oldey
, oldex
, oldsby
, oldsey
, oldtype
;
424 if (done
&& sel
.mode
== SEL_EMPTY
) {
435 sel
.alt
= IS_SET(MODE_ALTSCREEN
);
441 if (oldey
!= sel
.oe
.y
|| oldex
!= sel
.oe
.x
|| oldtype
!= sel
.type
)
442 tsetdirt(MIN(sel
.nb
.y
, oldsby
), MAX(sel
.ne
.y
, oldsey
));
444 sel
.mode
= done
? SEL_IDLE
: SEL_READY
;
452 if (sel
.type
== SEL_REGULAR
&& sel
.ob
.y
!= sel
.oe
.y
) {
453 sel
.nb
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.ob
.x
: sel
.oe
.x
;
454 sel
.ne
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.oe
.x
: sel
.ob
.x
;
456 sel
.nb
.x
= MIN(sel
.ob
.x
, sel
.oe
.x
);
457 sel
.ne
.x
= MAX(sel
.ob
.x
, sel
.oe
.x
);
459 sel
.nb
.y
= MIN(sel
.ob
.y
, sel
.oe
.y
);
460 sel
.ne
.y
= MAX(sel
.ob
.y
, sel
.oe
.y
);
462 selsnap(&sel
.nb
.x
, &sel
.nb
.y
, -1);
463 selsnap(&sel
.ne
.x
, &sel
.ne
.y
, +1);
465 /* expand selection over line breaks */
466 if (sel
.type
== SEL_RECTANGULAR
)
468 i
= tlinelen(sel
.nb
.y
);
471 if (tlinelen(sel
.ne
.y
) <= sel
.ne
.x
)
472 sel
.ne
.x
= term
.col
- 1;
476 selected(int x
, int y
)
478 if (sel
.mode
== SEL_EMPTY
|| sel
.ob
.x
== -1 ||
479 sel
.alt
!= IS_SET(MODE_ALTSCREEN
))
482 if (sel
.type
== SEL_RECTANGULAR
)
483 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
484 && BETWEEN(x
, sel
.nb
.x
, sel
.ne
.x
);
486 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
487 && (y
!= sel
.nb
.y
|| x
>= sel
.nb
.x
)
488 && (y
!= sel
.ne
.y
|| x
<= sel
.ne
.x
);
492 selsnap(int *x
, int *y
, int direction
)
494 int newx
, newy
, xt
, yt
;
495 int delim
, prevdelim
;
501 * Snap around if the word wraps around at the end or
502 * beginning of a line.
504 prevgp
= &term
.line
[*y
][*x
];
505 prevdelim
= ISDELIM(prevgp
->u
);
507 newx
= *x
+ direction
;
509 if (!BETWEEN(newx
, 0, term
.col
- 1)) {
511 newx
= (newx
+ term
.col
) % term
.col
;
512 if (!BETWEEN(newy
, 0, term
.row
- 1))
518 yt
= newy
, xt
= newx
;
519 if (!(term
.line
[yt
][xt
].mode
& ATTR_WRAP
))
523 if (newx
>= tlinelen(newy
))
526 gp
= &term
.line
[newy
][newx
];
527 delim
= ISDELIM(gp
->u
);
528 if (!(gp
->mode
& ATTR_WDUMMY
) && (delim
!= prevdelim
529 || (delim
&& gp
->u
!= prevgp
->u
)))
540 * Snap around if the the previous line or the current one
541 * has set ATTR_WRAP at its end. Then the whole next or
542 * previous line will be selected.
544 *x
= (direction
< 0) ? 0 : term
.col
- 1;
546 for (; *y
> 0; *y
+= direction
) {
547 if (!(term
.line
[*y
-1][term
.col
-1].mode
552 } else if (direction
> 0) {
553 for (; *y
< term
.row
-1; *y
+= direction
) {
554 if (!(term
.line
[*y
][term
.col
-1].mode
568 int y
, bufsize
, lastx
, linelen
;
574 bufsize
= (term
.col
+1) * (sel
.ne
.y
-sel
.nb
.y
+1) * UTF_SIZ
;
575 ptr
= str
= xmalloc(bufsize
);
577 /* append every set & selected glyph to the selection */
578 for (y
= sel
.nb
.y
; y
<= sel
.ne
.y
; y
++) {
579 if ((linelen
= tlinelen(y
)) == 0) {
584 if (sel
.type
== SEL_RECTANGULAR
) {
585 gp
= &term
.line
[y
][sel
.nb
.x
];
588 gp
= &term
.line
[y
][sel
.nb
.y
== y
? sel
.nb
.x
: 0];
589 lastx
= (sel
.ne
.y
== y
) ? sel
.ne
.x
: term
.col
-1;
591 last
= &term
.line
[y
][MIN(lastx
, linelen
-1)];
592 while (last
>= gp
&& last
->u
== ' ')
595 for ( ; gp
<= last
; ++gp
) {
596 if (gp
->mode
& ATTR_WDUMMY
)
599 ptr
+= utf8encode(gp
->u
, ptr
);
603 * Copy and pasting of line endings is inconsistent
604 * in the inconsistent terminal and GUI world.
605 * The best solution seems like to produce '\n' when
606 * something is copied from st and convert '\n' to
607 * '\r', when something to be pasted is received by
609 * FIXME: Fix the computer world.
611 if ((y
< sel
.ne
.y
|| lastx
>= linelen
) && !(last
->mode
& ATTR_WRAP
))
625 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
629 die(const char *errstr
, ...)
633 va_start(ap
, errstr
);
634 vfprintf(stderr
, errstr
, ap
);
643 const struct passwd
*pw
;
646 if ((pw
= getpwuid(getuid())) == NULL
) {
648 die("getpwuid:%s\n", strerror(errno
));
650 die("who are you?\n");
653 if ((sh
= getenv("SHELL")) == NULL
)
654 sh
= (pw
->pw_shell
[0]) ? pw
->pw_shell
: shell
;
662 DEFAULT(args
, ((char *[]) {prog
, NULL
}));
667 setenv("LOGNAME", pw
->pw_name
, 1);
668 setenv("USER", pw
->pw_name
, 1);
669 setenv("SHELL", sh
, 1);
670 setenv("HOME", pw
->pw_dir
, 1);
671 setenv("TERM", termname
, 1);
673 signal(SIGCHLD
, SIG_DFL
);
674 signal(SIGHUP
, SIG_DFL
);
675 signal(SIGINT
, SIG_DFL
);
676 signal(SIGQUIT
, SIG_DFL
);
677 signal(SIGTERM
, SIG_DFL
);
678 signal(SIGALRM
, SIG_DFL
);
690 if ((p
= waitpid(pid
, &stat
, WNOHANG
)) < 0)
691 die("Waiting for pid %hd failed: %s\n", pid
, strerror(errno
));
696 if (!WIFEXITED(stat
) || WEXITSTATUS(stat
))
697 die("child finished with error '%d'\n", stat
);
705 char cmd
[_POSIX_ARG_MAX
], **p
, *q
, *s
;
708 if ((n
= strlen(stty_args
)) > sizeof(cmd
)-1)
709 die("incorrect stty parameters\n");
710 memcpy(cmd
, stty_args
, n
);
712 siz
= sizeof(cmd
) - n
;
713 for (p
= args
; p
&& (s
= *p
); ++p
) {
714 if ((n
= strlen(s
)) > siz
-1)
715 die("stty parameter length too long\n");
722 if (system(cmd
) != 0)
723 perror("Couldn't call stty");
727 ttynew(char *line
, char *out
, char **args
)
732 term
.mode
|= MODE_PRINT
;
733 iofd
= (!strcmp(out
, "-")) ?
734 1 : open(out
, O_WRONLY
| O_CREAT
, 0666);
736 fprintf(stderr
, "Error opening %s:%s\n",
737 out
, strerror(errno
));
742 if ((cmdfd
= open(line
, O_RDWR
)) < 0)
743 die("open line failed: %s\n", strerror(errno
));
749 /* seems to work fine on linux, openbsd and freebsd */
750 if (openpty(&m
, &s
, NULL
, NULL
, NULL
) < 0)
751 die("openpty failed: %s\n", strerror(errno
));
753 switch (pid
= fork()) {
755 die("fork failed\n");
759 setsid(); /* create a new process group */
763 if (ioctl(s
, TIOCSCTTY
, NULL
) < 0)
764 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno
));
772 signal(SIGCHLD
, sigchld
);
780 static char buf
[BUFSIZ
];
781 static int buflen
= 0;
785 /* append read bytes to unprocessed bytes */
786 if ((ret
= read(cmdfd
, buf
+buflen
, LEN(buf
)-buflen
)) < 0)
787 die("Couldn't read from shell: %s\n", strerror(errno
));
790 written
= twrite(buf
, buflen
, 0);
792 /* keep any uncomplete utf8 char for the next call */
794 memmove(buf
, buf
+ written
, buflen
);
800 ttywrite(const char *s
, size_t n
, int may_echo
)
804 if (may_echo
&& IS_SET(MODE_ECHO
))
807 if (!IS_SET(MODE_CRLF
)) {
812 /* This is similar to how the kernel handles ONLCR for ttys */
816 ttywriteraw("\r\n", 2);
818 next
= memchr(s
, '\r', n
);
819 DEFAULT(next
, s
+ n
);
820 ttywriteraw(s
, next
- s
);
828 ttywriteraw(const char *s
, size_t n
)
835 * Remember that we are using a pty, which might be a modem line.
836 * Writing too much will clog the line. That's why we are doing this
838 * FIXME: Migrate the world to Plan 9.
846 /* Check if we can write. */
847 if (pselect(cmdfd
+1, &rfd
, &wfd
, NULL
, NULL
, NULL
) < 0) {
850 die("select failed: %s\n", strerror(errno
));
852 if (FD_ISSET(cmdfd
, &wfd
)) {
854 * Only write the bytes written by ttywrite() or the
855 * default of 256. This seems to be a reasonable value
856 * for a serial line. Bigger values might clog the I/O.
858 if ((r
= write(cmdfd
, s
, (n
< lim
)? n
: lim
)) < 0)
862 * We weren't able to write out everything.
863 * This means the buffer is getting full
871 /* All bytes have been written. */
875 if (FD_ISSET(cmdfd
, &rfd
))
881 die("write error on tty: %s\n", strerror(errno
));
885 ttyresize(int tw
, int th
)
893 if (ioctl(cmdfd
, TIOCSWINSZ
, &w
) < 0)
894 fprintf(stderr
, "Couldn't set window size: %s\n", strerror(errno
));
902 for (i
= 0; i
< term
.row
-1; i
++) {
903 for (j
= 0; j
< term
.col
-1; j
++) {
904 if (term
.line
[i
][j
].mode
& attr
)
913 tsetdirt(int top
, int bot
)
917 LIMIT(top
, 0, term
.row
-1);
918 LIMIT(bot
, 0, term
.row
-1);
920 for (i
= top
; i
<= bot
; i
++)
925 tsetdirtattr(int attr
)
929 for (i
= 0; i
< term
.row
-1; i
++) {
930 for (j
= 0; j
< term
.col
-1; j
++) {
931 if (term
.line
[i
][j
].mode
& attr
) {
942 tsetdirt(0, term
.row
-1);
949 int alt
= IS_SET(MODE_ALTSCREEN
);
951 if (mode
== CURSOR_SAVE
) {
953 } else if (mode
== CURSOR_LOAD
) {
955 tmoveto(c
[alt
].x
, c
[alt
].y
);
968 }, .x
= 0, .y
= 0, .state
= CURSOR_DEFAULT
};
970 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
971 for (i
= tabspaces
; i
< term
.col
; i
+= tabspaces
)
974 term
.bot
= term
.row
- 1;
975 term
.mode
= MODE_WRAP
|MODE_UTF8
;
976 memset(term
.trantbl
, CS_USA
, sizeof(term
.trantbl
));
979 for (i
= 0; i
< 2; i
++) {
981 tcursor(CURSOR_SAVE
);
982 tclearregion(0, 0, term
.col
-1, term
.row
-1);
988 tnew(int col
, int row
)
990 term
= (Term
){ .c
= { .attr
= { .fg
= defaultfg
, .bg
= defaultbg
} } };
998 Line
*tmp
= term
.line
;
1000 term
.line
= term
.alt
;
1002 term
.mode
^= MODE_ALTSCREEN
;
1007 tscrolldown(int orig
, int n
)
1012 LIMIT(n
, 0, term
.bot
-orig
+1);
1014 tsetdirt(orig
, term
.bot
-n
);
1015 tclearregion(0, term
.bot
-n
+1, term
.col
-1, term
.bot
);
1017 for (i
= term
.bot
; i
>= orig
+n
; i
--) {
1018 temp
= term
.line
[i
];
1019 term
.line
[i
] = term
.line
[i
-n
];
1020 term
.line
[i
-n
] = temp
;
1027 tscrollup(int orig
, int n
)
1032 LIMIT(n
, 0, term
.bot
-orig
+1);
1034 tclearregion(0, orig
, term
.col
-1, orig
+n
-1);
1035 tsetdirt(orig
+n
, term
.bot
);
1037 for (i
= orig
; i
<= term
.bot
-n
; i
++) {
1038 temp
= term
.line
[i
];
1039 term
.line
[i
] = term
.line
[i
+n
];
1040 term
.line
[i
+n
] = temp
;
1043 selscroll(orig
, -n
);
1047 selscroll(int orig
, int n
)
1052 if (BETWEEN(sel
.ob
.y
, orig
, term
.bot
) || BETWEEN(sel
.oe
.y
, orig
, term
.bot
)) {
1053 if ((sel
.ob
.y
+= n
) > term
.bot
|| (sel
.oe
.y
+= n
) < term
.top
) {
1057 if (sel
.type
== SEL_RECTANGULAR
) {
1058 if (sel
.ob
.y
< term
.top
)
1059 sel
.ob
.y
= term
.top
;
1060 if (sel
.oe
.y
> term
.bot
)
1061 sel
.oe
.y
= term
.bot
;
1063 if (sel
.ob
.y
< term
.top
) {
1064 sel
.ob
.y
= term
.top
;
1067 if (sel
.oe
.y
> term
.bot
) {
1068 sel
.oe
.y
= term
.bot
;
1069 sel
.oe
.x
= term
.col
;
1077 tnewline(int first_col
)
1081 if (y
== term
.bot
) {
1082 tscrollup(term
.top
, 1);
1086 tmoveto(first_col
? 0 : term
.c
.x
, y
);
1092 char *p
= csiescseq
.buf
, *np
;
1101 csiescseq
.buf
[csiescseq
.len
] = '\0';
1102 while (p
< csiescseq
.buf
+csiescseq
.len
) {
1104 v
= strtol(p
, &np
, 10);
1107 if (v
== LONG_MAX
|| v
== LONG_MIN
)
1109 csiescseq
.arg
[csiescseq
.narg
++] = v
;
1111 if (*p
!= ';' || csiescseq
.narg
== ESC_ARG_SIZ
)
1115 csiescseq
.mode
[0] = *p
++;
1116 csiescseq
.mode
[1] = (p
< csiescseq
.buf
+csiescseq
.len
) ? *p
: '\0';
1119 /* for absolute user moves, when decom is set */
1121 tmoveato(int x
, int y
)
1123 tmoveto(x
, y
+ ((term
.c
.state
& CURSOR_ORIGIN
) ? term
.top
: 0));
1127 tmoveto(int x
, int y
)
1131 if (term
.c
.state
& CURSOR_ORIGIN
) {
1136 maxy
= term
.row
- 1;
1138 term
.c
.state
&= ~CURSOR_WRAPNEXT
;
1139 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1140 term
.c
.y
= LIMIT(y
, miny
, maxy
);
1144 tsetchar(Rune u
, Glyph
*attr
, int x
, int y
)
1146 static char *vt100_0
[62] = { /* 0x41 - 0x7e */
1147 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1148 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1149 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1150 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1151 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1152 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1153 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1154 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1158 * The table is proudly stolen from rxvt.
1160 if (term
.trantbl
[term
.charset
] == CS_GRAPHIC0
&&
1161 BETWEEN(u
, 0x41, 0x7e) && vt100_0
[u
- 0x41])
1162 utf8decode(vt100_0
[u
- 0x41], &u
, UTF_SIZ
);
1164 if (term
.line
[y
][x
].mode
& ATTR_WIDE
) {
1165 if (x
+1 < term
.col
) {
1166 term
.line
[y
][x
+1].u
= ' ';
1167 term
.line
[y
][x
+1].mode
&= ~ATTR_WDUMMY
;
1169 } else if (term
.line
[y
][x
].mode
& ATTR_WDUMMY
) {
1170 term
.line
[y
][x
-1].u
= ' ';
1171 term
.line
[y
][x
-1].mode
&= ~ATTR_WIDE
;
1175 term
.line
[y
][x
] = *attr
;
1176 term
.line
[y
][x
].u
= u
;
1180 tclearregion(int x1
, int y1
, int x2
, int y2
)
1186 temp
= x1
, x1
= x2
, x2
= temp
;
1188 temp
= y1
, y1
= y2
, y2
= temp
;
1190 LIMIT(x1
, 0, term
.col
-1);
1191 LIMIT(x2
, 0, term
.col
-1);
1192 LIMIT(y1
, 0, term
.row
-1);
1193 LIMIT(y2
, 0, term
.row
-1);
1195 for (y
= y1
; y
<= y2
; y
++) {
1197 for (x
= x1
; x
<= x2
; x
++) {
1198 gp
= &term
.line
[y
][x
];
1201 gp
->fg
= term
.c
.attr
.fg
;
1202 gp
->bg
= term
.c
.attr
.bg
;
1215 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1219 size
= term
.col
- src
;
1220 line
= term
.line
[term
.c
.y
];
1222 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1223 tclearregion(term
.col
-n
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1232 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1236 size
= term
.col
- dst
;
1237 line
= term
.line
[term
.c
.y
];
1239 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1240 tclearregion(src
, term
.c
.y
, dst
- 1, term
.c
.y
);
1244 tinsertblankline(int n
)
1246 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1247 tscrolldown(term
.c
.y
, n
);
1253 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1254 tscrollup(term
.c
.y
, n
);
1258 tdefcolor(int *attr
, int *npar
, int l
)
1263 switch (attr
[*npar
+ 1]) {
1264 case 2: /* direct color in RGB space */
1265 if (*npar
+ 4 >= l
) {
1267 "erresc(38): Incorrect number of parameters (%d)\n",
1271 r
= attr
[*npar
+ 2];
1272 g
= attr
[*npar
+ 3];
1273 b
= attr
[*npar
+ 4];
1275 if (!BETWEEN(r
, 0, 255) || !BETWEEN(g
, 0, 255) || !BETWEEN(b
, 0, 255))
1276 fprintf(stderr
, "erresc: bad rgb color (%u,%u,%u)\n",
1279 idx
= TRUECOLOR(r
, g
, b
);
1281 case 5: /* indexed color */
1282 if (*npar
+ 2 >= l
) {
1284 "erresc(38): Incorrect number of parameters (%d)\n",
1289 if (!BETWEEN(attr
[*npar
], 0, 255))
1290 fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[*npar
]);
1294 case 0: /* implemented defined (only foreground) */
1295 case 1: /* transparent */
1296 case 3: /* direct color in CMY space */
1297 case 4: /* direct color in CMYK space */
1300 "erresc(38): gfx attr %d unknown\n", attr
[*npar
]);
1308 tsetattr(int *attr
, int l
)
1313 for (i
= 0; i
< l
; i
++) {
1316 term
.c
.attr
.mode
&= ~(
1325 term
.c
.attr
.fg
= defaultfg
;
1326 term
.c
.attr
.bg
= defaultbg
;
1329 term
.c
.attr
.mode
|= ATTR_BOLD
;
1332 term
.c
.attr
.mode
|= ATTR_FAINT
;
1335 term
.c
.attr
.mode
|= ATTR_ITALIC
;
1338 term
.c
.attr
.mode
|= ATTR_UNDERLINE
;
1340 case 5: /* slow blink */
1342 case 6: /* rapid blink */
1343 term
.c
.attr
.mode
|= ATTR_BLINK
;
1346 term
.c
.attr
.mode
|= ATTR_REVERSE
;
1349 term
.c
.attr
.mode
|= ATTR_INVISIBLE
;
1352 term
.c
.attr
.mode
|= ATTR_STRUCK
;
1355 term
.c
.attr
.mode
&= ~(ATTR_BOLD
| ATTR_FAINT
);
1358 term
.c
.attr
.mode
&= ~ATTR_ITALIC
;
1361 term
.c
.attr
.mode
&= ~ATTR_UNDERLINE
;
1364 term
.c
.attr
.mode
&= ~ATTR_BLINK
;
1367 term
.c
.attr
.mode
&= ~ATTR_REVERSE
;
1370 term
.c
.attr
.mode
&= ~ATTR_INVISIBLE
;
1373 term
.c
.attr
.mode
&= ~ATTR_STRUCK
;
1376 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1377 term
.c
.attr
.fg
= idx
;
1380 term
.c
.attr
.fg
= defaultfg
;
1383 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1384 term
.c
.attr
.bg
= idx
;
1387 term
.c
.attr
.bg
= defaultbg
;
1390 if (BETWEEN(attr
[i
], 30, 37)) {
1391 term
.c
.attr
.fg
= attr
[i
] - 30;
1392 } else if (BETWEEN(attr
[i
], 40, 47)) {
1393 term
.c
.attr
.bg
= attr
[i
] - 40;
1394 } else if (BETWEEN(attr
[i
], 90, 97)) {
1395 term
.c
.attr
.fg
= attr
[i
] - 90 + 8;
1396 } else if (BETWEEN(attr
[i
], 100, 107)) {
1397 term
.c
.attr
.bg
= attr
[i
] - 100 + 8;
1400 "erresc(default): gfx attr %d unknown\n",
1401 attr
[i
]), csidump();
1409 tsetscroll(int t
, int b
)
1413 LIMIT(t
, 0, term
.row
-1);
1414 LIMIT(b
, 0, term
.row
-1);
1425 tsetmode(int priv
, int set
, int *args
, int narg
)
1429 for (lim
= args
+ narg
; args
< lim
; ++args
) {
1432 case 1: /* DECCKM -- Cursor key */
1433 xsetmode(set
, MODE_APPCURSOR
);
1435 case 5: /* DECSCNM -- Reverse video */
1436 xsetmode(set
, MODE_REVERSE
);
1438 case 6: /* DECOM -- Origin */
1439 MODBIT(term
.c
.state
, set
, CURSOR_ORIGIN
);
1442 case 7: /* DECAWM -- Auto wrap */
1443 MODBIT(term
.mode
, set
, MODE_WRAP
);
1445 case 0: /* Error (IGNORED) */
1446 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1447 case 3: /* DECCOLM -- Column (IGNORED) */
1448 case 4: /* DECSCLM -- Scroll (IGNORED) */
1449 case 8: /* DECARM -- Auto repeat (IGNORED) */
1450 case 18: /* DECPFF -- Printer feed (IGNORED) */
1451 case 19: /* DECPEX -- Printer extent (IGNORED) */
1452 case 42: /* DECNRCM -- National characters (IGNORED) */
1453 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1455 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1456 xsetmode(!set
, MODE_HIDE
);
1458 case 9: /* X10 mouse compatibility mode */
1459 xsetpointermotion(0);
1460 xsetmode(0, MODE_MOUSE
);
1461 xsetmode(set
, MODE_MOUSEX10
);
1463 case 1000: /* 1000: report button press */
1464 xsetpointermotion(0);
1465 xsetmode(0, MODE_MOUSE
);
1466 xsetmode(set
, MODE_MOUSEBTN
);
1468 case 1002: /* 1002: report motion on button press */
1469 xsetpointermotion(0);
1470 xsetmode(0, MODE_MOUSE
);
1471 xsetmode(set
, MODE_MOUSEMOTION
);
1473 case 1003: /* 1003: enable all mouse motions */
1474 xsetpointermotion(set
);
1475 xsetmode(0, MODE_MOUSE
);
1476 xsetmode(set
, MODE_MOUSEMANY
);
1478 case 1004: /* 1004: send focus events to tty */
1479 xsetmode(set
, MODE_FOCUS
);
1481 case 1006: /* 1006: extended reporting mode */
1482 xsetmode(set
, MODE_MOUSESGR
);
1485 xsetmode(set
, MODE_8BIT
);
1487 case 1049: /* swap screen & set/restore cursor as xterm */
1488 if (!allowaltscreen
)
1490 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1492 case 47: /* swap screen */
1494 if (!allowaltscreen
)
1496 alt
= IS_SET(MODE_ALTSCREEN
);
1498 tclearregion(0, 0, term
.col
-1,
1501 if (set
^ alt
) /* set is always 1 or 0 */
1507 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1509 case 2004: /* 2004: bracketed paste mode */
1510 xsetmode(set
, MODE_BRCKTPASTE
);
1512 /* Not implemented mouse modes. See comments there. */
1513 case 1001: /* mouse highlight mode; can hang the
1514 terminal by design when implemented. */
1515 case 1005: /* UTF-8 mouse mode; will confuse
1516 applications not supporting UTF-8
1518 case 1015: /* urxvt mangled mouse mode; incompatible
1519 and can be mistaken for other control
1523 "erresc: unknown private set/reset mode %d\n",
1529 case 0: /* Error (IGNORED) */
1532 xsetmode(set
, MODE_KBDLOCK
);
1534 case 4: /* IRM -- Insertion-replacement */
1535 MODBIT(term
.mode
, set
, MODE_INSERT
);
1537 case 12: /* SRM -- Send/Receive */
1538 MODBIT(term
.mode
, !set
, MODE_ECHO
);
1540 case 20: /* LNM -- Linefeed/new line */
1541 MODBIT(term
.mode
, set
, MODE_CRLF
);
1545 "erresc: unknown set/reset mode %d\n",
1559 switch (csiescseq
.mode
[0]) {
1562 fprintf(stderr
, "erresc: unknown csi ");
1566 case '@': /* ICH -- Insert <n> blank char */
1567 DEFAULT(csiescseq
.arg
[0], 1);
1568 tinsertblank(csiescseq
.arg
[0]);
1570 case 'A': /* CUU -- Cursor <n> Up */
1571 DEFAULT(csiescseq
.arg
[0], 1);
1572 tmoveto(term
.c
.x
, term
.c
.y
-csiescseq
.arg
[0]);
1574 case 'B': /* CUD -- Cursor <n> Down */
1575 case 'e': /* VPR --Cursor <n> Down */
1576 DEFAULT(csiescseq
.arg
[0], 1);
1577 tmoveto(term
.c
.x
, term
.c
.y
+csiescseq
.arg
[0]);
1579 case 'i': /* MC -- Media Copy */
1580 switch (csiescseq
.arg
[0]) {
1585 tdumpline(term
.c
.y
);
1591 term
.mode
&= ~MODE_PRINT
;
1594 term
.mode
|= MODE_PRINT
;
1598 case 'c': /* DA -- Device Attributes */
1599 if (csiescseq
.arg
[0] == 0)
1600 ttywrite(vtiden
, strlen(vtiden
), 0);
1602 case 'C': /* CUF -- Cursor <n> Forward */
1603 case 'a': /* HPR -- Cursor <n> Forward */
1604 DEFAULT(csiescseq
.arg
[0], 1);
1605 tmoveto(term
.c
.x
+csiescseq
.arg
[0], term
.c
.y
);
1607 case 'D': /* CUB -- Cursor <n> Backward */
1608 DEFAULT(csiescseq
.arg
[0], 1);
1609 tmoveto(term
.c
.x
-csiescseq
.arg
[0], term
.c
.y
);
1611 case 'E': /* CNL -- Cursor <n> Down and first col */
1612 DEFAULT(csiescseq
.arg
[0], 1);
1613 tmoveto(0, term
.c
.y
+csiescseq
.arg
[0]);
1615 case 'F': /* CPL -- Cursor <n> Up and first col */
1616 DEFAULT(csiescseq
.arg
[0], 1);
1617 tmoveto(0, term
.c
.y
-csiescseq
.arg
[0]);
1619 case 'g': /* TBC -- Tabulation clear */
1620 switch (csiescseq
.arg
[0]) {
1621 case 0: /* clear current tab stop */
1622 term
.tabs
[term
.c
.x
] = 0;
1624 case 3: /* clear all the tabs */
1625 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1631 case 'G': /* CHA -- Move to <col> */
1633 DEFAULT(csiescseq
.arg
[0], 1);
1634 tmoveto(csiescseq
.arg
[0]-1, term
.c
.y
);
1636 case 'H': /* CUP -- Move to <row> <col> */
1638 DEFAULT(csiescseq
.arg
[0], 1);
1639 DEFAULT(csiescseq
.arg
[1], 1);
1640 tmoveato(csiescseq
.arg
[1]-1, csiescseq
.arg
[0]-1);
1642 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1643 DEFAULT(csiescseq
.arg
[0], 1);
1644 tputtab(csiescseq
.arg
[0]);
1646 case 'J': /* ED -- Clear screen */
1648 switch (csiescseq
.arg
[0]) {
1650 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1651 if (term
.c
.y
< term
.row
-1) {
1652 tclearregion(0, term
.c
.y
+1, term
.col
-1,
1658 tclearregion(0, 0, term
.col
-1, term
.c
.y
-1);
1659 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1662 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1668 case 'K': /* EL -- Clear line */
1669 switch (csiescseq
.arg
[0]) {
1671 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1,
1675 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1678 tclearregion(0, term
.c
.y
, term
.col
-1, term
.c
.y
);
1682 case 'S': /* SU -- Scroll <n> line up */
1683 DEFAULT(csiescseq
.arg
[0], 1);
1684 tscrollup(term
.top
, csiescseq
.arg
[0]);
1686 case 'T': /* SD -- Scroll <n> line down */
1687 DEFAULT(csiescseq
.arg
[0], 1);
1688 tscrolldown(term
.top
, csiescseq
.arg
[0]);
1690 case 'L': /* IL -- Insert <n> blank lines */
1691 DEFAULT(csiescseq
.arg
[0], 1);
1692 tinsertblankline(csiescseq
.arg
[0]);
1694 case 'l': /* RM -- Reset Mode */
1695 tsetmode(csiescseq
.priv
, 0, csiescseq
.arg
, csiescseq
.narg
);
1697 case 'M': /* DL -- Delete <n> lines */
1698 DEFAULT(csiescseq
.arg
[0], 1);
1699 tdeleteline(csiescseq
.arg
[0]);
1701 case 'X': /* ECH -- Erase <n> char */
1702 DEFAULT(csiescseq
.arg
[0], 1);
1703 tclearregion(term
.c
.x
, term
.c
.y
,
1704 term
.c
.x
+ csiescseq
.arg
[0] - 1, term
.c
.y
);
1706 case 'P': /* DCH -- Delete <n> char */
1707 DEFAULT(csiescseq
.arg
[0], 1);
1708 tdeletechar(csiescseq
.arg
[0]);
1710 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1711 DEFAULT(csiescseq
.arg
[0], 1);
1712 tputtab(-csiescseq
.arg
[0]);
1714 case 'd': /* VPA -- Move to <row> */
1715 DEFAULT(csiescseq
.arg
[0], 1);
1716 tmoveato(term
.c
.x
, csiescseq
.arg
[0]-1);
1718 case 'h': /* SM -- Set terminal mode */
1719 tsetmode(csiescseq
.priv
, 1, csiescseq
.arg
, csiescseq
.narg
);
1721 case 'm': /* SGR -- Terminal attribute (color) */
1722 tsetattr(csiescseq
.arg
, csiescseq
.narg
);
1724 case 'n': /* DSR – Device Status Report (cursor position) */
1725 if (csiescseq
.arg
[0] == 6) {
1726 len
= snprintf(buf
, sizeof(buf
),"\033[%i;%iR",
1727 term
.c
.y
+1, term
.c
.x
+1);
1728 ttywrite(buf
, len
, 0);
1731 case 'r': /* DECSTBM -- Set Scrolling Region */
1732 if (csiescseq
.priv
) {
1735 DEFAULT(csiescseq
.arg
[0], 1);
1736 DEFAULT(csiescseq
.arg
[1], term
.row
);
1737 tsetscroll(csiescseq
.arg
[0]-1, csiescseq
.arg
[1]-1);
1741 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1742 tcursor(CURSOR_SAVE
);
1744 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1745 tcursor(CURSOR_LOAD
);
1748 switch (csiescseq
.mode
[1]) {
1749 case 'q': /* DECSCUSR -- Set Cursor Style */
1750 if (xsetcursor(csiescseq
.arg
[0]))
1766 fprintf(stderr
, "ESC[");
1767 for (i
= 0; i
< csiescseq
.len
; i
++) {
1768 c
= csiescseq
.buf
[i
] & 0xff;
1771 } else if (c
== '\n') {
1772 fprintf(stderr
, "(\\n)");
1773 } else if (c
== '\r') {
1774 fprintf(stderr
, "(\\r)");
1775 } else if (c
== 0x1b) {
1776 fprintf(stderr
, "(\\e)");
1778 fprintf(stderr
, "(%02x)", c
);
1787 memset(&csiescseq
, 0, sizeof(csiescseq
));
1796 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
1798 par
= (narg
= strescseq
.narg
) ? atoi(strescseq
.args
[0]) : 0;
1800 switch (strescseq
.type
) {
1801 case ']': /* OSC -- Operating System Command */
1807 xsettitle(strescseq
.args
[1]);
1813 dec
= base64dec(strescseq
.args
[2]);
1818 fprintf(stderr
, "erresc: invalid base64\n");
1822 case 4: /* color set */
1825 p
= strescseq
.args
[2];
1827 case 104: /* color reset, here p = NULL */
1828 j
= (narg
> 1) ? atoi(strescseq
.args
[1]) : -1;
1829 if (xsetcolorname(j
, p
)) {
1830 fprintf(stderr
, "erresc: invalid color %s\n", p
);
1833 * TODO if defaultbg color is changed, borders
1841 case 'k': /* old title set compatibility */
1842 xsettitle(strescseq
.args
[0]);
1844 case 'P': /* DCS -- Device Control String */
1845 term
.mode
|= ESC_DCS
;
1846 case '_': /* APC -- Application Program Command */
1847 case '^': /* PM -- Privacy Message */
1851 fprintf(stderr
, "erresc: unknown str ");
1859 char *p
= strescseq
.buf
;
1862 strescseq
.buf
[strescseq
.len
] = '\0';
1867 while (strescseq
.narg
< STR_ARG_SIZ
) {
1868 strescseq
.args
[strescseq
.narg
++] = p
;
1869 while ((c
= *p
) != ';' && c
!= '\0')
1883 fprintf(stderr
, "ESC%c", strescseq
.type
);
1884 for (i
= 0; i
< strescseq
.len
; i
++) {
1885 c
= strescseq
.buf
[i
] & 0xff;
1889 } else if (isprint(c
)) {
1891 } else if (c
== '\n') {
1892 fprintf(stderr
, "(\\n)");
1893 } else if (c
== '\r') {
1894 fprintf(stderr
, "(\\r)");
1895 } else if (c
== 0x1b) {
1896 fprintf(stderr
, "(\\e)");
1898 fprintf(stderr
, "(%02x)", c
);
1901 fprintf(stderr
, "ESC\\\n");
1907 memset(&strescseq
, 0, sizeof(strescseq
));
1911 sendbreak(const Arg
*arg
)
1913 if (tcsendbreak(cmdfd
, 0))
1914 perror("Error sending break");
1918 tprinter(char *s
, size_t len
)
1920 if (iofd
!= -1 && xwrite(iofd
, s
, len
) < 0) {
1921 perror("Error writing to output file");
1928 iso14755(const Arg
*arg
)
1931 char *us
, *e
, codepoint
[9], uc
[UTF_SIZ
];
1932 unsigned long utf32
;
1934 if (!(p
= popen(ISO14755CMD
, "r")))
1937 us
= fgets(codepoint
, sizeof(codepoint
), p
);
1940 if (!us
|| *us
== '\0' || *us
== '-' || strlen(us
) > 7)
1942 if ((utf32
= strtoul(us
, &e
, 16)) == ULONG_MAX
||
1943 (*e
!= '\n' && *e
!= '\0'))
1946 ttywrite(uc
, utf8encode(utf32
, uc
), 1);
1950 toggleprinter(const Arg
*arg
)
1952 term
.mode
^= MODE_PRINT
;
1956 printscreen(const Arg
*arg
)
1962 printsel(const Arg
*arg
)
1972 if ((ptr
= getsel())) {
1973 tprinter(ptr
, strlen(ptr
));
1984 bp
= &term
.line
[n
][0];
1985 end
= &bp
[MIN(tlinelen(n
), term
.col
) - 1];
1986 if (bp
!= end
|| bp
->u
!= ' ') {
1987 for ( ;bp
<= end
; ++bp
)
1988 tprinter(buf
, utf8encode(bp
->u
, buf
));
1998 for (i
= 0; i
< term
.row
; ++i
)
2008 while (x
< term
.col
&& n
--)
2009 for (++x
; x
< term
.col
&& !term
.tabs
[x
]; ++x
)
2012 while (x
> 0 && n
++)
2013 for (--x
; x
> 0 && !term
.tabs
[x
]; --x
)
2016 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
2020 tdefutf8(char ascii
)
2023 term
.mode
|= MODE_UTF8
;
2024 else if (ascii
== '@')
2025 term
.mode
&= ~MODE_UTF8
;
2029 tdeftran(char ascii
)
2031 static char cs
[] = "0B";
2032 static int vcs
[] = {CS_GRAPHIC0
, CS_USA
};
2035 if ((p
= strchr(cs
, ascii
)) == NULL
) {
2036 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
2038 term
.trantbl
[term
.icharset
] = vcs
[p
- cs
];
2047 if (c
== '8') { /* DEC screen alignment test. */
2048 for (x
= 0; x
< term
.col
; ++x
) {
2049 for (y
= 0; y
< term
.row
; ++y
)
2050 tsetchar('E', &term
.c
.attr
, x
, y
);
2056 tstrsequence(uchar c
)
2061 case 0x90: /* DCS -- Device Control String */
2063 term
.esc
|= ESC_DCS
;
2065 case 0x9f: /* APC -- Application Program Command */
2068 case 0x9e: /* PM -- Privacy Message */
2071 case 0x9d: /* OSC -- Operating System Command */
2076 term
.esc
|= ESC_STR
;
2080 tcontrolcode(uchar ascii
)
2087 tmoveto(term
.c
.x
-1, term
.c
.y
);
2090 tmoveto(0, term
.c
.y
);
2095 /* go to first col if the mode is set */
2096 tnewline(IS_SET(MODE_CRLF
));
2098 case '\a': /* BEL */
2099 if (term
.esc
& ESC_STR_END
) {
2100 /* backwards compatibility to xterm */
2106 case '\033': /* ESC */
2108 term
.esc
&= ~(ESC_CSI
|ESC_ALTCHARSET
|ESC_TEST
);
2109 term
.esc
|= ESC_START
;
2111 case '\016': /* SO (LS1 -- Locking shift 1) */
2112 case '\017': /* SI (LS0 -- Locking shift 0) */
2113 term
.charset
= 1 - (ascii
- '\016');
2115 case '\032': /* SUB */
2116 tsetchar('?', &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2117 case '\030': /* CAN */
2120 case '\005': /* ENQ (IGNORED) */
2121 case '\000': /* NUL (IGNORED) */
2122 case '\021': /* XON (IGNORED) */
2123 case '\023': /* XOFF (IGNORED) */
2124 case 0177: /* DEL (IGNORED) */
2126 case 0x80: /* TODO: PAD */
2127 case 0x81: /* TODO: HOP */
2128 case 0x82: /* TODO: BPH */
2129 case 0x83: /* TODO: NBH */
2130 case 0x84: /* TODO: IND */
2132 case 0x85: /* NEL -- Next line */
2133 tnewline(1); /* always go to first col */
2135 case 0x86: /* TODO: SSA */
2136 case 0x87: /* TODO: ESA */
2138 case 0x88: /* HTS -- Horizontal tab stop */
2139 term
.tabs
[term
.c
.x
] = 1;
2141 case 0x89: /* TODO: HTJ */
2142 case 0x8a: /* TODO: VTS */
2143 case 0x8b: /* TODO: PLD */
2144 case 0x8c: /* TODO: PLU */
2145 case 0x8d: /* TODO: RI */
2146 case 0x8e: /* TODO: SS2 */
2147 case 0x8f: /* TODO: SS3 */
2148 case 0x91: /* TODO: PU1 */
2149 case 0x92: /* TODO: PU2 */
2150 case 0x93: /* TODO: STS */
2151 case 0x94: /* TODO: CCH */
2152 case 0x95: /* TODO: MW */
2153 case 0x96: /* TODO: SPA */
2154 case 0x97: /* TODO: EPA */
2155 case 0x98: /* TODO: SOS */
2156 case 0x99: /* TODO: SGCI */
2158 case 0x9a: /* DECID -- Identify Terminal */
2159 ttywrite(vtiden
, strlen(vtiden
), 0);
2161 case 0x9b: /* TODO: CSI */
2162 case 0x9c: /* TODO: ST */
2164 case 0x90: /* DCS -- Device Control String */
2165 case 0x9d: /* OSC -- Operating System Command */
2166 case 0x9e: /* PM -- Privacy Message */
2167 case 0x9f: /* APC -- Application Program Command */
2168 tstrsequence(ascii
);
2171 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2172 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
2176 * returns 1 when the sequence is finished and it hasn't to read
2177 * more characters for this sequence, otherwise 0
2180 eschandle(uchar ascii
)
2184 term
.esc
|= ESC_CSI
;
2187 term
.esc
|= ESC_TEST
;
2190 term
.esc
|= ESC_UTF8
;
2192 case 'P': /* DCS -- Device Control String */
2193 case '_': /* APC -- Application Program Command */
2194 case '^': /* PM -- Privacy Message */
2195 case ']': /* OSC -- Operating System Command */
2196 case 'k': /* old title set compatibility */
2197 tstrsequence(ascii
);
2199 case 'n': /* LS2 -- Locking shift 2 */
2200 case 'o': /* LS3 -- Locking shift 3 */
2201 term
.charset
= 2 + (ascii
- 'n');
2203 case '(': /* GZD4 -- set primary charset G0 */
2204 case ')': /* G1D4 -- set secondary charset G1 */
2205 case '*': /* G2D4 -- set tertiary charset G2 */
2206 case '+': /* G3D4 -- set quaternary charset G3 */
2207 term
.icharset
= ascii
- '(';
2208 term
.esc
|= ESC_ALTCHARSET
;
2210 case 'D': /* IND -- Linefeed */
2211 if (term
.c
.y
== term
.bot
) {
2212 tscrollup(term
.top
, 1);
2214 tmoveto(term
.c
.x
, term
.c
.y
+1);
2217 case 'E': /* NEL -- Next line */
2218 tnewline(1); /* always go to first col */
2220 case 'H': /* HTS -- Horizontal tab stop */
2221 term
.tabs
[term
.c
.x
] = 1;
2223 case 'M': /* RI -- Reverse index */
2224 if (term
.c
.y
== term
.top
) {
2225 tscrolldown(term
.top
, 1);
2227 tmoveto(term
.c
.x
, term
.c
.y
-1);
2230 case 'Z': /* DECID -- Identify Terminal */
2231 ttywrite(vtiden
, strlen(vtiden
), 0);
2233 case 'c': /* RIS -- Reset to inital state */
2238 case '=': /* DECPAM -- Application keypad */
2239 xsetmode(1, MODE_APPKEYPAD
);
2241 case '>': /* DECPNM -- Normal keypad */
2242 xsetmode(0, MODE_APPKEYPAD
);
2244 case '7': /* DECSC -- Save Cursor */
2245 tcursor(CURSOR_SAVE
);
2247 case '8': /* DECRC -- Restore Cursor */
2248 tcursor(CURSOR_LOAD
);
2250 case '\\': /* ST -- String Terminator */
2251 if (term
.esc
& ESC_STR_END
)
2255 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2256 (uchar
) ascii
, isprint(ascii
)? ascii
:'.');
2270 control
= ISCONTROL(u
);
2271 if (!IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2275 len
= utf8encode(u
, c
);
2276 if (!control
&& (width
= wcwidth(u
)) == -1) {
2277 memcpy(c
, "\357\277\275", 4); /* UTF_INVALID */
2282 if (IS_SET(MODE_PRINT
))
2286 * STR sequence must be checked before anything else
2287 * because it uses all following characters until it
2288 * receives a ESC, a SUB, a ST or any other C1 control
2291 if (term
.esc
& ESC_STR
) {
2292 if (u
== '\a' || u
== 030 || u
== 032 || u
== 033 ||
2294 term
.esc
&= ~(ESC_START
|ESC_STR
|ESC_DCS
);
2295 if (IS_SET(MODE_SIXEL
)) {
2296 /* TODO: render sixel */;
2297 term
.mode
&= ~MODE_SIXEL
;
2300 term
.esc
|= ESC_STR_END
;
2301 goto check_control_code
;
2305 if (IS_SET(MODE_SIXEL
)) {
2306 /* TODO: implement sixel mode */
2309 if (term
.esc
&ESC_DCS
&& strescseq
.len
== 0 && u
== 'q')
2310 term
.mode
|= MODE_SIXEL
;
2312 if (strescseq
.len
+len
>= sizeof(strescseq
.buf
)-1) {
2314 * Here is a bug in terminals. If the user never sends
2315 * some code to stop the str or esc command, then st
2316 * will stop responding. But this is better than
2317 * silently failing with unknown characters. At least
2318 * then users will report back.
2320 * In the case users ever get fixed, here is the code:
2329 memmove(&strescseq
.buf
[strescseq
.len
], c
, len
);
2330 strescseq
.len
+= len
;
2336 * Actions of control codes must be performed as soon they arrive
2337 * because they can be embedded inside a control sequence, and
2338 * they must not cause conflicts with sequences.
2343 * control codes are not shown ever
2346 } else if (term
.esc
& ESC_START
) {
2347 if (term
.esc
& ESC_CSI
) {
2348 csiescseq
.buf
[csiescseq
.len
++] = u
;
2349 if (BETWEEN(u
, 0x40, 0x7E)
2350 || csiescseq
.len
>= \
2351 sizeof(csiescseq
.buf
)-1) {
2357 } else if (term
.esc
& ESC_UTF8
) {
2359 } else if (term
.esc
& ESC_ALTCHARSET
) {
2361 } else if (term
.esc
& ESC_TEST
) {
2366 /* sequence already finished */
2370 * All characters which form part of a sequence are not
2375 if (sel
.ob
.x
!= -1 && BETWEEN(term
.c
.y
, sel
.ob
.y
, sel
.oe
.y
))
2378 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2379 if (IS_SET(MODE_WRAP
) && (term
.c
.state
& CURSOR_WRAPNEXT
)) {
2380 gp
->mode
|= ATTR_WRAP
;
2382 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2385 if (IS_SET(MODE_INSERT
) && term
.c
.x
+width
< term
.col
)
2386 memmove(gp
+width
, gp
, (term
.col
- term
.c
.x
- width
) * sizeof(Glyph
));
2388 if (term
.c
.x
+width
> term
.col
) {
2390 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2393 tsetchar(u
, &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2396 gp
->mode
|= ATTR_WIDE
;
2397 if (term
.c
.x
+1 < term
.col
) {
2399 gp
[1].mode
= ATTR_WDUMMY
;
2402 if (term
.c
.x
+width
< term
.col
) {
2403 tmoveto(term
.c
.x
+width
, term
.c
.y
);
2405 term
.c
.state
|= CURSOR_WRAPNEXT
;
2410 twrite(const char *buf
, int buflen
, int show_ctrl
)
2416 for (n
= 0; n
< buflen
; n
+= charsize
) {
2417 if (IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2418 /* process a complete utf8 char */
2419 charsize
= utf8decode(buf
+ n
, &u
, buflen
- n
);
2426 if (show_ctrl
&& ISCONTROL(u
)) {
2431 } else if (u
!= '\n' && u
!= '\r' && u
!= '\t') {
2442 tresize(int col
, int row
)
2445 int minrow
= MIN(row
, term
.row
);
2446 int mincol
= MIN(col
, term
.col
);
2450 if (col
< 1 || row
< 1) {
2452 "tresize: error resizing to %dx%d\n", col
, row
);
2457 * slide screen to keep cursor where we expect it -
2458 * tscrollup would work here, but we can optimize to
2459 * memmove because we're freeing the earlier lines
2461 for (i
= 0; i
<= term
.c
.y
- row
; i
++) {
2465 /* ensure that both src and dst are not NULL */
2467 memmove(term
.line
, term
.line
+ i
, row
* sizeof(Line
));
2468 memmove(term
.alt
, term
.alt
+ i
, row
* sizeof(Line
));
2470 for (i
+= row
; i
< term
.row
; i
++) {
2475 /* resize to new height */
2476 term
.line
= xrealloc(term
.line
, row
* sizeof(Line
));
2477 term
.alt
= xrealloc(term
.alt
, row
* sizeof(Line
));
2478 term
.dirty
= xrealloc(term
.dirty
, row
* sizeof(*term
.dirty
));
2479 term
.tabs
= xrealloc(term
.tabs
, col
* sizeof(*term
.tabs
));
2481 /* resize each row to new width, zero-pad if needed */
2482 for (i
= 0; i
< minrow
; i
++) {
2483 term
.line
[i
] = xrealloc(term
.line
[i
], col
* sizeof(Glyph
));
2484 term
.alt
[i
] = xrealloc(term
.alt
[i
], col
* sizeof(Glyph
));
2487 /* allocate any new rows */
2488 for (/* i = minrow */; i
< row
; i
++) {
2489 term
.line
[i
] = xmalloc(col
* sizeof(Glyph
));
2490 term
.alt
[i
] = xmalloc(col
* sizeof(Glyph
));
2492 if (col
> term
.col
) {
2493 bp
= term
.tabs
+ term
.col
;
2495 memset(bp
, 0, sizeof(*term
.tabs
) * (col
- term
.col
));
2496 while (--bp
> term
.tabs
&& !*bp
)
2498 for (bp
+= tabspaces
; bp
< term
.tabs
+ col
; bp
+= tabspaces
)
2501 /* update terminal size */
2504 /* reset scrolling region */
2505 tsetscroll(0, row
-1);
2506 /* make use of the LIMIT in tmoveto */
2507 tmoveto(term
.c
.x
, term
.c
.y
);
2508 /* Clearing both screens (it makes dirty all lines) */
2510 for (i
= 0; i
< 2; i
++) {
2511 if (mincol
< col
&& 0 < minrow
) {
2512 tclearregion(mincol
, 0, col
- 1, minrow
- 1);
2514 if (0 < col
&& minrow
< row
) {
2515 tclearregion(0, minrow
, col
- 1, row
- 1);
2518 tcursor(CURSOR_LOAD
);