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