Xinqi Bao's Git
1 /* See LICENSE for license details. */
14 #include <sys/ioctl.h>
15 #include <sys/select.h>
18 #include <sys/types.h>
24 #include <fontconfig/fontconfig.h>
28 #include <X11/cursorfont.h>
29 #include <X11/Xft/Xft.h>
36 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
38 #elif defined(__FreeBSD__) || defined(__DragonFly__)
43 #define UTF_INVALID 0xFFFD
44 #define ESC_BUF_SIZ (128*UTF_SIZ)
45 #define ESC_ARG_SIZ 16
46 #define STR_BUF_SIZ ESC_BUF_SIZ
47 #define STR_ARG_SIZ ESC_ARG_SIZ
50 #define NUMMAXLEN(x) ((int)(sizeof(x) * 2.56 + 0.5) + 1)
51 #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == '\177')
52 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
53 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
54 #define ISDELIM(u) (utf8strchr(worddelimiters, u) != NULL)
57 #define ISO14755CMD "dmenu -w \"$WINDOWID\" -p codepoint: </dev/null"
59 enum cursor_movement
{
83 ESC_STR
= 4, /* OSC, PM, APC */
85 ESC_STR_END
= 16, /* a final string was encountered */
86 ESC_TEST
= 32, /* Enter in test mode */
91 /* CSI Escape sequence structs */
92 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
94 char buf
[ESC_BUF_SIZ
]; /* raw string */
95 int len
; /* raw string length */
98 int narg
; /* nb of args */
102 /* STR Escape sequence structs */
103 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
105 char type
; /* ESC type ... */
106 char buf
[STR_BUF_SIZ
]; /* raw string */
107 int len
; /* raw string length */
108 char *args
[STR_ARG_SIZ
];
109 int narg
; /* nb of args */
112 /* function definitions used in config.h */
113 static void clipcopy(const Arg
*);
114 static void clippaste(const Arg
*);
115 static void numlock(const Arg
*);
116 static void selpaste(const Arg
*);
117 static void printsel(const Arg
*);
118 static void printscreen(const Arg
*) ;
119 static void iso14755(const Arg
*);
120 static void toggleprinter(const Arg
*);
121 static void sendbreak(const Arg
*);
123 /* config.h for applying patches and the configuration. */
126 static void execsh(char **);
127 static void stty(char **);
128 static void sigchld(int);
130 static void csidump(void);
131 static void csihandle(void);
132 static void csiparse(void);
133 static void csireset(void);
134 static int eschandle(uchar
);
135 static void strdump(void);
136 static void strhandle(void);
137 static void strparse(void);
138 static void strreset(void);
140 static void tprinter(char *, size_t);
141 static void tdumpsel(void);
142 static void tdumpline(int);
143 static void tdump(void);
144 static void tclearregion(int, int, int, int);
145 static void tcursor(int);
146 static void tdeletechar(int);
147 static void tdeleteline(int);
148 static void tinsertblank(int);
149 static void tinsertblankline(int);
150 static int tlinelen(int);
151 static void tmoveto(int, int);
152 static void tmoveato(int, int);
153 static void tnewline(int);
154 static void tputtab(int);
155 static void tputc(Rune
);
156 static void treset(void);
157 static void tscrollup(int, int);
158 static void tscrolldown(int, int);
159 static void tsetattr(int *, int);
160 static void tsetchar(Rune
, Glyph
*, int, int);
161 static void tsetscroll(int, int);
162 static void tswapscreen(void);
163 static void tsetmode(int, int, int *, int);
164 static int twrite(const char *, int, int);
165 static void tfulldirt(void);
166 static void tcontrolcode(uchar
);
167 static void tdectest(char );
168 static void tdefutf8(char);
169 static int32_t tdefcolor(int *, int *, int);
170 static void tdeftran(char);
171 static void tstrsequence(uchar
);
173 static void selscroll(int, int);
174 static void selsnap(int *, int *, int);
176 static Rune
utf8decodebyte(char, size_t *);
177 static char utf8encodebyte(Rune
, size_t);
178 static char *utf8strchr(char *s
, Rune u
);
179 static size_t utf8validate(Rune
*, size_t);
181 static char *base64dec(const char *);
183 static ssize_t
xwrite(int, const char *, size_t);
191 int oldbutton
= 3; /* button event on startup: 3 = release */
193 static CSIEscape csiescseq
;
194 static STREscape strescseq
;
197 static uchar utfbyte
[UTF_SIZ
+ 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
198 static uchar utfmask
[UTF_SIZ
+ 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
199 static Rune utfmin
[UTF_SIZ
+ 1] = { 0, 0, 0x80, 0x800, 0x10000};
200 static Rune utfmax
[UTF_SIZ
+ 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
202 /* config.h array lengths */
203 size_t colornamelen
= LEN(colorname
);
204 size_t mshortcutslen
= LEN(mshortcuts
);
205 size_t shortcutslen
= LEN(shortcuts
);
206 size_t selmaskslen
= LEN(selmasks
);
207 size_t keyslen
= LEN(key
);
208 size_t mappedkeyslen
= LEN(mappedkeys
);
211 xwrite(int fd
, const char *s
, size_t len
)
217 r
= write(fd
, s
, len
);
230 void *p
= malloc(len
);
233 die("Out of memory\n");
239 xrealloc(void *p
, size_t len
)
241 if ((p
= realloc(p
, len
)) == NULL
)
242 die("Out of memory\n");
250 if ((s
= strdup(s
)) == NULL
)
251 die("Out of memory\n");
257 utf8decode(const char *c
, Rune
*u
, size_t clen
)
259 size_t i
, j
, len
, type
;
265 udecoded
= utf8decodebyte(c
[0], &len
);
266 if (!BETWEEN(len
, 1, UTF_SIZ
))
268 for (i
= 1, j
= 1; i
< clen
&& j
< len
; ++i
, ++j
) {
269 udecoded
= (udecoded
<< 6) | utf8decodebyte(c
[i
], &type
);
276 utf8validate(u
, len
);
282 utf8decodebyte(char c
, size_t *i
)
284 for (*i
= 0; *i
< LEN(utfmask
); ++(*i
))
285 if (((uchar
)c
& utfmask
[*i
]) == utfbyte
[*i
])
286 return (uchar
)c
& ~utfmask
[*i
];
292 utf8encode(Rune u
, char *c
)
296 len
= utf8validate(&u
, 0);
300 for (i
= len
- 1; i
!= 0; --i
) {
301 c
[i
] = utf8encodebyte(u
, 0);
304 c
[0] = utf8encodebyte(u
, len
);
310 utf8encodebyte(Rune u
, size_t i
)
312 return utfbyte
[i
] | (u
& ~utfmask
[i
]);
316 utf8strchr(char *s
, Rune u
)
322 for (i
= 0, j
= 0; i
< len
; i
+= j
) {
323 if (!(j
= utf8decode(&s
[i
], &r
, len
- i
)))
333 utf8validate(Rune
*u
, size_t i
)
335 if (!BETWEEN(*u
, utfmin
[i
], utfmax
[i
]) || BETWEEN(*u
, 0xD800, 0xDFFF))
337 for (i
= 1; *u
> utfmax
[i
]; ++i
)
343 static const char base64_digits
[] = {
344 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
345 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0,
346 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
347 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
348 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
349 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0,
350 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
351 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
352 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
353 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
354 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
355 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
359 base64dec_getc(const char **src
)
361 while (**src
&& !isprint(**src
)) (*src
)++;
366 base64dec(const char *src
)
368 size_t in_len
= strlen(src
);
372 in_len
+= 4 - (in_len
% 4);
373 result
= dst
= xmalloc(in_len
/ 4 * 3 + 1);
375 int a
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
376 int b
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
377 int c
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
378 int d
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
380 *dst
++ = (a
<< 2) | ((b
& 0x30) >> 4);
383 *dst
++ = ((b
& 0x0f) << 4) | ((c
& 0x3c) >> 2);
386 *dst
++ = ((c
& 0x03) << 6) | d
;
395 clock_gettime(CLOCK_MONOTONIC
, &sel
.tclick1
);
396 clock_gettime(CLOCK_MONOTONIC
, &sel
.tclick2
);
401 sel
.clipboard
= NULL
;
409 if (term
.line
[y
][i
- 1].mode
& ATTR_WRAP
)
412 while (i
> 0 && term
.line
[y
][i
- 1].u
== ' ')
423 if (sel
.type
== SEL_REGULAR
&& sel
.ob
.y
!= sel
.oe
.y
) {
424 sel
.nb
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.ob
.x
: sel
.oe
.x
;
425 sel
.ne
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.oe
.x
: sel
.ob
.x
;
427 sel
.nb
.x
= MIN(sel
.ob
.x
, sel
.oe
.x
);
428 sel
.ne
.x
= MAX(sel
.ob
.x
, sel
.oe
.x
);
430 sel
.nb
.y
= MIN(sel
.ob
.y
, sel
.oe
.y
);
431 sel
.ne
.y
= MAX(sel
.ob
.y
, sel
.oe
.y
);
433 selsnap(&sel
.nb
.x
, &sel
.nb
.y
, -1);
434 selsnap(&sel
.ne
.x
, &sel
.ne
.y
, +1);
436 /* expand selection over line breaks */
437 if (sel
.type
== SEL_RECTANGULAR
)
439 i
= tlinelen(sel
.nb
.y
);
442 if (tlinelen(sel
.ne
.y
) <= sel
.ne
.x
)
443 sel
.ne
.x
= term
.col
- 1;
447 selected(int x
, int y
)
449 if (sel
.mode
== SEL_EMPTY
)
452 if (sel
.type
== SEL_RECTANGULAR
)
453 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
454 && BETWEEN(x
, sel
.nb
.x
, sel
.ne
.x
);
456 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
457 && (y
!= sel
.nb
.y
|| x
>= sel
.nb
.x
)
458 && (y
!= sel
.ne
.y
|| x
<= sel
.ne
.x
);
462 selsnap(int *x
, int *y
, int direction
)
464 int newx
, newy
, xt
, yt
;
465 int delim
, prevdelim
;
471 * Snap around if the word wraps around at the end or
472 * beginning of a line.
474 prevgp
= &term
.line
[*y
][*x
];
475 prevdelim
= ISDELIM(prevgp
->u
);
477 newx
= *x
+ direction
;
479 if (!BETWEEN(newx
, 0, term
.col
- 1)) {
481 newx
= (newx
+ term
.col
) % term
.col
;
482 if (!BETWEEN(newy
, 0, term
.row
- 1))
488 yt
= newy
, xt
= newx
;
489 if (!(term
.line
[yt
][xt
].mode
& ATTR_WRAP
))
493 if (newx
>= tlinelen(newy
))
496 gp
= &term
.line
[newy
][newx
];
497 delim
= ISDELIM(gp
->u
);
498 if (!(gp
->mode
& ATTR_WDUMMY
) && (delim
!= prevdelim
499 || (delim
&& gp
->u
!= prevgp
->u
)))
510 * Snap around if the the previous line or the current one
511 * has set ATTR_WRAP at its end. Then the whole next or
512 * previous line will be selected.
514 *x
= (direction
< 0) ? 0 : term
.col
- 1;
516 for (; *y
> 0; *y
+= direction
) {
517 if (!(term
.line
[*y
-1][term
.col
-1].mode
522 } else if (direction
> 0) {
523 for (; *y
< term
.row
-1; *y
+= direction
) {
524 if (!(term
.line
[*y
][term
.col
-1].mode
538 int y
, bufsize
, lastx
, linelen
;
544 bufsize
= (term
.col
+1) * (sel
.ne
.y
-sel
.nb
.y
+1) * UTF_SIZ
;
545 ptr
= str
= xmalloc(bufsize
);
547 /* append every set & selected glyph to the selection */
548 for (y
= sel
.nb
.y
; y
<= sel
.ne
.y
; y
++) {
549 if ((linelen
= tlinelen(y
)) == 0) {
554 if (sel
.type
== SEL_RECTANGULAR
) {
555 gp
= &term
.line
[y
][sel
.nb
.x
];
558 gp
= &term
.line
[y
][sel
.nb
.y
== y
? sel
.nb
.x
: 0];
559 lastx
= (sel
.ne
.y
== y
) ? sel
.ne
.x
: term
.col
-1;
561 last
= &term
.line
[y
][MIN(lastx
, linelen
-1)];
562 while (last
>= gp
&& last
->u
== ' ')
565 for ( ; gp
<= last
; ++gp
) {
566 if (gp
->mode
& ATTR_WDUMMY
)
569 ptr
+= utf8encode(gp
->u
, ptr
);
573 * Copy and pasting of line endings is inconsistent
574 * in the inconsistent terminal and GUI world.
575 * The best solution seems like to produce '\n' when
576 * something is copied from st and convert '\n' to
577 * '\r', when something to be pasted is received by
579 * FIXME: Fix the computer world.
581 if ((y
< sel
.ne
.y
|| lastx
>= linelen
) && !(last
->mode
& ATTR_WRAP
))
589 selpaste(const Arg
*dummy
)
595 clipcopy(const Arg
*dummy
)
601 clippaste(const Arg
*dummy
)
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
)
718 struct winsize w
= {term
.row
, term
.col
, 0, 0};
721 term
.mode
|= MODE_PRINT
;
722 iofd
= (!strcmp(out
, "-")) ?
723 1 : open(out
, O_WRONLY
| O_CREAT
, 0666);
725 fprintf(stderr
, "Error opening %s:%s\n",
726 out
, strerror(errno
));
731 if ((cmdfd
= open(line
, O_RDWR
)) < 0)
732 die("open line failed: %s\n", strerror(errno
));
738 /* seems to work fine on linux, openbsd and freebsd */
739 if (openpty(&m
, &s
, NULL
, NULL
, &w
) < 0)
740 die("openpty failed: %s\n", strerror(errno
));
742 switch (pid
= fork()) {
744 die("fork failed\n");
748 setsid(); /* create a new process group */
752 if (ioctl(s
, TIOCSCTTY
, NULL
) < 0)
753 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno
));
761 signal(SIGCHLD
, sigchld
);
769 static char buf
[BUFSIZ
];
770 static int buflen
= 0;
774 /* append read bytes to unprocessed bytes */
775 if ((ret
= read(cmdfd
, buf
+buflen
, LEN(buf
)-buflen
)) < 0)
776 die("Couldn't read from shell: %s\n", strerror(errno
));
779 written
= twrite(buf
, buflen
, 0);
781 /* keep any uncomplete utf8 char for the next call */
783 memmove(buf
, buf
+ written
, buflen
);
789 ttywrite(const char *s
, size_t n
)
796 * Remember that we are using a pty, which might be a modem line.
797 * Writing too much will clog the line. That's why we are doing this
799 * FIXME: Migrate the world to Plan 9.
807 /* Check if we can write. */
808 if (pselect(cmdfd
+1, &rfd
, &wfd
, NULL
, NULL
, NULL
) < 0) {
811 die("select failed: %s\n", strerror(errno
));
813 if (FD_ISSET(cmdfd
, &wfd
)) {
815 * Only write the bytes written by ttywrite() or the
816 * default of 256. This seems to be a reasonable value
817 * for a serial line. Bigger values might clog the I/O.
819 if ((r
= write(cmdfd
, s
, (n
< lim
)? n
: lim
)) < 0)
823 * We weren't able to write out everything.
824 * This means the buffer is getting full
832 /* All bytes have been written. */
836 if (FD_ISSET(cmdfd
, &rfd
))
842 die("write error on tty: %s\n", strerror(errno
));
846 ttysend(char *s
, size_t n
)
849 if (IS_SET(MODE_ECHO
))
854 ttyresize(int tw
, int th
)
862 if (ioctl(cmdfd
, TIOCSWINSZ
, &w
) < 0)
863 fprintf(stderr
, "Couldn't set window size: %s\n", strerror(errno
));
871 for (i
= 0; i
< term
.row
-1; i
++) {
872 for (j
= 0; j
< term
.col
-1; j
++) {
873 if (term
.line
[i
][j
].mode
& attr
)
882 tsetdirt(int top
, int bot
)
886 LIMIT(top
, 0, term
.row
-1);
887 LIMIT(bot
, 0, term
.row
-1);
889 for (i
= top
; i
<= bot
; i
++)
894 tsetdirtattr(int attr
)
898 for (i
= 0; i
< term
.row
-1; i
++) {
899 for (j
= 0; j
< term
.col
-1; j
++) {
900 if (term
.line
[i
][j
].mode
& attr
) {
911 tsetdirt(0, term
.row
-1);
918 int alt
= IS_SET(MODE_ALTSCREEN
);
920 if (mode
== CURSOR_SAVE
) {
922 } else if (mode
== CURSOR_LOAD
) {
924 tmoveto(c
[alt
].x
, c
[alt
].y
);
937 }, .x
= 0, .y
= 0, .state
= CURSOR_DEFAULT
};
939 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
940 for (i
= tabspaces
; i
< term
.col
; i
+= tabspaces
)
943 term
.bot
= term
.row
- 1;
944 term
.mode
= MODE_WRAP
|MODE_UTF8
;
945 memset(term
.trantbl
, CS_USA
, sizeof(term
.trantbl
));
948 for (i
= 0; i
< 2; i
++) {
950 tcursor(CURSOR_SAVE
);
951 tclearregion(0, 0, term
.col
-1, term
.row
-1);
957 tnew(int col
, int row
)
959 term
= (Term
){ .c
= { .attr
= { .fg
= defaultfg
, .bg
= defaultbg
} } };
969 Line
*tmp
= term
.line
;
971 term
.line
= term
.alt
;
973 term
.mode
^= MODE_ALTSCREEN
;
978 tscrolldown(int orig
, int n
)
983 LIMIT(n
, 0, term
.bot
-orig
+1);
985 tsetdirt(orig
, term
.bot
-n
);
986 tclearregion(0, term
.bot
-n
+1, term
.col
-1, term
.bot
);
988 for (i
= term
.bot
; i
>= orig
+n
; i
--) {
990 term
.line
[i
] = term
.line
[i
-n
];
991 term
.line
[i
-n
] = temp
;
998 tscrollup(int orig
, int n
)
1003 LIMIT(n
, 0, term
.bot
-orig
+1);
1005 tclearregion(0, orig
, term
.col
-1, orig
+n
-1);
1006 tsetdirt(orig
+n
, term
.bot
);
1008 for (i
= orig
; i
<= term
.bot
-n
; i
++) {
1009 temp
= term
.line
[i
];
1010 term
.line
[i
] = term
.line
[i
+n
];
1011 term
.line
[i
+n
] = temp
;
1014 selscroll(orig
, -n
);
1018 selscroll(int orig
, int n
)
1023 if (BETWEEN(sel
.ob
.y
, orig
, term
.bot
) || BETWEEN(sel
.oe
.y
, orig
, term
.bot
)) {
1024 if ((sel
.ob
.y
+= n
) > term
.bot
|| (sel
.oe
.y
+= n
) < term
.top
) {
1028 if (sel
.type
== SEL_RECTANGULAR
) {
1029 if (sel
.ob
.y
< term
.top
)
1030 sel
.ob
.y
= term
.top
;
1031 if (sel
.oe
.y
> term
.bot
)
1032 sel
.oe
.y
= term
.bot
;
1034 if (sel
.ob
.y
< term
.top
) {
1035 sel
.ob
.y
= term
.top
;
1038 if (sel
.oe
.y
> term
.bot
) {
1039 sel
.oe
.y
= term
.bot
;
1040 sel
.oe
.x
= term
.col
;
1048 tnewline(int first_col
)
1052 if (y
== term
.bot
) {
1053 tscrollup(term
.top
, 1);
1057 tmoveto(first_col
? 0 : term
.c
.x
, y
);
1063 char *p
= csiescseq
.buf
, *np
;
1072 csiescseq
.buf
[csiescseq
.len
] = '\0';
1073 while (p
< csiescseq
.buf
+csiescseq
.len
) {
1075 v
= strtol(p
, &np
, 10);
1078 if (v
== LONG_MAX
|| v
== LONG_MIN
)
1080 csiescseq
.arg
[csiescseq
.narg
++] = v
;
1082 if (*p
!= ';' || csiescseq
.narg
== ESC_ARG_SIZ
)
1086 csiescseq
.mode
[0] = *p
++;
1087 csiescseq
.mode
[1] = (p
< csiescseq
.buf
+csiescseq
.len
) ? *p
: '\0';
1090 /* for absolute user moves, when decom is set */
1092 tmoveato(int x
, int y
)
1094 tmoveto(x
, y
+ ((term
.c
.state
& CURSOR_ORIGIN
) ? term
.top
: 0));
1098 tmoveto(int x
, int y
)
1102 if (term
.c
.state
& CURSOR_ORIGIN
) {
1107 maxy
= term
.row
- 1;
1109 term
.c
.state
&= ~CURSOR_WRAPNEXT
;
1110 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1111 term
.c
.y
= LIMIT(y
, miny
, maxy
);
1115 tsetchar(Rune u
, Glyph
*attr
, int x
, int y
)
1117 static char *vt100_0
[62] = { /* 0x41 - 0x7e */
1118 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1119 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1120 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1121 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1122 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1123 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1124 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1125 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1129 * The table is proudly stolen from rxvt.
1131 if (term
.trantbl
[term
.charset
] == CS_GRAPHIC0
&&
1132 BETWEEN(u
, 0x41, 0x7e) && vt100_0
[u
- 0x41])
1133 utf8decode(vt100_0
[u
- 0x41], &u
, UTF_SIZ
);
1135 if (term
.line
[y
][x
].mode
& ATTR_WIDE
) {
1136 if (x
+1 < term
.col
) {
1137 term
.line
[y
][x
+1].u
= ' ';
1138 term
.line
[y
][x
+1].mode
&= ~ATTR_WDUMMY
;
1140 } else if (term
.line
[y
][x
].mode
& ATTR_WDUMMY
) {
1141 term
.line
[y
][x
-1].u
= ' ';
1142 term
.line
[y
][x
-1].mode
&= ~ATTR_WIDE
;
1146 term
.line
[y
][x
] = *attr
;
1147 term
.line
[y
][x
].u
= u
;
1151 tclearregion(int x1
, int y1
, int x2
, int y2
)
1157 temp
= x1
, x1
= x2
, x2
= temp
;
1159 temp
= y1
, y1
= y2
, y2
= temp
;
1161 LIMIT(x1
, 0, term
.col
-1);
1162 LIMIT(x2
, 0, term
.col
-1);
1163 LIMIT(y1
, 0, term
.row
-1);
1164 LIMIT(y2
, 0, term
.row
-1);
1166 for (y
= y1
; y
<= y2
; y
++) {
1168 for (x
= x1
; x
<= x2
; x
++) {
1169 gp
= &term
.line
[y
][x
];
1172 gp
->fg
= term
.c
.attr
.fg
;
1173 gp
->bg
= term
.c
.attr
.bg
;
1186 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1190 size
= term
.col
- src
;
1191 line
= term
.line
[term
.c
.y
];
1193 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1194 tclearregion(term
.col
-n
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1203 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1207 size
= term
.col
- dst
;
1208 line
= term
.line
[term
.c
.y
];
1210 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1211 tclearregion(src
, term
.c
.y
, dst
- 1, term
.c
.y
);
1215 tinsertblankline(int n
)
1217 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1218 tscrolldown(term
.c
.y
, n
);
1224 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1225 tscrollup(term
.c
.y
, n
);
1229 tdefcolor(int *attr
, int *npar
, int l
)
1234 switch (attr
[*npar
+ 1]) {
1235 case 2: /* direct color in RGB space */
1236 if (*npar
+ 4 >= l
) {
1238 "erresc(38): Incorrect number of parameters (%d)\n",
1242 r
= attr
[*npar
+ 2];
1243 g
= attr
[*npar
+ 3];
1244 b
= attr
[*npar
+ 4];
1246 if (!BETWEEN(r
, 0, 255) || !BETWEEN(g
, 0, 255) || !BETWEEN(b
, 0, 255))
1247 fprintf(stderr
, "erresc: bad rgb color (%u,%u,%u)\n",
1250 idx
= TRUECOLOR(r
, g
, b
);
1252 case 5: /* indexed color */
1253 if (*npar
+ 2 >= l
) {
1255 "erresc(38): Incorrect number of parameters (%d)\n",
1260 if (!BETWEEN(attr
[*npar
], 0, 255))
1261 fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[*npar
]);
1265 case 0: /* implemented defined (only foreground) */
1266 case 1: /* transparent */
1267 case 3: /* direct color in CMY space */
1268 case 4: /* direct color in CMYK space */
1271 "erresc(38): gfx attr %d unknown\n", attr
[*npar
]);
1279 tsetattr(int *attr
, int l
)
1284 for (i
= 0; i
< l
; i
++) {
1287 term
.c
.attr
.mode
&= ~(
1296 term
.c
.attr
.fg
= defaultfg
;
1297 term
.c
.attr
.bg
= defaultbg
;
1300 term
.c
.attr
.mode
|= ATTR_BOLD
;
1303 term
.c
.attr
.mode
|= ATTR_FAINT
;
1306 term
.c
.attr
.mode
|= ATTR_ITALIC
;
1309 term
.c
.attr
.mode
|= ATTR_UNDERLINE
;
1311 case 5: /* slow blink */
1313 case 6: /* rapid blink */
1314 term
.c
.attr
.mode
|= ATTR_BLINK
;
1317 term
.c
.attr
.mode
|= ATTR_REVERSE
;
1320 term
.c
.attr
.mode
|= ATTR_INVISIBLE
;
1323 term
.c
.attr
.mode
|= ATTR_STRUCK
;
1326 term
.c
.attr
.mode
&= ~(ATTR_BOLD
| ATTR_FAINT
);
1329 term
.c
.attr
.mode
&= ~ATTR_ITALIC
;
1332 term
.c
.attr
.mode
&= ~ATTR_UNDERLINE
;
1335 term
.c
.attr
.mode
&= ~ATTR_BLINK
;
1338 term
.c
.attr
.mode
&= ~ATTR_REVERSE
;
1341 term
.c
.attr
.mode
&= ~ATTR_INVISIBLE
;
1344 term
.c
.attr
.mode
&= ~ATTR_STRUCK
;
1347 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1348 term
.c
.attr
.fg
= idx
;
1351 term
.c
.attr
.fg
= defaultfg
;
1354 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1355 term
.c
.attr
.bg
= idx
;
1358 term
.c
.attr
.bg
= defaultbg
;
1361 if (BETWEEN(attr
[i
], 30, 37)) {
1362 term
.c
.attr
.fg
= attr
[i
] - 30;
1363 } else if (BETWEEN(attr
[i
], 40, 47)) {
1364 term
.c
.attr
.bg
= attr
[i
] - 40;
1365 } else if (BETWEEN(attr
[i
], 90, 97)) {
1366 term
.c
.attr
.fg
= attr
[i
] - 90 + 8;
1367 } else if (BETWEEN(attr
[i
], 100, 107)) {
1368 term
.c
.attr
.bg
= attr
[i
] - 100 + 8;
1371 "erresc(default): gfx attr %d unknown\n",
1372 attr
[i
]), csidump();
1380 tsetscroll(int t
, int b
)
1384 LIMIT(t
, 0, term
.row
-1);
1385 LIMIT(b
, 0, term
.row
-1);
1396 tsetmode(int priv
, int set
, int *args
, int narg
)
1401 for (lim
= args
+ narg
; args
< lim
; ++args
) {
1404 case 1: /* DECCKM -- Cursor key */
1405 MODBIT(term
.mode
, set
, MODE_APPCURSOR
);
1407 case 5: /* DECSCNM -- Reverse video */
1409 MODBIT(term
.mode
, set
, MODE_REVERSE
);
1410 if (mode
!= term
.mode
)
1413 case 6: /* DECOM -- Origin */
1414 MODBIT(term
.c
.state
, set
, CURSOR_ORIGIN
);
1417 case 7: /* DECAWM -- Auto wrap */
1418 MODBIT(term
.mode
, set
, MODE_WRAP
);
1420 case 0: /* Error (IGNORED) */
1421 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1422 case 3: /* DECCOLM -- Column (IGNORED) */
1423 case 4: /* DECSCLM -- Scroll (IGNORED) */
1424 case 8: /* DECARM -- Auto repeat (IGNORED) */
1425 case 18: /* DECPFF -- Printer feed (IGNORED) */
1426 case 19: /* DECPEX -- Printer extent (IGNORED) */
1427 case 42: /* DECNRCM -- National characters (IGNORED) */
1428 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1430 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1431 MODBIT(term
.mode
, !set
, MODE_HIDE
);
1433 case 9: /* X10 mouse compatibility mode */
1434 xsetpointermotion(0);
1435 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1436 MODBIT(term
.mode
, set
, MODE_MOUSEX10
);
1438 case 1000: /* 1000: report button press */
1439 xsetpointermotion(0);
1440 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1441 MODBIT(term
.mode
, set
, MODE_MOUSEBTN
);
1443 case 1002: /* 1002: report motion on button press */
1444 xsetpointermotion(0);
1445 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1446 MODBIT(term
.mode
, set
, MODE_MOUSEMOTION
);
1448 case 1003: /* 1003: enable all mouse motions */
1449 xsetpointermotion(set
);
1450 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1451 MODBIT(term
.mode
, set
, MODE_MOUSEMANY
);
1453 case 1004: /* 1004: send focus events to tty */
1454 MODBIT(term
.mode
, set
, MODE_FOCUS
);
1456 case 1006: /* 1006: extended reporting mode */
1457 MODBIT(term
.mode
, set
, MODE_MOUSESGR
);
1460 MODBIT(term
.mode
, set
, MODE_8BIT
);
1462 case 1049: /* swap screen & set/restore cursor as xterm */
1463 if (!allowaltscreen
)
1465 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1467 case 47: /* swap screen */
1469 if (!allowaltscreen
)
1471 alt
= IS_SET(MODE_ALTSCREEN
);
1473 tclearregion(0, 0, term
.col
-1,
1476 if (set
^ alt
) /* set is always 1 or 0 */
1482 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1484 case 2004: /* 2004: bracketed paste mode */
1485 MODBIT(term
.mode
, set
, MODE_BRCKTPASTE
);
1487 /* Not implemented mouse modes. See comments there. */
1488 case 1001: /* mouse highlight mode; can hang the
1489 terminal by design when implemented. */
1490 case 1005: /* UTF-8 mouse mode; will confuse
1491 applications not supporting UTF-8
1493 case 1015: /* urxvt mangled mouse mode; incompatible
1494 and can be mistaken for other control
1498 "erresc: unknown private set/reset mode %d\n",
1504 case 0: /* Error (IGNORED) */
1506 case 2: /* KAM -- keyboard action */
1507 MODBIT(term
.mode
, set
, MODE_KBDLOCK
);
1509 case 4: /* IRM -- Insertion-replacement */
1510 MODBIT(term
.mode
, set
, MODE_INSERT
);
1512 case 12: /* SRM -- Send/Receive */
1513 MODBIT(term
.mode
, !set
, MODE_ECHO
);
1515 case 20: /* LNM -- Linefeed/new line */
1516 MODBIT(term
.mode
, set
, MODE_CRLF
);
1520 "erresc: unknown set/reset mode %d\n",
1534 switch (csiescseq
.mode
[0]) {
1537 fprintf(stderr
, "erresc: unknown csi ");
1541 case '@': /* ICH -- Insert <n> blank char */
1542 DEFAULT(csiescseq
.arg
[0], 1);
1543 tinsertblank(csiescseq
.arg
[0]);
1545 case 'A': /* CUU -- Cursor <n> Up */
1546 DEFAULT(csiescseq
.arg
[0], 1);
1547 tmoveto(term
.c
.x
, term
.c
.y
-csiescseq
.arg
[0]);
1549 case 'B': /* CUD -- Cursor <n> Down */
1550 case 'e': /* VPR --Cursor <n> Down */
1551 DEFAULT(csiescseq
.arg
[0], 1);
1552 tmoveto(term
.c
.x
, term
.c
.y
+csiescseq
.arg
[0]);
1554 case 'i': /* MC -- Media Copy */
1555 switch (csiescseq
.arg
[0]) {
1560 tdumpline(term
.c
.y
);
1566 term
.mode
&= ~MODE_PRINT
;
1569 term
.mode
|= MODE_PRINT
;
1573 case 'c': /* DA -- Device Attributes */
1574 if (csiescseq
.arg
[0] == 0)
1575 ttywrite(vtiden
, sizeof(vtiden
) - 1);
1577 case 'C': /* CUF -- Cursor <n> Forward */
1578 case 'a': /* HPR -- Cursor <n> Forward */
1579 DEFAULT(csiescseq
.arg
[0], 1);
1580 tmoveto(term
.c
.x
+csiescseq
.arg
[0], term
.c
.y
);
1582 case 'D': /* CUB -- Cursor <n> Backward */
1583 DEFAULT(csiescseq
.arg
[0], 1);
1584 tmoveto(term
.c
.x
-csiescseq
.arg
[0], term
.c
.y
);
1586 case 'E': /* CNL -- Cursor <n> Down and first col */
1587 DEFAULT(csiescseq
.arg
[0], 1);
1588 tmoveto(0, term
.c
.y
+csiescseq
.arg
[0]);
1590 case 'F': /* CPL -- Cursor <n> Up and first col */
1591 DEFAULT(csiescseq
.arg
[0], 1);
1592 tmoveto(0, term
.c
.y
-csiescseq
.arg
[0]);
1594 case 'g': /* TBC -- Tabulation clear */
1595 switch (csiescseq
.arg
[0]) {
1596 case 0: /* clear current tab stop */
1597 term
.tabs
[term
.c
.x
] = 0;
1599 case 3: /* clear all the tabs */
1600 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1606 case 'G': /* CHA -- Move to <col> */
1608 DEFAULT(csiescseq
.arg
[0], 1);
1609 tmoveto(csiescseq
.arg
[0]-1, term
.c
.y
);
1611 case 'H': /* CUP -- Move to <row> <col> */
1613 DEFAULT(csiescseq
.arg
[0], 1);
1614 DEFAULT(csiescseq
.arg
[1], 1);
1615 tmoveato(csiescseq
.arg
[1]-1, csiescseq
.arg
[0]-1);
1617 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1618 DEFAULT(csiescseq
.arg
[0], 1);
1619 tputtab(csiescseq
.arg
[0]);
1621 case 'J': /* ED -- Clear screen */
1623 switch (csiescseq
.arg
[0]) {
1625 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1626 if (term
.c
.y
< term
.row
-1) {
1627 tclearregion(0, term
.c
.y
+1, term
.col
-1,
1633 tclearregion(0, 0, term
.col
-1, term
.c
.y
-1);
1634 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1637 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1643 case 'K': /* EL -- Clear line */
1644 switch (csiescseq
.arg
[0]) {
1646 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1,
1650 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1653 tclearregion(0, term
.c
.y
, term
.col
-1, term
.c
.y
);
1657 case 'S': /* SU -- Scroll <n> line up */
1658 DEFAULT(csiescseq
.arg
[0], 1);
1659 tscrollup(term
.top
, csiescseq
.arg
[0]);
1661 case 'T': /* SD -- Scroll <n> line down */
1662 DEFAULT(csiescseq
.arg
[0], 1);
1663 tscrolldown(term
.top
, csiescseq
.arg
[0]);
1665 case 'L': /* IL -- Insert <n> blank lines */
1666 DEFAULT(csiescseq
.arg
[0], 1);
1667 tinsertblankline(csiescseq
.arg
[0]);
1669 case 'l': /* RM -- Reset Mode */
1670 tsetmode(csiescseq
.priv
, 0, csiescseq
.arg
, csiescseq
.narg
);
1672 case 'M': /* DL -- Delete <n> lines */
1673 DEFAULT(csiescseq
.arg
[0], 1);
1674 tdeleteline(csiescseq
.arg
[0]);
1676 case 'X': /* ECH -- Erase <n> char */
1677 DEFAULT(csiescseq
.arg
[0], 1);
1678 tclearregion(term
.c
.x
, term
.c
.y
,
1679 term
.c
.x
+ csiescseq
.arg
[0] - 1, term
.c
.y
);
1681 case 'P': /* DCH -- Delete <n> char */
1682 DEFAULT(csiescseq
.arg
[0], 1);
1683 tdeletechar(csiescseq
.arg
[0]);
1685 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1686 DEFAULT(csiescseq
.arg
[0], 1);
1687 tputtab(-csiescseq
.arg
[0]);
1689 case 'd': /* VPA -- Move to <row> */
1690 DEFAULT(csiescseq
.arg
[0], 1);
1691 tmoveato(term
.c
.x
, csiescseq
.arg
[0]-1);
1693 case 'h': /* SM -- Set terminal mode */
1694 tsetmode(csiescseq
.priv
, 1, csiescseq
.arg
, csiescseq
.narg
);
1696 case 'm': /* SGR -- Terminal attribute (color) */
1697 tsetattr(csiescseq
.arg
, csiescseq
.narg
);
1699 case 'n': /* DSR – Device Status Report (cursor position) */
1700 if (csiescseq
.arg
[0] == 6) {
1701 len
= snprintf(buf
, sizeof(buf
),"\033[%i;%iR",
1702 term
.c
.y
+1, term
.c
.x
+1);
1706 case 'r': /* DECSTBM -- Set Scrolling Region */
1707 if (csiescseq
.priv
) {
1710 DEFAULT(csiescseq
.arg
[0], 1);
1711 DEFAULT(csiescseq
.arg
[1], term
.row
);
1712 tsetscroll(csiescseq
.arg
[0]-1, csiescseq
.arg
[1]-1);
1716 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1717 tcursor(CURSOR_SAVE
);
1719 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1720 tcursor(CURSOR_LOAD
);
1723 switch (csiescseq
.mode
[1]) {
1724 case 'q': /* DECSCUSR -- Set Cursor Style */
1725 DEFAULT(csiescseq
.arg
[0], 1);
1726 if (!BETWEEN(csiescseq
.arg
[0], 0, 6)) {
1729 win
.cursor
= csiescseq
.arg
[0];
1744 fprintf(stderr
, "ESC[");
1745 for (i
= 0; i
< csiescseq
.len
; i
++) {
1746 c
= csiescseq
.buf
[i
] & 0xff;
1749 } else if (c
== '\n') {
1750 fprintf(stderr
, "(\\n)");
1751 } else if (c
== '\r') {
1752 fprintf(stderr
, "(\\r)");
1753 } else if (c
== 0x1b) {
1754 fprintf(stderr
, "(\\e)");
1756 fprintf(stderr
, "(%02x)", c
);
1765 memset(&csiescseq
, 0, sizeof(csiescseq
));
1774 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
1776 par
= (narg
= strescseq
.narg
) ? atoi(strescseq
.args
[0]) : 0;
1778 switch (strescseq
.type
) {
1779 case ']': /* OSC -- Operating System Command */
1785 xsettitle(strescseq
.args
[1]);
1791 dec
= base64dec(strescseq
.args
[2]);
1793 xsetsel(dec
, CurrentTime
);
1796 fprintf(stderr
, "erresc: invalid base64\n");
1800 case 4: /* color set */
1803 p
= strescseq
.args
[2];
1805 case 104: /* color reset, here p = NULL */
1806 j
= (narg
> 1) ? atoi(strescseq
.args
[1]) : -1;
1807 if (xsetcolorname(j
, p
)) {
1808 fprintf(stderr
, "erresc: invalid color %s\n", p
);
1811 * TODO if defaultbg color is changed, borders
1819 case 'k': /* old title set compatibility */
1820 xsettitle(strescseq
.args
[0]);
1822 case 'P': /* DCS -- Device Control String */
1823 term
.mode
|= ESC_DCS
;
1824 case '_': /* APC -- Application Program Command */
1825 case '^': /* PM -- Privacy Message */
1829 fprintf(stderr
, "erresc: unknown str ");
1837 char *p
= strescseq
.buf
;
1840 strescseq
.buf
[strescseq
.len
] = '\0';
1845 while (strescseq
.narg
< STR_ARG_SIZ
) {
1846 strescseq
.args
[strescseq
.narg
++] = p
;
1847 while ((c
= *p
) != ';' && c
!= '\0')
1861 fprintf(stderr
, "ESC%c", strescseq
.type
);
1862 for (i
= 0; i
< strescseq
.len
; i
++) {
1863 c
= strescseq
.buf
[i
] & 0xff;
1867 } else if (isprint(c
)) {
1869 } else if (c
== '\n') {
1870 fprintf(stderr
, "(\\n)");
1871 } else if (c
== '\r') {
1872 fprintf(stderr
, "(\\r)");
1873 } else if (c
== 0x1b) {
1874 fprintf(stderr
, "(\\e)");
1876 fprintf(stderr
, "(%02x)", c
);
1879 fprintf(stderr
, "ESC\\\n");
1885 memset(&strescseq
, 0, sizeof(strescseq
));
1889 sendbreak(const Arg
*arg
)
1891 if (tcsendbreak(cmdfd
, 0))
1892 perror("Error sending break");
1896 tprinter(char *s
, size_t len
)
1898 if (iofd
!= -1 && xwrite(iofd
, s
, len
) < 0) {
1899 perror("Error writing to output file");
1906 iso14755(const Arg
*arg
)
1909 char *us
, *e
, codepoint
[9], uc
[UTF_SIZ
];
1910 unsigned long utf32
;
1912 if (!(p
= popen(ISO14755CMD
, "r")))
1915 us
= fgets(codepoint
, sizeof(codepoint
), p
);
1918 if (!us
|| *us
== '\0' || *us
== '-' || strlen(us
) > 7)
1920 if ((utf32
= strtoul(us
, &e
, 16)) == ULONG_MAX
||
1921 (*e
!= '\n' && *e
!= '\0'))
1924 ttysend(uc
, utf8encode(utf32
, uc
));
1928 toggleprinter(const Arg
*arg
)
1930 term
.mode
^= MODE_PRINT
;
1934 printscreen(const Arg
*arg
)
1940 printsel(const Arg
*arg
)
1950 if ((ptr
= getsel())) {
1951 tprinter(ptr
, strlen(ptr
));
1962 bp
= &term
.line
[n
][0];
1963 end
= &bp
[MIN(tlinelen(n
), term
.col
) - 1];
1964 if (bp
!= end
|| bp
->u
!= ' ') {
1965 for ( ;bp
<= end
; ++bp
)
1966 tprinter(buf
, utf8encode(bp
->u
, buf
));
1976 for (i
= 0; i
< term
.row
; ++i
)
1986 while (x
< term
.col
&& n
--)
1987 for (++x
; x
< term
.col
&& !term
.tabs
[x
]; ++x
)
1990 while (x
> 0 && n
++)
1991 for (--x
; x
> 0 && !term
.tabs
[x
]; --x
)
1994 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1998 tdefutf8(char ascii
)
2001 term
.mode
|= MODE_UTF8
;
2002 else if (ascii
== '@')
2003 term
.mode
&= ~MODE_UTF8
;
2007 tdeftran(char ascii
)
2009 static char cs
[] = "0B";
2010 static int vcs
[] = {CS_GRAPHIC0
, CS_USA
};
2013 if ((p
= strchr(cs
, ascii
)) == NULL
) {
2014 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
2016 term
.trantbl
[term
.icharset
] = vcs
[p
- cs
];
2025 if (c
== '8') { /* DEC screen alignment test. */
2026 for (x
= 0; x
< term
.col
; ++x
) {
2027 for (y
= 0; y
< term
.row
; ++y
)
2028 tsetchar('E', &term
.c
.attr
, x
, y
);
2034 tstrsequence(uchar c
)
2039 case 0x90: /* DCS -- Device Control String */
2041 term
.esc
|= ESC_DCS
;
2043 case 0x9f: /* APC -- Application Program Command */
2046 case 0x9e: /* PM -- Privacy Message */
2049 case 0x9d: /* OSC -- Operating System Command */
2054 term
.esc
|= ESC_STR
;
2058 tcontrolcode(uchar ascii
)
2065 tmoveto(term
.c
.x
-1, term
.c
.y
);
2068 tmoveto(0, term
.c
.y
);
2073 /* go to first col if the mode is set */
2074 tnewline(IS_SET(MODE_CRLF
));
2076 case '\a': /* BEL */
2077 if (term
.esc
& ESC_STR_END
) {
2078 /* backwards compatibility to xterm */
2084 case '\033': /* ESC */
2086 term
.esc
&= ~(ESC_CSI
|ESC_ALTCHARSET
|ESC_TEST
);
2087 term
.esc
|= ESC_START
;
2089 case '\016': /* SO (LS1 -- Locking shift 1) */
2090 case '\017': /* SI (LS0 -- Locking shift 0) */
2091 term
.charset
= 1 - (ascii
- '\016');
2093 case '\032': /* SUB */
2094 tsetchar('?', &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2095 case '\030': /* CAN */
2098 case '\005': /* ENQ (IGNORED) */
2099 case '\000': /* NUL (IGNORED) */
2100 case '\021': /* XON (IGNORED) */
2101 case '\023': /* XOFF (IGNORED) */
2102 case 0177: /* DEL (IGNORED) */
2104 case 0x80: /* TODO: PAD */
2105 case 0x81: /* TODO: HOP */
2106 case 0x82: /* TODO: BPH */
2107 case 0x83: /* TODO: NBH */
2108 case 0x84: /* TODO: IND */
2110 case 0x85: /* NEL -- Next line */
2111 tnewline(1); /* always go to first col */
2113 case 0x86: /* TODO: SSA */
2114 case 0x87: /* TODO: ESA */
2116 case 0x88: /* HTS -- Horizontal tab stop */
2117 term
.tabs
[term
.c
.x
] = 1;
2119 case 0x89: /* TODO: HTJ */
2120 case 0x8a: /* TODO: VTS */
2121 case 0x8b: /* TODO: PLD */
2122 case 0x8c: /* TODO: PLU */
2123 case 0x8d: /* TODO: RI */
2124 case 0x8e: /* TODO: SS2 */
2125 case 0x8f: /* TODO: SS3 */
2126 case 0x91: /* TODO: PU1 */
2127 case 0x92: /* TODO: PU2 */
2128 case 0x93: /* TODO: STS */
2129 case 0x94: /* TODO: CCH */
2130 case 0x95: /* TODO: MW */
2131 case 0x96: /* TODO: SPA */
2132 case 0x97: /* TODO: EPA */
2133 case 0x98: /* TODO: SOS */
2134 case 0x99: /* TODO: SGCI */
2136 case 0x9a: /* DECID -- Identify Terminal */
2137 ttywrite(vtiden
, sizeof(vtiden
) - 1);
2139 case 0x9b: /* TODO: CSI */
2140 case 0x9c: /* TODO: ST */
2142 case 0x90: /* DCS -- Device Control String */
2143 case 0x9d: /* OSC -- Operating System Command */
2144 case 0x9e: /* PM -- Privacy Message */
2145 case 0x9f: /* APC -- Application Program Command */
2146 tstrsequence(ascii
);
2149 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2150 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
2154 * returns 1 when the sequence is finished and it hasn't to read
2155 * more characters for this sequence, otherwise 0
2158 eschandle(uchar ascii
)
2162 term
.esc
|= ESC_CSI
;
2165 term
.esc
|= ESC_TEST
;
2168 term
.esc
|= ESC_UTF8
;
2170 case 'P': /* DCS -- Device Control String */
2171 case '_': /* APC -- Application Program Command */
2172 case '^': /* PM -- Privacy Message */
2173 case ']': /* OSC -- Operating System Command */
2174 case 'k': /* old title set compatibility */
2175 tstrsequence(ascii
);
2177 case 'n': /* LS2 -- Locking shift 2 */
2178 case 'o': /* LS3 -- Locking shift 3 */
2179 term
.charset
= 2 + (ascii
- 'n');
2181 case '(': /* GZD4 -- set primary charset G0 */
2182 case ')': /* G1D4 -- set secondary charset G1 */
2183 case '*': /* G2D4 -- set tertiary charset G2 */
2184 case '+': /* G3D4 -- set quaternary charset G3 */
2185 term
.icharset
= ascii
- '(';
2186 term
.esc
|= ESC_ALTCHARSET
;
2188 case 'D': /* IND -- Linefeed */
2189 if (term
.c
.y
== term
.bot
) {
2190 tscrollup(term
.top
, 1);
2192 tmoveto(term
.c
.x
, term
.c
.y
+1);
2195 case 'E': /* NEL -- Next line */
2196 tnewline(1); /* always go to first col */
2198 case 'H': /* HTS -- Horizontal tab stop */
2199 term
.tabs
[term
.c
.x
] = 1;
2201 case 'M': /* RI -- Reverse index */
2202 if (term
.c
.y
== term
.top
) {
2203 tscrolldown(term
.top
, 1);
2205 tmoveto(term
.c
.x
, term
.c
.y
-1);
2208 case 'Z': /* DECID -- Identify Terminal */
2209 ttywrite(vtiden
, sizeof(vtiden
) - 1);
2211 case 'c': /* RIS -- Reset to inital state */
2216 case '=': /* DECPAM -- Application keypad */
2217 term
.mode
|= MODE_APPKEYPAD
;
2219 case '>': /* DECPNM -- Normal keypad */
2220 term
.mode
&= ~MODE_APPKEYPAD
;
2222 case '7': /* DECSC -- Save Cursor */
2223 tcursor(CURSOR_SAVE
);
2225 case '8': /* DECRC -- Restore Cursor */
2226 tcursor(CURSOR_LOAD
);
2228 case '\\': /* ST -- String Terminator */
2229 if (term
.esc
& ESC_STR_END
)
2233 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2234 (uchar
) ascii
, isprint(ascii
)? ascii
:'.');
2248 control
= ISCONTROL(u
);
2249 if (!IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2253 len
= utf8encode(u
, c
);
2254 if (!control
&& (width
= wcwidth(u
)) == -1) {
2255 memcpy(c
, "\357\277\275", 4); /* UTF_INVALID */
2260 if (IS_SET(MODE_PRINT
))
2264 * STR sequence must be checked before anything else
2265 * because it uses all following characters until it
2266 * receives a ESC, a SUB, a ST or any other C1 control
2269 if (term
.esc
& ESC_STR
) {
2270 if (u
== '\a' || u
== 030 || u
== 032 || u
== 033 ||
2272 term
.esc
&= ~(ESC_START
|ESC_STR
|ESC_DCS
);
2273 if (IS_SET(MODE_SIXEL
)) {
2274 /* TODO: render sixel */;
2275 term
.mode
&= ~MODE_SIXEL
;
2278 term
.esc
|= ESC_STR_END
;
2279 goto check_control_code
;
2283 if (IS_SET(MODE_SIXEL
)) {
2284 /* TODO: implement sixel mode */
2287 if (term
.esc
&ESC_DCS
&& strescseq
.len
== 0 && u
== 'q')
2288 term
.mode
|= MODE_SIXEL
;
2290 if (strescseq
.len
+len
>= sizeof(strescseq
.buf
)-1) {
2292 * Here is a bug in terminals. If the user never sends
2293 * some code to stop the str or esc command, then st
2294 * will stop responding. But this is better than
2295 * silently failing with unknown characters. At least
2296 * then users will report back.
2298 * In the case users ever get fixed, here is the code:
2307 memmove(&strescseq
.buf
[strescseq
.len
], c
, len
);
2308 strescseq
.len
+= len
;
2314 * Actions of control codes must be performed as soon they arrive
2315 * because they can be embedded inside a control sequence, and
2316 * they must not cause conflicts with sequences.
2321 * control codes are not shown ever
2324 } else if (term
.esc
& ESC_START
) {
2325 if (term
.esc
& ESC_CSI
) {
2326 csiescseq
.buf
[csiescseq
.len
++] = u
;
2327 if (BETWEEN(u
, 0x40, 0x7E)
2328 || csiescseq
.len
>= \
2329 sizeof(csiescseq
.buf
)-1) {
2335 } else if (term
.esc
& ESC_UTF8
) {
2337 } else if (term
.esc
& ESC_ALTCHARSET
) {
2339 } else if (term
.esc
& ESC_TEST
) {
2344 /* sequence already finished */
2348 * All characters which form part of a sequence are not
2353 if (sel
.ob
.x
!= -1 && BETWEEN(term
.c
.y
, sel
.ob
.y
, sel
.oe
.y
))
2356 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2357 if (IS_SET(MODE_WRAP
) && (term
.c
.state
& CURSOR_WRAPNEXT
)) {
2358 gp
->mode
|= ATTR_WRAP
;
2360 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2363 if (IS_SET(MODE_INSERT
) && term
.c
.x
+width
< term
.col
)
2364 memmove(gp
+width
, gp
, (term
.col
- term
.c
.x
- width
) * sizeof(Glyph
));
2366 if (term
.c
.x
+width
> term
.col
) {
2368 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2371 tsetchar(u
, &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2374 gp
->mode
|= ATTR_WIDE
;
2375 if (term
.c
.x
+1 < term
.col
) {
2377 gp
[1].mode
= ATTR_WDUMMY
;
2380 if (term
.c
.x
+width
< term
.col
) {
2381 tmoveto(term
.c
.x
+width
, term
.c
.y
);
2383 term
.c
.state
|= CURSOR_WRAPNEXT
;
2388 twrite(const char *buf
, int buflen
, int show_ctrl
)
2394 for (n
= 0; n
< buflen
; n
+= charsize
) {
2395 if (IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2396 /* process a complete utf8 char */
2397 charsize
= utf8decode(buf
+ n
, &u
, buflen
- n
);
2404 if (show_ctrl
&& ISCONTROL(u
)) {
2409 } else if (u
!= '\n' && u
!= '\r' && u
!= '\t') {
2420 tresize(int col
, int row
)
2423 int minrow
= MIN(row
, term
.row
);
2424 int mincol
= MIN(col
, term
.col
);
2428 if (col
< 1 || row
< 1) {
2430 "tresize: error resizing to %dx%d\n", col
, row
);
2435 * slide screen to keep cursor where we expect it -
2436 * tscrollup would work here, but we can optimize to
2437 * memmove because we're freeing the earlier lines
2439 for (i
= 0; i
<= term
.c
.y
- row
; i
++) {
2443 /* ensure that both src and dst are not NULL */
2445 memmove(term
.line
, term
.line
+ i
, row
* sizeof(Line
));
2446 memmove(term
.alt
, term
.alt
+ i
, row
* sizeof(Line
));
2448 for (i
+= row
; i
< term
.row
; i
++) {
2453 /* resize to new height */
2454 term
.line
= xrealloc(term
.line
, row
* sizeof(Line
));
2455 term
.alt
= xrealloc(term
.alt
, row
* sizeof(Line
));
2456 term
.dirty
= xrealloc(term
.dirty
, row
* sizeof(*term
.dirty
));
2457 term
.tabs
= xrealloc(term
.tabs
, col
* sizeof(*term
.tabs
));
2459 /* resize each row to new width, zero-pad if needed */
2460 for (i
= 0; i
< minrow
; i
++) {
2461 term
.line
[i
] = xrealloc(term
.line
[i
], col
* sizeof(Glyph
));
2462 term
.alt
[i
] = xrealloc(term
.alt
[i
], col
* sizeof(Glyph
));
2465 /* allocate any new rows */
2466 for (/* i = minrow */; i
< row
; i
++) {
2467 term
.line
[i
] = xmalloc(col
* sizeof(Glyph
));
2468 term
.alt
[i
] = xmalloc(col
* sizeof(Glyph
));
2470 if (col
> term
.col
) {
2471 bp
= term
.tabs
+ term
.col
;
2473 memset(bp
, 0, sizeof(*term
.tabs
) * (col
- term
.col
));
2474 while (--bp
> term
.tabs
&& !*bp
)
2476 for (bp
+= tabspaces
; bp
< term
.tabs
+ col
; bp
+= tabspaces
)
2479 /* update terminal size */
2482 /* reset scrolling region */
2483 tsetscroll(0, row
-1);
2484 /* make use of the LIMIT in tmoveto */
2485 tmoveto(term
.c
.x
, term
.c
.y
);
2486 /* Clearing both screens (it makes dirty all lines) */
2488 for (i
= 0; i
< 2; i
++) {
2489 if (mincol
< col
&& 0 < minrow
) {
2490 tclearregion(mincol
, 0, col
- 1, minrow
- 1);
2492 if (0 < col
&& minrow
< row
) {
2493 tclearregion(0, minrow
, col
- 1, row
- 1);
2496 tcursor(CURSOR_LOAD
);
2515 numlock(const Arg
*dummy
)