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 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);
111 static void ttywriteraw(const char *, size_t);
113 static void csidump(void);
114 static void csihandle(void);
115 static void csiparse(void);
116 static void csireset(void);
117 static int eschandle(uchar
);
118 static void strdump(void);
119 static void strhandle(void);
120 static void strparse(void);
121 static void strreset(void);
123 static void tprinter(char *, size_t);
124 static void tdumpsel(void);
125 static void tdumpline(int);
126 static void tdump(void);
127 static void tclearregion(int, int, int, int);
128 static void tcursor(int);
129 static void tdeletechar(int);
130 static void tdeleteline(int);
131 static void tinsertblank(int);
132 static void tinsertblankline(int);
133 static int tlinelen(int);
134 static void tmoveto(int, int);
135 static void tmoveato(int, int);
136 static void tnewline(int);
137 static void tputtab(int);
138 static void tputc(Rune
);
139 static void treset(void);
140 static void tscrollup(int, int);
141 static void tscrolldown(int, int);
142 static void tsetattr(int *, int);
143 static void tsetchar(Rune
, Glyph
*, int, int);
144 static void tsetdirt(int, int);
145 static void tsetscroll(int, int);
146 static void tswapscreen(void);
147 static void tsetmode(int, int, int *, int);
148 static int twrite(const char *, int, int);
149 static void tfulldirt(void);
150 static void tcontrolcode(uchar
);
151 static void tdectest(char );
152 static void tdefutf8(char);
153 static int32_t tdefcolor(int *, int *, int);
154 static void tdeftran(char);
155 static void tstrsequence(uchar
);
157 static void selscroll(int, int);
158 static void selsnap(int *, int *, int);
160 static Rune
utf8decodebyte(char, size_t *);
161 static char utf8encodebyte(Rune
, size_t);
162 static char *utf8strchr(char *s
, Rune u
);
163 static size_t utf8validate(Rune
*, size_t);
165 static char *base64dec(const char *);
167 static ssize_t
xwrite(int, const char *, size_t);
173 int oldbutton
= 3; /* button event on startup: 3 = release */
175 static Selection sel
;
176 static CSIEscape csiescseq
;
177 static STREscape strescseq
;
180 static uchar utfbyte
[UTF_SIZ
+ 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
181 static uchar utfmask
[UTF_SIZ
+ 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
182 static Rune utfmin
[UTF_SIZ
+ 1] = { 0, 0, 0x80, 0x800, 0x10000};
183 static Rune utfmax
[UTF_SIZ
+ 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
186 xwrite(int fd
, const char *s
, size_t len
)
192 r
= write(fd
, s
, len
);
205 void *p
= malloc(len
);
208 die("Out of memory\n");
214 xrealloc(void *p
, size_t len
)
216 if ((p
= realloc(p
, len
)) == NULL
)
217 die("Out of memory\n");
225 if ((s
= strdup(s
)) == NULL
)
226 die("Out of memory\n");
232 utf8decode(const char *c
, Rune
*u
, size_t clen
)
234 size_t i
, j
, len
, type
;
240 udecoded
= utf8decodebyte(c
[0], &len
);
241 if (!BETWEEN(len
, 1, UTF_SIZ
))
243 for (i
= 1, j
= 1; i
< clen
&& j
< len
; ++i
, ++j
) {
244 udecoded
= (udecoded
<< 6) | utf8decodebyte(c
[i
], &type
);
251 utf8validate(u
, len
);
257 utf8decodebyte(char c
, size_t *i
)
259 for (*i
= 0; *i
< LEN(utfmask
); ++(*i
))
260 if (((uchar
)c
& utfmask
[*i
]) == utfbyte
[*i
])
261 return (uchar
)c
& ~utfmask
[*i
];
267 utf8encode(Rune u
, char *c
)
271 len
= utf8validate(&u
, 0);
275 for (i
= len
- 1; i
!= 0; --i
) {
276 c
[i
] = utf8encodebyte(u
, 0);
279 c
[0] = utf8encodebyte(u
, len
);
285 utf8encodebyte(Rune u
, size_t i
)
287 return utfbyte
[i
] | (u
& ~utfmask
[i
]);
291 utf8strchr(char *s
, Rune u
)
297 for (i
= 0, j
= 0; i
< len
; i
+= j
) {
298 if (!(j
= utf8decode(&s
[i
], &r
, len
- i
)))
308 utf8validate(Rune
*u
, size_t i
)
310 if (!BETWEEN(*u
, utfmin
[i
], utfmax
[i
]) || BETWEEN(*u
, 0xD800, 0xDFFF))
312 for (i
= 1; *u
> utfmax
[i
]; ++i
)
318 static const char base64_digits
[] = {
319 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
320 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0,
321 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
322 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
323 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
324 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
330 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
334 base64dec_getc(const char **src
)
336 while (**src
&& !isprint(**src
)) (*src
)++;
341 base64dec(const char *src
)
343 size_t in_len
= strlen(src
);
347 in_len
+= 4 - (in_len
% 4);
348 result
= dst
= xmalloc(in_len
/ 4 * 3 + 1);
350 int a
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
351 int b
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
352 int c
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
353 int d
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
355 *dst
++ = (a
<< 2) | ((b
& 0x30) >> 4);
358 *dst
++ = ((b
& 0x0f) << 4) | ((c
& 0x3c) >> 2);
361 *dst
++ = ((c
& 0x03) << 6) | d
;
380 if (term
.line
[y
][i
- 1].mode
& ATTR_WRAP
)
383 while (i
> 0 && term
.line
[y
][i
- 1].u
== ' ')
390 selstart(int col
, int row
, int snap
)
393 sel
.mode
= SEL_EMPTY
;
394 sel
.type
= SEL_REGULAR
;
396 sel
.oe
.x
= sel
.ob
.x
= col
;
397 sel
.oe
.y
= sel
.ob
.y
= row
;
401 sel
.mode
= SEL_READY
;
402 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
406 selextend(int col
, int row
, int type
, int done
)
408 int oldey
, oldex
, oldsby
, oldsey
, oldtype
;
412 if (done
&& sel
.mode
== SEL_EMPTY
) {
423 sel
.alt
= IS_SET(MODE_ALTSCREEN
);
429 if (oldey
!= sel
.oe
.y
|| oldex
!= sel
.oe
.x
|| oldtype
!= sel
.type
)
430 tsetdirt(MIN(sel
.nb
.y
, oldsby
), MAX(sel
.ne
.y
, oldsey
));
432 sel
.mode
= done
? SEL_IDLE
: SEL_READY
;
440 if (sel
.type
== SEL_REGULAR
&& sel
.ob
.y
!= sel
.oe
.y
) {
441 sel
.nb
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.ob
.x
: sel
.oe
.x
;
442 sel
.ne
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.oe
.x
: sel
.ob
.x
;
444 sel
.nb
.x
= MIN(sel
.ob
.x
, sel
.oe
.x
);
445 sel
.ne
.x
= MAX(sel
.ob
.x
, sel
.oe
.x
);
447 sel
.nb
.y
= MIN(sel
.ob
.y
, sel
.oe
.y
);
448 sel
.ne
.y
= MAX(sel
.ob
.y
, sel
.oe
.y
);
450 selsnap(&sel
.nb
.x
, &sel
.nb
.y
, -1);
451 selsnap(&sel
.ne
.x
, &sel
.ne
.y
, +1);
453 /* expand selection over line breaks */
454 if (sel
.type
== SEL_RECTANGULAR
)
456 i
= tlinelen(sel
.nb
.y
);
459 if (tlinelen(sel
.ne
.y
) <= sel
.ne
.x
)
460 sel
.ne
.x
= term
.col
- 1;
464 selected(int x
, int y
)
466 if (sel
.mode
== SEL_EMPTY
|| sel
.ob
.x
== -1 ||
467 sel
.alt
!= IS_SET(MODE_ALTSCREEN
))
470 if (sel
.type
== SEL_RECTANGULAR
)
471 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
472 && BETWEEN(x
, sel
.nb
.x
, sel
.ne
.x
);
474 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
475 && (y
!= sel
.nb
.y
|| x
>= sel
.nb
.x
)
476 && (y
!= sel
.ne
.y
|| x
<= sel
.ne
.x
);
480 selsnap(int *x
, int *y
, int direction
)
482 int newx
, newy
, xt
, yt
;
483 int delim
, prevdelim
;
489 * Snap around if the word wraps around at the end or
490 * beginning of a line.
492 prevgp
= &term
.line
[*y
][*x
];
493 prevdelim
= ISDELIM(prevgp
->u
);
495 newx
= *x
+ direction
;
497 if (!BETWEEN(newx
, 0, term
.col
- 1)) {
499 newx
= (newx
+ term
.col
) % term
.col
;
500 if (!BETWEEN(newy
, 0, term
.row
- 1))
506 yt
= newy
, xt
= newx
;
507 if (!(term
.line
[yt
][xt
].mode
& ATTR_WRAP
))
511 if (newx
>= tlinelen(newy
))
514 gp
= &term
.line
[newy
][newx
];
515 delim
= ISDELIM(gp
->u
);
516 if (!(gp
->mode
& ATTR_WDUMMY
) && (delim
!= prevdelim
517 || (delim
&& gp
->u
!= prevgp
->u
)))
528 * Snap around if the the previous line or the current one
529 * has set ATTR_WRAP at its end. Then the whole next or
530 * previous line will be selected.
532 *x
= (direction
< 0) ? 0 : term
.col
- 1;
534 for (; *y
> 0; *y
+= direction
) {
535 if (!(term
.line
[*y
-1][term
.col
-1].mode
540 } else if (direction
> 0) {
541 for (; *y
< term
.row
-1; *y
+= direction
) {
542 if (!(term
.line
[*y
][term
.col
-1].mode
556 int y
, bufsize
, lastx
, linelen
;
562 bufsize
= (term
.col
+1) * (sel
.ne
.y
-sel
.nb
.y
+1) * UTF_SIZ
;
563 ptr
= str
= xmalloc(bufsize
);
565 /* append every set & selected glyph to the selection */
566 for (y
= sel
.nb
.y
; y
<= sel
.ne
.y
; y
++) {
567 if ((linelen
= tlinelen(y
)) == 0) {
572 if (sel
.type
== SEL_RECTANGULAR
) {
573 gp
= &term
.line
[y
][sel
.nb
.x
];
576 gp
= &term
.line
[y
][sel
.nb
.y
== y
? sel
.nb
.x
: 0];
577 lastx
= (sel
.ne
.y
== y
) ? sel
.ne
.x
: term
.col
-1;
579 last
= &term
.line
[y
][MIN(lastx
, linelen
-1)];
580 while (last
>= gp
&& last
->u
== ' ')
583 for ( ; gp
<= last
; ++gp
) {
584 if (gp
->mode
& ATTR_WDUMMY
)
587 ptr
+= utf8encode(gp
->u
, ptr
);
591 * Copy and pasting of line endings is inconsistent
592 * in the inconsistent terminal and GUI world.
593 * The best solution seems like to produce '\n' when
594 * something is copied from st and convert '\n' to
595 * '\r', when something to be pasted is received by
597 * FIXME: Fix the computer world.
599 if ((y
< sel
.ne
.y
|| lastx
>= linelen
) && !(last
->mode
& ATTR_WRAP
))
613 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
617 die(const char *errstr
, ...)
621 va_start(ap
, errstr
);
622 vfprintf(stderr
, errstr
, ap
);
631 const struct passwd
*pw
;
634 if ((pw
= getpwuid(getuid())) == NULL
) {
636 die("getpwuid:%s\n", strerror(errno
));
638 die("who are you?\n");
641 if ((sh
= getenv("SHELL")) == NULL
)
642 sh
= (pw
->pw_shell
[0]) ? pw
->pw_shell
: shell
;
650 DEFAULT(args
, ((char *[]) {prog
, NULL
}));
655 setenv("LOGNAME", pw
->pw_name
, 1);
656 setenv("USER", pw
->pw_name
, 1);
657 setenv("SHELL", sh
, 1);
658 setenv("HOME", pw
->pw_dir
, 1);
659 setenv("TERM", termname
, 1);
661 signal(SIGCHLD
, SIG_DFL
);
662 signal(SIGHUP
, SIG_DFL
);
663 signal(SIGINT
, SIG_DFL
);
664 signal(SIGQUIT
, SIG_DFL
);
665 signal(SIGTERM
, SIG_DFL
);
666 signal(SIGALRM
, SIG_DFL
);
678 if ((p
= waitpid(pid
, &stat
, WNOHANG
)) < 0)
679 die("Waiting for pid %hd failed: %s\n", pid
, strerror(errno
));
684 if (!WIFEXITED(stat
) || WEXITSTATUS(stat
))
685 die("child finished with error '%d'\n", stat
);
693 char cmd
[_POSIX_ARG_MAX
], **p
, *q
, *s
;
696 if ((n
= strlen(stty_args
)) > sizeof(cmd
)-1)
697 die("incorrect stty parameters\n");
698 memcpy(cmd
, stty_args
, n
);
700 siz
= sizeof(cmd
) - n
;
701 for (p
= args
; p
&& (s
= *p
); ++p
) {
702 if ((n
= strlen(s
)) > siz
-1)
703 die("stty parameter length too long\n");
710 if (system(cmd
) != 0)
711 perror("Couldn't call stty");
715 ttynew(char *line
, char *out
, char **args
)
720 term
.mode
|= MODE_PRINT
;
721 iofd
= (!strcmp(out
, "-")) ?
722 1 : open(out
, O_WRONLY
| O_CREAT
, 0666);
724 fprintf(stderr
, "Error opening %s:%s\n",
725 out
, strerror(errno
));
730 if ((cmdfd
= open(line
, O_RDWR
)) < 0)
731 die("open line failed: %s\n", strerror(errno
));
737 /* seems to work fine on linux, openbsd and freebsd */
738 if (openpty(&m
, &s
, NULL
, NULL
, NULL
) < 0)
739 die("openpty failed: %s\n", strerror(errno
));
741 switch (pid
= fork()) {
743 die("fork failed\n");
747 setsid(); /* create a new process group */
751 if (ioctl(s
, TIOCSCTTY
, NULL
) < 0)
752 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno
));
760 signal(SIGCHLD
, sigchld
);
768 static char buf
[BUFSIZ
];
769 static int buflen
= 0;
773 /* append read bytes to unprocessed bytes */
774 if ((ret
= read(cmdfd
, buf
+buflen
, LEN(buf
)-buflen
)) < 0)
775 die("Couldn't read from shell: %s\n", strerror(errno
));
778 written
= twrite(buf
, buflen
, 0);
780 /* keep any uncomplete utf8 char for the next call */
782 memmove(buf
, buf
+ written
, buflen
);
788 ttywrite(const char *s
, size_t n
, int may_echo
)
792 if (may_echo
&& IS_SET(MODE_ECHO
))
795 if (!IS_SET(MODE_CRLF
)) {
800 /* This is similar to how the kernel handles ONLCR for ttys */
804 ttywriteraw("\r\n", 2);
806 next
= memchr(s
, '\r', n
);
807 DEFAULT(next
, s
+ n
);
808 ttywriteraw(s
, next
- s
);
816 ttywriteraw(const char *s
, size_t n
)
823 * Remember that we are using a pty, which might be a modem line.
824 * Writing too much will clog the line. That's why we are doing this
826 * FIXME: Migrate the world to Plan 9.
834 /* Check if we can write. */
835 if (pselect(cmdfd
+1, &rfd
, &wfd
, NULL
, NULL
, NULL
) < 0) {
838 die("select failed: %s\n", strerror(errno
));
840 if (FD_ISSET(cmdfd
, &wfd
)) {
842 * Only write the bytes written by ttywrite() or the
843 * default of 256. This seems to be a reasonable value
844 * for a serial line. Bigger values might clog the I/O.
846 if ((r
= write(cmdfd
, s
, (n
< lim
)? n
: lim
)) < 0)
850 * We weren't able to write out everything.
851 * This means the buffer is getting full
859 /* All bytes have been written. */
863 if (FD_ISSET(cmdfd
, &rfd
))
869 die("write error on tty: %s\n", strerror(errno
));
873 ttyresize(int tw
, int th
)
881 if (ioctl(cmdfd
, TIOCSWINSZ
, &w
) < 0)
882 fprintf(stderr
, "Couldn't set window size: %s\n", strerror(errno
));
890 for (i
= 0; i
< term
.row
-1; i
++) {
891 for (j
= 0; j
< term
.col
-1; j
++) {
892 if (term
.line
[i
][j
].mode
& attr
)
901 tsetdirt(int top
, int bot
)
905 LIMIT(top
, 0, term
.row
-1);
906 LIMIT(bot
, 0, term
.row
-1);
908 for (i
= top
; i
<= bot
; i
++)
913 tsetdirtattr(int attr
)
917 for (i
= 0; i
< term
.row
-1; i
++) {
918 for (j
= 0; j
< term
.col
-1; j
++) {
919 if (term
.line
[i
][j
].mode
& attr
) {
930 tsetdirt(0, term
.row
-1);
937 int alt
= IS_SET(MODE_ALTSCREEN
);
939 if (mode
== CURSOR_SAVE
) {
941 } else if (mode
== CURSOR_LOAD
) {
943 tmoveto(c
[alt
].x
, c
[alt
].y
);
956 }, .x
= 0, .y
= 0, .state
= CURSOR_DEFAULT
};
958 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
959 for (i
= tabspaces
; i
< term
.col
; i
+= tabspaces
)
962 term
.bot
= term
.row
- 1;
963 term
.mode
= MODE_WRAP
|MODE_UTF8
;
964 memset(term
.trantbl
, CS_USA
, sizeof(term
.trantbl
));
967 for (i
= 0; i
< 2; i
++) {
969 tcursor(CURSOR_SAVE
);
970 tclearregion(0, 0, term
.col
-1, term
.row
-1);
976 tnew(int col
, int row
)
978 term
= (Term
){ .c
= { .attr
= { .fg
= defaultfg
, .bg
= defaultbg
} } };
988 Line
*tmp
= term
.line
;
990 term
.line
= term
.alt
;
992 term
.mode
^= MODE_ALTSCREEN
;
997 tscrolldown(int orig
, int n
)
1002 LIMIT(n
, 0, term
.bot
-orig
+1);
1004 tsetdirt(orig
, term
.bot
-n
);
1005 tclearregion(0, term
.bot
-n
+1, term
.col
-1, term
.bot
);
1007 for (i
= term
.bot
; i
>= orig
+n
; i
--) {
1008 temp
= term
.line
[i
];
1009 term
.line
[i
] = term
.line
[i
-n
];
1010 term
.line
[i
-n
] = temp
;
1017 tscrollup(int orig
, int n
)
1022 LIMIT(n
, 0, term
.bot
-orig
+1);
1024 tclearregion(0, orig
, term
.col
-1, orig
+n
-1);
1025 tsetdirt(orig
+n
, term
.bot
);
1027 for (i
= orig
; i
<= term
.bot
-n
; i
++) {
1028 temp
= term
.line
[i
];
1029 term
.line
[i
] = term
.line
[i
+n
];
1030 term
.line
[i
+n
] = temp
;
1033 selscroll(orig
, -n
);
1037 selscroll(int orig
, int n
)
1042 if (BETWEEN(sel
.ob
.y
, orig
, term
.bot
) || BETWEEN(sel
.oe
.y
, orig
, term
.bot
)) {
1043 if ((sel
.ob
.y
+= n
) > term
.bot
|| (sel
.oe
.y
+= n
) < term
.top
) {
1047 if (sel
.type
== SEL_RECTANGULAR
) {
1048 if (sel
.ob
.y
< term
.top
)
1049 sel
.ob
.y
= term
.top
;
1050 if (sel
.oe
.y
> term
.bot
)
1051 sel
.oe
.y
= term
.bot
;
1053 if (sel
.ob
.y
< term
.top
) {
1054 sel
.ob
.y
= term
.top
;
1057 if (sel
.oe
.y
> term
.bot
) {
1058 sel
.oe
.y
= term
.bot
;
1059 sel
.oe
.x
= term
.col
;
1067 tnewline(int first_col
)
1071 if (y
== term
.bot
) {
1072 tscrollup(term
.top
, 1);
1076 tmoveto(first_col
? 0 : term
.c
.x
, y
);
1082 char *p
= csiescseq
.buf
, *np
;
1091 csiescseq
.buf
[csiescseq
.len
] = '\0';
1092 while (p
< csiescseq
.buf
+csiescseq
.len
) {
1094 v
= strtol(p
, &np
, 10);
1097 if (v
== LONG_MAX
|| v
== LONG_MIN
)
1099 csiescseq
.arg
[csiescseq
.narg
++] = v
;
1101 if (*p
!= ';' || csiescseq
.narg
== ESC_ARG_SIZ
)
1105 csiescseq
.mode
[0] = *p
++;
1106 csiescseq
.mode
[1] = (p
< csiescseq
.buf
+csiescseq
.len
) ? *p
: '\0';
1109 /* for absolute user moves, when decom is set */
1111 tmoveato(int x
, int y
)
1113 tmoveto(x
, y
+ ((term
.c
.state
& CURSOR_ORIGIN
) ? term
.top
: 0));
1117 tmoveto(int x
, int y
)
1121 if (term
.c
.state
& CURSOR_ORIGIN
) {
1126 maxy
= term
.row
- 1;
1128 term
.c
.state
&= ~CURSOR_WRAPNEXT
;
1129 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1130 term
.c
.y
= LIMIT(y
, miny
, maxy
);
1134 tsetchar(Rune u
, Glyph
*attr
, int x
, int y
)
1136 static char *vt100_0
[62] = { /* 0x41 - 0x7e */
1137 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1138 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1139 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1140 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1141 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1142 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1143 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1144 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1148 * The table is proudly stolen from rxvt.
1150 if (term
.trantbl
[term
.charset
] == CS_GRAPHIC0
&&
1151 BETWEEN(u
, 0x41, 0x7e) && vt100_0
[u
- 0x41])
1152 utf8decode(vt100_0
[u
- 0x41], &u
, UTF_SIZ
);
1154 if (term
.line
[y
][x
].mode
& ATTR_WIDE
) {
1155 if (x
+1 < term
.col
) {
1156 term
.line
[y
][x
+1].u
= ' ';
1157 term
.line
[y
][x
+1].mode
&= ~ATTR_WDUMMY
;
1159 } else if (term
.line
[y
][x
].mode
& ATTR_WDUMMY
) {
1160 term
.line
[y
][x
-1].u
= ' ';
1161 term
.line
[y
][x
-1].mode
&= ~ATTR_WIDE
;
1165 term
.line
[y
][x
] = *attr
;
1166 term
.line
[y
][x
].u
= u
;
1170 tclearregion(int x1
, int y1
, int x2
, int y2
)
1176 temp
= x1
, x1
= x2
, x2
= temp
;
1178 temp
= y1
, y1
= y2
, y2
= temp
;
1180 LIMIT(x1
, 0, term
.col
-1);
1181 LIMIT(x2
, 0, term
.col
-1);
1182 LIMIT(y1
, 0, term
.row
-1);
1183 LIMIT(y2
, 0, term
.row
-1);
1185 for (y
= y1
; y
<= y2
; y
++) {
1187 for (x
= x1
; x
<= x2
; x
++) {
1188 gp
= &term
.line
[y
][x
];
1191 gp
->fg
= term
.c
.attr
.fg
;
1192 gp
->bg
= term
.c
.attr
.bg
;
1205 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1209 size
= term
.col
- src
;
1210 line
= term
.line
[term
.c
.y
];
1212 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1213 tclearregion(term
.col
-n
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1222 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1226 size
= term
.col
- dst
;
1227 line
= term
.line
[term
.c
.y
];
1229 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1230 tclearregion(src
, term
.c
.y
, dst
- 1, term
.c
.y
);
1234 tinsertblankline(int n
)
1236 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1237 tscrolldown(term
.c
.y
, n
);
1243 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1244 tscrollup(term
.c
.y
, n
);
1248 tdefcolor(int *attr
, int *npar
, int l
)
1253 switch (attr
[*npar
+ 1]) {
1254 case 2: /* direct color in RGB space */
1255 if (*npar
+ 4 >= l
) {
1257 "erresc(38): Incorrect number of parameters (%d)\n",
1261 r
= attr
[*npar
+ 2];
1262 g
= attr
[*npar
+ 3];
1263 b
= attr
[*npar
+ 4];
1265 if (!BETWEEN(r
, 0, 255) || !BETWEEN(g
, 0, 255) || !BETWEEN(b
, 0, 255))
1266 fprintf(stderr
, "erresc: bad rgb color (%u,%u,%u)\n",
1269 idx
= TRUECOLOR(r
, g
, b
);
1271 case 5: /* indexed color */
1272 if (*npar
+ 2 >= l
) {
1274 "erresc(38): Incorrect number of parameters (%d)\n",
1279 if (!BETWEEN(attr
[*npar
], 0, 255))
1280 fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[*npar
]);
1284 case 0: /* implemented defined (only foreground) */
1285 case 1: /* transparent */
1286 case 3: /* direct color in CMY space */
1287 case 4: /* direct color in CMYK space */
1290 "erresc(38): gfx attr %d unknown\n", attr
[*npar
]);
1298 tsetattr(int *attr
, int l
)
1303 for (i
= 0; i
< l
; i
++) {
1306 term
.c
.attr
.mode
&= ~(
1315 term
.c
.attr
.fg
= defaultfg
;
1316 term
.c
.attr
.bg
= defaultbg
;
1319 term
.c
.attr
.mode
|= ATTR_BOLD
;
1322 term
.c
.attr
.mode
|= ATTR_FAINT
;
1325 term
.c
.attr
.mode
|= ATTR_ITALIC
;
1328 term
.c
.attr
.mode
|= ATTR_UNDERLINE
;
1330 case 5: /* slow blink */
1332 case 6: /* rapid blink */
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 term
.c
.attr
.mode
&= ~(ATTR_BOLD
| ATTR_FAINT
);
1348 term
.c
.attr
.mode
&= ~ATTR_ITALIC
;
1351 term
.c
.attr
.mode
&= ~ATTR_UNDERLINE
;
1354 term
.c
.attr
.mode
&= ~ATTR_BLINK
;
1357 term
.c
.attr
.mode
&= ~ATTR_REVERSE
;
1360 term
.c
.attr
.mode
&= ~ATTR_INVISIBLE
;
1363 term
.c
.attr
.mode
&= ~ATTR_STRUCK
;
1366 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1367 term
.c
.attr
.fg
= idx
;
1370 term
.c
.attr
.fg
= defaultfg
;
1373 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1374 term
.c
.attr
.bg
= idx
;
1377 term
.c
.attr
.bg
= defaultbg
;
1380 if (BETWEEN(attr
[i
], 30, 37)) {
1381 term
.c
.attr
.fg
= attr
[i
] - 30;
1382 } else if (BETWEEN(attr
[i
], 40, 47)) {
1383 term
.c
.attr
.bg
= attr
[i
] - 40;
1384 } else if (BETWEEN(attr
[i
], 90, 97)) {
1385 term
.c
.attr
.fg
= attr
[i
] - 90 + 8;
1386 } else if (BETWEEN(attr
[i
], 100, 107)) {
1387 term
.c
.attr
.bg
= attr
[i
] - 100 + 8;
1390 "erresc(default): gfx attr %d unknown\n",
1391 attr
[i
]), csidump();
1399 tsetscroll(int t
, int b
)
1403 LIMIT(t
, 0, term
.row
-1);
1404 LIMIT(b
, 0, term
.row
-1);
1415 tsetmode(int priv
, int set
, int *args
, int narg
)
1420 for (lim
= args
+ narg
; args
< lim
; ++args
) {
1423 case 1: /* DECCKM -- Cursor key */
1424 MODBIT(term
.mode
, set
, MODE_APPCURSOR
);
1426 case 5: /* DECSCNM -- Reverse video */
1428 MODBIT(term
.mode
, set
, MODE_REVERSE
);
1429 if (mode
!= term
.mode
)
1432 case 6: /* DECOM -- Origin */
1433 MODBIT(term
.c
.state
, set
, CURSOR_ORIGIN
);
1436 case 7: /* DECAWM -- Auto wrap */
1437 MODBIT(term
.mode
, set
, MODE_WRAP
);
1439 case 0: /* Error (IGNORED) */
1440 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1441 case 3: /* DECCOLM -- Column (IGNORED) */
1442 case 4: /* DECSCLM -- Scroll (IGNORED) */
1443 case 8: /* DECARM -- Auto repeat (IGNORED) */
1444 case 18: /* DECPFF -- Printer feed (IGNORED) */
1445 case 19: /* DECPEX -- Printer extent (IGNORED) */
1446 case 42: /* DECNRCM -- National characters (IGNORED) */
1447 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1449 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1450 MODBIT(term
.mode
, !set
, MODE_HIDE
);
1452 case 9: /* X10 mouse compatibility mode */
1453 xsetpointermotion(0);
1454 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1455 MODBIT(term
.mode
, set
, MODE_MOUSEX10
);
1457 case 1000: /* 1000: report button press */
1458 xsetpointermotion(0);
1459 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1460 MODBIT(term
.mode
, set
, MODE_MOUSEBTN
);
1462 case 1002: /* 1002: report motion on button press */
1463 xsetpointermotion(0);
1464 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1465 MODBIT(term
.mode
, set
, MODE_MOUSEMOTION
);
1467 case 1003: /* 1003: enable all mouse motions */
1468 xsetpointermotion(set
);
1469 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1470 MODBIT(term
.mode
, set
, MODE_MOUSEMANY
);
1472 case 1004: /* 1004: send focus events to tty */
1473 MODBIT(term
.mode
, set
, MODE_FOCUS
);
1475 case 1006: /* 1006: extended reporting mode */
1476 MODBIT(term
.mode
, set
, MODE_MOUSESGR
);
1479 MODBIT(term
.mode
, set
, MODE_8BIT
);
1481 case 1049: /* swap screen & set/restore cursor as xterm */
1482 if (!allowaltscreen
)
1484 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1486 case 47: /* swap screen */
1488 if (!allowaltscreen
)
1490 alt
= IS_SET(MODE_ALTSCREEN
);
1492 tclearregion(0, 0, term
.col
-1,
1495 if (set
^ alt
) /* set is always 1 or 0 */
1501 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1503 case 2004: /* 2004: bracketed paste mode */
1504 MODBIT(term
.mode
, set
, MODE_BRCKTPASTE
);
1506 /* Not implemented mouse modes. See comments there. */
1507 case 1001: /* mouse highlight mode; can hang the
1508 terminal by design when implemented. */
1509 case 1005: /* UTF-8 mouse mode; will confuse
1510 applications not supporting UTF-8
1512 case 1015: /* urxvt mangled mouse mode; incompatible
1513 and can be mistaken for other control
1517 "erresc: unknown private set/reset mode %d\n",
1523 case 0: /* Error (IGNORED) */
1525 case 2: /* KAM -- keyboard action */
1526 MODBIT(term
.mode
, set
, MODE_KBDLOCK
);
1528 case 4: /* IRM -- Insertion-replacement */
1529 MODBIT(term
.mode
, set
, MODE_INSERT
);
1531 case 12: /* SRM -- Send/Receive */
1532 MODBIT(term
.mode
, !set
, MODE_ECHO
);
1534 case 20: /* LNM -- Linefeed/new line */
1535 MODBIT(term
.mode
, set
, MODE_CRLF
);
1539 "erresc: unknown set/reset mode %d\n",
1553 switch (csiescseq
.mode
[0]) {
1556 fprintf(stderr
, "erresc: unknown csi ");
1560 case '@': /* ICH -- Insert <n> blank char */
1561 DEFAULT(csiescseq
.arg
[0], 1);
1562 tinsertblank(csiescseq
.arg
[0]);
1564 case 'A': /* CUU -- Cursor <n> Up */
1565 DEFAULT(csiescseq
.arg
[0], 1);
1566 tmoveto(term
.c
.x
, term
.c
.y
-csiescseq
.arg
[0]);
1568 case 'B': /* CUD -- Cursor <n> Down */
1569 case 'e': /* VPR --Cursor <n> Down */
1570 DEFAULT(csiescseq
.arg
[0], 1);
1571 tmoveto(term
.c
.x
, term
.c
.y
+csiescseq
.arg
[0]);
1573 case 'i': /* MC -- Media Copy */
1574 switch (csiescseq
.arg
[0]) {
1579 tdumpline(term
.c
.y
);
1585 term
.mode
&= ~MODE_PRINT
;
1588 term
.mode
|= MODE_PRINT
;
1592 case 'c': /* DA -- Device Attributes */
1593 if (csiescseq
.arg
[0] == 0)
1594 ttywrite(vtiden
, strlen(vtiden
), 0);
1596 case 'C': /* CUF -- Cursor <n> Forward */
1597 case 'a': /* HPR -- Cursor <n> Forward */
1598 DEFAULT(csiescseq
.arg
[0], 1);
1599 tmoveto(term
.c
.x
+csiescseq
.arg
[0], term
.c
.y
);
1601 case 'D': /* CUB -- Cursor <n> Backward */
1602 DEFAULT(csiescseq
.arg
[0], 1);
1603 tmoveto(term
.c
.x
-csiescseq
.arg
[0], term
.c
.y
);
1605 case 'E': /* CNL -- Cursor <n> Down and first col */
1606 DEFAULT(csiescseq
.arg
[0], 1);
1607 tmoveto(0, term
.c
.y
+csiescseq
.arg
[0]);
1609 case 'F': /* CPL -- Cursor <n> Up and first col */
1610 DEFAULT(csiescseq
.arg
[0], 1);
1611 tmoveto(0, term
.c
.y
-csiescseq
.arg
[0]);
1613 case 'g': /* TBC -- Tabulation clear */
1614 switch (csiescseq
.arg
[0]) {
1615 case 0: /* clear current tab stop */
1616 term
.tabs
[term
.c
.x
] = 0;
1618 case 3: /* clear all the tabs */
1619 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1625 case 'G': /* CHA -- Move to <col> */
1627 DEFAULT(csiescseq
.arg
[0], 1);
1628 tmoveto(csiescseq
.arg
[0]-1, term
.c
.y
);
1630 case 'H': /* CUP -- Move to <row> <col> */
1632 DEFAULT(csiescseq
.arg
[0], 1);
1633 DEFAULT(csiescseq
.arg
[1], 1);
1634 tmoveato(csiescseq
.arg
[1]-1, csiescseq
.arg
[0]-1);
1636 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1637 DEFAULT(csiescseq
.arg
[0], 1);
1638 tputtab(csiescseq
.arg
[0]);
1640 case 'J': /* ED -- Clear screen */
1642 switch (csiescseq
.arg
[0]) {
1644 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1645 if (term
.c
.y
< term
.row
-1) {
1646 tclearregion(0, term
.c
.y
+1, term
.col
-1,
1652 tclearregion(0, 0, term
.col
-1, term
.c
.y
-1);
1653 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1656 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1662 case 'K': /* EL -- Clear line */
1663 switch (csiescseq
.arg
[0]) {
1665 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1,
1669 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1672 tclearregion(0, term
.c
.y
, term
.col
-1, term
.c
.y
);
1676 case 'S': /* SU -- Scroll <n> line up */
1677 DEFAULT(csiescseq
.arg
[0], 1);
1678 tscrollup(term
.top
, csiescseq
.arg
[0]);
1680 case 'T': /* SD -- Scroll <n> line down */
1681 DEFAULT(csiescseq
.arg
[0], 1);
1682 tscrolldown(term
.top
, csiescseq
.arg
[0]);
1684 case 'L': /* IL -- Insert <n> blank lines */
1685 DEFAULT(csiescseq
.arg
[0], 1);
1686 tinsertblankline(csiescseq
.arg
[0]);
1688 case 'l': /* RM -- Reset Mode */
1689 tsetmode(csiescseq
.priv
, 0, csiescseq
.arg
, csiescseq
.narg
);
1691 case 'M': /* DL -- Delete <n> lines */
1692 DEFAULT(csiescseq
.arg
[0], 1);
1693 tdeleteline(csiescseq
.arg
[0]);
1695 case 'X': /* ECH -- Erase <n> char */
1696 DEFAULT(csiescseq
.arg
[0], 1);
1697 tclearregion(term
.c
.x
, term
.c
.y
,
1698 term
.c
.x
+ csiescseq
.arg
[0] - 1, term
.c
.y
);
1700 case 'P': /* DCH -- Delete <n> char */
1701 DEFAULT(csiescseq
.arg
[0], 1);
1702 tdeletechar(csiescseq
.arg
[0]);
1704 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1705 DEFAULT(csiescseq
.arg
[0], 1);
1706 tputtab(-csiescseq
.arg
[0]);
1708 case 'd': /* VPA -- Move to <row> */
1709 DEFAULT(csiescseq
.arg
[0], 1);
1710 tmoveato(term
.c
.x
, csiescseq
.arg
[0]-1);
1712 case 'h': /* SM -- Set terminal mode */
1713 tsetmode(csiescseq
.priv
, 1, csiescseq
.arg
, csiescseq
.narg
);
1715 case 'm': /* SGR -- Terminal attribute (color) */
1716 tsetattr(csiescseq
.arg
, csiescseq
.narg
);
1718 case 'n': /* DSR – Device Status Report (cursor position) */
1719 if (csiescseq
.arg
[0] == 6) {
1720 len
= snprintf(buf
, sizeof(buf
),"\033[%i;%iR",
1721 term
.c
.y
+1, term
.c
.x
+1);
1722 ttywrite(buf
, len
, 0);
1725 case 'r': /* DECSTBM -- Set Scrolling Region */
1726 if (csiescseq
.priv
) {
1729 DEFAULT(csiescseq
.arg
[0], 1);
1730 DEFAULT(csiescseq
.arg
[1], term
.row
);
1731 tsetscroll(csiescseq
.arg
[0]-1, csiescseq
.arg
[1]-1);
1735 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1736 tcursor(CURSOR_SAVE
);
1738 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1739 tcursor(CURSOR_LOAD
);
1742 switch (csiescseq
.mode
[1]) {
1743 case 'q': /* DECSCUSR -- Set Cursor Style */
1744 if (xsetcursor(csiescseq
.arg
[0]))
1760 fprintf(stderr
, "ESC[");
1761 for (i
= 0; i
< csiescseq
.len
; i
++) {
1762 c
= csiescseq
.buf
[i
] & 0xff;
1765 } else if (c
== '\n') {
1766 fprintf(stderr
, "(\\n)");
1767 } else if (c
== '\r') {
1768 fprintf(stderr
, "(\\r)");
1769 } else if (c
== 0x1b) {
1770 fprintf(stderr
, "(\\e)");
1772 fprintf(stderr
, "(%02x)", c
);
1781 memset(&csiescseq
, 0, sizeof(csiescseq
));
1790 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
1792 par
= (narg
= strescseq
.narg
) ? atoi(strescseq
.args
[0]) : 0;
1794 switch (strescseq
.type
) {
1795 case ']': /* OSC -- Operating System Command */
1801 xsettitle(strescseq
.args
[1]);
1807 dec
= base64dec(strescseq
.args
[2]);
1812 fprintf(stderr
, "erresc: invalid base64\n");
1816 case 4: /* color set */
1819 p
= strescseq
.args
[2];
1821 case 104: /* color reset, here p = NULL */
1822 j
= (narg
> 1) ? atoi(strescseq
.args
[1]) : -1;
1823 if (xsetcolorname(j
, p
)) {
1824 fprintf(stderr
, "erresc: invalid color %s\n", p
);
1827 * TODO if defaultbg color is changed, borders
1835 case 'k': /* old title set compatibility */
1836 xsettitle(strescseq
.args
[0]);
1838 case 'P': /* DCS -- Device Control String */
1839 term
.mode
|= ESC_DCS
;
1840 case '_': /* APC -- Application Program Command */
1841 case '^': /* PM -- Privacy Message */
1845 fprintf(stderr
, "erresc: unknown str ");
1853 char *p
= strescseq
.buf
;
1856 strescseq
.buf
[strescseq
.len
] = '\0';
1861 while (strescseq
.narg
< STR_ARG_SIZ
) {
1862 strescseq
.args
[strescseq
.narg
++] = p
;
1863 while ((c
= *p
) != ';' && c
!= '\0')
1877 fprintf(stderr
, "ESC%c", strescseq
.type
);
1878 for (i
= 0; i
< strescseq
.len
; i
++) {
1879 c
= strescseq
.buf
[i
] & 0xff;
1883 } else if (isprint(c
)) {
1885 } else if (c
== '\n') {
1886 fprintf(stderr
, "(\\n)");
1887 } else if (c
== '\r') {
1888 fprintf(stderr
, "(\\r)");
1889 } else if (c
== 0x1b) {
1890 fprintf(stderr
, "(\\e)");
1892 fprintf(stderr
, "(%02x)", c
);
1895 fprintf(stderr
, "ESC\\\n");
1901 memset(&strescseq
, 0, sizeof(strescseq
));
1905 sendbreak(const Arg
*arg
)
1907 if (tcsendbreak(cmdfd
, 0))
1908 perror("Error sending break");
1912 tprinter(char *s
, size_t len
)
1914 if (iofd
!= -1 && xwrite(iofd
, s
, len
) < 0) {
1915 perror("Error writing to output file");
1922 iso14755(const Arg
*arg
)
1925 char *us
, *e
, codepoint
[9], uc
[UTF_SIZ
];
1926 unsigned long utf32
;
1928 if (!(p
= popen(ISO14755CMD
, "r")))
1931 us
= fgets(codepoint
, sizeof(codepoint
), p
);
1934 if (!us
|| *us
== '\0' || *us
== '-' || strlen(us
) > 7)
1936 if ((utf32
= strtoul(us
, &e
, 16)) == ULONG_MAX
||
1937 (*e
!= '\n' && *e
!= '\0'))
1940 ttywrite(uc
, utf8encode(utf32
, uc
), 1);
1944 toggleprinter(const Arg
*arg
)
1946 term
.mode
^= MODE_PRINT
;
1950 printscreen(const Arg
*arg
)
1956 printsel(const Arg
*arg
)
1966 if ((ptr
= getsel())) {
1967 tprinter(ptr
, strlen(ptr
));
1978 bp
= &term
.line
[n
][0];
1979 end
= &bp
[MIN(tlinelen(n
), term
.col
) - 1];
1980 if (bp
!= end
|| bp
->u
!= ' ') {
1981 for ( ;bp
<= end
; ++bp
)
1982 tprinter(buf
, utf8encode(bp
->u
, buf
));
1992 for (i
= 0; i
< term
.row
; ++i
)
2002 while (x
< term
.col
&& n
--)
2003 for (++x
; x
< term
.col
&& !term
.tabs
[x
]; ++x
)
2006 while (x
> 0 && n
++)
2007 for (--x
; x
> 0 && !term
.tabs
[x
]; --x
)
2010 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
2014 tdefutf8(char ascii
)
2017 term
.mode
|= MODE_UTF8
;
2018 else if (ascii
== '@')
2019 term
.mode
&= ~MODE_UTF8
;
2023 tdeftran(char ascii
)
2025 static char cs
[] = "0B";
2026 static int vcs
[] = {CS_GRAPHIC0
, CS_USA
};
2029 if ((p
= strchr(cs
, ascii
)) == NULL
) {
2030 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
2032 term
.trantbl
[term
.icharset
] = vcs
[p
- cs
];
2041 if (c
== '8') { /* DEC screen alignment test. */
2042 for (x
= 0; x
< term
.col
; ++x
) {
2043 for (y
= 0; y
< term
.row
; ++y
)
2044 tsetchar('E', &term
.c
.attr
, x
, y
);
2050 tstrsequence(uchar c
)
2055 case 0x90: /* DCS -- Device Control String */
2057 term
.esc
|= ESC_DCS
;
2059 case 0x9f: /* APC -- Application Program Command */
2062 case 0x9e: /* PM -- Privacy Message */
2065 case 0x9d: /* OSC -- Operating System Command */
2070 term
.esc
|= ESC_STR
;
2074 tcontrolcode(uchar ascii
)
2081 tmoveto(term
.c
.x
-1, term
.c
.y
);
2084 tmoveto(0, term
.c
.y
);
2089 /* go to first col if the mode is set */
2090 tnewline(IS_SET(MODE_CRLF
));
2092 case '\a': /* BEL */
2093 if (term
.esc
& ESC_STR_END
) {
2094 /* backwards compatibility to xterm */
2100 case '\033': /* ESC */
2102 term
.esc
&= ~(ESC_CSI
|ESC_ALTCHARSET
|ESC_TEST
);
2103 term
.esc
|= ESC_START
;
2105 case '\016': /* SO (LS1 -- Locking shift 1) */
2106 case '\017': /* SI (LS0 -- Locking shift 0) */
2107 term
.charset
= 1 - (ascii
- '\016');
2109 case '\032': /* SUB */
2110 tsetchar('?', &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2111 case '\030': /* CAN */
2114 case '\005': /* ENQ (IGNORED) */
2115 case '\000': /* NUL (IGNORED) */
2116 case '\021': /* XON (IGNORED) */
2117 case '\023': /* XOFF (IGNORED) */
2118 case 0177: /* DEL (IGNORED) */
2120 case 0x80: /* TODO: PAD */
2121 case 0x81: /* TODO: HOP */
2122 case 0x82: /* TODO: BPH */
2123 case 0x83: /* TODO: NBH */
2124 case 0x84: /* TODO: IND */
2126 case 0x85: /* NEL -- Next line */
2127 tnewline(1); /* always go to first col */
2129 case 0x86: /* TODO: SSA */
2130 case 0x87: /* TODO: ESA */
2132 case 0x88: /* HTS -- Horizontal tab stop */
2133 term
.tabs
[term
.c
.x
] = 1;
2135 case 0x89: /* TODO: HTJ */
2136 case 0x8a: /* TODO: VTS */
2137 case 0x8b: /* TODO: PLD */
2138 case 0x8c: /* TODO: PLU */
2139 case 0x8d: /* TODO: RI */
2140 case 0x8e: /* TODO: SS2 */
2141 case 0x8f: /* TODO: SS3 */
2142 case 0x91: /* TODO: PU1 */
2143 case 0x92: /* TODO: PU2 */
2144 case 0x93: /* TODO: STS */
2145 case 0x94: /* TODO: CCH */
2146 case 0x95: /* TODO: MW */
2147 case 0x96: /* TODO: SPA */
2148 case 0x97: /* TODO: EPA */
2149 case 0x98: /* TODO: SOS */
2150 case 0x99: /* TODO: SGCI */
2152 case 0x9a: /* DECID -- Identify Terminal */
2153 ttywrite(vtiden
, strlen(vtiden
), 0);
2155 case 0x9b: /* TODO: CSI */
2156 case 0x9c: /* TODO: ST */
2158 case 0x90: /* DCS -- Device Control String */
2159 case 0x9d: /* OSC -- Operating System Command */
2160 case 0x9e: /* PM -- Privacy Message */
2161 case 0x9f: /* APC -- Application Program Command */
2162 tstrsequence(ascii
);
2165 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2166 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
2170 * returns 1 when the sequence is finished and it hasn't to read
2171 * more characters for this sequence, otherwise 0
2174 eschandle(uchar ascii
)
2178 term
.esc
|= ESC_CSI
;
2181 term
.esc
|= ESC_TEST
;
2184 term
.esc
|= ESC_UTF8
;
2186 case 'P': /* DCS -- Device Control String */
2187 case '_': /* APC -- Application Program Command */
2188 case '^': /* PM -- Privacy Message */
2189 case ']': /* OSC -- Operating System Command */
2190 case 'k': /* old title set compatibility */
2191 tstrsequence(ascii
);
2193 case 'n': /* LS2 -- Locking shift 2 */
2194 case 'o': /* LS3 -- Locking shift 3 */
2195 term
.charset
= 2 + (ascii
- 'n');
2197 case '(': /* GZD4 -- set primary charset G0 */
2198 case ')': /* G1D4 -- set secondary charset G1 */
2199 case '*': /* G2D4 -- set tertiary charset G2 */
2200 case '+': /* G3D4 -- set quaternary charset G3 */
2201 term
.icharset
= ascii
- '(';
2202 term
.esc
|= ESC_ALTCHARSET
;
2204 case 'D': /* IND -- Linefeed */
2205 if (term
.c
.y
== term
.bot
) {
2206 tscrollup(term
.top
, 1);
2208 tmoveto(term
.c
.x
, term
.c
.y
+1);
2211 case 'E': /* NEL -- Next line */
2212 tnewline(1); /* always go to first col */
2214 case 'H': /* HTS -- Horizontal tab stop */
2215 term
.tabs
[term
.c
.x
] = 1;
2217 case 'M': /* RI -- Reverse index */
2218 if (term
.c
.y
== term
.top
) {
2219 tscrolldown(term
.top
, 1);
2221 tmoveto(term
.c
.x
, term
.c
.y
-1);
2224 case 'Z': /* DECID -- Identify Terminal */
2225 ttywrite(vtiden
, strlen(vtiden
), 0);
2227 case 'c': /* RIS -- Reset to inital state */
2232 case '=': /* DECPAM -- Application keypad */
2233 term
.mode
|= MODE_APPKEYPAD
;
2235 case '>': /* DECPNM -- Normal keypad */
2236 term
.mode
&= ~MODE_APPKEYPAD
;
2238 case '7': /* DECSC -- Save Cursor */
2239 tcursor(CURSOR_SAVE
);
2241 case '8': /* DECRC -- Restore Cursor */
2242 tcursor(CURSOR_LOAD
);
2244 case '\\': /* ST -- String Terminator */
2245 if (term
.esc
& ESC_STR_END
)
2249 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2250 (uchar
) ascii
, isprint(ascii
)? ascii
:'.');
2264 control
= ISCONTROL(u
);
2265 if (!IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2269 len
= utf8encode(u
, c
);
2270 if (!control
&& (width
= wcwidth(u
)) == -1) {
2271 memcpy(c
, "\357\277\275", 4); /* UTF_INVALID */
2276 if (IS_SET(MODE_PRINT
))
2280 * STR sequence must be checked before anything else
2281 * because it uses all following characters until it
2282 * receives a ESC, a SUB, a ST or any other C1 control
2285 if (term
.esc
& ESC_STR
) {
2286 if (u
== '\a' || u
== 030 || u
== 032 || u
== 033 ||
2288 term
.esc
&= ~(ESC_START
|ESC_STR
|ESC_DCS
);
2289 if (IS_SET(MODE_SIXEL
)) {
2290 /* TODO: render sixel */;
2291 term
.mode
&= ~MODE_SIXEL
;
2294 term
.esc
|= ESC_STR_END
;
2295 goto check_control_code
;
2299 if (IS_SET(MODE_SIXEL
)) {
2300 /* TODO: implement sixel mode */
2303 if (term
.esc
&ESC_DCS
&& strescseq
.len
== 0 && u
== 'q')
2304 term
.mode
|= MODE_SIXEL
;
2306 if (strescseq
.len
+len
>= sizeof(strescseq
.buf
)-1) {
2308 * Here is a bug in terminals. If the user never sends
2309 * some code to stop the str or esc command, then st
2310 * will stop responding. But this is better than
2311 * silently failing with unknown characters. At least
2312 * then users will report back.
2314 * In the case users ever get fixed, here is the code:
2323 memmove(&strescseq
.buf
[strescseq
.len
], c
, len
);
2324 strescseq
.len
+= len
;
2330 * Actions of control codes must be performed as soon they arrive
2331 * because they can be embedded inside a control sequence, and
2332 * they must not cause conflicts with sequences.
2337 * control codes are not shown ever
2340 } else if (term
.esc
& ESC_START
) {
2341 if (term
.esc
& ESC_CSI
) {
2342 csiescseq
.buf
[csiescseq
.len
++] = u
;
2343 if (BETWEEN(u
, 0x40, 0x7E)
2344 || csiescseq
.len
>= \
2345 sizeof(csiescseq
.buf
)-1) {
2351 } else if (term
.esc
& ESC_UTF8
) {
2353 } else if (term
.esc
& ESC_ALTCHARSET
) {
2355 } else if (term
.esc
& ESC_TEST
) {
2360 /* sequence already finished */
2364 * All characters which form part of a sequence are not
2369 if (sel
.ob
.x
!= -1 && BETWEEN(term
.c
.y
, sel
.ob
.y
, sel
.oe
.y
))
2372 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2373 if (IS_SET(MODE_WRAP
) && (term
.c
.state
& CURSOR_WRAPNEXT
)) {
2374 gp
->mode
|= ATTR_WRAP
;
2376 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2379 if (IS_SET(MODE_INSERT
) && term
.c
.x
+width
< term
.col
)
2380 memmove(gp
+width
, gp
, (term
.col
- term
.c
.x
- width
) * sizeof(Glyph
));
2382 if (term
.c
.x
+width
> term
.col
) {
2384 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2387 tsetchar(u
, &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2390 gp
->mode
|= ATTR_WIDE
;
2391 if (term
.c
.x
+1 < term
.col
) {
2393 gp
[1].mode
= ATTR_WDUMMY
;
2396 if (term
.c
.x
+width
< term
.col
) {
2397 tmoveto(term
.c
.x
+width
, term
.c
.y
);
2399 term
.c
.state
|= CURSOR_WRAPNEXT
;
2404 twrite(const char *buf
, int buflen
, int show_ctrl
)
2410 for (n
= 0; n
< buflen
; n
+= charsize
) {
2411 if (IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2412 /* process a complete utf8 char */
2413 charsize
= utf8decode(buf
+ n
, &u
, buflen
- n
);
2420 if (show_ctrl
&& ISCONTROL(u
)) {
2425 } else if (u
!= '\n' && u
!= '\r' && u
!= '\t') {
2436 tresize(int col
, int row
)
2439 int minrow
= MIN(row
, term
.row
);
2440 int mincol
= MIN(col
, term
.col
);
2444 if (col
< 1 || row
< 1) {
2446 "tresize: error resizing to %dx%d\n", col
, row
);
2451 * slide screen to keep cursor where we expect it -
2452 * tscrollup would work here, but we can optimize to
2453 * memmove because we're freeing the earlier lines
2455 for (i
= 0; i
<= term
.c
.y
- row
; i
++) {
2459 /* ensure that both src and dst are not NULL */
2461 memmove(term
.line
, term
.line
+ i
, row
* sizeof(Line
));
2462 memmove(term
.alt
, term
.alt
+ i
, row
* sizeof(Line
));
2464 for (i
+= row
; i
< term
.row
; i
++) {
2469 /* resize to new height */
2470 term
.line
= xrealloc(term
.line
, row
* sizeof(Line
));
2471 term
.alt
= xrealloc(term
.alt
, row
* sizeof(Line
));
2472 term
.dirty
= xrealloc(term
.dirty
, row
* sizeof(*term
.dirty
));
2473 term
.tabs
= xrealloc(term
.tabs
, col
* sizeof(*term
.tabs
));
2475 /* resize each row to new width, zero-pad if needed */
2476 for (i
= 0; i
< minrow
; i
++) {
2477 term
.line
[i
] = xrealloc(term
.line
[i
], col
* sizeof(Glyph
));
2478 term
.alt
[i
] = xrealloc(term
.alt
[i
], col
* sizeof(Glyph
));
2481 /* allocate any new rows */
2482 for (/* i = minrow */; i
< row
; i
++) {
2483 term
.line
[i
] = xmalloc(col
* sizeof(Glyph
));
2484 term
.alt
[i
] = xmalloc(col
* sizeof(Glyph
));
2486 if (col
> term
.col
) {
2487 bp
= term
.tabs
+ term
.col
;
2489 memset(bp
, 0, sizeof(*term
.tabs
) * (col
- term
.col
));
2490 while (--bp
> term
.tabs
&& !*bp
)
2492 for (bp
+= tabspaces
; bp
< term
.tabs
+ col
; bp
+= tabspaces
)
2495 /* update terminal size */
2498 /* reset scrolling region */
2499 tsetscroll(0, row
-1);
2500 /* make use of the LIMIT in tmoveto */
2501 tmoveto(term
.c
.x
, term
.c
.y
);
2502 /* Clearing both screens (it makes dirty all lines) */
2504 for (i
= 0; i
< 2; i
++) {
2505 if (mincol
< col
&& 0 < minrow
) {
2506 tclearregion(mincol
, 0, col
- 1, minrow
- 1);
2508 if (0 < col
&& minrow
< row
) {
2509 tclearregion(0, minrow
, col
- 1, row
- 1);
2512 tcursor(CURSOR_LOAD
);
2531 numlock(const Arg
*dummy
)