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 drawregion(int, int, int, int);
171 static void selscroll(int, int);
172 static void selsnap(int *, int *, int);
174 static Rune
utf8decodebyte(char, size_t *);
175 static char utf8encodebyte(Rune
, size_t);
176 static char *utf8strchr(char *s
, Rune u
);
177 static size_t utf8validate(Rune
*, size_t);
179 static char *base64dec(const char *);
181 static ssize_t
xwrite(int, const char *, size_t);
187 int oldbutton
= 3; /* button event on startup: 3 = release */
189 static Selection sel
;
190 static CSIEscape csiescseq
;
191 static STREscape strescseq
;
194 static uchar utfbyte
[UTF_SIZ
+ 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
195 static uchar utfmask
[UTF_SIZ
+ 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
196 static Rune utfmin
[UTF_SIZ
+ 1] = { 0, 0, 0x80, 0x800, 0x10000};
197 static Rune utfmax
[UTF_SIZ
+ 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
200 xwrite(int fd
, const char *s
, size_t len
)
206 r
= write(fd
, s
, len
);
219 void *p
= malloc(len
);
222 die("Out of memory\n");
228 xrealloc(void *p
, size_t len
)
230 if ((p
= realloc(p
, len
)) == NULL
)
231 die("Out of memory\n");
239 if ((s
= strdup(s
)) == NULL
)
240 die("Out of memory\n");
246 utf8decode(const char *c
, Rune
*u
, size_t clen
)
248 size_t i
, j
, len
, type
;
254 udecoded
= utf8decodebyte(c
[0], &len
);
255 if (!BETWEEN(len
, 1, UTF_SIZ
))
257 for (i
= 1, j
= 1; i
< clen
&& j
< len
; ++i
, ++j
) {
258 udecoded
= (udecoded
<< 6) | utf8decodebyte(c
[i
], &type
);
265 utf8validate(u
, len
);
271 utf8decodebyte(char c
, size_t *i
)
273 for (*i
= 0; *i
< LEN(utfmask
); ++(*i
))
274 if (((uchar
)c
& utfmask
[*i
]) == utfbyte
[*i
])
275 return (uchar
)c
& ~utfmask
[*i
];
281 utf8encode(Rune u
, char *c
)
285 len
= utf8validate(&u
, 0);
289 for (i
= len
- 1; i
!= 0; --i
) {
290 c
[i
] = utf8encodebyte(u
, 0);
293 c
[0] = utf8encodebyte(u
, len
);
299 utf8encodebyte(Rune u
, size_t i
)
301 return utfbyte
[i
] | (u
& ~utfmask
[i
]);
305 utf8strchr(char *s
, Rune u
)
311 for (i
= 0, j
= 0; i
< len
; i
+= j
) {
312 if (!(j
= utf8decode(&s
[i
], &r
, len
- i
)))
322 utf8validate(Rune
*u
, size_t i
)
324 if (!BETWEEN(*u
, utfmin
[i
], utfmax
[i
]) || BETWEEN(*u
, 0xD800, 0xDFFF))
326 for (i
= 1; *u
> utfmax
[i
]; ++i
)
332 static const char base64_digits
[] = {
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, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0,
335 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
336 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
337 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
338 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
343 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
344 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
348 base64dec_getc(const char **src
)
350 while (**src
&& !isprint(**src
)) (*src
)++;
355 base64dec(const char *src
)
357 size_t in_len
= strlen(src
);
361 in_len
+= 4 - (in_len
% 4);
362 result
= dst
= xmalloc(in_len
/ 4 * 3 + 1);
364 int a
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
365 int b
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
366 int c
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
367 int d
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
369 *dst
++ = (a
<< 2) | ((b
& 0x30) >> 4);
372 *dst
++ = ((b
& 0x0f) << 4) | ((c
& 0x3c) >> 2);
375 *dst
++ = ((c
& 0x03) << 6) | d
;
394 if (term
.line
[y
][i
- 1].mode
& ATTR_WRAP
)
397 while (i
> 0 && term
.line
[y
][i
- 1].u
== ' ')
404 selstart(int col
, int row
, int snap
)
407 sel
.mode
= SEL_EMPTY
;
408 sel
.type
= SEL_REGULAR
;
410 sel
.oe
.x
= sel
.ob
.x
= col
;
411 sel
.oe
.y
= sel
.ob
.y
= row
;
415 sel
.mode
= SEL_READY
;
416 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
420 selextend(int col
, int row
, int type
, int done
)
422 int oldey
, oldex
, oldsby
, oldsey
, oldtype
;
426 if (done
&& sel
.mode
== SEL_EMPTY
) {
437 sel
.alt
= IS_SET(MODE_ALTSCREEN
);
443 if (oldey
!= sel
.oe
.y
|| oldex
!= sel
.oe
.x
|| oldtype
!= sel
.type
)
444 tsetdirt(MIN(sel
.nb
.y
, oldsby
), MAX(sel
.ne
.y
, oldsey
));
446 sel
.mode
= done
? SEL_IDLE
: SEL_READY
;
454 if (sel
.type
== SEL_REGULAR
&& sel
.ob
.y
!= sel
.oe
.y
) {
455 sel
.nb
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.ob
.x
: sel
.oe
.x
;
456 sel
.ne
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.oe
.x
: sel
.ob
.x
;
458 sel
.nb
.x
= MIN(sel
.ob
.x
, sel
.oe
.x
);
459 sel
.ne
.x
= MAX(sel
.ob
.x
, sel
.oe
.x
);
461 sel
.nb
.y
= MIN(sel
.ob
.y
, sel
.oe
.y
);
462 sel
.ne
.y
= MAX(sel
.ob
.y
, sel
.oe
.y
);
464 selsnap(&sel
.nb
.x
, &sel
.nb
.y
, -1);
465 selsnap(&sel
.ne
.x
, &sel
.ne
.y
, +1);
467 /* expand selection over line breaks */
468 if (sel
.type
== SEL_RECTANGULAR
)
470 i
= tlinelen(sel
.nb
.y
);
473 if (tlinelen(sel
.ne
.y
) <= sel
.ne
.x
)
474 sel
.ne
.x
= term
.col
- 1;
478 selected(int x
, int y
)
480 if (sel
.mode
== SEL_EMPTY
|| sel
.ob
.x
== -1 ||
481 sel
.alt
!= IS_SET(MODE_ALTSCREEN
))
484 if (sel
.type
== SEL_RECTANGULAR
)
485 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
486 && BETWEEN(x
, sel
.nb
.x
, sel
.ne
.x
);
488 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
489 && (y
!= sel
.nb
.y
|| x
>= sel
.nb
.x
)
490 && (y
!= sel
.ne
.y
|| x
<= sel
.ne
.x
);
494 selsnap(int *x
, int *y
, int direction
)
496 int newx
, newy
, xt
, yt
;
497 int delim
, prevdelim
;
503 * Snap around if the word wraps around at the end or
504 * beginning of a line.
506 prevgp
= &term
.line
[*y
][*x
];
507 prevdelim
= ISDELIM(prevgp
->u
);
509 newx
= *x
+ direction
;
511 if (!BETWEEN(newx
, 0, term
.col
- 1)) {
513 newx
= (newx
+ term
.col
) % term
.col
;
514 if (!BETWEEN(newy
, 0, term
.row
- 1))
520 yt
= newy
, xt
= newx
;
521 if (!(term
.line
[yt
][xt
].mode
& ATTR_WRAP
))
525 if (newx
>= tlinelen(newy
))
528 gp
= &term
.line
[newy
][newx
];
529 delim
= ISDELIM(gp
->u
);
530 if (!(gp
->mode
& ATTR_WDUMMY
) && (delim
!= prevdelim
531 || (delim
&& gp
->u
!= prevgp
->u
)))
542 * Snap around if the the previous line or the current one
543 * has set ATTR_WRAP at its end. Then the whole next or
544 * previous line will be selected.
546 *x
= (direction
< 0) ? 0 : term
.col
- 1;
548 for (; *y
> 0; *y
+= direction
) {
549 if (!(term
.line
[*y
-1][term
.col
-1].mode
554 } else if (direction
> 0) {
555 for (; *y
< term
.row
-1; *y
+= direction
) {
556 if (!(term
.line
[*y
][term
.col
-1].mode
570 int y
, bufsize
, lastx
, linelen
;
576 bufsize
= (term
.col
+1) * (sel
.ne
.y
-sel
.nb
.y
+1) * UTF_SIZ
;
577 ptr
= str
= xmalloc(bufsize
);
579 /* append every set & selected glyph to the selection */
580 for (y
= sel
.nb
.y
; y
<= sel
.ne
.y
; y
++) {
581 if ((linelen
= tlinelen(y
)) == 0) {
586 if (sel
.type
== SEL_RECTANGULAR
) {
587 gp
= &term
.line
[y
][sel
.nb
.x
];
590 gp
= &term
.line
[y
][sel
.nb
.y
== y
? sel
.nb
.x
: 0];
591 lastx
= (sel
.ne
.y
== y
) ? sel
.ne
.x
: term
.col
-1;
593 last
= &term
.line
[y
][MIN(lastx
, linelen
-1)];
594 while (last
>= gp
&& last
->u
== ' ')
597 for ( ; gp
<= last
; ++gp
) {
598 if (gp
->mode
& ATTR_WDUMMY
)
601 ptr
+= utf8encode(gp
->u
, ptr
);
605 * Copy and pasting of line endings is inconsistent
606 * in the inconsistent terminal and GUI world.
607 * The best solution seems like to produce '\n' when
608 * something is copied from st and convert '\n' to
609 * '\r', when something to be pasted is received by
611 * FIXME: Fix the computer world.
613 if ((y
< sel
.ne
.y
|| lastx
>= linelen
) && !(last
->mode
& ATTR_WRAP
))
627 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
631 die(const char *errstr
, ...)
635 va_start(ap
, errstr
);
636 vfprintf(stderr
, errstr
, ap
);
645 const struct passwd
*pw
;
648 if ((pw
= getpwuid(getuid())) == NULL
) {
650 die("getpwuid:%s\n", strerror(errno
));
652 die("who are you?\n");
655 if ((sh
= getenv("SHELL")) == NULL
)
656 sh
= (pw
->pw_shell
[0]) ? pw
->pw_shell
: shell
;
664 DEFAULT(args
, ((char *[]) {prog
, NULL
}));
669 setenv("LOGNAME", pw
->pw_name
, 1);
670 setenv("USER", pw
->pw_name
, 1);
671 setenv("SHELL", sh
, 1);
672 setenv("HOME", pw
->pw_dir
, 1);
673 setenv("TERM", termname
, 1);
675 signal(SIGCHLD
, SIG_DFL
);
676 signal(SIGHUP
, SIG_DFL
);
677 signal(SIGINT
, SIG_DFL
);
678 signal(SIGQUIT
, SIG_DFL
);
679 signal(SIGTERM
, SIG_DFL
);
680 signal(SIGALRM
, SIG_DFL
);
692 if ((p
= waitpid(pid
, &stat
, WNOHANG
)) < 0)
693 die("Waiting for pid %hd failed: %s\n", pid
, strerror(errno
));
698 if (!WIFEXITED(stat
) || WEXITSTATUS(stat
))
699 die("child finished with error '%d'\n", stat
);
707 char cmd
[_POSIX_ARG_MAX
], **p
, *q
, *s
;
710 if ((n
= strlen(stty_args
)) > sizeof(cmd
)-1)
711 die("incorrect stty parameters\n");
712 memcpy(cmd
, stty_args
, n
);
714 siz
= sizeof(cmd
) - n
;
715 for (p
= args
; p
&& (s
= *p
); ++p
) {
716 if ((n
= strlen(s
)) > siz
-1)
717 die("stty parameter length too long\n");
724 if (system(cmd
) != 0)
725 perror("Couldn't call stty");
729 ttynew(char *line
, char *out
, char **args
)
734 term
.mode
|= MODE_PRINT
;
735 iofd
= (!strcmp(out
, "-")) ?
736 1 : open(out
, O_WRONLY
| O_CREAT
, 0666);
738 fprintf(stderr
, "Error opening %s:%s\n",
739 out
, strerror(errno
));
744 if ((cmdfd
= open(line
, O_RDWR
)) < 0)
745 die("open line failed: %s\n", strerror(errno
));
751 /* seems to work fine on linux, openbsd and freebsd */
752 if (openpty(&m
, &s
, NULL
, NULL
, NULL
) < 0)
753 die("openpty failed: %s\n", strerror(errno
));
755 switch (pid
= fork()) {
757 die("fork failed\n");
761 setsid(); /* create a new process group */
765 if (ioctl(s
, TIOCSCTTY
, NULL
) < 0)
766 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno
));
774 signal(SIGCHLD
, sigchld
);
782 static char buf
[BUFSIZ
];
783 static int buflen
= 0;
787 /* append read bytes to unprocessed bytes */
788 if ((ret
= read(cmdfd
, buf
+buflen
, LEN(buf
)-buflen
)) < 0)
789 die("Couldn't read from shell: %s\n", strerror(errno
));
792 written
= twrite(buf
, buflen
, 0);
794 /* keep any uncomplete utf8 char for the next call */
796 memmove(buf
, buf
+ written
, buflen
);
802 ttywrite(const char *s
, size_t n
, int may_echo
)
806 if (may_echo
&& IS_SET(MODE_ECHO
))
809 if (!IS_SET(MODE_CRLF
)) {
814 /* This is similar to how the kernel handles ONLCR for ttys */
818 ttywriteraw("\r\n", 2);
820 next
= memchr(s
, '\r', n
);
821 DEFAULT(next
, s
+ n
);
822 ttywriteraw(s
, next
- s
);
830 ttywriteraw(const char *s
, size_t n
)
837 * Remember that we are using a pty, which might be a modem line.
838 * Writing too much will clog the line. That's why we are doing this
840 * FIXME: Migrate the world to Plan 9.
848 /* Check if we can write. */
849 if (pselect(cmdfd
+1, &rfd
, &wfd
, NULL
, NULL
, NULL
) < 0) {
852 die("select failed: %s\n", strerror(errno
));
854 if (FD_ISSET(cmdfd
, &wfd
)) {
856 * Only write the bytes written by ttywrite() or the
857 * default of 256. This seems to be a reasonable value
858 * for a serial line. Bigger values might clog the I/O.
860 if ((r
= write(cmdfd
, s
, (n
< lim
)? n
: lim
)) < 0)
864 * We weren't able to write out everything.
865 * This means the buffer is getting full
873 /* All bytes have been written. */
877 if (FD_ISSET(cmdfd
, &rfd
))
883 die("write error on tty: %s\n", strerror(errno
));
887 ttyresize(int tw
, int th
)
895 if (ioctl(cmdfd
, TIOCSWINSZ
, &w
) < 0)
896 fprintf(stderr
, "Couldn't set window size: %s\n", strerror(errno
));
904 for (i
= 0; i
< term
.row
-1; i
++) {
905 for (j
= 0; j
< term
.col
-1; j
++) {
906 if (term
.line
[i
][j
].mode
& attr
)
915 tsetdirt(int top
, int bot
)
919 LIMIT(top
, 0, term
.row
-1);
920 LIMIT(bot
, 0, term
.row
-1);
922 for (i
= top
; i
<= bot
; i
++)
927 tsetdirtattr(int attr
)
931 for (i
= 0; i
< term
.row
-1; i
++) {
932 for (j
= 0; j
< term
.col
-1; j
++) {
933 if (term
.line
[i
][j
].mode
& attr
) {
944 tsetdirt(0, term
.row
-1);
951 int alt
= IS_SET(MODE_ALTSCREEN
);
953 if (mode
== CURSOR_SAVE
) {
955 } else if (mode
== CURSOR_LOAD
) {
957 tmoveto(c
[alt
].x
, c
[alt
].y
);
970 }, .x
= 0, .y
= 0, .state
= CURSOR_DEFAULT
};
972 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
973 for (i
= tabspaces
; i
< term
.col
; i
+= tabspaces
)
976 term
.bot
= term
.row
- 1;
977 term
.mode
= MODE_WRAP
|MODE_UTF8
;
978 memset(term
.trantbl
, CS_USA
, sizeof(term
.trantbl
));
981 for (i
= 0; i
< 2; i
++) {
983 tcursor(CURSOR_SAVE
);
984 tclearregion(0, 0, term
.col
-1, term
.row
-1);
990 tnew(int col
, int row
)
992 term
= (Term
){ .c
= { .attr
= { .fg
= defaultfg
, .bg
= defaultbg
} } };
1000 Line
*tmp
= term
.line
;
1002 term
.line
= term
.alt
;
1004 term
.mode
^= MODE_ALTSCREEN
;
1009 tscrolldown(int orig
, int n
)
1014 LIMIT(n
, 0, term
.bot
-orig
+1);
1016 tsetdirt(orig
, term
.bot
-n
);
1017 tclearregion(0, term
.bot
-n
+1, term
.col
-1, term
.bot
);
1019 for (i
= term
.bot
; i
>= orig
+n
; i
--) {
1020 temp
= term
.line
[i
];
1021 term
.line
[i
] = term
.line
[i
-n
];
1022 term
.line
[i
-n
] = temp
;
1029 tscrollup(int orig
, int n
)
1034 LIMIT(n
, 0, term
.bot
-orig
+1);
1036 tclearregion(0, orig
, term
.col
-1, orig
+n
-1);
1037 tsetdirt(orig
+n
, term
.bot
);
1039 for (i
= orig
; i
<= term
.bot
-n
; i
++) {
1040 temp
= term
.line
[i
];
1041 term
.line
[i
] = term
.line
[i
+n
];
1042 term
.line
[i
+n
] = temp
;
1045 selscroll(orig
, -n
);
1049 selscroll(int orig
, int n
)
1054 if (BETWEEN(sel
.ob
.y
, orig
, term
.bot
) || BETWEEN(sel
.oe
.y
, orig
, term
.bot
)) {
1055 if ((sel
.ob
.y
+= n
) > term
.bot
|| (sel
.oe
.y
+= n
) < term
.top
) {
1059 if (sel
.type
== SEL_RECTANGULAR
) {
1060 if (sel
.ob
.y
< term
.top
)
1061 sel
.ob
.y
= term
.top
;
1062 if (sel
.oe
.y
> term
.bot
)
1063 sel
.oe
.y
= term
.bot
;
1065 if (sel
.ob
.y
< term
.top
) {
1066 sel
.ob
.y
= term
.top
;
1069 if (sel
.oe
.y
> term
.bot
) {
1070 sel
.oe
.y
= term
.bot
;
1071 sel
.oe
.x
= term
.col
;
1079 tnewline(int first_col
)
1083 if (y
== term
.bot
) {
1084 tscrollup(term
.top
, 1);
1088 tmoveto(first_col
? 0 : term
.c
.x
, y
);
1094 char *p
= csiescseq
.buf
, *np
;
1103 csiescseq
.buf
[csiescseq
.len
] = '\0';
1104 while (p
< csiescseq
.buf
+csiescseq
.len
) {
1106 v
= strtol(p
, &np
, 10);
1109 if (v
== LONG_MAX
|| v
== LONG_MIN
)
1111 csiescseq
.arg
[csiescseq
.narg
++] = v
;
1113 if (*p
!= ';' || csiescseq
.narg
== ESC_ARG_SIZ
)
1117 csiescseq
.mode
[0] = *p
++;
1118 csiescseq
.mode
[1] = (p
< csiescseq
.buf
+csiescseq
.len
) ? *p
: '\0';
1121 /* for absolute user moves, when decom is set */
1123 tmoveato(int x
, int y
)
1125 tmoveto(x
, y
+ ((term
.c
.state
& CURSOR_ORIGIN
) ? term
.top
: 0));
1129 tmoveto(int x
, int y
)
1133 if (term
.c
.state
& CURSOR_ORIGIN
) {
1138 maxy
= term
.row
- 1;
1140 term
.c
.state
&= ~CURSOR_WRAPNEXT
;
1141 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1142 term
.c
.y
= LIMIT(y
, miny
, maxy
);
1146 tsetchar(Rune u
, Glyph
*attr
, int x
, int y
)
1148 static char *vt100_0
[62] = { /* 0x41 - 0x7e */
1149 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1150 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1151 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1152 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1153 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1154 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1155 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1156 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1160 * The table is proudly stolen from rxvt.
1162 if (term
.trantbl
[term
.charset
] == CS_GRAPHIC0
&&
1163 BETWEEN(u
, 0x41, 0x7e) && vt100_0
[u
- 0x41])
1164 utf8decode(vt100_0
[u
- 0x41], &u
, UTF_SIZ
);
1166 if (term
.line
[y
][x
].mode
& ATTR_WIDE
) {
1167 if (x
+1 < term
.col
) {
1168 term
.line
[y
][x
+1].u
= ' ';
1169 term
.line
[y
][x
+1].mode
&= ~ATTR_WDUMMY
;
1171 } else if (term
.line
[y
][x
].mode
& ATTR_WDUMMY
) {
1172 term
.line
[y
][x
-1].u
= ' ';
1173 term
.line
[y
][x
-1].mode
&= ~ATTR_WIDE
;
1177 term
.line
[y
][x
] = *attr
;
1178 term
.line
[y
][x
].u
= u
;
1182 tclearregion(int x1
, int y1
, int x2
, int y2
)
1188 temp
= x1
, x1
= x2
, x2
= temp
;
1190 temp
= y1
, y1
= y2
, y2
= temp
;
1192 LIMIT(x1
, 0, term
.col
-1);
1193 LIMIT(x2
, 0, term
.col
-1);
1194 LIMIT(y1
, 0, term
.row
-1);
1195 LIMIT(y2
, 0, term
.row
-1);
1197 for (y
= y1
; y
<= y2
; y
++) {
1199 for (x
= x1
; x
<= x2
; x
++) {
1200 gp
= &term
.line
[y
][x
];
1203 gp
->fg
= term
.c
.attr
.fg
;
1204 gp
->bg
= term
.c
.attr
.bg
;
1217 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1221 size
= term
.col
- src
;
1222 line
= term
.line
[term
.c
.y
];
1224 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1225 tclearregion(term
.col
-n
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1234 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1238 size
= term
.col
- dst
;
1239 line
= term
.line
[term
.c
.y
];
1241 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1242 tclearregion(src
, term
.c
.y
, dst
- 1, term
.c
.y
);
1246 tinsertblankline(int n
)
1248 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1249 tscrolldown(term
.c
.y
, n
);
1255 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1256 tscrollup(term
.c
.y
, n
);
1260 tdefcolor(int *attr
, int *npar
, int l
)
1265 switch (attr
[*npar
+ 1]) {
1266 case 2: /* direct color in RGB space */
1267 if (*npar
+ 4 >= l
) {
1269 "erresc(38): Incorrect number of parameters (%d)\n",
1273 r
= attr
[*npar
+ 2];
1274 g
= attr
[*npar
+ 3];
1275 b
= attr
[*npar
+ 4];
1277 if (!BETWEEN(r
, 0, 255) || !BETWEEN(g
, 0, 255) || !BETWEEN(b
, 0, 255))
1278 fprintf(stderr
, "erresc: bad rgb color (%u,%u,%u)\n",
1281 idx
= TRUECOLOR(r
, g
, b
);
1283 case 5: /* indexed color */
1284 if (*npar
+ 2 >= l
) {
1286 "erresc(38): Incorrect number of parameters (%d)\n",
1291 if (!BETWEEN(attr
[*npar
], 0, 255))
1292 fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[*npar
]);
1296 case 0: /* implemented defined (only foreground) */
1297 case 1: /* transparent */
1298 case 3: /* direct color in CMY space */
1299 case 4: /* direct color in CMYK space */
1302 "erresc(38): gfx attr %d unknown\n", attr
[*npar
]);
1310 tsetattr(int *attr
, int l
)
1315 for (i
= 0; i
< l
; i
++) {
1318 term
.c
.attr
.mode
&= ~(
1327 term
.c
.attr
.fg
= defaultfg
;
1328 term
.c
.attr
.bg
= defaultbg
;
1331 term
.c
.attr
.mode
|= ATTR_BOLD
;
1334 term
.c
.attr
.mode
|= ATTR_FAINT
;
1337 term
.c
.attr
.mode
|= ATTR_ITALIC
;
1340 term
.c
.attr
.mode
|= ATTR_UNDERLINE
;
1342 case 5: /* slow blink */
1344 case 6: /* rapid blink */
1345 term
.c
.attr
.mode
|= ATTR_BLINK
;
1348 term
.c
.attr
.mode
|= ATTR_REVERSE
;
1351 term
.c
.attr
.mode
|= ATTR_INVISIBLE
;
1354 term
.c
.attr
.mode
|= ATTR_STRUCK
;
1357 term
.c
.attr
.mode
&= ~(ATTR_BOLD
| ATTR_FAINT
);
1360 term
.c
.attr
.mode
&= ~ATTR_ITALIC
;
1363 term
.c
.attr
.mode
&= ~ATTR_UNDERLINE
;
1366 term
.c
.attr
.mode
&= ~ATTR_BLINK
;
1369 term
.c
.attr
.mode
&= ~ATTR_REVERSE
;
1372 term
.c
.attr
.mode
&= ~ATTR_INVISIBLE
;
1375 term
.c
.attr
.mode
&= ~ATTR_STRUCK
;
1378 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1379 term
.c
.attr
.fg
= idx
;
1382 term
.c
.attr
.fg
= defaultfg
;
1385 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1386 term
.c
.attr
.bg
= idx
;
1389 term
.c
.attr
.bg
= defaultbg
;
1392 if (BETWEEN(attr
[i
], 30, 37)) {
1393 term
.c
.attr
.fg
= attr
[i
] - 30;
1394 } else if (BETWEEN(attr
[i
], 40, 47)) {
1395 term
.c
.attr
.bg
= attr
[i
] - 40;
1396 } else if (BETWEEN(attr
[i
], 90, 97)) {
1397 term
.c
.attr
.fg
= attr
[i
] - 90 + 8;
1398 } else if (BETWEEN(attr
[i
], 100, 107)) {
1399 term
.c
.attr
.bg
= attr
[i
] - 100 + 8;
1402 "erresc(default): gfx attr %d unknown\n",
1403 attr
[i
]), csidump();
1411 tsetscroll(int t
, int b
)
1415 LIMIT(t
, 0, term
.row
-1);
1416 LIMIT(b
, 0, term
.row
-1);
1427 tsetmode(int priv
, int set
, int *args
, int narg
)
1431 for (lim
= args
+ narg
; args
< lim
; ++args
) {
1434 case 1: /* DECCKM -- Cursor key */
1435 xsetmode(set
, MODE_APPCURSOR
);
1437 case 5: /* DECSCNM -- Reverse video */
1438 xsetmode(set
, MODE_REVERSE
);
1440 case 6: /* DECOM -- Origin */
1441 MODBIT(term
.c
.state
, set
, CURSOR_ORIGIN
);
1444 case 7: /* DECAWM -- Auto wrap */
1445 MODBIT(term
.mode
, set
, MODE_WRAP
);
1447 case 0: /* Error (IGNORED) */
1448 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1449 case 3: /* DECCOLM -- Column (IGNORED) */
1450 case 4: /* DECSCLM -- Scroll (IGNORED) */
1451 case 8: /* DECARM -- Auto repeat (IGNORED) */
1452 case 18: /* DECPFF -- Printer feed (IGNORED) */
1453 case 19: /* DECPEX -- Printer extent (IGNORED) */
1454 case 42: /* DECNRCM -- National characters (IGNORED) */
1455 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1457 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1458 xsetmode(!set
, MODE_HIDE
);
1460 case 9: /* X10 mouse compatibility mode */
1461 xsetpointermotion(0);
1462 xsetmode(0, MODE_MOUSE
);
1463 xsetmode(set
, MODE_MOUSEX10
);
1465 case 1000: /* 1000: report button press */
1466 xsetpointermotion(0);
1467 xsetmode(0, MODE_MOUSE
);
1468 xsetmode(set
, MODE_MOUSEBTN
);
1470 case 1002: /* 1002: report motion on button press */
1471 xsetpointermotion(0);
1472 xsetmode(0, MODE_MOUSE
);
1473 xsetmode(set
, MODE_MOUSEMOTION
);
1475 case 1003: /* 1003: enable all mouse motions */
1476 xsetpointermotion(set
);
1477 xsetmode(0, MODE_MOUSE
);
1478 xsetmode(set
, MODE_MOUSEMANY
);
1480 case 1004: /* 1004: send focus events to tty */
1481 xsetmode(set
, MODE_FOCUS
);
1483 case 1006: /* 1006: extended reporting mode */
1484 xsetmode(set
, MODE_MOUSESGR
);
1487 xsetmode(set
, MODE_8BIT
);
1489 case 1049: /* swap screen & set/restore cursor as xterm */
1490 if (!allowaltscreen
)
1492 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1494 case 47: /* swap screen */
1496 if (!allowaltscreen
)
1498 alt
= IS_SET(MODE_ALTSCREEN
);
1500 tclearregion(0, 0, term
.col
-1,
1503 if (set
^ alt
) /* set is always 1 or 0 */
1509 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1511 case 2004: /* 2004: bracketed paste mode */
1512 xsetmode(set
, MODE_BRCKTPASTE
);
1514 /* Not implemented mouse modes. See comments there. */
1515 case 1001: /* mouse highlight mode; can hang the
1516 terminal by design when implemented. */
1517 case 1005: /* UTF-8 mouse mode; will confuse
1518 applications not supporting UTF-8
1520 case 1015: /* urxvt mangled mouse mode; incompatible
1521 and can be mistaken for other control
1525 "erresc: unknown private set/reset mode %d\n",
1531 case 0: /* Error (IGNORED) */
1534 xsetmode(set
, MODE_KBDLOCK
);
1536 case 4: /* IRM -- Insertion-replacement */
1537 MODBIT(term
.mode
, set
, MODE_INSERT
);
1539 case 12: /* SRM -- Send/Receive */
1540 MODBIT(term
.mode
, !set
, MODE_ECHO
);
1542 case 20: /* LNM -- Linefeed/new line */
1543 MODBIT(term
.mode
, set
, MODE_CRLF
);
1547 "erresc: unknown set/reset mode %d\n",
1561 switch (csiescseq
.mode
[0]) {
1564 fprintf(stderr
, "erresc: unknown csi ");
1568 case '@': /* ICH -- Insert <n> blank char */
1569 DEFAULT(csiescseq
.arg
[0], 1);
1570 tinsertblank(csiescseq
.arg
[0]);
1572 case 'A': /* CUU -- Cursor <n> Up */
1573 DEFAULT(csiescseq
.arg
[0], 1);
1574 tmoveto(term
.c
.x
, term
.c
.y
-csiescseq
.arg
[0]);
1576 case 'B': /* CUD -- Cursor <n> Down */
1577 case 'e': /* VPR --Cursor <n> Down */
1578 DEFAULT(csiescseq
.arg
[0], 1);
1579 tmoveto(term
.c
.x
, term
.c
.y
+csiescseq
.arg
[0]);
1581 case 'i': /* MC -- Media Copy */
1582 switch (csiescseq
.arg
[0]) {
1587 tdumpline(term
.c
.y
);
1593 term
.mode
&= ~MODE_PRINT
;
1596 term
.mode
|= MODE_PRINT
;
1600 case 'c': /* DA -- Device Attributes */
1601 if (csiescseq
.arg
[0] == 0)
1602 ttywrite(vtiden
, strlen(vtiden
), 0);
1604 case 'C': /* CUF -- Cursor <n> Forward */
1605 case 'a': /* HPR -- Cursor <n> Forward */
1606 DEFAULT(csiescseq
.arg
[0], 1);
1607 tmoveto(term
.c
.x
+csiescseq
.arg
[0], term
.c
.y
);
1609 case 'D': /* CUB -- Cursor <n> Backward */
1610 DEFAULT(csiescseq
.arg
[0], 1);
1611 tmoveto(term
.c
.x
-csiescseq
.arg
[0], term
.c
.y
);
1613 case 'E': /* CNL -- Cursor <n> Down and first col */
1614 DEFAULT(csiescseq
.arg
[0], 1);
1615 tmoveto(0, term
.c
.y
+csiescseq
.arg
[0]);
1617 case 'F': /* CPL -- Cursor <n> Up and first col */
1618 DEFAULT(csiescseq
.arg
[0], 1);
1619 tmoveto(0, term
.c
.y
-csiescseq
.arg
[0]);
1621 case 'g': /* TBC -- Tabulation clear */
1622 switch (csiescseq
.arg
[0]) {
1623 case 0: /* clear current tab stop */
1624 term
.tabs
[term
.c
.x
] = 0;
1626 case 3: /* clear all the tabs */
1627 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1633 case 'G': /* CHA -- Move to <col> */
1635 DEFAULT(csiescseq
.arg
[0], 1);
1636 tmoveto(csiescseq
.arg
[0]-1, term
.c
.y
);
1638 case 'H': /* CUP -- Move to <row> <col> */
1640 DEFAULT(csiescseq
.arg
[0], 1);
1641 DEFAULT(csiescseq
.arg
[1], 1);
1642 tmoveato(csiescseq
.arg
[1]-1, csiescseq
.arg
[0]-1);
1644 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1645 DEFAULT(csiescseq
.arg
[0], 1);
1646 tputtab(csiescseq
.arg
[0]);
1648 case 'J': /* ED -- Clear screen */
1650 switch (csiescseq
.arg
[0]) {
1652 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1653 if (term
.c
.y
< term
.row
-1) {
1654 tclearregion(0, term
.c
.y
+1, term
.col
-1,
1660 tclearregion(0, 0, term
.col
-1, term
.c
.y
-1);
1661 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1664 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1670 case 'K': /* EL -- Clear line */
1671 switch (csiescseq
.arg
[0]) {
1673 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1,
1677 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1680 tclearregion(0, term
.c
.y
, term
.col
-1, term
.c
.y
);
1684 case 'S': /* SU -- Scroll <n> line up */
1685 DEFAULT(csiescseq
.arg
[0], 1);
1686 tscrollup(term
.top
, csiescseq
.arg
[0]);
1688 case 'T': /* SD -- Scroll <n> line down */
1689 DEFAULT(csiescseq
.arg
[0], 1);
1690 tscrolldown(term
.top
, csiescseq
.arg
[0]);
1692 case 'L': /* IL -- Insert <n> blank lines */
1693 DEFAULT(csiescseq
.arg
[0], 1);
1694 tinsertblankline(csiescseq
.arg
[0]);
1696 case 'l': /* RM -- Reset Mode */
1697 tsetmode(csiescseq
.priv
, 0, csiescseq
.arg
, csiescseq
.narg
);
1699 case 'M': /* DL -- Delete <n> lines */
1700 DEFAULT(csiescseq
.arg
[0], 1);
1701 tdeleteline(csiescseq
.arg
[0]);
1703 case 'X': /* ECH -- Erase <n> char */
1704 DEFAULT(csiescseq
.arg
[0], 1);
1705 tclearregion(term
.c
.x
, term
.c
.y
,
1706 term
.c
.x
+ csiescseq
.arg
[0] - 1, term
.c
.y
);
1708 case 'P': /* DCH -- Delete <n> char */
1709 DEFAULT(csiescseq
.arg
[0], 1);
1710 tdeletechar(csiescseq
.arg
[0]);
1712 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1713 DEFAULT(csiescseq
.arg
[0], 1);
1714 tputtab(-csiescseq
.arg
[0]);
1716 case 'd': /* VPA -- Move to <row> */
1717 DEFAULT(csiescseq
.arg
[0], 1);
1718 tmoveato(term
.c
.x
, csiescseq
.arg
[0]-1);
1720 case 'h': /* SM -- Set terminal mode */
1721 tsetmode(csiescseq
.priv
, 1, csiescseq
.arg
, csiescseq
.narg
);
1723 case 'm': /* SGR -- Terminal attribute (color) */
1724 tsetattr(csiescseq
.arg
, csiescseq
.narg
);
1726 case 'n': /* DSR – Device Status Report (cursor position) */
1727 if (csiescseq
.arg
[0] == 6) {
1728 len
= snprintf(buf
, sizeof(buf
),"\033[%i;%iR",
1729 term
.c
.y
+1, term
.c
.x
+1);
1730 ttywrite(buf
, len
, 0);
1733 case 'r': /* DECSTBM -- Set Scrolling Region */
1734 if (csiescseq
.priv
) {
1737 DEFAULT(csiescseq
.arg
[0], 1);
1738 DEFAULT(csiescseq
.arg
[1], term
.row
);
1739 tsetscroll(csiescseq
.arg
[0]-1, csiescseq
.arg
[1]-1);
1743 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1744 tcursor(CURSOR_SAVE
);
1746 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1747 tcursor(CURSOR_LOAD
);
1750 switch (csiescseq
.mode
[1]) {
1751 case 'q': /* DECSCUSR -- Set Cursor Style */
1752 if (xsetcursor(csiescseq
.arg
[0]))
1768 fprintf(stderr
, "ESC[");
1769 for (i
= 0; i
< csiescseq
.len
; i
++) {
1770 c
= csiescseq
.buf
[i
] & 0xff;
1773 } else if (c
== '\n') {
1774 fprintf(stderr
, "(\\n)");
1775 } else if (c
== '\r') {
1776 fprintf(stderr
, "(\\r)");
1777 } else if (c
== 0x1b) {
1778 fprintf(stderr
, "(\\e)");
1780 fprintf(stderr
, "(%02x)", c
);
1789 memset(&csiescseq
, 0, sizeof(csiescseq
));
1798 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
1800 par
= (narg
= strescseq
.narg
) ? atoi(strescseq
.args
[0]) : 0;
1802 switch (strescseq
.type
) {
1803 case ']': /* OSC -- Operating System Command */
1809 xsettitle(strescseq
.args
[1]);
1815 dec
= base64dec(strescseq
.args
[2]);
1820 fprintf(stderr
, "erresc: invalid base64\n");
1824 case 4: /* color set */
1827 p
= strescseq
.args
[2];
1829 case 104: /* color reset, here p = NULL */
1830 j
= (narg
> 1) ? atoi(strescseq
.args
[1]) : -1;
1831 if (xsetcolorname(j
, p
)) {
1832 fprintf(stderr
, "erresc: invalid color %s\n", p
);
1835 * TODO if defaultbg color is changed, borders
1843 case 'k': /* old title set compatibility */
1844 xsettitle(strescseq
.args
[0]);
1846 case 'P': /* DCS -- Device Control String */
1847 term
.mode
|= ESC_DCS
;
1848 case '_': /* APC -- Application Program Command */
1849 case '^': /* PM -- Privacy Message */
1853 fprintf(stderr
, "erresc: unknown str ");
1861 char *p
= strescseq
.buf
;
1864 strescseq
.buf
[strescseq
.len
] = '\0';
1869 while (strescseq
.narg
< STR_ARG_SIZ
) {
1870 strescseq
.args
[strescseq
.narg
++] = p
;
1871 while ((c
= *p
) != ';' && c
!= '\0')
1885 fprintf(stderr
, "ESC%c", strescseq
.type
);
1886 for (i
= 0; i
< strescseq
.len
; i
++) {
1887 c
= strescseq
.buf
[i
] & 0xff;
1891 } else if (isprint(c
)) {
1893 } else if (c
== '\n') {
1894 fprintf(stderr
, "(\\n)");
1895 } else if (c
== '\r') {
1896 fprintf(stderr
, "(\\r)");
1897 } else if (c
== 0x1b) {
1898 fprintf(stderr
, "(\\e)");
1900 fprintf(stderr
, "(%02x)", c
);
1903 fprintf(stderr
, "ESC\\\n");
1909 memset(&strescseq
, 0, sizeof(strescseq
));
1913 sendbreak(const Arg
*arg
)
1915 if (tcsendbreak(cmdfd
, 0))
1916 perror("Error sending break");
1920 tprinter(char *s
, size_t len
)
1922 if (iofd
!= -1 && xwrite(iofd
, s
, len
) < 0) {
1923 perror("Error writing to output file");
1930 iso14755(const Arg
*arg
)
1933 char *us
, *e
, codepoint
[9], uc
[UTF_SIZ
];
1934 unsigned long utf32
;
1936 if (!(p
= popen(ISO14755CMD
, "r")))
1939 us
= fgets(codepoint
, sizeof(codepoint
), p
);
1942 if (!us
|| *us
== '\0' || *us
== '-' || strlen(us
) > 7)
1944 if ((utf32
= strtoul(us
, &e
, 16)) == ULONG_MAX
||
1945 (*e
!= '\n' && *e
!= '\0'))
1948 ttywrite(uc
, utf8encode(utf32
, uc
), 1);
1952 toggleprinter(const Arg
*arg
)
1954 term
.mode
^= MODE_PRINT
;
1958 printscreen(const Arg
*arg
)
1964 printsel(const Arg
*arg
)
1974 if ((ptr
= getsel())) {
1975 tprinter(ptr
, strlen(ptr
));
1986 bp
= &term
.line
[n
][0];
1987 end
= &bp
[MIN(tlinelen(n
), term
.col
) - 1];
1988 if (bp
!= end
|| bp
->u
!= ' ') {
1989 for ( ;bp
<= end
; ++bp
)
1990 tprinter(buf
, utf8encode(bp
->u
, buf
));
2000 for (i
= 0; i
< term
.row
; ++i
)
2010 while (x
< term
.col
&& n
--)
2011 for (++x
; x
< term
.col
&& !term
.tabs
[x
]; ++x
)
2014 while (x
> 0 && n
++)
2015 for (--x
; x
> 0 && !term
.tabs
[x
]; --x
)
2018 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
2022 tdefutf8(char ascii
)
2025 term
.mode
|= MODE_UTF8
;
2026 else if (ascii
== '@')
2027 term
.mode
&= ~MODE_UTF8
;
2031 tdeftran(char ascii
)
2033 static char cs
[] = "0B";
2034 static int vcs
[] = {CS_GRAPHIC0
, CS_USA
};
2037 if ((p
= strchr(cs
, ascii
)) == NULL
) {
2038 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
2040 term
.trantbl
[term
.icharset
] = vcs
[p
- cs
];
2049 if (c
== '8') { /* DEC screen alignment test. */
2050 for (x
= 0; x
< term
.col
; ++x
) {
2051 for (y
= 0; y
< term
.row
; ++y
)
2052 tsetchar('E', &term
.c
.attr
, x
, y
);
2058 tstrsequence(uchar c
)
2063 case 0x90: /* DCS -- Device Control String */
2065 term
.esc
|= ESC_DCS
;
2067 case 0x9f: /* APC -- Application Program Command */
2070 case 0x9e: /* PM -- Privacy Message */
2073 case 0x9d: /* OSC -- Operating System Command */
2078 term
.esc
|= ESC_STR
;
2082 tcontrolcode(uchar ascii
)
2089 tmoveto(term
.c
.x
-1, term
.c
.y
);
2092 tmoveto(0, term
.c
.y
);
2097 /* go to first col if the mode is set */
2098 tnewline(IS_SET(MODE_CRLF
));
2100 case '\a': /* BEL */
2101 if (term
.esc
& ESC_STR_END
) {
2102 /* backwards compatibility to xterm */
2108 case '\033': /* ESC */
2110 term
.esc
&= ~(ESC_CSI
|ESC_ALTCHARSET
|ESC_TEST
);
2111 term
.esc
|= ESC_START
;
2113 case '\016': /* SO (LS1 -- Locking shift 1) */
2114 case '\017': /* SI (LS0 -- Locking shift 0) */
2115 term
.charset
= 1 - (ascii
- '\016');
2117 case '\032': /* SUB */
2118 tsetchar('?', &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2119 case '\030': /* CAN */
2122 case '\005': /* ENQ (IGNORED) */
2123 case '\000': /* NUL (IGNORED) */
2124 case '\021': /* XON (IGNORED) */
2125 case '\023': /* XOFF (IGNORED) */
2126 case 0177: /* DEL (IGNORED) */
2128 case 0x80: /* TODO: PAD */
2129 case 0x81: /* TODO: HOP */
2130 case 0x82: /* TODO: BPH */
2131 case 0x83: /* TODO: NBH */
2132 case 0x84: /* TODO: IND */
2134 case 0x85: /* NEL -- Next line */
2135 tnewline(1); /* always go to first col */
2137 case 0x86: /* TODO: SSA */
2138 case 0x87: /* TODO: ESA */
2140 case 0x88: /* HTS -- Horizontal tab stop */
2141 term
.tabs
[term
.c
.x
] = 1;
2143 case 0x89: /* TODO: HTJ */
2144 case 0x8a: /* TODO: VTS */
2145 case 0x8b: /* TODO: PLD */
2146 case 0x8c: /* TODO: PLU */
2147 case 0x8d: /* TODO: RI */
2148 case 0x8e: /* TODO: SS2 */
2149 case 0x8f: /* TODO: SS3 */
2150 case 0x91: /* TODO: PU1 */
2151 case 0x92: /* TODO: PU2 */
2152 case 0x93: /* TODO: STS */
2153 case 0x94: /* TODO: CCH */
2154 case 0x95: /* TODO: MW */
2155 case 0x96: /* TODO: SPA */
2156 case 0x97: /* TODO: EPA */
2157 case 0x98: /* TODO: SOS */
2158 case 0x99: /* TODO: SGCI */
2160 case 0x9a: /* DECID -- Identify Terminal */
2161 ttywrite(vtiden
, strlen(vtiden
), 0);
2163 case 0x9b: /* TODO: CSI */
2164 case 0x9c: /* TODO: ST */
2166 case 0x90: /* DCS -- Device Control String */
2167 case 0x9d: /* OSC -- Operating System Command */
2168 case 0x9e: /* PM -- Privacy Message */
2169 case 0x9f: /* APC -- Application Program Command */
2170 tstrsequence(ascii
);
2173 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2174 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
2178 * returns 1 when the sequence is finished and it hasn't to read
2179 * more characters for this sequence, otherwise 0
2182 eschandle(uchar ascii
)
2186 term
.esc
|= ESC_CSI
;
2189 term
.esc
|= ESC_TEST
;
2192 term
.esc
|= ESC_UTF8
;
2194 case 'P': /* DCS -- Device Control String */
2195 case '_': /* APC -- Application Program Command */
2196 case '^': /* PM -- Privacy Message */
2197 case ']': /* OSC -- Operating System Command */
2198 case 'k': /* old title set compatibility */
2199 tstrsequence(ascii
);
2201 case 'n': /* LS2 -- Locking shift 2 */
2202 case 'o': /* LS3 -- Locking shift 3 */
2203 term
.charset
= 2 + (ascii
- 'n');
2205 case '(': /* GZD4 -- set primary charset G0 */
2206 case ')': /* G1D4 -- set secondary charset G1 */
2207 case '*': /* G2D4 -- set tertiary charset G2 */
2208 case '+': /* G3D4 -- set quaternary charset G3 */
2209 term
.icharset
= ascii
- '(';
2210 term
.esc
|= ESC_ALTCHARSET
;
2212 case 'D': /* IND -- Linefeed */
2213 if (term
.c
.y
== term
.bot
) {
2214 tscrollup(term
.top
, 1);
2216 tmoveto(term
.c
.x
, term
.c
.y
+1);
2219 case 'E': /* NEL -- Next line */
2220 tnewline(1); /* always go to first col */
2222 case 'H': /* HTS -- Horizontal tab stop */
2223 term
.tabs
[term
.c
.x
] = 1;
2225 case 'M': /* RI -- Reverse index */
2226 if (term
.c
.y
== term
.top
) {
2227 tscrolldown(term
.top
, 1);
2229 tmoveto(term
.c
.x
, term
.c
.y
-1);
2232 case 'Z': /* DECID -- Identify Terminal */
2233 ttywrite(vtiden
, strlen(vtiden
), 0);
2235 case 'c': /* RIS -- Reset to inital state */
2240 case '=': /* DECPAM -- Application keypad */
2241 xsetmode(1, MODE_APPKEYPAD
);
2243 case '>': /* DECPNM -- Normal keypad */
2244 xsetmode(0, MODE_APPKEYPAD
);
2246 case '7': /* DECSC -- Save Cursor */
2247 tcursor(CURSOR_SAVE
);
2249 case '8': /* DECRC -- Restore Cursor */
2250 tcursor(CURSOR_LOAD
);
2252 case '\\': /* ST -- String Terminator */
2253 if (term
.esc
& ESC_STR_END
)
2257 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2258 (uchar
) ascii
, isprint(ascii
)? ascii
:'.');
2272 control
= ISCONTROL(u
);
2273 if (!IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2277 len
= utf8encode(u
, c
);
2278 if (!control
&& (width
= wcwidth(u
)) == -1) {
2279 memcpy(c
, "\357\277\275", 4); /* UTF_INVALID */
2284 if (IS_SET(MODE_PRINT
))
2288 * STR sequence must be checked before anything else
2289 * because it uses all following characters until it
2290 * receives a ESC, a SUB, a ST or any other C1 control
2293 if (term
.esc
& ESC_STR
) {
2294 if (u
== '\a' || u
== 030 || u
== 032 || u
== 033 ||
2296 term
.esc
&= ~(ESC_START
|ESC_STR
|ESC_DCS
);
2297 if (IS_SET(MODE_SIXEL
)) {
2298 /* TODO: render sixel */;
2299 term
.mode
&= ~MODE_SIXEL
;
2302 term
.esc
|= ESC_STR_END
;
2303 goto check_control_code
;
2307 if (IS_SET(MODE_SIXEL
)) {
2308 /* TODO: implement sixel mode */
2311 if (term
.esc
&ESC_DCS
&& strescseq
.len
== 0 && u
== 'q')
2312 term
.mode
|= MODE_SIXEL
;
2314 if (strescseq
.len
+len
>= sizeof(strescseq
.buf
)-1) {
2316 * Here is a bug in terminals. If the user never sends
2317 * some code to stop the str or esc command, then st
2318 * will stop responding. But this is better than
2319 * silently failing with unknown characters. At least
2320 * then users will report back.
2322 * In the case users ever get fixed, here is the code:
2331 memmove(&strescseq
.buf
[strescseq
.len
], c
, len
);
2332 strescseq
.len
+= len
;
2338 * Actions of control codes must be performed as soon they arrive
2339 * because they can be embedded inside a control sequence, and
2340 * they must not cause conflicts with sequences.
2345 * control codes are not shown ever
2348 } else if (term
.esc
& ESC_START
) {
2349 if (term
.esc
& ESC_CSI
) {
2350 csiescseq
.buf
[csiescseq
.len
++] = u
;
2351 if (BETWEEN(u
, 0x40, 0x7E)
2352 || csiescseq
.len
>= \
2353 sizeof(csiescseq
.buf
)-1) {
2359 } else if (term
.esc
& ESC_UTF8
) {
2361 } else if (term
.esc
& ESC_ALTCHARSET
) {
2363 } else if (term
.esc
& ESC_TEST
) {
2368 /* sequence already finished */
2372 * All characters which form part of a sequence are not
2377 if (sel
.ob
.x
!= -1 && BETWEEN(term
.c
.y
, sel
.ob
.y
, sel
.oe
.y
))
2380 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2381 if (IS_SET(MODE_WRAP
) && (term
.c
.state
& CURSOR_WRAPNEXT
)) {
2382 gp
->mode
|= ATTR_WRAP
;
2384 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2387 if (IS_SET(MODE_INSERT
) && term
.c
.x
+width
< term
.col
)
2388 memmove(gp
+width
, gp
, (term
.col
- term
.c
.x
- width
) * sizeof(Glyph
));
2390 if (term
.c
.x
+width
> term
.col
) {
2392 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2395 tsetchar(u
, &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2398 gp
->mode
|= ATTR_WIDE
;
2399 if (term
.c
.x
+1 < term
.col
) {
2401 gp
[1].mode
= ATTR_WDUMMY
;
2404 if (term
.c
.x
+width
< term
.col
) {
2405 tmoveto(term
.c
.x
+width
, term
.c
.y
);
2407 term
.c
.state
|= CURSOR_WRAPNEXT
;
2412 twrite(const char *buf
, int buflen
, int show_ctrl
)
2418 for (n
= 0; n
< buflen
; n
+= charsize
) {
2419 if (IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2420 /* process a complete utf8 char */
2421 charsize
= utf8decode(buf
+ n
, &u
, buflen
- n
);
2428 if (show_ctrl
&& ISCONTROL(u
)) {
2433 } else if (u
!= '\n' && u
!= '\r' && u
!= '\t') {
2444 tresize(int col
, int row
)
2447 int minrow
= MIN(row
, term
.row
);
2448 int mincol
= MIN(col
, term
.col
);
2452 if (col
< 1 || row
< 1) {
2454 "tresize: error resizing to %dx%d\n", col
, row
);
2459 * slide screen to keep cursor where we expect it -
2460 * tscrollup would work here, but we can optimize to
2461 * memmove because we're freeing the earlier lines
2463 for (i
= 0; i
<= term
.c
.y
- row
; i
++) {
2467 /* ensure that both src and dst are not NULL */
2469 memmove(term
.line
, term
.line
+ i
, row
* sizeof(Line
));
2470 memmove(term
.alt
, term
.alt
+ i
, row
* sizeof(Line
));
2472 for (i
+= row
; i
< term
.row
; i
++) {
2477 /* resize to new height */
2478 term
.line
= xrealloc(term
.line
, row
* sizeof(Line
));
2479 term
.alt
= xrealloc(term
.alt
, row
* sizeof(Line
));
2480 term
.dirty
= xrealloc(term
.dirty
, row
* sizeof(*term
.dirty
));
2481 term
.tabs
= xrealloc(term
.tabs
, col
* sizeof(*term
.tabs
));
2483 /* resize each row to new width, zero-pad if needed */
2484 for (i
= 0; i
< minrow
; i
++) {
2485 term
.line
[i
] = xrealloc(term
.line
[i
], col
* sizeof(Glyph
));
2486 term
.alt
[i
] = xrealloc(term
.alt
[i
], col
* sizeof(Glyph
));
2489 /* allocate any new rows */
2490 for (/* i = minrow */; i
< row
; i
++) {
2491 term
.line
[i
] = xmalloc(col
* sizeof(Glyph
));
2492 term
.alt
[i
] = xmalloc(col
* sizeof(Glyph
));
2494 if (col
> term
.col
) {
2495 bp
= term
.tabs
+ term
.col
;
2497 memset(bp
, 0, sizeof(*term
.tabs
) * (col
- term
.col
));
2498 while (--bp
> term
.tabs
&& !*bp
)
2500 for (bp
+= tabspaces
; bp
< term
.tabs
+ col
; bp
+= tabspaces
)
2503 /* update terminal size */
2506 /* reset scrolling region */
2507 tsetscroll(0, row
-1);
2508 /* make use of the LIMIT in tmoveto */
2509 tmoveto(term
.c
.x
, term
.c
.y
);
2510 /* Clearing both screens (it makes dirty all lines) */
2512 for (i
= 0; i
< 2; i
++) {
2513 if (mincol
< col
&& 0 < minrow
) {
2514 tclearregion(mincol
, 0, col
- 1, minrow
- 1);
2516 if (0 < col
&& minrow
< row
) {
2517 tclearregion(0, minrow
, col
- 1, row
- 1);
2520 tcursor(CURSOR_LOAD
);
2532 drawregion(int x1
, int y1
, int x2
, int y2
)
2535 for (y
= y1
; y
< y2
; y
++) {
2540 xdrawline(term
.line
[y
], x1
, y
, x2
);
2549 drawregion(0, 0, term
.col
, term
.row
);