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