st.c (58345B) - raw
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 int tisaltscr(void) 1061 { 1062 return IS_SET(MODE_ALTSCREEN); 1063 } 1064 1065 void 1066 tswapscreen(void) 1067 { 1068 Line *tmp = term.line; 1069 1070 term.line = term.alt; 1071 term.alt = tmp; 1072 term.mode ^= MODE_ALTSCREEN; 1073 tfulldirt(); 1074 } 1075 1076 void 1077 kscrolldown(const Arg* a) 1078 { 1079 int n = a->i; 1080 1081 if (n < 0) 1082 n = term.row + n; 1083 1084 if (n > term.scr) 1085 n = term.scr; 1086 1087 if (term.scr > 0) { 1088 term.scr -= n; 1089 selscroll(0, -n); 1090 tfulldirt(); 1091 } 1092 } 1093 1094 void 1095 kscrollup(const Arg* a) 1096 { 1097 int n = a->i; 1098 1099 if (n < 0) 1100 n = term.row + n; 1101 1102 if (term.scr <= HISTSIZE-n) { 1103 term.scr += n; 1104 selscroll(0, n); 1105 tfulldirt(); 1106 } 1107 } 1108 1109 void 1110 tscrolldown(int orig, int n, int copyhist) 1111 { 1112 int i; 1113 Line temp; 1114 1115 LIMIT(n, 0, term.bot-orig+1); 1116 1117 if (copyhist) { 1118 term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE; 1119 temp = term.hist[term.histi]; 1120 term.hist[term.histi] = term.line[term.bot]; 1121 term.line[term.bot] = temp; 1122 } 1123 1124 tsetdirt(orig, term.bot-n); 1125 tclearregion(0, term.bot-n+1, term.col-1, term.bot); 1126 1127 for (i = term.bot; i >= orig+n; i--) { 1128 temp = term.line[i]; 1129 term.line[i] = term.line[i-n]; 1130 term.line[i-n] = temp; 1131 } 1132 1133 if (term.scr == 0) 1134 selscroll(orig, n); 1135 } 1136 1137 void 1138 tscrollup(int orig, int n, int copyhist) 1139 { 1140 int i; 1141 Line temp; 1142 1143 LIMIT(n, 0, term.bot-orig+1); 1144 1145 if (copyhist) { 1146 term.histi = (term.histi + 1) % HISTSIZE; 1147 temp = term.hist[term.histi]; 1148 term.hist[term.histi] = term.line[orig]; 1149 term.line[orig] = temp; 1150 } 1151 1152 if (term.scr > 0 && term.scr < HISTSIZE) 1153 term.scr = MIN(term.scr + n, HISTSIZE-1); 1154 1155 tclearregion(0, orig, term.col-1, orig+n-1); 1156 tsetdirt(orig+n, term.bot); 1157 1158 for (i = orig; i <= term.bot-n; i++) { 1159 temp = term.line[i]; 1160 term.line[i] = term.line[i+n]; 1161 term.line[i+n] = temp; 1162 } 1163 1164 if (term.scr == 0) 1165 selscroll(orig, -n); 1166 } 1167 1168 void 1169 selscroll(int orig, int n) 1170 { 1171 if (sel.ob.x == -1) 1172 return; 1173 1174 if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) { 1175 selclear(); 1176 } else if (BETWEEN(sel.nb.y, orig, term.bot)) { 1177 sel.ob.y += n; 1178 sel.oe.y += n; 1179 if (sel.ob.y < term.top || sel.ob.y > term.bot || 1180 sel.oe.y < term.top || sel.oe.y > term.bot) { 1181 selclear(); 1182 } else { 1183 selnormalize(); 1184 } 1185 } 1186 } 1187 1188 void 1189 tnewline(int first_col) 1190 { 1191 int y = term.c.y; 1192 1193 if (y == term.bot) { 1194 tscrollup(term.top, 1, 1); 1195 } else { 1196 y++; 1197 } 1198 tmoveto(first_col ? 0 : term.c.x, y); 1199 } 1200 1201 void 1202 csiparse(void) 1203 { 1204 char *p = csiescseq.buf, *np; 1205 long int v; 1206 1207 csiescseq.narg = 0; 1208 if (*p == '?') { 1209 csiescseq.priv = 1; 1210 p++; 1211 } 1212 1213 csiescseq.buf[csiescseq.len] = '\0'; 1214 while (p < csiescseq.buf+csiescseq.len) { 1215 np = NULL; 1216 v = strtol(p, &np, 10); 1217 if (np == p) 1218 v = 0; 1219 if (v == LONG_MAX || v == LONG_MIN) 1220 v = -1; 1221 csiescseq.arg[csiescseq.narg++] = v; 1222 p = np; 1223 if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ) 1224 break; 1225 p++; 1226 } 1227 csiescseq.mode[0] = *p++; 1228 csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0'; 1229 } 1230 1231 /* for absolute user moves, when decom is set */ 1232 void 1233 tmoveato(int x, int y) 1234 { 1235 tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0)); 1236 } 1237 1238 void 1239 tmoveto(int x, int y) 1240 { 1241 int miny, maxy; 1242 1243 if (term.c.state & CURSOR_ORIGIN) { 1244 miny = term.top; 1245 maxy = term.bot; 1246 } else { 1247 miny = 0; 1248 maxy = term.row - 1; 1249 } 1250 term.c.state &= ~CURSOR_WRAPNEXT; 1251 term.c.x = LIMIT(x, 0, term.col-1); 1252 term.c.y = LIMIT(y, miny, maxy); 1253 } 1254 1255 void 1256 tsetchar(Rune u, Glyph *attr, int x, int y) 1257 { 1258 static char *vt100_0[62] = { /* 0x41 - 0x7e */ 1259 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */ 1260 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */ 1261 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */ 1262 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */ 1263 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */ 1264 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */ 1265 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */ 1266 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */ 1267 }; 1268 1269 /* 1270 * The table is proudly stolen from rxvt. 1271 */ 1272 if (term.trantbl[term.charset] == CS_GRAPHIC0 && 1273 BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41]) 1274 utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ); 1275 1276 if (term.line[y][x].mode & ATTR_WIDE) { 1277 if (x+1 < term.col) { 1278 term.line[y][x+1].u = ' '; 1279 term.line[y][x+1].mode &= ~ATTR_WDUMMY; 1280 } 1281 } else if (term.line[y][x].mode & ATTR_WDUMMY) { 1282 term.line[y][x-1].u = ' '; 1283 term.line[y][x-1].mode &= ~ATTR_WIDE; 1284 } 1285 1286 term.dirty[y] = 1; 1287 term.line[y][x] = *attr; 1288 term.line[y][x].u = u; 1289 } 1290 1291 void 1292 tclearregion(int x1, int y1, int x2, int y2) 1293 { 1294 int x, y, temp; 1295 Glyph *gp; 1296 1297 if (x1 > x2) 1298 temp = x1, x1 = x2, x2 = temp; 1299 if (y1 > y2) 1300 temp = y1, y1 = y2, y2 = temp; 1301 1302 LIMIT(x1, 0, term.col-1); 1303 LIMIT(x2, 0, term.col-1); 1304 LIMIT(y1, 0, term.row-1); 1305 LIMIT(y2, 0, term.row-1); 1306 1307 for (y = y1; y <= y2; y++) { 1308 term.dirty[y] = 1; 1309 for (x = x1; x <= x2; x++) { 1310 gp = &term.line[y][x]; 1311 if (selected(x, y)) 1312 selclear(); 1313 gp->fg = term.c.attr.fg; 1314 gp->bg = term.c.attr.bg; 1315 gp->mode = 0; 1316 gp->u = ' '; 1317 } 1318 } 1319 } 1320 1321 void 1322 tdeletechar(int n) 1323 { 1324 int dst, src, size; 1325 Glyph *line; 1326 1327 LIMIT(n, 0, term.col - term.c.x); 1328 1329 dst = term.c.x; 1330 src = term.c.x + n; 1331 size = term.col - src; 1332 line = term.line[term.c.y]; 1333 1334 memmove(&line[dst], &line[src], size * sizeof(Glyph)); 1335 tclearregion(term.col-n, term.c.y, term.col-1, term.c.y); 1336 } 1337 1338 void 1339 tinsertblank(int n) 1340 { 1341 int dst, src, size; 1342 Glyph *line; 1343 1344 LIMIT(n, 0, term.col - term.c.x); 1345 1346 dst = term.c.x + n; 1347 src = term.c.x; 1348 size = term.col - dst; 1349 line = term.line[term.c.y]; 1350 1351 memmove(&line[dst], &line[src], size * sizeof(Glyph)); 1352 tclearregion(src, term.c.y, dst - 1, term.c.y); 1353 } 1354 1355 void 1356 tinsertblankline(int n) 1357 { 1358 if (BETWEEN(term.c.y, term.top, term.bot)) 1359 tscrolldown(term.c.y, n, 0); 1360 } 1361 1362 void 1363 tdeleteline(int n) 1364 { 1365 if (BETWEEN(term.c.y, term.top, term.bot)) 1366 tscrollup(term.c.y, n, 0); 1367 } 1368 1369 int32_t 1370 tdefcolor(int *attr, int *npar, int l) 1371 { 1372 int32_t idx = -1; 1373 uint r, g, b; 1374 1375 switch (attr[*npar + 1]) { 1376 case 2: /* direct color in RGB space */ 1377 if (*npar + 4 >= l) { 1378 fprintf(stderr, 1379 "erresc(38): Incorrect number of parameters (%d)\n", 1380 *npar); 1381 break; 1382 } 1383 r = attr[*npar + 2]; 1384 g = attr[*npar + 3]; 1385 b = attr[*npar + 4]; 1386 *npar += 4; 1387 if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255)) 1388 fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n", 1389 r, g, b); 1390 else 1391 idx = TRUECOLOR(r, g, b); 1392 break; 1393 case 5: /* indexed color */ 1394 if (*npar + 2 >= l) { 1395 fprintf(stderr, 1396 "erresc(38): Incorrect number of parameters (%d)\n", 1397 *npar); 1398 break; 1399 } 1400 *npar += 2; 1401 if (!BETWEEN(attr[*npar], 0, 255)) 1402 fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]); 1403 else 1404 idx = attr[*npar]; 1405 break; 1406 case 0: /* implemented defined (only foreground) */ 1407 case 1: /* transparent */ 1408 case 3: /* direct color in CMY space */ 1409 case 4: /* direct color in CMYK space */ 1410 default: 1411 fprintf(stderr, 1412 "erresc(38): gfx attr %d unknown\n", attr[*npar]); 1413 break; 1414 } 1415 1416 return idx; 1417 } 1418 1419 void 1420 tsetattr(int *attr, int l) 1421 { 1422 int i; 1423 int32_t idx; 1424 1425 for (i = 0; i < l; i++) { 1426 switch (attr[i]) { 1427 case 0: 1428 term.c.attr.mode &= ~( 1429 ATTR_BOLD | 1430 ATTR_FAINT | 1431 ATTR_ITALIC | 1432 ATTR_UNDERLINE | 1433 ATTR_BLINK | 1434 ATTR_REVERSE | 1435 ATTR_INVISIBLE | 1436 ATTR_STRUCK ); 1437 term.c.attr.fg = defaultfg; 1438 term.c.attr.bg = defaultbg; 1439 break; 1440 case 1: 1441 term.c.attr.mode |= ATTR_BOLD; 1442 break; 1443 case 2: 1444 term.c.attr.mode |= ATTR_FAINT; 1445 break; 1446 case 3: 1447 term.c.attr.mode |= ATTR_ITALIC; 1448 break; 1449 case 4: 1450 term.c.attr.mode |= ATTR_UNDERLINE; 1451 break; 1452 case 5: /* slow blink */ 1453 /* FALLTHROUGH */ 1454 case 6: /* rapid blink */ 1455 term.c.attr.mode |= ATTR_BLINK; 1456 break; 1457 case 7: 1458 term.c.attr.mode |= ATTR_REVERSE; 1459 break; 1460 case 8: 1461 term.c.attr.mode |= ATTR_INVISIBLE; 1462 break; 1463 case 9: 1464 term.c.attr.mode |= ATTR_STRUCK; 1465 break; 1466 case 22: 1467 term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT); 1468 break; 1469 case 23: 1470 term.c.attr.mode &= ~ATTR_ITALIC; 1471 break; 1472 case 24: 1473 term.c.attr.mode &= ~ATTR_UNDERLINE; 1474 break; 1475 case 25: 1476 term.c.attr.mode &= ~ATTR_BLINK; 1477 break; 1478 case 27: 1479 term.c.attr.mode &= ~ATTR_REVERSE; 1480 break; 1481 case 28: 1482 term.c.attr.mode &= ~ATTR_INVISIBLE; 1483 break; 1484 case 29: 1485 term.c.attr.mode &= ~ATTR_STRUCK; 1486 break; 1487 case 38: 1488 if ((idx = tdefcolor(attr, &i, l)) >= 0) 1489 term.c.attr.fg = idx; 1490 break; 1491 case 39: 1492 term.c.attr.fg = defaultfg; 1493 break; 1494 case 48: 1495 if ((idx = tdefcolor(attr, &i, l)) >= 0) 1496 term.c.attr.bg = idx; 1497 break; 1498 case 49: 1499 term.c.attr.bg = defaultbg; 1500 break; 1501 default: 1502 if (BETWEEN(attr[i], 30, 37)) { 1503 term.c.attr.fg = attr[i] - 30; 1504 } else if (BETWEEN(attr[i], 40, 47)) { 1505 term.c.attr.bg = attr[i] - 40; 1506 } else if (BETWEEN(attr[i], 90, 97)) { 1507 term.c.attr.fg = attr[i] - 90 + 8; 1508 } else if (BETWEEN(attr[i], 100, 107)) { 1509 term.c.attr.bg = attr[i] - 100 + 8; 1510 } else { 1511 fprintf(stderr, 1512 "erresc(default): gfx attr %d unknown\n", 1513 attr[i]); 1514 csidump(); 1515 } 1516 break; 1517 } 1518 } 1519 } 1520 1521 void 1522 tsetscroll(int t, int b) 1523 { 1524 int temp; 1525 1526 LIMIT(t, 0, term.row-1); 1527 LIMIT(b, 0, term.row-1); 1528 if (t > b) { 1529 temp = t; 1530 t = b; 1531 b = temp; 1532 } 1533 term.top = t; 1534 term.bot = b; 1535 } 1536 1537 void 1538 tsetmode(int priv, int set, int *args, int narg) 1539 { 1540 int alt, *lim; 1541 1542 for (lim = args + narg; args < lim; ++args) { 1543 if (priv) { 1544 switch (*args) { 1545 case 1: /* DECCKM -- Cursor key */ 1546 xsetmode(set, MODE_APPCURSOR); 1547 break; 1548 case 5: /* DECSCNM -- Reverse video */ 1549 xsetmode(set, MODE_REVERSE); 1550 break; 1551 case 6: /* DECOM -- Origin */ 1552 MODBIT(term.c.state, set, CURSOR_ORIGIN); 1553 tmoveato(0, 0); 1554 break; 1555 case 7: /* DECAWM -- Auto wrap */ 1556 MODBIT(term.mode, set, MODE_WRAP); 1557 break; 1558 case 0: /* Error (IGNORED) */ 1559 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */ 1560 case 3: /* DECCOLM -- Column (IGNORED) */ 1561 case 4: /* DECSCLM -- Scroll (IGNORED) */ 1562 case 8: /* DECARM -- Auto repeat (IGNORED) */ 1563 case 18: /* DECPFF -- Printer feed (IGNORED) */ 1564 case 19: /* DECPEX -- Printer extent (IGNORED) */ 1565 case 42: /* DECNRCM -- National characters (IGNORED) */ 1566 case 12: /* att610 -- Start blinking cursor (IGNORED) */ 1567 break; 1568 case 25: /* DECTCEM -- Text Cursor Enable Mode */ 1569 xsetmode(!set, MODE_HIDE); 1570 break; 1571 case 9: /* X10 mouse compatibility mode */ 1572 xsetpointermotion(0); 1573 xsetmode(0, MODE_MOUSE); 1574 xsetmode(set, MODE_MOUSEX10); 1575 break; 1576 case 1000: /* 1000: report button press */ 1577 xsetpointermotion(0); 1578 xsetmode(0, MODE_MOUSE); 1579 xsetmode(set, MODE_MOUSEBTN); 1580 break; 1581 case 1002: /* 1002: report motion on button press */ 1582 xsetpointermotion(0); 1583 xsetmode(0, MODE_MOUSE); 1584 xsetmode(set, MODE_MOUSEMOTION); 1585 break; 1586 case 1003: /* 1003: enable all mouse motions */ 1587 xsetpointermotion(set); 1588 xsetmode(0, MODE_MOUSE); 1589 xsetmode(set, MODE_MOUSEMANY); 1590 break; 1591 case 1004: /* 1004: send focus events to tty */ 1592 xsetmode(set, MODE_FOCUS); 1593 break; 1594 case 1006: /* 1006: extended reporting mode */ 1595 xsetmode(set, MODE_MOUSESGR); 1596 break; 1597 case 1034: 1598 xsetmode(set, MODE_8BIT); 1599 break; 1600 case 1049: /* swap screen & set/restore cursor as xterm */ 1601 if (!allowaltscreen) 1602 break; 1603 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); 1604 /* FALLTHROUGH */ 1605 case 47: /* swap screen */ 1606 case 1047: 1607 if (!allowaltscreen) 1608 break; 1609 alt = IS_SET(MODE_ALTSCREEN); 1610 if (alt) { 1611 tclearregion(0, 0, term.col-1, 1612 term.row-1); 1613 } 1614 if (set ^ alt) /* set is always 1 or 0 */ 1615 tswapscreen(); 1616 if (*args != 1049) 1617 break; 1618 /* FALLTHROUGH */ 1619 case 1048: 1620 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); 1621 break; 1622 case 2004: /* 2004: bracketed paste mode */ 1623 xsetmode(set, MODE_BRCKTPASTE); 1624 break; 1625 /* Not implemented mouse modes. See comments there. */ 1626 case 1001: /* mouse highlight mode; can hang the 1627 terminal by design when implemented. */ 1628 case 1005: /* UTF-8 mouse mode; will confuse 1629 applications not supporting UTF-8 1630 and luit. */ 1631 case 1015: /* urxvt mangled mouse mode; incompatible 1632 and can be mistaken for other control 1633 codes. */ 1634 break; 1635 default: 1636 fprintf(stderr, 1637 "erresc: unknown private set/reset mode %d\n", 1638 *args); 1639 break; 1640 } 1641 } else { 1642 switch (*args) { 1643 case 0: /* Error (IGNORED) */ 1644 break; 1645 case 2: 1646 xsetmode(set, MODE_KBDLOCK); 1647 break; 1648 case 4: /* IRM -- Insertion-replacement */ 1649 MODBIT(term.mode, set, MODE_INSERT); 1650 break; 1651 case 12: /* SRM -- Send/Receive */ 1652 MODBIT(term.mode, !set, MODE_ECHO); 1653 break; 1654 case 20: /* LNM -- Linefeed/new line */ 1655 MODBIT(term.mode, set, MODE_CRLF); 1656 break; 1657 default: 1658 fprintf(stderr, 1659 "erresc: unknown set/reset mode %d\n", 1660 *args); 1661 break; 1662 } 1663 } 1664 } 1665 } 1666 1667 void 1668 csihandle(void) 1669 { 1670 char buf[40]; 1671 int len; 1672 1673 switch (csiescseq.mode[0]) { 1674 default: 1675 unknown: 1676 fprintf(stderr, "erresc: unknown csi "); 1677 csidump(); 1678 /* die(""); */ 1679 break; 1680 case '@': /* ICH -- Insert <n> blank char */ 1681 DEFAULT(csiescseq.arg[0], 1); 1682 tinsertblank(csiescseq.arg[0]); 1683 break; 1684 case 'A': /* CUU -- Cursor <n> Up */ 1685 DEFAULT(csiescseq.arg[0], 1); 1686 tmoveto(term.c.x, term.c.y-csiescseq.arg[0]); 1687 break; 1688 case 'B': /* CUD -- Cursor <n> Down */ 1689 case 'e': /* VPR --Cursor <n> Down */ 1690 DEFAULT(csiescseq.arg[0], 1); 1691 tmoveto(term.c.x, term.c.y+csiescseq.arg[0]); 1692 break; 1693 case 'i': /* MC -- Media Copy */ 1694 switch (csiescseq.arg[0]) { 1695 case 0: 1696 tdump(); 1697 break; 1698 case 1: 1699 tdumpline(term.c.y); 1700 break; 1701 case 2: 1702 tdumpsel(); 1703 break; 1704 case 4: 1705 term.mode &= ~MODE_PRINT; 1706 break; 1707 case 5: 1708 term.mode |= MODE_PRINT; 1709 break; 1710 } 1711 break; 1712 case 'c': /* DA -- Device Attributes */ 1713 if (csiescseq.arg[0] == 0) 1714 ttywrite(vtiden, strlen(vtiden), 0); 1715 break; 1716 case 'b': /* REP -- if last char is printable print it <n> more times */ 1717 DEFAULT(csiescseq.arg[0], 1); 1718 if (term.lastc) 1719 while (csiescseq.arg[0]-- > 0) 1720 tputc(term.lastc); 1721 break; 1722 case 'C': /* CUF -- Cursor <n> Forward */ 1723 case 'a': /* HPR -- Cursor <n> Forward */ 1724 DEFAULT(csiescseq.arg[0], 1); 1725 tmoveto(term.c.x+csiescseq.arg[0], term.c.y); 1726 break; 1727 case 'D': /* CUB -- Cursor <n> Backward */ 1728 DEFAULT(csiescseq.arg[0], 1); 1729 tmoveto(term.c.x-csiescseq.arg[0], term.c.y); 1730 break; 1731 case 'E': /* CNL -- Cursor <n> Down and first col */ 1732 DEFAULT(csiescseq.arg[0], 1); 1733 tmoveto(0, term.c.y+csiescseq.arg[0]); 1734 break; 1735 case 'F': /* CPL -- Cursor <n> Up and first col */ 1736 DEFAULT(csiescseq.arg[0], 1); 1737 tmoveto(0, term.c.y-csiescseq.arg[0]); 1738 break; 1739 case 'g': /* TBC -- Tabulation clear */ 1740 switch (csiescseq.arg[0]) { 1741 case 0: /* clear current tab stop */ 1742 term.tabs[term.c.x] = 0; 1743 break; 1744 case 3: /* clear all the tabs */ 1745 memset(term.tabs, 0, term.col * sizeof(*term.tabs)); 1746 break; 1747 default: 1748 goto unknown; 1749 } 1750 break; 1751 case 'G': /* CHA -- Move to <col> */ 1752 case '`': /* HPA */ 1753 DEFAULT(csiescseq.arg[0], 1); 1754 tmoveto(csiescseq.arg[0]-1, term.c.y); 1755 break; 1756 case 'H': /* CUP -- Move to <row> <col> */ 1757 case 'f': /* HVP */ 1758 DEFAULT(csiescseq.arg[0], 1); 1759 DEFAULT(csiescseq.arg[1], 1); 1760 tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1); 1761 break; 1762 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */ 1763 DEFAULT(csiescseq.arg[0], 1); 1764 tputtab(csiescseq.arg[0]); 1765 break; 1766 case 'J': /* ED -- Clear screen */ 1767 switch (csiescseq.arg[0]) { 1768 case 0: /* below */ 1769 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y); 1770 if (term.c.y < term.row-1) { 1771 tclearregion(0, term.c.y+1, term.col-1, 1772 term.row-1); 1773 } 1774 break; 1775 case 1: /* above */ 1776 if (term.c.y > 1) 1777 tclearregion(0, 0, term.col-1, term.c.y-1); 1778 tclearregion(0, term.c.y, term.c.x, term.c.y); 1779 break; 1780 case 2: /* all */ 1781 tclearregion(0, 0, term.col-1, term.row-1); 1782 break; 1783 default: 1784 goto unknown; 1785 } 1786 break; 1787 case 'K': /* EL -- Clear line */ 1788 switch (csiescseq.arg[0]) { 1789 case 0: /* right */ 1790 tclearregion(term.c.x, term.c.y, term.col-1, 1791 term.c.y); 1792 break; 1793 case 1: /* left */ 1794 tclearregion(0, term.c.y, term.c.x, term.c.y); 1795 break; 1796 case 2: /* all */ 1797 tclearregion(0, term.c.y, term.col-1, term.c.y); 1798 break; 1799 } 1800 break; 1801 case 'S': /* SU -- Scroll <n> line up */ 1802 DEFAULT(csiescseq.arg[0], 1); 1803 tscrollup(term.top, csiescseq.arg[0], 0); 1804 break; 1805 case 'T': /* SD -- Scroll <n> line down */ 1806 DEFAULT(csiescseq.arg[0], 1); 1807 tscrolldown(term.top, csiescseq.arg[0], 0); 1808 break; 1809 case 'L': /* IL -- Insert <n> blank lines */ 1810 DEFAULT(csiescseq.arg[0], 1); 1811 tinsertblankline(csiescseq.arg[0]); 1812 break; 1813 case 'l': /* RM -- Reset Mode */ 1814 tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg); 1815 break; 1816 case 'M': /* DL -- Delete <n> lines */ 1817 DEFAULT(csiescseq.arg[0], 1); 1818 tdeleteline(csiescseq.arg[0]); 1819 break; 1820 case 'X': /* ECH -- Erase <n> char */ 1821 DEFAULT(csiescseq.arg[0], 1); 1822 tclearregion(term.c.x, term.c.y, 1823 term.c.x + csiescseq.arg[0] - 1, term.c.y); 1824 break; 1825 case 'P': /* DCH -- Delete <n> char */ 1826 DEFAULT(csiescseq.arg[0], 1); 1827 tdeletechar(csiescseq.arg[0]); 1828 break; 1829 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */ 1830 DEFAULT(csiescseq.arg[0], 1); 1831 tputtab(-csiescseq.arg[0]); 1832 break; 1833 case 'd': /* VPA -- Move to <row> */ 1834 DEFAULT(csiescseq.arg[0], 1); 1835 tmoveato(term.c.x, csiescseq.arg[0]-1); 1836 break; 1837 case 'h': /* SM -- Set terminal mode */ 1838 tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg); 1839 break; 1840 case 'm': /* SGR -- Terminal attribute (color) */ 1841 tsetattr(csiescseq.arg, csiescseq.narg); 1842 break; 1843 case 'n': /* DSR – Device Status Report (cursor position) */ 1844 if (csiescseq.arg[0] == 6) { 1845 len = snprintf(buf, sizeof(buf), "\033[%i;%iR", 1846 term.c.y+1, term.c.x+1); 1847 ttywrite(buf, len, 0); 1848 } 1849 break; 1850 case 'r': /* DECSTBM -- Set Scrolling Region */ 1851 if (csiescseq.priv) { 1852 goto unknown; 1853 } else { 1854 DEFAULT(csiescseq.arg[0], 1); 1855 DEFAULT(csiescseq.arg[1], term.row); 1856 tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1); 1857 tmoveato(0, 0); 1858 } 1859 break; 1860 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */ 1861 tcursor(CURSOR_SAVE); 1862 break; 1863 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */ 1864 tcursor(CURSOR_LOAD); 1865 break; 1866 case ' ': 1867 switch (csiescseq.mode[1]) { 1868 case 'q': /* DECSCUSR -- Set Cursor Style */ 1869 if (xsetcursor(csiescseq.arg[0])) 1870 goto unknown; 1871 break; 1872 default: 1873 goto unknown; 1874 } 1875 break; 1876 } 1877 } 1878 1879 void 1880 csidump(void) 1881 { 1882 size_t i; 1883 uint c; 1884 1885 fprintf(stderr, "ESC["); 1886 for (i = 0; i < csiescseq.len; i++) { 1887 c = csiescseq.buf[i] & 0xff; 1888 if (isprint(c)) { 1889 putc(c, stderr); 1890 } else if (c == '\n') { 1891 fprintf(stderr, "(\\n)"); 1892 } else if (c == '\r') { 1893 fprintf(stderr, "(\\r)"); 1894 } else if (c == 0x1b) { 1895 fprintf(stderr, "(\\e)"); 1896 } else { 1897 fprintf(stderr, "(%02x)", c); 1898 } 1899 } 1900 putc('\n', stderr); 1901 } 1902 1903 void 1904 csireset(void) 1905 { 1906 memset(&csiescseq, 0, sizeof(csiescseq)); 1907 } 1908 1909 void 1910 strhandle(void) 1911 { 1912 char *p = NULL, *dec; 1913 int j, narg, par; 1914 1915 term.esc &= ~(ESC_STR_END|ESC_STR); 1916 strparse(); 1917 par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0; 1918 1919 switch (strescseq.type) { 1920 case ']': /* OSC -- Operating System Command */ 1921 switch (par) { 1922 case 0: 1923 case 1: 1924 case 2: 1925 if (narg > 1) 1926 xsettitle(strescseq.args[1]); 1927 return; 1928 case 52: 1929 if (narg > 2 && allowwindowops) { 1930 dec = base64dec(strescseq.args[2]); 1931 if (dec) { 1932 xsetsel(dec); 1933 xclipcopy(); 1934 } else { 1935 fprintf(stderr, "erresc: invalid base64\n"); 1936 } 1937 } 1938 return; 1939 case 4: /* color set */ 1940 if (narg < 3) 1941 break; 1942 p = strescseq.args[2]; 1943 /* FALLTHROUGH */ 1944 case 104: /* color reset, here p = NULL */ 1945 j = (narg > 1) ? atoi(strescseq.args[1]) : -1; 1946 if (xsetcolorname(j, p)) { 1947 if (par == 104 && narg <= 1) 1948 return; /* color reset without parameter */ 1949 fprintf(stderr, "erresc: invalid color j=%d, p=%s\n", 1950 j, p ? p : "(null)"); 1951 } else { 1952 /* 1953 * TODO if defaultbg color is changed, borders 1954 * are dirty 1955 */ 1956 redraw(); 1957 } 1958 return; 1959 } 1960 break; 1961 case 'k': /* old title set compatibility */ 1962 xsettitle(strescseq.args[0]); 1963 return; 1964 case 'P': /* DCS -- Device Control String */ 1965 case '_': /* APC -- Application Program Command */ 1966 case '^': /* PM -- Privacy Message */ 1967 return; 1968 } 1969 1970 fprintf(stderr, "erresc: unknown str "); 1971 strdump(); 1972 } 1973 1974 void 1975 strparse(void) 1976 { 1977 int c; 1978 char *p = strescseq.buf; 1979 1980 strescseq.narg = 0; 1981 strescseq.buf[strescseq.len] = '\0'; 1982 1983 if (*p == '\0') 1984 return; 1985 1986 while (strescseq.narg < STR_ARG_SIZ) { 1987 strescseq.args[strescseq.narg++] = p; 1988 while ((c = *p) != ';' && c != '\0') 1989 ++p; 1990 if (c == '\0') 1991 return; 1992 *p++ = '\0'; 1993 } 1994 } 1995 1996 void 1997 externalpipe(const Arg *arg) 1998 { 1999 int to[2]; 2000 char buf[UTF_SIZ]; 2001 void (*oldsigpipe)(int); 2002 Glyph *bp, *end; 2003 int lastpos, n, newline; 2004 2005 if (pipe(to) == -1) 2006 return; 2007 2008 switch (fork()) { 2009 case -1: 2010 close(to[0]); 2011 close(to[1]); 2012 return; 2013 case 0: 2014 dup2(to[0], STDIN_FILENO); 2015 close(to[0]); 2016 close(to[1]); 2017 execvp(((char **)arg->v)[0], (char **)arg->v); 2018 fprintf(stderr, "st: execvp %s\n", ((char **)arg->v)[0]); 2019 perror("failed"); 2020 exit(0); 2021 } 2022 2023 close(to[0]); 2024 /* ignore sigpipe for now, in case child exists early */ 2025 oldsigpipe = signal(SIGPIPE, SIG_IGN); 2026 newline = 0; 2027 for (n = 0; n < term.row; n++) { 2028 bp = term.line[n]; 2029 lastpos = MIN(tlinelen(n) + 1, term.col) - 1; 2030 if (lastpos < 0) 2031 break; 2032 end = &bp[lastpos + 1]; 2033 for (; bp < end; ++bp) 2034 if (xwrite(to[1], buf, utf8encode(bp->u, buf)) < 0) 2035 break; 2036 if ((newline = term.line[n][lastpos].mode & ATTR_WRAP)) 2037 continue; 2038 if (xwrite(to[1], "\n", 1) < 0) 2039 break; 2040 newline = 0; 2041 } 2042 if (newline) 2043 (void)xwrite(to[1], "\n", 1); 2044 close(to[1]); 2045 /* restore */ 2046 signal(SIGPIPE, oldsigpipe); 2047 } 2048 2049 void 2050 strdump(void) 2051 { 2052 size_t i; 2053 uint c; 2054 2055 fprintf(stderr, "ESC%c", strescseq.type); 2056 for (i = 0; i < strescseq.len; i++) { 2057 c = strescseq.buf[i] & 0xff; 2058 if (c == '\0') { 2059 putc('\n', stderr); 2060 return; 2061 } else if (isprint(c)) { 2062 putc(c, stderr); 2063 } else if (c == '\n') { 2064 fprintf(stderr, "(\\n)"); 2065 } else if (c == '\r') { 2066 fprintf(stderr, "(\\r)"); 2067 } else if (c == 0x1b) { 2068 fprintf(stderr, "(\\e)"); 2069 } else { 2070 fprintf(stderr, "(%02x)", c); 2071 } 2072 } 2073 fprintf(stderr, "ESC\\\n"); 2074 } 2075 2076 void 2077 strreset(void) 2078 { 2079 strescseq = (STREscape){ 2080 .buf = xrealloc(strescseq.buf, STR_BUF_SIZ), 2081 .siz = STR_BUF_SIZ, 2082 }; 2083 } 2084 2085 void 2086 sendbreak(const Arg *arg) 2087 { 2088 if (tcsendbreak(cmdfd, 0)) 2089 perror("Error sending break"); 2090 } 2091 2092 void 2093 tprinter(char *s, size_t len) 2094 { 2095 if (iofd != -1 && xwrite(iofd, s, len) < 0) { 2096 perror("Error writing to output file"); 2097 close(iofd); 2098 iofd = -1; 2099 } 2100 } 2101 2102 void 2103 toggleprinter(const Arg *arg) 2104 { 2105 term.mode ^= MODE_PRINT; 2106 } 2107 2108 void 2109 printscreen(const Arg *arg) 2110 { 2111 tdump(); 2112 } 2113 2114 void 2115 printsel(const Arg *arg) 2116 { 2117 tdumpsel(); 2118 } 2119 2120 void 2121 tdumpsel(void) 2122 { 2123 char *ptr; 2124 2125 if ((ptr = getsel())) { 2126 tprinter(ptr, strlen(ptr)); 2127 free(ptr); 2128 } 2129 } 2130 2131 void 2132 tdumpline(int n) 2133 { 2134 char buf[UTF_SIZ]; 2135 Glyph *bp, *end; 2136 2137 bp = &term.line[n][0]; 2138 end = &bp[MIN(tlinelen(n), term.col) - 1]; 2139 if (bp != end || bp->u != ' ') { 2140 for ( ; bp <= end; ++bp) 2141 tprinter(buf, utf8encode(bp->u, buf)); 2142 } 2143 tprinter("\n", 1); 2144 } 2145 2146 void 2147 tdump(void) 2148 { 2149 int i; 2150 2151 for (i = 0; i < term.row; ++i) 2152 tdumpline(i); 2153 } 2154 2155 void 2156 tputtab(int n) 2157 { 2158 uint x = term.c.x; 2159 2160 if (n > 0) { 2161 while (x < term.col && n--) 2162 for (++x; x < term.col && !term.tabs[x]; ++x) 2163 /* nothing */ ; 2164 } else if (n < 0) { 2165 while (x > 0 && n++) 2166 for (--x; x > 0 && !term.tabs[x]; --x) 2167 /* nothing */ ; 2168 } 2169 term.c.x = LIMIT(x, 0, term.col-1); 2170 } 2171 2172 void 2173 tdefutf8(char ascii) 2174 { 2175 if (ascii == 'G') 2176 term.mode |= MODE_UTF8; 2177 else if (ascii == '@') 2178 term.mode &= ~MODE_UTF8; 2179 } 2180 2181 void 2182 tdeftran(char ascii) 2183 { 2184 static char cs[] = "0B"; 2185 static int vcs[] = {CS_GRAPHIC0, CS_USA}; 2186 char *p; 2187 2188 if ((p = strchr(cs, ascii)) == NULL) { 2189 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii); 2190 } else { 2191 term.trantbl[term.icharset] = vcs[p - cs]; 2192 } 2193 } 2194 2195 void 2196 tdectest(char c) 2197 { 2198 int x, y; 2199 2200 if (c == '8') { /* DEC screen alignment test. */ 2201 for (x = 0; x < term.col; ++x) { 2202 for (y = 0; y < term.row; ++y) 2203 tsetchar('E', &term.c.attr, x, y); 2204 } 2205 } 2206 } 2207 2208 void 2209 tstrsequence(uchar c) 2210 { 2211 switch (c) { 2212 case 0x90: /* DCS -- Device Control String */ 2213 c = 'P'; 2214 break; 2215 case 0x9f: /* APC -- Application Program Command */ 2216 c = '_'; 2217 break; 2218 case 0x9e: /* PM -- Privacy Message */ 2219 c = '^'; 2220 break; 2221 case 0x9d: /* OSC -- Operating System Command */ 2222 c = ']'; 2223 break; 2224 } 2225 strreset(); 2226 strescseq.type = c; 2227 term.esc |= ESC_STR; 2228 } 2229 2230 void 2231 tcontrolcode(uchar ascii) 2232 { 2233 switch (ascii) { 2234 case '\t': /* HT */ 2235 tputtab(1); 2236 return; 2237 case '\b': /* BS */ 2238 tmoveto(term.c.x-1, term.c.y); 2239 return; 2240 case '\r': /* CR */ 2241 tmoveto(0, term.c.y); 2242 return; 2243 case '\f': /* LF */ 2244 case '\v': /* VT */ 2245 case '\n': /* LF */ 2246 /* go to first col if the mode is set */ 2247 tnewline(IS_SET(MODE_CRLF)); 2248 return; 2249 case '\a': /* BEL */ 2250 if (term.esc & ESC_STR_END) { 2251 /* backwards compatibility to xterm */ 2252 strhandle(); 2253 } else { 2254 xbell(); 2255 } 2256 break; 2257 case '\033': /* ESC */ 2258 csireset(); 2259 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST); 2260 term.esc |= ESC_START; 2261 return; 2262 case '\016': /* SO (LS1 -- Locking shift 1) */ 2263 case '\017': /* SI (LS0 -- Locking shift 0) */ 2264 term.charset = 1 - (ascii - '\016'); 2265 return; 2266 case '\032': /* SUB */ 2267 tsetchar('?', &term.c.attr, term.c.x, term.c.y); 2268 /* FALLTHROUGH */ 2269 case '\030': /* CAN */ 2270 csireset(); 2271 break; 2272 case '\005': /* ENQ (IGNORED) */ 2273 case '\000': /* NUL (IGNORED) */ 2274 case '\021': /* XON (IGNORED) */ 2275 case '\023': /* XOFF (IGNORED) */ 2276 case 0177: /* DEL (IGNORED) */ 2277 return; 2278 case 0x80: /* TODO: PAD */ 2279 case 0x81: /* TODO: HOP */ 2280 case 0x82: /* TODO: BPH */ 2281 case 0x83: /* TODO: NBH */ 2282 case 0x84: /* TODO: IND */ 2283 break; 2284 case 0x85: /* NEL -- Next line */ 2285 tnewline(1); /* always go to first col */ 2286 break; 2287 case 0x86: /* TODO: SSA */ 2288 case 0x87: /* TODO: ESA */ 2289 break; 2290 case 0x88: /* HTS -- Horizontal tab stop */ 2291 term.tabs[term.c.x] = 1; 2292 break; 2293 case 0x89: /* TODO: HTJ */ 2294 case 0x8a: /* TODO: VTS */ 2295 case 0x8b: /* TODO: PLD */ 2296 case 0x8c: /* TODO: PLU */ 2297 case 0x8d: /* TODO: RI */ 2298 case 0x8e: /* TODO: SS2 */ 2299 case 0x8f: /* TODO: SS3 */ 2300 case 0x91: /* TODO: PU1 */ 2301 case 0x92: /* TODO: PU2 */ 2302 case 0x93: /* TODO: STS */ 2303 case 0x94: /* TODO: CCH */ 2304 case 0x95: /* TODO: MW */ 2305 case 0x96: /* TODO: SPA */ 2306 case 0x97: /* TODO: EPA */ 2307 case 0x98: /* TODO: SOS */ 2308 case 0x99: /* TODO: SGCI */ 2309 break; 2310 case 0x9a: /* DECID -- Identify Terminal */ 2311 ttywrite(vtiden, strlen(vtiden), 0); 2312 break; 2313 case 0x9b: /* TODO: CSI */ 2314 case 0x9c: /* TODO: ST */ 2315 break; 2316 case 0x90: /* DCS -- Device Control String */ 2317 case 0x9d: /* OSC -- Operating System Command */ 2318 case 0x9e: /* PM -- Privacy Message */ 2319 case 0x9f: /* APC -- Application Program Command */ 2320 tstrsequence(ascii); 2321 return; 2322 } 2323 /* only CAN, SUB, \a and C1 chars interrupt a sequence */ 2324 term.esc &= ~(ESC_STR_END|ESC_STR); 2325 } 2326 2327 /* 2328 * returns 1 when the sequence is finished and it hasn't to read 2329 * more characters for this sequence, otherwise 0 2330 */ 2331 int 2332 eschandle(uchar ascii) 2333 { 2334 switch (ascii) { 2335 case '[': 2336 term.esc |= ESC_CSI; 2337 return 0; 2338 case '#': 2339 term.esc |= ESC_TEST; 2340 return 0; 2341 case '%': 2342 term.esc |= ESC_UTF8; 2343 return 0; 2344 case 'P': /* DCS -- Device Control String */ 2345 case '_': /* APC -- Application Program Command */ 2346 case '^': /* PM -- Privacy Message */ 2347 case ']': /* OSC -- Operating System Command */ 2348 case 'k': /* old title set compatibility */ 2349 tstrsequence(ascii); 2350 return 0; 2351 case 'n': /* LS2 -- Locking shift 2 */ 2352 case 'o': /* LS3 -- Locking shift 3 */ 2353 term.charset = 2 + (ascii - 'n'); 2354 break; 2355 case '(': /* GZD4 -- set primary charset G0 */ 2356 case ')': /* G1D4 -- set secondary charset G1 */ 2357 case '*': /* G2D4 -- set tertiary charset G2 */ 2358 case '+': /* G3D4 -- set quaternary charset G3 */ 2359 term.icharset = ascii - '('; 2360 term.esc |= ESC_ALTCHARSET; 2361 return 0; 2362 case 'D': /* IND -- Linefeed */ 2363 if (term.c.y == term.bot) { 2364 tscrollup(term.top, 1, 1); 2365 } else { 2366 tmoveto(term.c.x, term.c.y+1); 2367 } 2368 break; 2369 case 'E': /* NEL -- Next line */ 2370 tnewline(1); /* always go to first col */ 2371 break; 2372 case 'H': /* HTS -- Horizontal tab stop */ 2373 term.tabs[term.c.x] = 1; 2374 break; 2375 case 'M': /* RI -- Reverse index */ 2376 if (term.c.y == term.top) { 2377 tscrolldown(term.top, 1, 1); 2378 } else { 2379 tmoveto(term.c.x, term.c.y-1); 2380 } 2381 break; 2382 case 'Z': /* DECID -- Identify Terminal */ 2383 ttywrite(vtiden, strlen(vtiden), 0); 2384 break; 2385 case 'c': /* RIS -- Reset to initial state */ 2386 treset(); 2387 resettitle(); 2388 xloadcols(); 2389 break; 2390 case '=': /* DECPAM -- Application keypad */ 2391 xsetmode(1, MODE_APPKEYPAD); 2392 break; 2393 case '>': /* DECPNM -- Normal keypad */ 2394 xsetmode(0, MODE_APPKEYPAD); 2395 break; 2396 case '7': /* DECSC -- Save Cursor */ 2397 tcursor(CURSOR_SAVE); 2398 break; 2399 case '8': /* DECRC -- Restore Cursor */ 2400 tcursor(CURSOR_LOAD); 2401 break; 2402 case '\\': /* ST -- String Terminator */ 2403 if (term.esc & ESC_STR_END) 2404 strhandle(); 2405 break; 2406 default: 2407 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n", 2408 (uchar) ascii, isprint(ascii)? ascii:'.'); 2409 break; 2410 } 2411 return 1; 2412 } 2413 2414 void 2415 tputc(Rune u) 2416 { 2417 char c[UTF_SIZ]; 2418 int control; 2419 int width, len; 2420 Glyph *gp; 2421 2422 control = ISCONTROL(u); 2423 if (u < 127 || !IS_SET(MODE_UTF8)) { 2424 c[0] = u; 2425 width = len = 1; 2426 } else { 2427 len = utf8encode(u, c); 2428 if (!control && (width = wcwidth(u)) == -1) 2429 width = 1; 2430 } 2431 2432 if (IS_SET(MODE_PRINT)) 2433 tprinter(c, len); 2434 2435 /* 2436 * STR sequence must be checked before anything else 2437 * because it uses all following characters until it 2438 * receives a ESC, a SUB, a ST or any other C1 control 2439 * character. 2440 */ 2441 if (term.esc & ESC_STR) { 2442 if (u == '\a' || u == 030 || u == 032 || u == 033 || 2443 ISCONTROLC1(u)) { 2444 term.esc &= ~(ESC_START|ESC_STR); 2445 term.esc |= ESC_STR_END; 2446 goto check_control_code; 2447 } 2448 2449 if (strescseq.len+len >= strescseq.siz) { 2450 /* 2451 * Here is a bug in terminals. If the user never sends 2452 * some code to stop the str or esc command, then st 2453 * will stop responding. But this is better than 2454 * silently failing with unknown characters. At least 2455 * then users will report back. 2456 * 2457 * In the case users ever get fixed, here is the code: 2458 */ 2459 /* 2460 * term.esc = 0; 2461 * strhandle(); 2462 */ 2463 if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2) 2464 return; 2465 strescseq.siz *= 2; 2466 strescseq.buf = xrealloc(strescseq.buf, strescseq.siz); 2467 } 2468 2469 memmove(&strescseq.buf[strescseq.len], c, len); 2470 strescseq.len += len; 2471 return; 2472 } 2473 2474 check_control_code: 2475 /* 2476 * Actions of control codes must be performed as soon they arrive 2477 * because they can be embedded inside a control sequence, and 2478 * they must not cause conflicts with sequences. 2479 */ 2480 if (control) { 2481 tcontrolcode(u); 2482 /* 2483 * control codes are not shown ever 2484 */ 2485 if (!term.esc) 2486 term.lastc = 0; 2487 return; 2488 } else if (term.esc & ESC_START) { 2489 if (term.esc & ESC_CSI) { 2490 csiescseq.buf[csiescseq.len++] = u; 2491 if (BETWEEN(u, 0x40, 0x7E) 2492 || csiescseq.len >= \ 2493 sizeof(csiescseq.buf)-1) { 2494 term.esc = 0; 2495 csiparse(); 2496 csihandle(); 2497 } 2498 return; 2499 } else if (term.esc & ESC_UTF8) { 2500 tdefutf8(u); 2501 } else if (term.esc & ESC_ALTCHARSET) { 2502 tdeftran(u); 2503 } else if (term.esc & ESC_TEST) { 2504 tdectest(u); 2505 } else { 2506 if (!eschandle(u)) 2507 return; 2508 /* sequence already finished */ 2509 } 2510 term.esc = 0; 2511 /* 2512 * All characters which form part of a sequence are not 2513 * printed 2514 */ 2515 return; 2516 } 2517 if (selected(term.c.x, term.c.y)) 2518 selclear(); 2519 2520 gp = &term.line[term.c.y][term.c.x]; 2521 if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) { 2522 gp->mode |= ATTR_WRAP; 2523 tnewline(1); 2524 gp = &term.line[term.c.y][term.c.x]; 2525 } 2526 2527 if (IS_SET(MODE_INSERT) && term.c.x+width < term.col) 2528 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph)); 2529 2530 if (term.c.x+width > term.col) { 2531 tnewline(1); 2532 gp = &term.line[term.c.y][term.c.x]; 2533 } 2534 2535 tsetchar(u, &term.c.attr, term.c.x, term.c.y); 2536 term.lastc = u; 2537 2538 if (width == 2) { 2539 gp->mode |= ATTR_WIDE; 2540 if (term.c.x+1 < term.col) { 2541 gp[1].u = '\0'; 2542 gp[1].mode = ATTR_WDUMMY; 2543 } 2544 } 2545 if (term.c.x+width < term.col) { 2546 tmoveto(term.c.x+width, term.c.y); 2547 } else { 2548 term.c.state |= CURSOR_WRAPNEXT; 2549 } 2550 } 2551 2552 int 2553 twrite(const char *buf, int buflen, int show_ctrl) 2554 { 2555 int charsize; 2556 Rune u; 2557 int n; 2558 2559 for (n = 0; n < buflen; n += charsize) { 2560 if (IS_SET(MODE_UTF8)) { 2561 /* process a complete utf8 char */ 2562 charsize = utf8decode(buf + n, &u, buflen - n); 2563 if (charsize == 0) 2564 break; 2565 } else { 2566 u = buf[n] & 0xFF; 2567 charsize = 1; 2568 } 2569 if (show_ctrl && ISCONTROL(u)) { 2570 if (u & 0x80) { 2571 u &= 0x7f; 2572 tputc('^'); 2573 tputc('['); 2574 } else if (u != '\n' && u != '\r' && u != '\t') { 2575 u ^= 0x40; 2576 tputc('^'); 2577 } 2578 } 2579 tputc(u); 2580 } 2581 return n; 2582 } 2583 2584 void 2585 tresize(int col, int row) 2586 { 2587 int i, j; 2588 int minrow = MIN(row, term.row); 2589 int mincol = MIN(col, term.col); 2590 int *bp; 2591 TCursor c; 2592 2593 if (col < 1 || row < 1) { 2594 fprintf(stderr, 2595 "tresize: error resizing to %dx%d\n", col, row); 2596 return; 2597 } 2598 2599 /* 2600 * slide screen to keep cursor where we expect it - 2601 * tscrollup would work here, but we can optimize to 2602 * memmove because we're freeing the earlier lines 2603 */ 2604 for (i = 0; i <= term.c.y - row; i++) { 2605 free(term.line[i]); 2606 free(term.alt[i]); 2607 } 2608 /* ensure that both src and dst are not NULL */ 2609 if (i > 0) { 2610 memmove(term.line, term.line + i, row * sizeof(Line)); 2611 memmove(term.alt, term.alt + i, row * sizeof(Line)); 2612 } 2613 for (i += row; i < term.row; i++) { 2614 free(term.line[i]); 2615 free(term.alt[i]); 2616 } 2617 2618 /* resize to new height */ 2619 term.line = xrealloc(term.line, row * sizeof(Line)); 2620 term.alt = xrealloc(term.alt, row * sizeof(Line)); 2621 term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); 2622 term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); 2623 2624 for (i = 0; i < HISTSIZE; i++) { 2625 term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph)); 2626 for (j = mincol; j < col; j++) { 2627 term.hist[i][j] = term.c.attr; 2628 term.hist[i][j].u = ' '; 2629 } 2630 } 2631 2632 /* resize each row to new width, zero-pad if needed */ 2633 for (i = 0; i < minrow; i++) { 2634 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); 2635 term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph)); 2636 } 2637 2638 /* allocate any new rows */ 2639 for (/* i = minrow */; i < row; i++) { 2640 term.line[i] = xmalloc(col * sizeof(Glyph)); 2641 term.alt[i] = xmalloc(col * sizeof(Glyph)); 2642 } 2643 if (col > term.col) { 2644 bp = term.tabs + term.col; 2645 2646 memset(bp, 0, sizeof(*term.tabs) * (col - term.col)); 2647 while (--bp > term.tabs && !*bp) 2648 /* nothing */ ; 2649 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces) 2650 *bp = 1; 2651 } 2652 /* update terminal size */ 2653 term.col = col; 2654 term.row = row; 2655 /* reset scrolling region */ 2656 tsetscroll(0, row-1); 2657 /* make use of the LIMIT in tmoveto */ 2658 tmoveto(term.c.x, term.c.y); 2659 /* Clearing both screens (it makes dirty all lines) */ 2660 c = term.c; 2661 for (i = 0; i < 2; i++) { 2662 if (mincol < col && 0 < minrow) { 2663 tclearregion(mincol, 0, col - 1, minrow - 1); 2664 } 2665 if (0 < col && minrow < row) { 2666 tclearregion(0, minrow, col - 1, row - 1); 2667 } 2668 tswapscreen(); 2669 tcursor(CURSOR_LOAD); 2670 } 2671 term.c = c; 2672 } 2673 2674 void 2675 resettitle(void) 2676 { 2677 xsettitle(NULL); 2678 } 2679 2680 void 2681 drawregion(int x1, int y1, int x2, int y2) 2682 { 2683 int y; 2684 2685 for (y = y1; y < y2; y++) { 2686 if (!term.dirty[y]) 2687 continue; 2688 2689 term.dirty[y] = 0; 2690 xdrawline(TLINE(y), x1, y, x2); 2691 } 2692 } 2693 2694 void 2695 draw(void) 2696 { 2697 int cx = term.c.x, ocx = term.ocx, ocy = term.ocy; 2698 2699 if (!xstartdraw()) 2700 return; 2701 2702 /* adjust cursor position */ 2703 LIMIT(term.ocx, 0, term.col-1); 2704 LIMIT(term.ocy, 0, term.row-1); 2705 if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY) 2706 term.ocx--; 2707 if (term.line[term.c.y][cx].mode & ATTR_WDUMMY) 2708 cx--; 2709 2710 drawregion(0, 0, term.col, term.row); 2711 if (term.scr == 0) 2712 xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], 2713 term.ocx, term.ocy, term.line[term.ocy][term.ocx]); 2714 term.ocx = cx; 2715 term.ocy = term.c.y; 2716 xfinishdraw(); 2717 if (ocx != term.ocx || ocy != term.ocy) 2718 xximspot(term.ocx, term.ocy); 2719 } 2720 2721 void 2722 redraw(void) 2723 { 2724 tfulldirt(); 2725 draw(); 2726 }