st.c (58268B)
1 /* See LICENSE for license details. */ 2 #include <ctype.h> 3 #include <errno.h> 4 #include <fcntl.h> 5 #include <limits.h> 6 #include <pwd.h> 7 #include <stdarg.h> 8 #include <stdio.h> 9 #include <stdlib.h> 10 #include <string.h> 11 #include <signal.h> 12 #include <sys/ioctl.h> 13 #include <sys/select.h> 14 #include <sys/types.h> 15 #include <sys/wait.h> 16 #include <termios.h> 17 #include <unistd.h> 18 #include <wchar.h> 19 20 #include "st.h" 21 #include "win.h" 22 23 #if defined(__linux) 24 #include <pty.h> 25 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) 26 #include <util.h> 27 #elif defined(__FreeBSD__) || defined(__DragonFly__) 28 #include <libutil.h> 29 #endif 30 31 /* Arbitrary sizes */ 32 #define UTF_INVALID 0xFFFD 33 #define UTF_SIZ 4 34 #define ESC_BUF_SIZ (128*UTF_SIZ) 35 #define ESC_ARG_SIZ 16 36 #define STR_BUF_SIZ ESC_BUF_SIZ 37 #define STR_ARG_SIZ ESC_ARG_SIZ 38 #define HISTSIZE 2000 39 40 /* macros */ 41 #define IS_SET(flag) ((term.mode & (flag)) != 0) 42 #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == '\177') 43 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f)) 44 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) 45 #define ISDELIM(u) (u && wcschr(worddelimiters, u)) 46 #define TLINE(y) ((y) < term.scr ? term.hist[((y) + term.histi - \ 47 term.scr + HISTSIZE + 1) % HISTSIZE] : \ 48 term.line[(y) - term.scr]) 49 50 enum term_mode { 51 MODE_WRAP = 1 << 0, 52 MODE_INSERT = 1 << 1, 53 MODE_ALTSCREEN = 1 << 2, 54 MODE_CRLF = 1 << 3, 55 MODE_ECHO = 1 << 4, 56 MODE_PRINT = 1 << 5, 57 MODE_UTF8 = 1 << 6, 58 MODE_SIXEL = 1 << 7, 59 }; 60 61 enum cursor_movement { 62 CURSOR_SAVE, 63 CURSOR_LOAD 64 }; 65 66 enum cursor_state { 67 CURSOR_DEFAULT = 0, 68 CURSOR_WRAPNEXT = 1, 69 CURSOR_ORIGIN = 2 70 }; 71 72 enum charset { 73 CS_GRAPHIC0, 74 CS_GRAPHIC1, 75 CS_UK, 76 CS_USA, 77 CS_MULTI, 78 CS_GER, 79 CS_FIN 80 }; 81 82 enum escape_state { 83 ESC_START = 1, 84 ESC_CSI = 2, 85 ESC_STR = 4, /* OSC, PM, APC */ 86 ESC_ALTCHARSET = 8, 87 ESC_STR_END = 16, /* a final string was encountered */ 88 ESC_TEST = 32, /* Enter in test mode */ 89 ESC_UTF8 = 64, 90 ESC_DCS =128, 91 }; 92 93 typedef struct { 94 Glyph attr; /* current char attributes */ 95 int x; 96 int y; 97 char state; 98 } TCursor; 99 100 typedef struct { 101 int mode; 102 int type; 103 int snap; 104 /* 105 * Selection variables: 106 * nb – normalized coordinates of the beginning of the selection 107 * ne – normalized coordinates of the end of the selection 108 * ob – original coordinates of the beginning of the selection 109 * oe – original coordinates of the end of the selection 110 */ 111 struct { 112 int x, y; 113 } nb, ne, ob, oe; 114 115 int alt; 116 } Selection; 117 118 /* Internal representation of the screen */ 119 typedef struct { 120 int row; /* nb row */ 121 int col; /* nb col */ 122 Line *line; /* screen */ 123 Line *alt; /* alternate screen */ 124 Line hist[HISTSIZE]; /* history buffer */ 125 int histi; /* history index */ 126 int scr; /* scroll back */ 127 int *dirty; /* dirtyness of lines */ 128 TCursor c; /* cursor */ 129 int ocx; /* old cursor col */ 130 int ocy; /* old cursor row */ 131 int top; /* top scroll limit */ 132 int bot; /* bottom scroll limit */ 133 int mode; /* terminal mode flags */ 134 int esc; /* escape state flags */ 135 char trantbl[4]; /* charset table translation */ 136 int charset; /* current charset */ 137 int icharset; /* selected charset for sequence */ 138 int *tabs; 139 } Term; 140 141 /* CSI Escape sequence structs */ 142 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */ 143 typedef struct { 144 char buf[ESC_BUF_SIZ]; /* raw string */ 145 size_t len; /* raw string length */ 146 char priv; 147 int arg[ESC_ARG_SIZ]; 148 int narg; /* nb of args */ 149 char mode[2]; 150 } CSIEscape; 151 152 /* STR Escape sequence structs */ 153 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */ 154 typedef struct { 155 char type; /* ESC type ... */ 156 char *buf; /* allocated raw string */ 157 size_t siz; /* allocation size */ 158 size_t len; /* raw string length */ 159 char *args[STR_ARG_SIZ]; 160 int narg; /* nb of args */ 161 } STREscape; 162 163 static void execsh(char *, char **); 164 static void stty(char **); 165 static void sigchld(int); 166 static void ttywriteraw(const char *, size_t); 167 168 static void csidump(void); 169 static void csihandle(void); 170 static void csiparse(void); 171 static void csireset(void); 172 static int eschandle(uchar); 173 static void strdump(void); 174 static void strhandle(void); 175 static void strparse(void); 176 static void strreset(void); 177 178 static void tprinter(char *, size_t); 179 static void tdumpsel(void); 180 static void tdumpline(int); 181 static void tdump(void); 182 static void tclearregion(int, int, int, int); 183 static void tcursor(int); 184 static void tdeletechar(int); 185 static void tdeleteline(int); 186 static void tinsertblank(int); 187 static void tinsertblankline(int); 188 static int tlinelen(int); 189 static void tmoveto(int, int); 190 static void tmoveato(int, int); 191 static void tnewline(int); 192 static void tputtab(int); 193 static void tputc(Rune); 194 static void treset(void); 195 static void tscrollup(int, int, int); 196 static void tscrolldown(int, int, int); 197 static void tsetattr(int *, int); 198 static void tsetchar(Rune, Glyph *, int, int); 199 static void tsetdirt(int, int); 200 static void tsetscroll(int, int); 201 static void tswapscreen(void); 202 static void tsetmode(int, int, int *, int); 203 static int twrite(const char *, int, int); 204 static void tfulldirt(void); 205 static void tcontrolcode(uchar ); 206 static void tdectest(char ); 207 static void tdefutf8(char); 208 static int32_t tdefcolor(int *, int *, int); 209 static void tdeftran(char); 210 static void tstrsequence(uchar); 211 212 static void drawregion(int, int, int, int); 213 214 static void selnormalize(void); 215 static void selscroll(int, int); 216 static void selsnap(int *, int *, int); 217 218 static size_t utf8decode(const char *, Rune *, size_t); 219 static Rune utf8decodebyte(char, size_t *); 220 static char utf8encodebyte(Rune, size_t); 221 static size_t utf8validate(Rune *, size_t); 222 223 static char *base64dec(const char *); 224 static char base64dec_getc(const char **); 225 226 static ssize_t xwrite(int, const char *, size_t); 227 228 /* Globals */ 229 static Term term; 230 static Selection sel; 231 static CSIEscape csiescseq; 232 static STREscape strescseq; 233 static int iofd = 1; 234 static int cmdfd; 235 static pid_t pid; 236 237 static uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; 238 static uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; 239 static Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; 240 static Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; 241 242 ssize_t 243 xwrite(int fd, const char *s, size_t len) 244 { 245 size_t aux = len; 246 ssize_t r; 247 248 while (len > 0) { 249 r = write(fd, s, len); 250 if (r < 0) 251 return r; 252 len -= r; 253 s += r; 254 } 255 256 return aux; 257 } 258 259 void * 260 xmalloc(size_t len) 261 { 262 void *p; 263 264 if (!(p = malloc(len))) 265 die("malloc: %s\n", strerror(errno)); 266 267 return p; 268 } 269 270 void * 271 xrealloc(void *p, size_t len) 272 { 273 if ((p = realloc(p, len)) == NULL) 274 die("realloc: %s\n", strerror(errno)); 275 276 return p; 277 } 278 279 char * 280 xstrdup(char *s) 281 { 282 if ((s = strdup(s)) == NULL) 283 die("strdup: %s\n", strerror(errno)); 284 285 return s; 286 } 287 288 size_t 289 utf8decode(const char *c, Rune *u, size_t clen) 290 { 291 size_t i, j, len, type; 292 Rune udecoded; 293 294 *u = UTF_INVALID; 295 if (!clen) 296 return 0; 297 udecoded = utf8decodebyte(c[0], &len); 298 if (!BETWEEN(len, 1, UTF_SIZ)) 299 return 1; 300 for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { 301 udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); 302 if (type != 0) 303 return j; 304 } 305 if (j < len) 306 return 0; 307 *u = udecoded; 308 utf8validate(u, len); 309 310 return len; 311 } 312 313 Rune 314 utf8decodebyte(char c, size_t *i) 315 { 316 for (*i = 0; *i < LEN(utfmask); ++(*i)) 317 if (((uchar)c & utfmask[*i]) == utfbyte[*i]) 318 return (uchar)c & ~utfmask[*i]; 319 320 return 0; 321 } 322 323 size_t 324 utf8encode(Rune u, char *c) 325 { 326 size_t len, i; 327 328 len = utf8validate(&u, 0); 329 if (len > UTF_SIZ) 330 return 0; 331 332 for (i = len - 1; i != 0; --i) { 333 c[i] = utf8encodebyte(u, 0); 334 u >>= 6; 335 } 336 c[0] = utf8encodebyte(u, len); 337 338 return len; 339 } 340 341 char 342 utf8encodebyte(Rune u, size_t i) 343 { 344 return utfbyte[i] | (u & ~utfmask[i]); 345 } 346 347 size_t 348 utf8validate(Rune *u, size_t i) 349 { 350 if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) 351 *u = UTF_INVALID; 352 for (i = 1; *u > utfmax[i]; ++i) 353 ; 354 355 return i; 356 } 357 358 static const char base64_digits[] = { 359 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 360 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0, 361 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1, 362 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 363 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 364 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0, 365 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 366 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 367 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 368 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 369 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 370 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 371 }; 372 373 char 374 base64dec_getc(const char **src) 375 { 376 while (**src && !isprint(**src)) (*src)++; 377 return **src ? *((*src)++) : '='; /* emulate padding if string ends */ 378 } 379 380 char * 381 base64dec(const char *src) 382 { 383 size_t in_len = strlen(src); 384 char *result, *dst; 385 386 if (in_len % 4) 387 in_len += 4 - (in_len % 4); 388 result = dst = xmalloc(in_len / 4 * 3 + 1); 389 while (*src) { 390 int a = base64_digits[(unsigned char) base64dec_getc(&src)]; 391 int b = base64_digits[(unsigned char) base64dec_getc(&src)]; 392 int c = base64_digits[(unsigned char) base64dec_getc(&src)]; 393 int d = base64_digits[(unsigned char) base64dec_getc(&src)]; 394 395 /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */ 396 if (a == -1 || b == -1) 397 break; 398 399 *dst++ = (a << 2) | ((b & 0x30) >> 4); 400 if (c == -1) 401 break; 402 *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2); 403 if (d == -1) 404 break; 405 *dst++ = ((c & 0x03) << 6) | d; 406 } 407 *dst = '\0'; 408 return result; 409 } 410 411 void 412 selinit(void) 413 { 414 sel.mode = SEL_IDLE; 415 sel.snap = 0; 416 sel.ob.x = -1; 417 } 418 419 int 420 tlinelen(int y) 421 { 422 int i = term.col; 423 424 if (TLINE(y)[i - 1].mode & ATTR_WRAP) 425 return i; 426 427 while (i > 0 && TLINE(y)[i - 1].u == ' ') 428 --i; 429 430 return i; 431 } 432 433 void 434 selstart(int col, int row, int snap) 435 { 436 selclear(); 437 sel.mode = SEL_EMPTY; 438 sel.type = SEL_REGULAR; 439 sel.alt = IS_SET(MODE_ALTSCREEN); 440 sel.snap = snap; 441 sel.oe.x = sel.ob.x = col; 442 sel.oe.y = sel.ob.y = row; 443 selnormalize(); 444 445 if (sel.snap != 0) 446 sel.mode = SEL_READY; 447 tsetdirt(sel.nb.y, sel.ne.y); 448 } 449 450 void 451 selextend(int col, int row, int type, int done) 452 { 453 int oldey, oldex, oldsby, oldsey, oldtype; 454 455 if (sel.mode == SEL_IDLE) 456 return; 457 if (done && sel.mode == SEL_EMPTY) { 458 selclear(); 459 return; 460 } 461 462 oldey = sel.oe.y; 463 oldex = sel.oe.x; 464 oldsby = sel.nb.y; 465 oldsey = sel.ne.y; 466 oldtype = sel.type; 467 468 sel.oe.x = col; 469 sel.oe.y = row; 470 selnormalize(); 471 sel.type = type; 472 473 if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY) 474 tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey)); 475 476 sel.mode = done ? SEL_IDLE : SEL_READY; 477 } 478 479 void 480 selnormalize(void) 481 { 482 int i; 483 484 if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) { 485 sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x; 486 sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x; 487 } else { 488 sel.nb.x = MIN(sel.ob.x, sel.oe.x); 489 sel.ne.x = MAX(sel.ob.x, sel.oe.x); 490 } 491 sel.nb.y = MIN(sel.ob.y, sel.oe.y); 492 sel.ne.y = MAX(sel.ob.y, sel.oe.y); 493 494 selsnap(&sel.nb.x, &sel.nb.y, -1); 495 selsnap(&sel.ne.x, &sel.ne.y, +1); 496 497 /* expand selection over line breaks */ 498 if (sel.type == SEL_RECTANGULAR) 499 return; 500 i = tlinelen(sel.nb.y); 501 if (i < sel.nb.x) 502 sel.nb.x = i; 503 if (tlinelen(sel.ne.y) <= sel.ne.x) 504 sel.ne.x = term.col - 1; 505 } 506 507 int 508 selected(int x, int y) 509 { 510 if (sel.mode == SEL_EMPTY || sel.ob.x == -1 || 511 sel.alt != IS_SET(MODE_ALTSCREEN)) 512 return 0; 513 514 if (sel.type == SEL_RECTANGULAR) 515 return BETWEEN(y, sel.nb.y, sel.ne.y) 516 && BETWEEN(x, sel.nb.x, sel.ne.x); 517 518 return BETWEEN(y, sel.nb.y, sel.ne.y) 519 && (y != sel.nb.y || x >= sel.nb.x) 520 && (y != sel.ne.y || x <= sel.ne.x); 521 } 522 523 void 524 selsnap(int *x, int *y, int direction) 525 { 526 int newx, newy, xt, yt; 527 int delim, prevdelim; 528 Glyph *gp, *prevgp; 529 530 switch (sel.snap) { 531 case SNAP_WORD: 532 /* 533 * Snap around if the word wraps around at the end or 534 * beginning of a line. 535 */ 536 prevgp = &TLINE(*y)[*x]; 537 prevdelim = ISDELIM(prevgp->u); 538 for (;;) { 539 newx = *x + direction; 540 newy = *y; 541 if (!BETWEEN(newx, 0, term.col - 1)) { 542 newy += direction; 543 newx = (newx + term.col) % term.col; 544 if (!BETWEEN(newy, 0, term.row - 1)) 545 break; 546 547 if (direction > 0) 548 yt = *y, xt = *x; 549 else 550 yt = newy, xt = newx; 551 if (!(TLINE(yt)[xt].mode & ATTR_WRAP)) 552 break; 553 } 554 555 if (newx >= tlinelen(newy)) 556 break; 557 558 gp = &TLINE(newy)[newx]; 559 delim = ISDELIM(gp->u); 560 if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim 561 || (delim && gp->u != prevgp->u))) 562 break; 563 564 *x = newx; 565 *y = newy; 566 prevgp = gp; 567 prevdelim = delim; 568 } 569 break; 570 case SNAP_LINE: 571 /* 572 * Snap around if the the previous line or the current one 573 * has set ATTR_WRAP at its end. Then the whole next or 574 * previous line will be selected. 575 */ 576 *x = (direction < 0) ? 0 : term.col - 1; 577 if (direction < 0) { 578 for (; *y > 0; *y += direction) { 579 if (!(TLINE(*y-1)[term.col-1].mode 580 & ATTR_WRAP)) { 581 break; 582 } 583 } 584 } else if (direction > 0) { 585 for (; *y < term.row-1; *y += direction) { 586 if (!(TLINE(*y)[term.col-1].mode 587 & ATTR_WRAP)) { 588 break; 589 } 590 } 591 } 592 break; 593 } 594 } 595 596 char * 597 getsel(void) 598 { 599 char *str, *ptr; 600 int y, bufsize, lastx, linelen; 601 Glyph *gp, *last; 602 603 if (sel.ob.x == -1) 604 return NULL; 605 606 bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ; 607 ptr = str = xmalloc(bufsize); 608 609 /* append every set & selected glyph to the selection */ 610 for (y = sel.nb.y; y <= sel.ne.y; y++) { 611 if ((linelen = tlinelen(y)) == 0) { 612 *ptr++ = '\n'; 613 continue; 614 } 615 616 if (sel.type == SEL_RECTANGULAR) { 617 gp = &TLINE(y)[sel.nb.x]; 618 lastx = sel.ne.x; 619 } else { 620 gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0]; 621 lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; 622 } 623 last = &TLINE(y)[MIN(lastx, linelen-1)]; 624 while (last >= gp && last->u == ' ') 625 --last; 626 627 for ( ; gp <= last; ++gp) { 628 if (gp->mode & ATTR_WDUMMY) 629 continue; 630 631 ptr += utf8encode(gp->u, ptr); 632 } 633 634 /* 635 * Copy and pasting of line endings is inconsistent 636 * in the inconsistent terminal and GUI world. 637 * The best solution seems like to produce '\n' when 638 * something is copied from st and convert '\n' to 639 * '\r', when something to be pasted is received by 640 * st. 641 * FIXME: Fix the computer world. 642 */ 643 if ((y < sel.ne.y || lastx >= linelen) && !(last->mode & ATTR_WRAP)) 644 *ptr++ = '\n'; 645 } 646 *ptr = 0; 647 return str; 648 } 649 650 void 651 selclear(void) 652 { 653 if (sel.ob.x == -1) 654 return; 655 sel.mode = SEL_IDLE; 656 sel.ob.x = -1; 657 tsetdirt(sel.nb.y, sel.ne.y); 658 } 659 660 void 661 die(const char *errstr, ...) 662 { 663 va_list ap; 664 665 va_start(ap, errstr); 666 vfprintf(stderr, errstr, ap); 667 va_end(ap); 668 exit(1); 669 } 670 671 void 672 execsh(char *cmd, char **args) 673 { 674 char *sh, *prog; 675 const struct passwd *pw; 676 677 errno = 0; 678 if ((pw = getpwuid(getuid())) == NULL) { 679 if (errno) 680 die("getpwuid: %s\n", strerror(errno)); 681 else 682 die("who are you?\n"); 683 } 684 685 if ((sh = getenv("SHELL")) == NULL) 686 sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd; 687 688 if (args) 689 prog = args[0]; 690 else if (utmp) 691 prog = utmp; 692 else 693 prog = sh; 694 DEFAULT(args, ((char *[]) {prog, NULL})); 695 696 unsetenv("COLUMNS"); 697 unsetenv("LINES"); 698 unsetenv("TERMCAP"); 699 setenv("LOGNAME", pw->pw_name, 1); 700 setenv("USER", pw->pw_name, 1); 701 setenv("SHELL", sh, 1); 702 setenv("HOME", pw->pw_dir, 1); 703 setenv("TERM", termname, 1); 704 705 signal(SIGCHLD, SIG_DFL); 706 signal(SIGHUP, SIG_DFL); 707 signal(SIGINT, SIG_DFL); 708 signal(SIGQUIT, SIG_DFL); 709 signal(SIGTERM, SIG_DFL); 710 signal(SIGALRM, SIG_DFL); 711 712 execvp(prog, args); 713 _exit(1); 714 } 715 716 void 717 sigchld(int a) 718 { 719 int stat; 720 pid_t p; 721 722 if ((p = waitpid(pid, &stat, WNOHANG)) < 0) 723 die("waiting for pid %hd failed: %s\n", pid, strerror(errno)); 724 725 if (pid != p) 726 return; 727 728 if (WIFEXITED(stat) && WEXITSTATUS(stat)) 729 die("child exited with status %d\n", WEXITSTATUS(stat)); 730 else if (WIFSIGNALED(stat)) 731 die("child terminated due to signal %d\n", WTERMSIG(stat)); 732 exit(0); 733 } 734 735 void 736 stty(char **args) 737 { 738 char cmd[_POSIX_ARG_MAX], **p, *q, *s; 739 size_t n, siz; 740 741 if ((n = strlen(stty_args)) > sizeof(cmd)-1) 742 die("incorrect stty parameters\n"); 743 memcpy(cmd, stty_args, n); 744 q = cmd + n; 745 siz = sizeof(cmd) - n; 746 for (p = args; p && (s = *p); ++p) { 747 if ((n = strlen(s)) > siz-1) 748 die("stty parameter length too long\n"); 749 *q++ = ' '; 750 memcpy(q, s, n); 751 q += n; 752 siz -= n + 1; 753 } 754 *q = '\0'; 755 if (system(cmd) != 0) 756 perror("Couldn't call stty"); 757 } 758 759 int 760 ttynew(char *line, char *cmd, char *out, char **args) 761 { 762 int m, s; 763 764 if (out) { 765 term.mode |= MODE_PRINT; 766 iofd = (!strcmp(out, "-")) ? 767 1 : open(out, O_WRONLY | O_CREAT, 0666); 768 if (iofd < 0) { 769 fprintf(stderr, "Error opening %s:%s\n", 770 out, strerror(errno)); 771 } 772 } 773 774 if (line) { 775 if ((cmdfd = open(line, O_RDWR)) < 0) 776 die("open line '%s' failed: %s\n", 777 line, strerror(errno)); 778 dup2(cmdfd, 0); 779 stty(args); 780 return cmdfd; 781 } 782 783 /* seems to work fine on linux, openbsd and freebsd */ 784 if (openpty(&m, &s, NULL, NULL, NULL) < 0) 785 die("openpty failed: %s\n", strerror(errno)); 786 787 switch (pid = fork()) { 788 case -1: 789 die("fork failed: %s\n", strerror(errno)); 790 break; 791 case 0: 792 close(iofd); 793 setsid(); /* create a new process group */ 794 dup2(s, 0); 795 dup2(s, 1); 796 dup2(s, 2); 797 if (ioctl(s, TIOCSCTTY, NULL) < 0) 798 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno)); 799 close(s); 800 close(m); 801 #ifdef __OpenBSD__ 802 if (pledge("stdio getpw proc exec", NULL) == -1) 803 die("pledge\n"); 804 #endif 805 execsh(cmd, args); 806 break; 807 default: 808 #ifdef __OpenBSD__ 809 if (pledge("stdio rpath tty proc", NULL) == -1) 810 die("pledge\n"); 811 #endif 812 close(s); 813 cmdfd = m; 814 signal(SIGCHLD, sigchld); 815 break; 816 } 817 return cmdfd; 818 } 819 820 size_t 821 ttyread(void) 822 { 823 static char buf[BUFSIZ]; 824 static int buflen = 0; 825 int written; 826 int ret; 827 828 /* append read bytes to unprocessed bytes */ 829 if ((ret = read(cmdfd, buf+buflen, LEN(buf)-buflen)) < 0) 830 die("couldn't read from shell: %s\n", strerror(errno)); 831 buflen += ret; 832 833 written = twrite(buf, buflen, 0); 834 buflen -= written; 835 /* keep any uncomplete utf8 char for the next call */ 836 if (buflen > 0) 837 memmove(buf, buf + written, buflen); 838 839 return ret; 840 } 841 842 void 843 ttywrite(const char *s, size_t n, int may_echo) 844 { 845 const char *next; 846 Arg arg = (Arg) { .i = term.scr }; 847 848 kscrolldown(&arg); 849 850 if (may_echo && IS_SET(MODE_ECHO)) 851 twrite(s, n, 1); 852 853 if (!IS_SET(MODE_CRLF)) { 854 ttywriteraw(s, n); 855 return; 856 } 857 858 /* This is similar to how the kernel handles ONLCR for ttys */ 859 while (n > 0) { 860 if (*s == '\r') { 861 next = s + 1; 862 ttywriteraw("\r\n", 2); 863 } else { 864 next = memchr(s, '\r', n); 865 DEFAULT(next, s + n); 866 ttywriteraw(s, next - s); 867 } 868 n -= next - s; 869 s = next; 870 } 871 } 872 873 void 874 ttywriteraw(const char *s, size_t n) 875 { 876 fd_set wfd, rfd; 877 ssize_t r; 878 size_t lim = 256; 879 880 /* 881 * Remember that we are using a pty, which might be a modem line. 882 * Writing too much will clog the line. That's why we are doing this 883 * dance. 884 * FIXME: Migrate the world to Plan 9. 885 */ 886 while (n > 0) { 887 FD_ZERO(&wfd); 888 FD_ZERO(&rfd); 889 FD_SET(cmdfd, &wfd); 890 FD_SET(cmdfd, &rfd); 891 892 /* Check if we can write. */ 893 if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) { 894 if (errno == EINTR) 895 continue; 896 die("select failed: %s\n", strerror(errno)); 897 } 898 if (FD_ISSET(cmdfd, &wfd)) { 899 /* 900 * Only write the bytes written by ttywrite() or the 901 * default of 256. This seems to be a reasonable value 902 * for a serial line. Bigger values might clog the I/O. 903 */ 904 if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0) 905 goto write_error; 906 if (r < n) { 907 /* 908 * We weren't able to write out everything. 909 * This means the buffer is getting full 910 * again. Empty it. 911 */ 912 if (n < lim) 913 lim = ttyread(); 914 n -= r; 915 s += r; 916 } else { 917 /* All bytes have been written. */ 918 break; 919 } 920 } 921 if (FD_ISSET(cmdfd, &rfd)) 922 lim = ttyread(); 923 } 924 return; 925 926 write_error: 927 die("write error on tty: %s\n", strerror(errno)); 928 } 929 930 void 931 ttyresize(int tw, int th) 932 { 933 struct winsize w; 934 935 w.ws_row = term.row; 936 w.ws_col = term.col; 937 w.ws_xpixel = tw; 938 w.ws_ypixel = th; 939 if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0) 940 fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno)); 941 } 942 943 void 944 ttyhangup() 945 { 946 /* Send SIGHUP to shell */ 947 kill(pid, SIGHUP); 948 } 949 950 int 951 tattrset(int attr) 952 { 953 int i, j; 954 955 for (i = 0; i < term.row-1; i++) { 956 for (j = 0; j < term.col-1; j++) { 957 if (term.line[i][j].mode & attr) 958 return 1; 959 } 960 } 961 962 return 0; 963 } 964 965 void 966 tsetdirt(int top, int bot) 967 { 968 int i; 969 970 LIMIT(top, 0, term.row-1); 971 LIMIT(bot, 0, term.row-1); 972 973 for (i = top; i <= bot; i++) 974 term.dirty[i] = 1; 975 } 976 977 void 978 tsetdirtattr(int attr) 979 { 980 int i, j; 981 982 for (i = 0; i < term.row-1; i++) { 983 for (j = 0; j < term.col-1; j++) { 984 if (term.line[i][j].mode & attr) { 985 tsetdirt(i, i); 986 break; 987 } 988 } 989 } 990 } 991 992 void 993 tfulldirt(void) 994 { 995 tsetdirt(0, term.row-1); 996 } 997 998 void 999 tcursor(int mode) 1000 { 1001 static TCursor c[2]; 1002 int alt = IS_SET(MODE_ALTSCREEN); 1003 1004 if (mode == CURSOR_SAVE) { 1005 c[alt] = term.c; 1006 } else if (mode == CURSOR_LOAD) { 1007 term.c = c[alt]; 1008 tmoveto(c[alt].x, c[alt].y); 1009 } 1010 } 1011 1012 void 1013 treset(void) 1014 { 1015 uint i; 1016 1017 term.c = (TCursor){{ 1018 .mode = ATTR_NULL, 1019 .fg = defaultfg, 1020 .bg = defaultbg 1021 }, .x = 0, .y = 0, .state = CURSOR_DEFAULT}; 1022 1023 memset(term.tabs, 0, term.col * sizeof(*term.tabs)); 1024 for (i = tabspaces; i < term.col; i += tabspaces) 1025 term.tabs[i] = 1; 1026 term.top = 0; 1027 term.bot = term.row - 1; 1028 term.mode = MODE_WRAP|MODE_UTF8; 1029 memset(term.trantbl, CS_USA, sizeof(term.trantbl)); 1030 term.charset = 0; 1031 1032 for (i = 0; i < 2; i++) { 1033 tmoveto(0, 0); 1034 tcursor(CURSOR_SAVE); 1035 tclearregion(0, 0, term.col-1, term.row-1); 1036 tswapscreen(); 1037 } 1038 } 1039 1040 void 1041 tnew(int col, int row) 1042 { 1043 term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } }; 1044 tresize(col, row); 1045 treset(); 1046 } 1047 1048 void 1049 tswapscreen(void) 1050 { 1051 Line *tmp = term.line; 1052 1053 term.line = term.alt; 1054 term.alt = tmp; 1055 term.mode ^= MODE_ALTSCREEN; 1056 tfulldirt(); 1057 } 1058 void 1059 kscrolldown(const Arg* a) 1060 { 1061 int n = a->i; 1062 1063 if (n < 0) 1064 n = term.row + n; 1065 1066 if (n > term.scr) 1067 n = term.scr; 1068 1069 if (term.scr > 0) { 1070 term.scr -= n; 1071 selscroll(0, -n); 1072 tfulldirt(); 1073 } 1074 } 1075 1076 void 1077 kscrollup(const Arg* a) 1078 { 1079 int n = a->i; 1080 if (n < 0) 1081 n = term.row + n; 1082 1083 if (term.scr <= HISTSIZE-n) { 1084 term.scr += n; 1085 selscroll(0, n); 1086 tfulldirt(); 1087 } 1088 } 1089 1090 void 1091 tscrolldown(int orig, int n, int copyhist) 1092 { 1093 int i; 1094 Line temp; 1095 1096 LIMIT(n, 0, term.bot-orig+1); 1097 1098 if (copyhist) { 1099 term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE; 1100 temp = term.hist[term.histi]; 1101 term.hist[term.histi] = term.line[term.bot]; 1102 term.line[term.bot] = temp; 1103 } 1104 1105 tsetdirt(orig, term.bot-n); 1106 tclearregion(0, term.bot-n+1, term.col-1, term.bot); 1107 1108 for (i = term.bot; i >= orig+n; i--) { 1109 temp = term.line[i]; 1110 term.line[i] = term.line[i-n]; 1111 term.line[i-n] = temp; 1112 } 1113 1114 selscroll(orig, n); 1115 } 1116 1117 void 1118 tscrollup(int orig, int n, int copyhist) 1119 { 1120 int i; 1121 Line temp; 1122 1123 LIMIT(n, 0, term.bot-orig+1); 1124 1125 if (copyhist) { 1126 term.histi = (term.histi + 1) % HISTSIZE; 1127 temp = term.hist[term.histi]; 1128 term.hist[term.histi] = term.line[orig]; 1129 term.line[orig] = temp; 1130 } 1131 1132 if (term.scr > 0 && term.scr < HISTSIZE) 1133 term.scr = MIN(term.scr + n, HISTSIZE-1); 1134 1135 tclearregion(0, orig, term.col-1, orig+n-1); 1136 tsetdirt(orig+n, term.bot); 1137 1138 for (i = orig; i <= term.bot-n; i++) { 1139 temp = term.line[i]; 1140 term.line[i] = term.line[i+n]; 1141 term.line[i+n] = temp; 1142 } 1143 1144 selscroll(orig, -n); 1145 } 1146 1147 void 1148 selscroll(int orig, int n) 1149 { 1150 if (sel.ob.x == -1) 1151 return; 1152 1153 if (BETWEEN(sel.ob.y, orig, term.bot) || BETWEEN(sel.oe.y, orig, term.bot)) { 1154 if ((sel.ob.y += n) > term.bot || (sel.oe.y += n) < term.top) { 1155 selclear(); 1156 return; 1157 } 1158 if (sel.type == SEL_RECTANGULAR) { 1159 if (sel.ob.y < term.top) 1160 sel.ob.y = term.top; 1161 if (sel.oe.y > term.bot) 1162 sel.oe.y = term.bot; 1163 } else { 1164 if (sel.ob.y < term.top) { 1165 sel.ob.y = term.top; 1166 sel.ob.x = 0; 1167 } 1168 if (sel.oe.y > term.bot) { 1169 sel.oe.y = term.bot; 1170 sel.oe.x = term.col; 1171 } 1172 } 1173 selnormalize(); 1174 } 1175 } 1176 1177 void 1178 tnewline(int first_col) 1179 { 1180 int y = term.c.y; 1181 1182 if (y == term.bot) { 1183 tscrollup(term.top, 1, 1); 1184 } else { 1185 y++; 1186 } 1187 tmoveto(first_col ? 0 : term.c.x, y); 1188 } 1189 1190 void 1191 csiparse(void) 1192 { 1193 char *p = csiescseq.buf, *np; 1194 long int v; 1195 1196 csiescseq.narg = 0; 1197 if (*p == '?') { 1198 csiescseq.priv = 1; 1199 p++; 1200 } 1201 1202 csiescseq.buf[csiescseq.len] = '\0'; 1203 while (p < csiescseq.buf+csiescseq.len) { 1204 np = NULL; 1205 v = strtol(p, &np, 10); 1206 if (np == p) 1207 v = 0; 1208 if (v == LONG_MAX || v == LONG_MIN) 1209 v = -1; 1210 csiescseq.arg[csiescseq.narg++] = v; 1211 p = np; 1212 if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ) 1213 break; 1214 p++; 1215 } 1216 csiescseq.mode[0] = *p++; 1217 csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0'; 1218 } 1219 1220 /* for absolute user moves, when decom is set */ 1221 void 1222 tmoveato(int x, int y) 1223 { 1224 tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0)); 1225 } 1226 1227 void 1228 tmoveto(int x, int y) 1229 { 1230 int miny, maxy; 1231 1232 if (term.c.state & CURSOR_ORIGIN) { 1233 miny = term.top; 1234 maxy = term.bot; 1235 } else { 1236 miny = 0; 1237 maxy = term.row - 1; 1238 } 1239 term.c.state &= ~CURSOR_WRAPNEXT; 1240 term.c.x = LIMIT(x, 0, term.col-1); 1241 term.c.y = LIMIT(y, miny, maxy); 1242 } 1243 1244 void 1245 tsetchar(Rune u, Glyph *attr, int x, int y) 1246 { 1247 static char *vt100_0[62] = { /* 0x41 - 0x7e */ 1248 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */ 1249 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */ 1250 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */ 1251 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */ 1252 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */ 1253 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */ 1254 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */ 1255 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */ 1256 }; 1257 1258 /* 1259 * The table is proudly stolen from rxvt. 1260 */ 1261 if (term.trantbl[term.charset] == CS_GRAPHIC0 && 1262 BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41]) 1263 utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ); 1264 1265 if (term.line[y][x].mode & ATTR_WIDE) { 1266 if (x+1 < term.col) { 1267 term.line[y][x+1].u = ' '; 1268 term.line[y][x+1].mode &= ~ATTR_WDUMMY; 1269 } 1270 } else if (term.line[y][x].mode & ATTR_WDUMMY) { 1271 term.line[y][x-1].u = ' '; 1272 term.line[y][x-1].mode &= ~ATTR_WIDE; 1273 } 1274 1275 term.dirty[y] = 1; 1276 term.line[y][x] = *attr; 1277 term.line[y][x].u = u; 1278 } 1279 1280 void 1281 tclearregion(int x1, int y1, int x2, int y2) 1282 { 1283 int x, y, temp; 1284 Glyph *gp; 1285 1286 if (x1 > x2) 1287 temp = x1, x1 = x2, x2 = temp; 1288 if (y1 > y2) 1289 temp = y1, y1 = y2, y2 = temp; 1290 1291 LIMIT(x1, 0, term.col-1); 1292 LIMIT(x2, 0, term.col-1); 1293 LIMIT(y1, 0, term.row-1); 1294 LIMIT(y2, 0, term.row-1); 1295 1296 for (y = y1; y <= y2; y++) { 1297 term.dirty[y] = 1; 1298 for (x = x1; x <= x2; x++) { 1299 gp = &term.line[y][x]; 1300 if (selected(x, y)) 1301 selclear(); 1302 gp->fg = term.c.attr.fg; 1303 gp->bg = term.c.attr.bg; 1304 gp->mode = 0; 1305 gp->u = ' '; 1306 } 1307 } 1308 } 1309 1310 void 1311 tdeletechar(int n) 1312 { 1313 int dst, src, size; 1314 Glyph *line; 1315 1316 LIMIT(n, 0, term.col - term.c.x); 1317 1318 dst = term.c.x; 1319 src = term.c.x + n; 1320 size = term.col - src; 1321 line = term.line[term.c.y]; 1322 1323 memmove(&line[dst], &line[src], size * sizeof(Glyph)); 1324 tclearregion(term.col-n, term.c.y, term.col-1, term.c.y); 1325 } 1326 1327 void 1328 tinsertblank(int n) 1329 { 1330 int dst, src, size; 1331 Glyph *line; 1332 1333 LIMIT(n, 0, term.col - term.c.x); 1334 1335 dst = term.c.x + n; 1336 src = term.c.x; 1337 size = term.col - dst; 1338 line = term.line[term.c.y]; 1339 1340 memmove(&line[dst], &line[src], size * sizeof(Glyph)); 1341 tclearregion(src, term.c.y, dst - 1, term.c.y); 1342 } 1343 1344 void 1345 tinsertblankline(int n) 1346 { 1347 if (BETWEEN(term.c.y, term.top, term.bot)) 1348 tscrolldown(term.c.y, n, 0); 1349 } 1350 1351 void 1352 tdeleteline(int n) 1353 { 1354 if (BETWEEN(term.c.y, term.top, term.bot)) 1355 tscrollup(term.c.y, n, 0); 1356 } 1357 1358 int32_t 1359 tdefcolor(int *attr, int *npar, int l) 1360 { 1361 int32_t idx = -1; 1362 uint r, g, b; 1363 1364 switch (attr[*npar + 1]) { 1365 case 2: /* direct color in RGB space */ 1366 if (*npar + 4 >= l) { 1367 fprintf(stderr, 1368 "erresc(38): Incorrect number of parameters (%d)\n", 1369 *npar); 1370 break; 1371 } 1372 r = attr[*npar + 2]; 1373 g = attr[*npar + 3]; 1374 b = attr[*npar + 4]; 1375 *npar += 4; 1376 if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255)) 1377 fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n", 1378 r, g, b); 1379 else 1380 idx = TRUECOLOR(r, g, b); 1381 break; 1382 case 5: /* indexed color */ 1383 if (*npar + 2 >= l) { 1384 fprintf(stderr, 1385 "erresc(38): Incorrect number of parameters (%d)\n", 1386 *npar); 1387 break; 1388 } 1389 *npar += 2; 1390 if (!BETWEEN(attr[*npar], 0, 255)) 1391 fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]); 1392 else 1393 idx = attr[*npar]; 1394 break; 1395 case 0: /* implemented defined (only foreground) */ 1396 case 1: /* transparent */ 1397 case 3: /* direct color in CMY space */ 1398 case 4: /* direct color in CMYK space */ 1399 default: 1400 fprintf(stderr, 1401 "erresc(38): gfx attr %d unknown\n", attr[*npar]); 1402 break; 1403 } 1404 1405 return idx; 1406 } 1407 1408 void 1409 tsetattr(int *attr, int l) 1410 { 1411 int i; 1412 int32_t idx; 1413 1414 for (i = 0; i < l; i++) { 1415 switch (attr[i]) { 1416 case 0: 1417 term.c.attr.mode &= ~( 1418 ATTR_BOLD | 1419 ATTR_FAINT | 1420 ATTR_ITALIC | 1421 ATTR_UNDERLINE | 1422 ATTR_BLINK | 1423 ATTR_REVERSE | 1424 ATTR_INVISIBLE | 1425 ATTR_STRUCK ); 1426 term.c.attr.fg = defaultfg; 1427 term.c.attr.bg = defaultbg; 1428 break; 1429 case 1: 1430 term.c.attr.mode |= ATTR_BOLD; 1431 break; 1432 case 2: 1433 term.c.attr.mode |= ATTR_FAINT; 1434 break; 1435 case 3: 1436 term.c.attr.mode |= ATTR_ITALIC; 1437 break; 1438 case 4: 1439 term.c.attr.mode |= ATTR_UNDERLINE; 1440 break; 1441 case 5: /* slow blink */ 1442 /* FALLTHROUGH */ 1443 case 6: /* rapid blink */ 1444 term.c.attr.mode |= ATTR_BLINK; 1445 break; 1446 case 7: 1447 term.c.attr.mode |= ATTR_REVERSE; 1448 break; 1449 case 8: 1450 term.c.attr.mode |= ATTR_INVISIBLE; 1451 break; 1452 case 9: 1453 term.c.attr.mode |= ATTR_STRUCK; 1454 break; 1455 case 22: 1456 term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT); 1457 break; 1458 case 23: 1459 term.c.attr.mode &= ~ATTR_ITALIC; 1460 break; 1461 case 24: 1462 term.c.attr.mode &= ~ATTR_UNDERLINE; 1463 break; 1464 case 25: 1465 term.c.attr.mode &= ~ATTR_BLINK; 1466 break; 1467 case 27: 1468 term.c.attr.mode &= ~ATTR_REVERSE; 1469 break; 1470 case 28: 1471 term.c.attr.mode &= ~ATTR_INVISIBLE; 1472 break; 1473 case 29: 1474 term.c.attr.mode &= ~ATTR_STRUCK; 1475 break; 1476 case 38: 1477 if ((idx = tdefcolor(attr, &i, l)) >= 0) 1478 term.c.attr.fg = idx; 1479 break; 1480 case 39: 1481 term.c.attr.fg = defaultfg; 1482 break; 1483 case 48: 1484 if ((idx = tdefcolor(attr, &i, l)) >= 0) 1485 term.c.attr.bg = idx; 1486 break; 1487 case 49: 1488 term.c.attr.bg = defaultbg; 1489 break; 1490 default: 1491 if (BETWEEN(attr[i], 30, 37)) { 1492 term.c.attr.fg = attr[i] - 30; 1493 } else if (BETWEEN(attr[i], 40, 47)) { 1494 term.c.attr.bg = attr[i] - 40; 1495 } else if (BETWEEN(attr[i], 90, 97)) { 1496 term.c.attr.fg = attr[i] - 90 + 8; 1497 } else if (BETWEEN(attr[i], 100, 107)) { 1498 term.c.attr.bg = attr[i] - 100 + 8; 1499 } else { 1500 fprintf(stderr, 1501 "erresc(default): gfx attr %d unknown\n", 1502 attr[i]); 1503 csidump(); 1504 } 1505 break; 1506 } 1507 } 1508 } 1509 1510 void 1511 tsetscroll(int t, int b) 1512 { 1513 int temp; 1514 1515 LIMIT(t, 0, term.row-1); 1516 LIMIT(b, 0, term.row-1); 1517 if (t > b) { 1518 temp = t; 1519 t = b; 1520 b = temp; 1521 } 1522 term.top = t; 1523 term.bot = b; 1524 } 1525 1526 void 1527 tsetmode(int priv, int set, int *args, int narg) 1528 { 1529 int alt, *lim; 1530 1531 for (lim = args + narg; args < lim; ++args) { 1532 if (priv) { 1533 switch (*args) { 1534 case 1: /* DECCKM -- Cursor key */ 1535 xsetmode(set, MODE_APPCURSOR); 1536 break; 1537 case 5: /* DECSCNM -- Reverse video */ 1538 xsetmode(set, MODE_REVERSE); 1539 break; 1540 case 6: /* DECOM -- Origin */ 1541 MODBIT(term.c.state, set, CURSOR_ORIGIN); 1542 tmoveato(0, 0); 1543 break; 1544 case 7: /* DECAWM -- Auto wrap */ 1545 MODBIT(term.mode, set, MODE_WRAP); 1546 break; 1547 case 0: /* Error (IGNORED) */ 1548 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */ 1549 case 3: /* DECCOLM -- Column (IGNORED) */ 1550 case 4: /* DECSCLM -- Scroll (IGNORED) */ 1551 case 8: /* DECARM -- Auto repeat (IGNORED) */ 1552 case 18: /* DECPFF -- Printer feed (IGNORED) */ 1553 case 19: /* DECPEX -- Printer extent (IGNORED) */ 1554 case 42: /* DECNRCM -- National characters (IGNORED) */ 1555 case 12: /* att610 -- Start blinking cursor (IGNORED) */ 1556 break; 1557 case 25: /* DECTCEM -- Text Cursor Enable Mode */ 1558 xsetmode(!set, MODE_HIDE); 1559 break; 1560 case 9: /* X10 mouse compatibility mode */ 1561 xsetpointermotion(0); 1562 xsetmode(0, MODE_MOUSE); 1563 xsetmode(set, MODE_MOUSEX10); 1564 break; 1565 case 1000: /* 1000: report button press */ 1566 xsetpointermotion(0); 1567 xsetmode(0, MODE_MOUSE); 1568 xsetmode(set, MODE_MOUSEBTN); 1569 break; 1570 case 1002: /* 1002: report motion on button press */ 1571 xsetpointermotion(0); 1572 xsetmode(0, MODE_MOUSE); 1573 xsetmode(set, MODE_MOUSEMOTION); 1574 break; 1575 case 1003: /* 1003: enable all mouse motions */ 1576 xsetpointermotion(set); 1577 xsetmode(0, MODE_MOUSE); 1578 xsetmode(set, MODE_MOUSEMANY); 1579 break; 1580 case 1004: /* 1004: send focus events to tty */ 1581 xsetmode(set, MODE_FOCUS); 1582 break; 1583 case 1006: /* 1006: extended reporting mode */ 1584 xsetmode(set, MODE_MOUSESGR); 1585 break; 1586 case 1034: 1587 xsetmode(set, MODE_8BIT); 1588 break; 1589 case 1049: /* swap screen & set/restore cursor as xterm */ 1590 if (!allowaltscreen) 1591 break; 1592 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); 1593 /* FALLTHROUGH */ 1594 case 47: /* swap screen */ 1595 case 1047: 1596 if (!allowaltscreen) 1597 break; 1598 alt = IS_SET(MODE_ALTSCREEN); 1599 if (alt) { 1600 tclearregion(0, 0, term.col-1, 1601 term.row-1); 1602 } 1603 if (set ^ alt) /* set is always 1 or 0 */ 1604 tswapscreen(); 1605 if (*args != 1049) 1606 break; 1607 /* FALLTHROUGH */ 1608 case 1048: 1609 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); 1610 break; 1611 case 2004: /* 2004: bracketed paste mode */ 1612 xsetmode(set, MODE_BRCKTPASTE); 1613 break; 1614 /* Not implemented mouse modes. See comments there. */ 1615 case 1001: /* mouse highlight mode; can hang the 1616 terminal by design when implemented. */ 1617 case 1005: /* UTF-8 mouse mode; will confuse 1618 applications not supporting UTF-8 1619 and luit. */ 1620 case 1015: /* urxvt mangled mouse mode; incompatible 1621 and can be mistaken for other control 1622 codes. */ 1623 break; 1624 default: 1625 fprintf(stderr, 1626 "erresc: unknown private set/reset mode %d\n", 1627 *args); 1628 break; 1629 } 1630 } else { 1631 switch (*args) { 1632 case 0: /* Error (IGNORED) */ 1633 break; 1634 case 2: 1635 xsetmode(set, MODE_KBDLOCK); 1636 break; 1637 case 4: /* IRM -- Insertion-replacement */ 1638 MODBIT(term.mode, set, MODE_INSERT); 1639 break; 1640 case 12: /* SRM -- Send/Receive */ 1641 MODBIT(term.mode, !set, MODE_ECHO); 1642 break; 1643 case 20: /* LNM -- Linefeed/new line */ 1644 MODBIT(term.mode, set, MODE_CRLF); 1645 break; 1646 default: 1647 fprintf(stderr, 1648 "erresc: unknown set/reset mode %d\n", 1649 *args); 1650 break; 1651 } 1652 } 1653 } 1654 } 1655 1656 void 1657 csihandle(void) 1658 { 1659 char buf[40]; 1660 int len; 1661 1662 switch (csiescseq.mode[0]) { 1663 default: 1664 unknown: 1665 fprintf(stderr, "erresc: unknown csi "); 1666 csidump(); 1667 /* die(""); */ 1668 break; 1669 case '@': /* ICH -- Insert <n> blank char */ 1670 DEFAULT(csiescseq.arg[0], 1); 1671 tinsertblank(csiescseq.arg[0]); 1672 break; 1673 case 'A': /* CUU -- Cursor <n> Up */ 1674 DEFAULT(csiescseq.arg[0], 1); 1675 tmoveto(term.c.x, term.c.y-csiescseq.arg[0]); 1676 break; 1677 case 'B': /* CUD -- Cursor <n> Down */ 1678 case 'e': /* VPR --Cursor <n> Down */ 1679 DEFAULT(csiescseq.arg[0], 1); 1680 tmoveto(term.c.x, term.c.y+csiescseq.arg[0]); 1681 break; 1682 case 'i': /* MC -- Media Copy */ 1683 switch (csiescseq.arg[0]) { 1684 case 0: 1685 tdump(); 1686 break; 1687 case 1: 1688 tdumpline(term.c.y); 1689 break; 1690 case 2: 1691 tdumpsel(); 1692 break; 1693 case 4: 1694 term.mode &= ~MODE_PRINT; 1695 break; 1696 case 5: 1697 term.mode |= MODE_PRINT; 1698 break; 1699 } 1700 break; 1701 case 'c': /* DA -- Device Attributes */ 1702 if (csiescseq.arg[0] == 0) 1703 ttywrite(vtiden, strlen(vtiden), 0); 1704 break; 1705 case 'C': /* CUF -- Cursor <n> Forward */ 1706 case 'a': /* HPR -- Cursor <n> Forward */ 1707 DEFAULT(csiescseq.arg[0], 1); 1708 tmoveto(term.c.x+csiescseq.arg[0], term.c.y); 1709 break; 1710 case 'D': /* CUB -- Cursor <n> Backward */ 1711 DEFAULT(csiescseq.arg[0], 1); 1712 tmoveto(term.c.x-csiescseq.arg[0], term.c.y); 1713 break; 1714 case 'E': /* CNL -- Cursor <n> Down and first col */ 1715 DEFAULT(csiescseq.arg[0], 1); 1716 tmoveto(0, term.c.y+csiescseq.arg[0]); 1717 break; 1718 case 'F': /* CPL -- Cursor <n> Up and first col */ 1719 DEFAULT(csiescseq.arg[0], 1); 1720 tmoveto(0, term.c.y-csiescseq.arg[0]); 1721 break; 1722 case 'g': /* TBC -- Tabulation clear */ 1723 switch (csiescseq.arg[0]) { 1724 case 0: /* clear current tab stop */ 1725 term.tabs[term.c.x] = 0; 1726 break; 1727 case 3: /* clear all the tabs */ 1728 memset(term.tabs, 0, term.col * sizeof(*term.tabs)); 1729 break; 1730 default: 1731 goto unknown; 1732 } 1733 break; 1734 case 'G': /* CHA -- Move to <col> */ 1735 case '`': /* HPA */ 1736 DEFAULT(csiescseq.arg[0], 1); 1737 tmoveto(csiescseq.arg[0]-1, term.c.y); 1738 break; 1739 case 'H': /* CUP -- Move to <row> <col> */ 1740 case 'f': /* HVP */ 1741 DEFAULT(csiescseq.arg[0], 1); 1742 DEFAULT(csiescseq.arg[1], 1); 1743 tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1); 1744 break; 1745 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */ 1746 DEFAULT(csiescseq.arg[0], 1); 1747 tputtab(csiescseq.arg[0]); 1748 break; 1749 case 'J': /* ED -- Clear screen */ 1750 switch (csiescseq.arg[0]) { 1751 case 0: /* below */ 1752 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y); 1753 if (term.c.y < term.row-1) { 1754 tclearregion(0, term.c.y+1, term.col-1, 1755 term.row-1); 1756 } 1757 break; 1758 case 1: /* above */ 1759 if (term.c.y > 1) 1760 tclearregion(0, 0, term.col-1, term.c.y-1); 1761 tclearregion(0, term.c.y, term.c.x, term.c.y); 1762 break; 1763 case 2: /* all */ 1764 tclearregion(0, 0, term.col-1, term.row-1); 1765 break; 1766 default: 1767 goto unknown; 1768 } 1769 break; 1770 case 'K': /* EL -- Clear line */ 1771 switch (csiescseq.arg[0]) { 1772 case 0: /* right */ 1773 tclearregion(term.c.x, term.c.y, term.col-1, 1774 term.c.y); 1775 break; 1776 case 1: /* left */ 1777 tclearregion(0, term.c.y, term.c.x, term.c.y); 1778 break; 1779 case 2: /* all */ 1780 tclearregion(0, term.c.y, term.col-1, term.c.y); 1781 break; 1782 } 1783 break; 1784 case 'S': /* SU -- Scroll <n> line up */ 1785 DEFAULT(csiescseq.arg[0], 1); 1786 tscrollup(term.top, csiescseq.arg[0], 0); 1787 break; 1788 case 'T': /* SD -- Scroll <n> line down */ 1789 DEFAULT(csiescseq.arg[0], 1); 1790 tscrolldown(term.top, csiescseq.arg[0], 0); 1791 break; 1792 case 'L': /* IL -- Insert <n> blank lines */ 1793 DEFAULT(csiescseq.arg[0], 1); 1794 tinsertblankline(csiescseq.arg[0]); 1795 break; 1796 case 'l': /* RM -- Reset Mode */ 1797 tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg); 1798 break; 1799 case 'M': /* DL -- Delete <n> lines */ 1800 DEFAULT(csiescseq.arg[0], 1); 1801 tdeleteline(csiescseq.arg[0]); 1802 break; 1803 case 'X': /* ECH -- Erase <n> char */ 1804 DEFAULT(csiescseq.arg[0], 1); 1805 tclearregion(term.c.x, term.c.y, 1806 term.c.x + csiescseq.arg[0] - 1, term.c.y); 1807 break; 1808 case 'P': /* DCH -- Delete <n> char */ 1809 DEFAULT(csiescseq.arg[0], 1); 1810 tdeletechar(csiescseq.arg[0]); 1811 break; 1812 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */ 1813 DEFAULT(csiescseq.arg[0], 1); 1814 tputtab(-csiescseq.arg[0]); 1815 break; 1816 case 'd': /* VPA -- Move to <row> */ 1817 DEFAULT(csiescseq.arg[0], 1); 1818 tmoveato(term.c.x, csiescseq.arg[0]-1); 1819 break; 1820 case 'h': /* SM -- Set terminal mode */ 1821 tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg); 1822 break; 1823 case 'm': /* SGR -- Terminal attribute (color) */ 1824 tsetattr(csiescseq.arg, csiescseq.narg); 1825 break; 1826 case 'n': /* DSR – Device Status Report (cursor position) */ 1827 if (csiescseq.arg[0] == 6) { 1828 len = snprintf(buf, sizeof(buf),"\033[%i;%iR", 1829 term.c.y+1, term.c.x+1); 1830 ttywrite(buf, len, 0); 1831 } 1832 break; 1833 case 'r': /* DECSTBM -- Set Scrolling Region */ 1834 if (csiescseq.priv) { 1835 goto unknown; 1836 } else { 1837 DEFAULT(csiescseq.arg[0], 1); 1838 DEFAULT(csiescseq.arg[1], term.row); 1839 tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1); 1840 tmoveato(0, 0); 1841 } 1842 break; 1843 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */ 1844 tcursor(CURSOR_SAVE); 1845 break; 1846 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */ 1847 tcursor(CURSOR_LOAD); 1848 break; 1849 case ' ': 1850 switch (csiescseq.mode[1]) { 1851 case 'q': /* DECSCUSR -- Set Cursor Style */ 1852 if (xsetcursor(csiescseq.arg[0])) 1853 goto unknown; 1854 break; 1855 default: 1856 goto unknown; 1857 } 1858 break; 1859 } 1860 } 1861 1862 void 1863 csidump(void) 1864 { 1865 size_t i; 1866 uint c; 1867 1868 fprintf(stderr, "ESC["); 1869 for (i = 0; i < csiescseq.len; i++) { 1870 c = csiescseq.buf[i] & 0xff; 1871 if (isprint(c)) { 1872 putc(c, stderr); 1873 } else if (c == '\n') { 1874 fprintf(stderr, "(\\n)"); 1875 } else if (c == '\r') { 1876 fprintf(stderr, "(\\r)"); 1877 } else if (c == 0x1b) { 1878 fprintf(stderr, "(\\e)"); 1879 } else { 1880 fprintf(stderr, "(%02x)", c); 1881 } 1882 } 1883 putc('\n', stderr); 1884 } 1885 1886 void 1887 csireset(void) 1888 { 1889 memset(&csiescseq, 0, sizeof(csiescseq)); 1890 } 1891 1892 void 1893 strhandle(void) 1894 { 1895 char *p = NULL, *dec; 1896 int j, narg, par; 1897 1898 term.esc &= ~(ESC_STR_END|ESC_STR); 1899 strparse(); 1900 par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0; 1901 1902 switch (strescseq.type) { 1903 case ']': /* OSC -- Operating System Command */ 1904 switch (par) { 1905 case 0: 1906 case 1: 1907 case 2: 1908 if (narg > 1) 1909 xsettitle(strescseq.args[1]); 1910 return; 1911 case 52: 1912 if (narg > 2) { 1913 dec = base64dec(strescseq.args[2]); 1914 if (dec) { 1915 xsetsel(dec); 1916 xclipcopy(); 1917 } else { 1918 fprintf(stderr, "erresc: invalid base64\n"); 1919 } 1920 } 1921 return; 1922 case 4: /* color set */ 1923 if (narg < 3) 1924 break; 1925 p = strescseq.args[2]; 1926 /* FALLTHROUGH */ 1927 case 104: /* color reset, here p = NULL */ 1928 j = (narg > 1) ? atoi(strescseq.args[1]) : -1; 1929 if (xsetcolorname(j, p)) { 1930 if (par == 104 && narg <= 1) 1931 return; /* color reset without parameter */ 1932 fprintf(stderr, "erresc: invalid color j=%d, p=%s\n", 1933 j, p ? p : "(null)"); 1934 } else { 1935 /* 1936 * TODO if defaultbg color is changed, borders 1937 * are dirty 1938 */ 1939 redraw(); 1940 } 1941 return; 1942 } 1943 break; 1944 case 'k': /* old title set compatibility */ 1945 xsettitle(strescseq.args[0]); 1946 return; 1947 case 'P': /* DCS -- Device Control String */ 1948 term.mode |= ESC_DCS; 1949 case '_': /* APC -- Application Program Command */ 1950 case '^': /* PM -- Privacy Message */ 1951 return; 1952 } 1953 1954 fprintf(stderr, "erresc: unknown str "); 1955 strdump(); 1956 } 1957 1958 void 1959 strparse(void) 1960 { 1961 int c; 1962 char *p = strescseq.buf; 1963 1964 strescseq.narg = 0; 1965 strescseq.buf[strescseq.len] = '\0'; 1966 1967 if (*p == '\0') 1968 return; 1969 1970 while (strescseq.narg < STR_ARG_SIZ) { 1971 strescseq.args[strescseq.narg++] = p; 1972 while ((c = *p) != ';' && c != '\0') 1973 ++p; 1974 if (c == '\0') 1975 return; 1976 *p++ = '\0'; 1977 } 1978 } 1979 1980 void 1981 externalpipe(const Arg *arg) 1982 { 1983 int to[2]; 1984 char buf[UTF_SIZ]; 1985 void (*oldsigpipe)(int); 1986 Glyph *bp, *end; 1987 int lastpos, n, newline; 1988 1989 if (pipe(to) == -1) 1990 return; 1991 1992 switch (fork()) { 1993 case -1: 1994 close(to[0]); 1995 close(to[1]); 1996 return; 1997 case 0: 1998 dup2(to[0], STDIN_FILENO); 1999 close(to[0]); 2000 close(to[1]); 2001 execvp(((char **)arg->v)[0], (char **)arg->v); 2002 fprintf(stderr, "st: execvp %s\n", ((char **)arg->v)[0]); 2003 perror("failed"); 2004 exit(0); 2005 } 2006 2007 close(to[0]); 2008 /* ignore sigpipe for now, in case child exists early */ 2009 oldsigpipe = signal(SIGPIPE, SIG_IGN); 2010 newline = 0; 2011 for (n = 0; n < term.row; n++) { 2012 bp = term.line[n]; 2013 lastpos = MIN(tlinelen(n) + 1, term.col) - 1; 2014 if (lastpos < 0) 2015 break; 2016 end = &bp[lastpos + 1]; 2017 for (; bp < end; ++bp) 2018 if (xwrite(to[1], buf, utf8encode(bp->u, buf)) < 0) 2019 break; 2020 if ((newline = term.line[n][lastpos].mode & ATTR_WRAP)) 2021 continue; 2022 if (xwrite(to[1], "\n", 1) < 0) 2023 break; 2024 newline = 0; 2025 } 2026 if (newline) 2027 (void)xwrite(to[1], "\n", 1); 2028 close(to[1]); 2029 /* restore */ 2030 signal(SIGPIPE, oldsigpipe); 2031 } 2032 2033 void 2034 strdump(void) 2035 { 2036 size_t i; 2037 uint c; 2038 2039 fprintf(stderr, "ESC%c", strescseq.type); 2040 for (i = 0; i < strescseq.len; i++) { 2041 c = strescseq.buf[i] & 0xff; 2042 if (c == '\0') { 2043 putc('\n', stderr); 2044 return; 2045 } else if (isprint(c)) { 2046 putc(c, stderr); 2047 } else if (c == '\n') { 2048 fprintf(stderr, "(\\n)"); 2049 } else if (c == '\r') { 2050 fprintf(stderr, "(\\r)"); 2051 } else if (c == 0x1b) { 2052 fprintf(stderr, "(\\e)"); 2053 } else { 2054 fprintf(stderr, "(%02x)", c); 2055 } 2056 } 2057 fprintf(stderr, "ESC\\\n"); 2058 } 2059 2060 void 2061 strreset(void) 2062 { 2063 strescseq = (STREscape){ 2064 .buf = xrealloc(strescseq.buf, STR_BUF_SIZ), 2065 .siz = STR_BUF_SIZ, 2066 }; 2067 } 2068 2069 void 2070 sendbreak(const Arg *arg) 2071 { 2072 if (tcsendbreak(cmdfd, 0)) 2073 perror("Error sending break"); 2074 } 2075 2076 void 2077 tprinter(char *s, size_t len) 2078 { 2079 if (iofd != -1 && xwrite(iofd, s, len) < 0) { 2080 perror("Error writing to output file"); 2081 close(iofd); 2082 iofd = -1; 2083 } 2084 } 2085 2086 void 2087 toggleprinter(const Arg *arg) 2088 { 2089 term.mode ^= MODE_PRINT; 2090 } 2091 2092 void 2093 printscreen(const Arg *arg) 2094 { 2095 tdump(); 2096 } 2097 2098 void 2099 printsel(const Arg *arg) 2100 { 2101 tdumpsel(); 2102 } 2103 2104 void 2105 tdumpsel(void) 2106 { 2107 char *ptr; 2108 2109 if ((ptr = getsel())) { 2110 tprinter(ptr, strlen(ptr)); 2111 free(ptr); 2112 } 2113 } 2114 2115 void 2116 tdumpline(int n) 2117 { 2118 char buf[UTF_SIZ]; 2119 Glyph *bp, *end; 2120 2121 bp = &term.line[n][0]; 2122 end = &bp[MIN(tlinelen(n), term.col) - 1]; 2123 if (bp != end || bp->u != ' ') { 2124 for ( ;bp <= end; ++bp) 2125 tprinter(buf, utf8encode(bp->u, buf)); 2126 } 2127 tprinter("\n", 1); 2128 } 2129 2130 void 2131 tdump(void) 2132 { 2133 int i; 2134 2135 for (i = 0; i < term.row; ++i) 2136 tdumpline(i); 2137 } 2138 2139 void 2140 tputtab(int n) 2141 { 2142 uint x = term.c.x; 2143 2144 if (n > 0) { 2145 while (x < term.col && n--) 2146 for (++x; x < term.col && !term.tabs[x]; ++x) 2147 /* nothing */ ; 2148 } else if (n < 0) { 2149 while (x > 0 && n++) 2150 for (--x; x > 0 && !term.tabs[x]; --x) 2151 /* nothing */ ; 2152 } 2153 term.c.x = LIMIT(x, 0, term.col-1); 2154 } 2155 2156 void 2157 tdefutf8(char ascii) 2158 { 2159 if (ascii == 'G') 2160 term.mode |= MODE_UTF8; 2161 else if (ascii == '@') 2162 term.mode &= ~MODE_UTF8; 2163 } 2164 2165 void 2166 tdeftran(char ascii) 2167 { 2168 static char cs[] = "0B"; 2169 static int vcs[] = {CS_GRAPHIC0, CS_USA}; 2170 char *p; 2171 2172 if ((p = strchr(cs, ascii)) == NULL) { 2173 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii); 2174 } else { 2175 term.trantbl[term.icharset] = vcs[p - cs]; 2176 } 2177 } 2178 2179 void 2180 tdectest(char c) 2181 { 2182 int x, y; 2183 2184 if (c == '8') { /* DEC screen alignment test. */ 2185 for (x = 0; x < term.col; ++x) { 2186 for (y = 0; y < term.row; ++y) 2187 tsetchar('E', &term.c.attr, x, y); 2188 } 2189 } 2190 } 2191 2192 void 2193 tstrsequence(uchar c) 2194 { 2195 strreset(); 2196 2197 switch (c) { 2198 case 0x90: /* DCS -- Device Control String */ 2199 c = 'P'; 2200 term.esc |= ESC_DCS; 2201 break; 2202 case 0x9f: /* APC -- Application Program Command */ 2203 c = '_'; 2204 break; 2205 case 0x9e: /* PM -- Privacy Message */ 2206 c = '^'; 2207 break; 2208 case 0x9d: /* OSC -- Operating System Command */ 2209 c = ']'; 2210 break; 2211 } 2212 strescseq.type = c; 2213 term.esc |= ESC_STR; 2214 } 2215 2216 void 2217 tcontrolcode(uchar ascii) 2218 { 2219 switch (ascii) { 2220 case '\t': /* HT */ 2221 tputtab(1); 2222 return; 2223 case '\b': /* BS */ 2224 tmoveto(term.c.x-1, term.c.y); 2225 return; 2226 case '\r': /* CR */ 2227 tmoveto(0, term.c.y); 2228 return; 2229 case '\f': /* LF */ 2230 case '\v': /* VT */ 2231 case '\n': /* LF */ 2232 /* go to first col if the mode is set */ 2233 tnewline(IS_SET(MODE_CRLF)); 2234 return; 2235 case '\a': /* BEL */ 2236 if (term.esc & ESC_STR_END) { 2237 /* backwards compatibility to xterm */ 2238 strhandle(); 2239 } else { 2240 xbell(); 2241 } 2242 break; 2243 case '\033': /* ESC */ 2244 csireset(); 2245 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST); 2246 term.esc |= ESC_START; 2247 return; 2248 case '\016': /* SO (LS1 -- Locking shift 1) */ 2249 case '\017': /* SI (LS0 -- Locking shift 0) */ 2250 term.charset = 1 - (ascii - '\016'); 2251 return; 2252 case '\032': /* SUB */ 2253 tsetchar('?', &term.c.attr, term.c.x, term.c.y); 2254 case '\030': /* CAN */ 2255 csireset(); 2256 break; 2257 case '\005': /* ENQ (IGNORED) */ 2258 case '\000': /* NUL (IGNORED) */ 2259 case '\021': /* XON (IGNORED) */ 2260 case '\023': /* XOFF (IGNORED) */ 2261 case 0177: /* DEL (IGNORED) */ 2262 return; 2263 case 0x80: /* TODO: PAD */ 2264 case 0x81: /* TODO: HOP */ 2265 case 0x82: /* TODO: BPH */ 2266 case 0x83: /* TODO: NBH */ 2267 case 0x84: /* TODO: IND */ 2268 break; 2269 case 0x85: /* NEL -- Next line */ 2270 tnewline(1); /* always go to first col */ 2271 break; 2272 case 0x86: /* TODO: SSA */ 2273 case 0x87: /* TODO: ESA */ 2274 break; 2275 case 0x88: /* HTS -- Horizontal tab stop */ 2276 term.tabs[term.c.x] = 1; 2277 break; 2278 case 0x89: /* TODO: HTJ */ 2279 case 0x8a: /* TODO: VTS */ 2280 case 0x8b: /* TODO: PLD */ 2281 case 0x8c: /* TODO: PLU */ 2282 case 0x8d: /* TODO: RI */ 2283 case 0x8e: /* TODO: SS2 */ 2284 case 0x8f: /* TODO: SS3 */ 2285 case 0x91: /* TODO: PU1 */ 2286 case 0x92: /* TODO: PU2 */ 2287 case 0x93: /* TODO: STS */ 2288 case 0x94: /* TODO: CCH */ 2289 case 0x95: /* TODO: MW */ 2290 case 0x96: /* TODO: SPA */ 2291 case 0x97: /* TODO: EPA */ 2292 case 0x98: /* TODO: SOS */ 2293 case 0x99: /* TODO: SGCI */ 2294 break; 2295 case 0x9a: /* DECID -- Identify Terminal */ 2296 ttywrite(vtiden, strlen(vtiden), 0); 2297 break; 2298 case 0x9b: /* TODO: CSI */ 2299 case 0x9c: /* TODO: ST */ 2300 break; 2301 case 0x90: /* DCS -- Device Control String */ 2302 case 0x9d: /* OSC -- Operating System Command */ 2303 case 0x9e: /* PM -- Privacy Message */ 2304 case 0x9f: /* APC -- Application Program Command */ 2305 tstrsequence(ascii); 2306 return; 2307 } 2308 /* only CAN, SUB, \a and C1 chars interrupt a sequence */ 2309 term.esc &= ~(ESC_STR_END|ESC_STR); 2310 } 2311 2312 /* 2313 * returns 1 when the sequence is finished and it hasn't to read 2314 * more characters for this sequence, otherwise 0 2315 */ 2316 int 2317 eschandle(uchar ascii) 2318 { 2319 switch (ascii) { 2320 case '[': 2321 term.esc |= ESC_CSI; 2322 return 0; 2323 case '#': 2324 term.esc |= ESC_TEST; 2325 return 0; 2326 case '%': 2327 term.esc |= ESC_UTF8; 2328 return 0; 2329 case 'P': /* DCS -- Device Control String */ 2330 case '_': /* APC -- Application Program Command */ 2331 case '^': /* PM -- Privacy Message */ 2332 case ']': /* OSC -- Operating System Command */ 2333 case 'k': /* old title set compatibility */ 2334 tstrsequence(ascii); 2335 return 0; 2336 case 'n': /* LS2 -- Locking shift 2 */ 2337 case 'o': /* LS3 -- Locking shift 3 */ 2338 term.charset = 2 + (ascii - 'n'); 2339 break; 2340 case '(': /* GZD4 -- set primary charset G0 */ 2341 case ')': /* G1D4 -- set secondary charset G1 */ 2342 case '*': /* G2D4 -- set tertiary charset G2 */ 2343 case '+': /* G3D4 -- set quaternary charset G3 */ 2344 term.icharset = ascii - '('; 2345 term.esc |= ESC_ALTCHARSET; 2346 return 0; 2347 case 'D': /* IND -- Linefeed */ 2348 if (term.c.y == term.bot) { 2349 tscrollup(term.top, 1, 1); 2350 } else { 2351 tmoveto(term.c.x, term.c.y+1); 2352 } 2353 break; 2354 case 'E': /* NEL -- Next line */ 2355 tnewline(1); /* always go to first col */ 2356 break; 2357 case 'H': /* HTS -- Horizontal tab stop */ 2358 term.tabs[term.c.x] = 1; 2359 break; 2360 case 'M': /* RI -- Reverse index */ 2361 if (term.c.y == term.top) { 2362 tscrolldown(term.top, 1, 1); 2363 } else { 2364 tmoveto(term.c.x, term.c.y-1); 2365 } 2366 break; 2367 case 'Z': /* DECID -- Identify Terminal */ 2368 ttywrite(vtiden, strlen(vtiden), 0); 2369 break; 2370 case 'c': /* RIS -- Reset to initial state */ 2371 treset(); 2372 resettitle(); 2373 xloadcols(); 2374 break; 2375 case '=': /* DECPAM -- Application keypad */ 2376 xsetmode(1, MODE_APPKEYPAD); 2377 break; 2378 case '>': /* DECPNM -- Normal keypad */ 2379 xsetmode(0, MODE_APPKEYPAD); 2380 break; 2381 case '7': /* DECSC -- Save Cursor */ 2382 tcursor(CURSOR_SAVE); 2383 break; 2384 case '8': /* DECRC -- Restore Cursor */ 2385 tcursor(CURSOR_LOAD); 2386 break; 2387 case '\\': /* ST -- String Terminator */ 2388 if (term.esc & ESC_STR_END) 2389 strhandle(); 2390 break; 2391 default: 2392 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n", 2393 (uchar) ascii, isprint(ascii)? ascii:'.'); 2394 break; 2395 } 2396 return 1; 2397 } 2398 2399 void 2400 tputc(Rune u) 2401 { 2402 char c[UTF_SIZ]; 2403 int control; 2404 int width, len; 2405 Glyph *gp; 2406 2407 control = ISCONTROL(u); 2408 if (!IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) { 2409 c[0] = u; 2410 width = len = 1; 2411 } else { 2412 len = utf8encode(u, c); 2413 if (!control && (width = wcwidth(u)) == -1) { 2414 memcpy(c, "\357\277\275", 4); /* UTF_INVALID */ 2415 width = 1; 2416 } 2417 } 2418 2419 if (IS_SET(MODE_PRINT)) 2420 tprinter(c, len); 2421 2422 /* 2423 * STR sequence must be checked before anything else 2424 * because it uses all following characters until it 2425 * receives a ESC, a SUB, a ST or any other C1 control 2426 * character. 2427 */ 2428 if (term.esc & ESC_STR) { 2429 if (u == '\a' || u == 030 || u == 032 || u == 033 || 2430 ISCONTROLC1(u)) { 2431 term.esc &= ~(ESC_START|ESC_STR|ESC_DCS); 2432 if (IS_SET(MODE_SIXEL)) { 2433 /* TODO: render sixel */; 2434 term.mode &= ~MODE_SIXEL; 2435 return; 2436 } 2437 term.esc |= ESC_STR_END; 2438 goto check_control_code; 2439 } 2440 2441 if (IS_SET(MODE_SIXEL)) { 2442 /* TODO: implement sixel mode */ 2443 return; 2444 } 2445 if (term.esc&ESC_DCS && strescseq.len == 0 && u == 'q') 2446 term.mode |= MODE_SIXEL; 2447 2448 if (strescseq.len+len >= strescseq.siz) { 2449 /* 2450 * Here is a bug in terminals. If the user never sends 2451 * some code to stop the str or esc command, then st 2452 * will stop responding. But this is better than 2453 * silently failing with unknown characters. At least 2454 * then users will report back. 2455 * 2456 * In the case users ever get fixed, here is the code: 2457 */ 2458 /* 2459 * term.esc = 0; 2460 * strhandle(); 2461 */ 2462 if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2) 2463 return; 2464 strescseq.siz *= 2; 2465 strescseq.buf = xrealloc(strescseq.buf, strescseq.siz); 2466 } 2467 2468 memmove(&strescseq.buf[strescseq.len], c, len); 2469 strescseq.len += len; 2470 return; 2471 } 2472 2473 check_control_code: 2474 /* 2475 * Actions of control codes must be performed as soon they arrive 2476 * because they can be embedded inside a control sequence, and 2477 * they must not cause conflicts with sequences. 2478 */ 2479 if (control) { 2480 tcontrolcode(u); 2481 /* 2482 * control codes are not shown ever 2483 */ 2484 return; 2485 } else if (term.esc & ESC_START) { 2486 if (term.esc & ESC_CSI) { 2487 csiescseq.buf[csiescseq.len++] = u; 2488 if (BETWEEN(u, 0x40, 0x7E) 2489 || csiescseq.len >= \ 2490 sizeof(csiescseq.buf)-1) { 2491 term.esc = 0; 2492 csiparse(); 2493 csihandle(); 2494 } 2495 return; 2496 } else if (term.esc & ESC_UTF8) { 2497 tdefutf8(u); 2498 } else if (term.esc & ESC_ALTCHARSET) { 2499 tdeftran(u); 2500 } else if (term.esc & ESC_TEST) { 2501 tdectest(u); 2502 } else { 2503 if (!eschandle(u)) 2504 return; 2505 /* sequence already finished */ 2506 } 2507 term.esc = 0; 2508 /* 2509 * All characters which form part of a sequence are not 2510 * printed 2511 */ 2512 return; 2513 } 2514 if (sel.ob.x != -1 && BETWEEN(term.c.y, sel.ob.y, sel.oe.y)) 2515 selclear(); 2516 2517 gp = &term.line[term.c.y][term.c.x]; 2518 if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) { 2519 gp->mode |= ATTR_WRAP; 2520 tnewline(1); 2521 gp = &term.line[term.c.y][term.c.x]; 2522 } 2523 2524 if (IS_SET(MODE_INSERT) && term.c.x+width < term.col) 2525 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph)); 2526 2527 if (term.c.x+width > term.col) { 2528 tnewline(1); 2529 gp = &term.line[term.c.y][term.c.x]; 2530 } 2531 2532 tsetchar(u, &term.c.attr, term.c.x, term.c.y); 2533 2534 if (width == 2) { 2535 gp->mode |= ATTR_WIDE; 2536 if (term.c.x+1 < term.col) { 2537 gp[1].u = '\0'; 2538 gp[1].mode = ATTR_WDUMMY; 2539 } 2540 } 2541 if (term.c.x+width < term.col) { 2542 tmoveto(term.c.x+width, term.c.y); 2543 } else { 2544 term.c.state |= CURSOR_WRAPNEXT; 2545 } 2546 } 2547 2548 int 2549 twrite(const char *buf, int buflen, int show_ctrl) 2550 { 2551 int charsize; 2552 Rune u; 2553 int n; 2554 2555 for (n = 0; n < buflen; n += charsize) { 2556 if (IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) { 2557 /* process a complete utf8 char */ 2558 charsize = utf8decode(buf + n, &u, buflen - n); 2559 if (charsize == 0) 2560 break; 2561 } else { 2562 u = buf[n] & 0xFF; 2563 charsize = 1; 2564 } 2565 if (show_ctrl && ISCONTROL(u)) { 2566 if (u & 0x80) { 2567 u &= 0x7f; 2568 tputc('^'); 2569 tputc('['); 2570 } else if (u != '\n' && u != '\r' && u != '\t') { 2571 u ^= 0x40; 2572 tputc('^'); 2573 } 2574 } 2575 tputc(u); 2576 } 2577 return n; 2578 } 2579 2580 void 2581 tresize(int col, int row) 2582 { 2583 int i, j; 2584 int minrow = MIN(row, term.row); 2585 int mincol = MIN(col, term.col); 2586 int *bp; 2587 TCursor c; 2588 2589 if (col < 1 || row < 1) { 2590 fprintf(stderr, 2591 "tresize: error resizing to %dx%d\n", col, row); 2592 return; 2593 } 2594 2595 /* 2596 * slide screen to keep cursor where we expect it - 2597 * tscrollup would work here, but we can optimize to 2598 * memmove because we're freeing the earlier lines 2599 */ 2600 for (i = 0; i <= term.c.y - row; i++) { 2601 free(term.line[i]); 2602 free(term.alt[i]); 2603 } 2604 /* ensure that both src and dst are not NULL */ 2605 if (i > 0) { 2606 memmove(term.line, term.line + i, row * sizeof(Line)); 2607 memmove(term.alt, term.alt + i, row * sizeof(Line)); 2608 } 2609 for (i += row; i < term.row; i++) { 2610 free(term.line[i]); 2611 free(term.alt[i]); 2612 } 2613 2614 /* resize to new height */ 2615 term.line = xrealloc(term.line, row * sizeof(Line)); 2616 term.alt = xrealloc(term.alt, row * sizeof(Line)); 2617 term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); 2618 term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); 2619 2620 for (i = 0; i < HISTSIZE; i++) { 2621 term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph)); 2622 for (j = mincol; j < col; j++) { 2623 term.hist[i][j] = term.c.attr; 2624 term.hist[i][j].u = ' '; 2625 } 2626 } 2627 2628 /* resize each row to new width, zero-pad if needed */ 2629 for (i = 0; i < minrow; i++) { 2630 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); 2631 term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph)); 2632 } 2633 2634 /* allocate any new rows */ 2635 for (/* i = minrow */; i < row; i++) { 2636 term.line[i] = xmalloc(col * sizeof(Glyph)); 2637 term.alt[i] = xmalloc(col * sizeof(Glyph)); 2638 } 2639 if (col > term.col) { 2640 bp = term.tabs + term.col; 2641 2642 memset(bp, 0, sizeof(*term.tabs) * (col - term.col)); 2643 while (--bp > term.tabs && !*bp) 2644 /* nothing */ ; 2645 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces) 2646 *bp = 1; 2647 } 2648 /* update terminal size */ 2649 term.col = col; 2650 term.row = row; 2651 /* reset scrolling region */ 2652 tsetscroll(0, row-1); 2653 /* make use of the LIMIT in tmoveto */ 2654 tmoveto(term.c.x, term.c.y); 2655 /* Clearing both screens (it makes dirty all lines) */ 2656 c = term.c; 2657 for (i = 0; i < 2; i++) { 2658 if (mincol < col && 0 < minrow) { 2659 tclearregion(mincol, 0, col - 1, minrow - 1); 2660 } 2661 if (0 < col && minrow < row) { 2662 tclearregion(0, minrow, col - 1, row - 1); 2663 } 2664 tswapscreen(); 2665 tcursor(CURSOR_LOAD); 2666 } 2667 term.c = c; 2668 } 2669 2670 void 2671 resettitle(void) 2672 { 2673 xsettitle(NULL); 2674 } 2675 2676 void 2677 drawregion(int x1, int y1, int x2, int y2) 2678 { 2679 int y; 2680 for (y = y1; y < y2; y++) { 2681 if (!term.dirty[y]) 2682 continue; 2683 2684 term.dirty[y] = 0; 2685 xdrawline(TLINE(y), x1, y, x2); 2686 } 2687 } 2688 2689 void 2690 draw(void) 2691 { 2692 int cx = term.c.x; 2693 2694 if (!xstartdraw()) 2695 return; 2696 2697 /* adjust cursor position */ 2698 LIMIT(term.ocx, 0, term.col-1); 2699 LIMIT(term.ocy, 0, term.row-1); 2700 if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY) 2701 term.ocx--; 2702 if (term.line[term.c.y][cx].mode & ATTR_WDUMMY) 2703 cx--; 2704 2705 drawregion(0, 0, term.col, term.row); 2706 if (term.scr == 0) 2707 xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], 2708 term.ocx, term.ocy, term.line[term.ocy][term.ocx]); 2709 term.ocx = cx, term.ocy = term.c.y; 2710 xfinishdraw(); 2711 xximspot(term.ocx, term.ocy); 2712 } 2713 2714 void 2715 redraw(void) 2716 { 2717 tfulldirt(); 2718 draw(); 2719 }