st.c (59200B)
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 if (copyhist) { 1115 term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE; 1116 temp = term.hist[term.histi]; 1117 term.hist[term.histi] = term.line[term.bot]; 1118 term.line[term.bot] = temp; 1119 } 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 osc4_color_response(int num) 1909 { 1910 int n; 1911 char buf[32]; 1912 unsigned char r, g, b; 1913 1914 if (xgetcolor(num, &r, &g, &b)) { 1915 fprintf(stderr, "erresc: failed to fetch osc4 color %d\n", num); 1916 return; 1917 } 1918 1919 n = snprintf(buf, sizeof buf, "\033]4;%d;rgb:%02x%02x/%02x%02x/%02x%02x\007", 1920 num, r, r, g, g, b, b); 1921 1922 ttywrite(buf, n, 1); 1923 } 1924 1925 void 1926 osc_color_response(int index, int num) 1927 { 1928 int n; 1929 char buf[32]; 1930 unsigned char r, g, b; 1931 1932 if (xgetcolor(index, &r, &g, &b)) { 1933 fprintf(stderr, "erresc: failed to fetch osc color %d\n", index); 1934 return; 1935 } 1936 1937 n = snprintf(buf, sizeof buf, "\033]%d;rgb:%02x%02x/%02x%02x/%02x%02x\007", 1938 num, r, r, g, g, b, b); 1939 1940 ttywrite(buf, n, 1); 1941 } 1942 1943 void 1944 strhandle(void) 1945 { 1946 char *p = NULL, *dec; 1947 int j, narg, par; 1948 1949 term.esc &= ~(ESC_STR_END|ESC_STR); 1950 strparse(); 1951 par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0; 1952 1953 switch (strescseq.type) { 1954 case ']': /* OSC -- Operating System Command */ 1955 switch (par) { 1956 case 0: 1957 if (narg > 1) { 1958 xsettitle(strescseq.args[1]); 1959 xseticontitle(strescseq.args[1]); 1960 } 1961 return; 1962 case 1: 1963 if (narg > 1) 1964 xseticontitle(strescseq.args[1]); 1965 return; 1966 case 2: 1967 if (narg > 1) 1968 xsettitle(strescseq.args[1]); 1969 return; 1970 case 52: 1971 if (narg > 2 && allowwindowops) { 1972 dec = base64dec(strescseq.args[2]); 1973 if (dec) { 1974 xsetsel(dec); 1975 xclipcopy(); 1976 } else { 1977 fprintf(stderr, "erresc: invalid base64\n"); 1978 } 1979 } 1980 return; 1981 case 10: 1982 if (narg < 2) 1983 break; 1984 1985 p = strescseq.args[1]; 1986 1987 if (!strcmp(p, "?")) 1988 osc_color_response(defaultfg, 10); 1989 else if (xsetcolorname(defaultfg, p)) 1990 fprintf(stderr, "erresc: invalid foreground color: %s\n", p); 1991 else 1992 tfulldirt(); 1993 return; 1994 case 11: 1995 if (narg < 2) 1996 break; 1997 1998 p = strescseq.args[1]; 1999 2000 if (!strcmp(p, "?")) 2001 osc_color_response(defaultbg, 11); 2002 else if (xsetcolorname(defaultbg, p)) 2003 fprintf(stderr, "erresc: invalid background color: %s\n", p); 2004 else 2005 tfulldirt(); 2006 return; 2007 case 12: 2008 if (narg < 2) 2009 break; 2010 2011 p = strescseq.args[1]; 2012 2013 if (!strcmp(p, "?")) 2014 osc_color_response(defaultcs, 12); 2015 else if (xsetcolorname(defaultcs, p)) 2016 fprintf(stderr, "erresc: invalid cursor color: %s\n", p); 2017 else 2018 tfulldirt(); 2019 return; 2020 case 4: /* color set */ 2021 if (narg < 3) 2022 break; 2023 p = strescseq.args[2]; 2024 /* FALLTHROUGH */ 2025 case 104: /* color reset */ 2026 j = (narg > 1) ? atoi(strescseq.args[1]) : -1; 2027 2028 if (p && !strcmp(p, "?")) 2029 osc4_color_response(j); 2030 else if (xsetcolorname(j, p)) { 2031 if (par == 104 && narg <= 1) 2032 return; /* color reset without parameter */ 2033 fprintf(stderr, "erresc: invalid color j=%d, p=%s\n", 2034 j, p ? p : "(null)"); 2035 } else { 2036 /* 2037 * TODO if defaultbg color is changed, borders 2038 * are dirty 2039 */ 2040 tfulldirt(); 2041 } 2042 return; 2043 } 2044 break; 2045 case 'k': /* old title set compatibility */ 2046 xsettitle(strescseq.args[0]); 2047 return; 2048 case 'P': /* DCS -- Device Control String */ 2049 case '_': /* APC -- Application Program Command */ 2050 case '^': /* PM -- Privacy Message */ 2051 return; 2052 } 2053 2054 fprintf(stderr, "erresc: unknown str "); 2055 strdump(); 2056 } 2057 2058 void 2059 strparse(void) 2060 { 2061 int c; 2062 char *p = strescseq.buf; 2063 2064 strescseq.narg = 0; 2065 strescseq.buf[strescseq.len] = '\0'; 2066 2067 if (*p == '\0') 2068 return; 2069 2070 while (strescseq.narg < STR_ARG_SIZ) { 2071 strescseq.args[strescseq.narg++] = p; 2072 while ((c = *p) != ';' && c != '\0') 2073 ++p; 2074 if (c == '\0') 2075 return; 2076 *p++ = '\0'; 2077 } 2078 } 2079 2080 void 2081 strdump(void) 2082 { 2083 size_t i; 2084 uint c; 2085 2086 fprintf(stderr, "ESC%c", strescseq.type); 2087 for (i = 0; i < strescseq.len; i++) { 2088 c = strescseq.buf[i] & 0xff; 2089 if (c == '\0') { 2090 putc('\n', stderr); 2091 return; 2092 } else if (isprint(c)) { 2093 putc(c, stderr); 2094 } else if (c == '\n') { 2095 fprintf(stderr, "(\\n)"); 2096 } else if (c == '\r') { 2097 fprintf(stderr, "(\\r)"); 2098 } else if (c == 0x1b) { 2099 fprintf(stderr, "(\\e)"); 2100 } else { 2101 fprintf(stderr, "(%02x)", c); 2102 } 2103 } 2104 fprintf(stderr, "ESC\\\n"); 2105 } 2106 2107 void 2108 strreset(void) 2109 { 2110 strescseq = (STREscape){ 2111 .buf = xrealloc(strescseq.buf, STR_BUF_SIZ), 2112 .siz = STR_BUF_SIZ, 2113 }; 2114 } 2115 2116 void 2117 sendbreak(const Arg *arg) 2118 { 2119 if (tcsendbreak(cmdfd, 0)) 2120 perror("Error sending break"); 2121 } 2122 2123 void 2124 tprinter(char *s, size_t len) 2125 { 2126 if (iofd != -1 && xwrite(iofd, s, len) < 0) { 2127 perror("Error writing to output file"); 2128 close(iofd); 2129 iofd = -1; 2130 } 2131 } 2132 2133 void 2134 toggleprinter(const Arg *arg) 2135 { 2136 term.mode ^= MODE_PRINT; 2137 } 2138 2139 void 2140 printscreen(const Arg *arg) 2141 { 2142 tdump(); 2143 } 2144 2145 void 2146 printsel(const Arg *arg) 2147 { 2148 tdumpsel(); 2149 } 2150 2151 void 2152 tdumpsel(void) 2153 { 2154 char *ptr; 2155 2156 if ((ptr = getsel())) { 2157 tprinter(ptr, strlen(ptr)); 2158 free(ptr); 2159 } 2160 } 2161 2162 void 2163 tdumpline(int n) 2164 { 2165 char buf[UTF_SIZ]; 2166 const Glyph *bp, *end; 2167 2168 bp = &term.line[n][0]; 2169 end = &bp[MIN(tlinelen(n), term.col) - 1]; 2170 if (bp != end || bp->u != ' ') { 2171 for ( ; bp <= end; ++bp) 2172 tprinter(buf, utf8encode(bp->u, buf)); 2173 } 2174 tprinter("\n", 1); 2175 } 2176 2177 void 2178 tdump(void) 2179 { 2180 int i; 2181 2182 for (i = 0; i < term.row; ++i) 2183 tdumpline(i); 2184 } 2185 2186 void 2187 tputtab(int n) 2188 { 2189 uint x = term.c.x; 2190 2191 if (n > 0) { 2192 while (x < term.col && n--) 2193 for (++x; x < term.col && !term.tabs[x]; ++x) 2194 /* nothing */ ; 2195 } else if (n < 0) { 2196 while (x > 0 && n++) 2197 for (--x; x > 0 && !term.tabs[x]; --x) 2198 /* nothing */ ; 2199 } 2200 term.c.x = LIMIT(x, 0, term.col-1); 2201 } 2202 2203 void 2204 tdefutf8(char ascii) 2205 { 2206 if (ascii == 'G') 2207 term.mode |= MODE_UTF8; 2208 else if (ascii == '@') 2209 term.mode &= ~MODE_UTF8; 2210 } 2211 2212 void 2213 tdeftran(char ascii) 2214 { 2215 static char cs[] = "0B"; 2216 static int vcs[] = {CS_GRAPHIC0, CS_USA}; 2217 char *p; 2218 2219 if ((p = strchr(cs, ascii)) == NULL) { 2220 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii); 2221 } else { 2222 term.trantbl[term.icharset] = vcs[p - cs]; 2223 } 2224 } 2225 2226 void 2227 tdectest(char c) 2228 { 2229 int x, y; 2230 2231 if (c == '8') { /* DEC screen alignment test. */ 2232 for (x = 0; x < term.col; ++x) { 2233 for (y = 0; y < term.row; ++y) 2234 tsetchar('E', &term.c.attr, x, y); 2235 } 2236 } 2237 } 2238 2239 void 2240 tstrsequence(uchar c) 2241 { 2242 switch (c) { 2243 case 0x90: /* DCS -- Device Control String */ 2244 c = 'P'; 2245 break; 2246 case 0x9f: /* APC -- Application Program Command */ 2247 c = '_'; 2248 break; 2249 case 0x9e: /* PM -- Privacy Message */ 2250 c = '^'; 2251 break; 2252 case 0x9d: /* OSC -- Operating System Command */ 2253 c = ']'; 2254 break; 2255 } 2256 strreset(); 2257 strescseq.type = c; 2258 term.esc |= ESC_STR; 2259 } 2260 2261 void 2262 tcontrolcode(uchar ascii) 2263 { 2264 switch (ascii) { 2265 case '\t': /* HT */ 2266 tputtab(1); 2267 return; 2268 case '\b': /* BS */ 2269 tmoveto(term.c.x-1, term.c.y); 2270 return; 2271 case '\r': /* CR */ 2272 tmoveto(0, term.c.y); 2273 return; 2274 case '\f': /* LF */ 2275 case '\v': /* VT */ 2276 case '\n': /* LF */ 2277 /* go to first col if the mode is set */ 2278 tnewline(IS_SET(MODE_CRLF)); 2279 return; 2280 case '\a': /* BEL */ 2281 if (term.esc & ESC_STR_END) { 2282 /* backwards compatibility to xterm */ 2283 strhandle(); 2284 } else { 2285 xbell(); 2286 } 2287 break; 2288 case '\033': /* ESC */ 2289 csireset(); 2290 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST); 2291 term.esc |= ESC_START; 2292 return; 2293 case '\016': /* SO (LS1 -- Locking shift 1) */ 2294 case '\017': /* SI (LS0 -- Locking shift 0) */ 2295 term.charset = 1 - (ascii - '\016'); 2296 return; 2297 case '\032': /* SUB */ 2298 tsetchar('?', &term.c.attr, term.c.x, term.c.y); 2299 /* FALLTHROUGH */ 2300 case '\030': /* CAN */ 2301 csireset(); 2302 break; 2303 case '\005': /* ENQ (IGNORED) */ 2304 case '\000': /* NUL (IGNORED) */ 2305 case '\021': /* XON (IGNORED) */ 2306 case '\023': /* XOFF (IGNORED) */ 2307 case 0177: /* DEL (IGNORED) */ 2308 return; 2309 case 0x80: /* TODO: PAD */ 2310 case 0x81: /* TODO: HOP */ 2311 case 0x82: /* TODO: BPH */ 2312 case 0x83: /* TODO: NBH */ 2313 case 0x84: /* TODO: IND */ 2314 break; 2315 case 0x85: /* NEL -- Next line */ 2316 tnewline(1); /* always go to first col */ 2317 break; 2318 case 0x86: /* TODO: SSA */ 2319 case 0x87: /* TODO: ESA */ 2320 break; 2321 case 0x88: /* HTS -- Horizontal tab stop */ 2322 term.tabs[term.c.x] = 1; 2323 break; 2324 case 0x89: /* TODO: HTJ */ 2325 case 0x8a: /* TODO: VTS */ 2326 case 0x8b: /* TODO: PLD */ 2327 case 0x8c: /* TODO: PLU */ 2328 case 0x8d: /* TODO: RI */ 2329 case 0x8e: /* TODO: SS2 */ 2330 case 0x8f: /* TODO: SS3 */ 2331 case 0x91: /* TODO: PU1 */ 2332 case 0x92: /* TODO: PU2 */ 2333 case 0x93: /* TODO: STS */ 2334 case 0x94: /* TODO: CCH */ 2335 case 0x95: /* TODO: MW */ 2336 case 0x96: /* TODO: SPA */ 2337 case 0x97: /* TODO: EPA */ 2338 case 0x98: /* TODO: SOS */ 2339 case 0x99: /* TODO: SGCI */ 2340 break; 2341 case 0x9a: /* DECID -- Identify Terminal */ 2342 ttywrite(vtiden, strlen(vtiden), 0); 2343 break; 2344 case 0x9b: /* TODO: CSI */ 2345 case 0x9c: /* TODO: ST */ 2346 break; 2347 case 0x90: /* DCS -- Device Control String */ 2348 case 0x9d: /* OSC -- Operating System Command */ 2349 case 0x9e: /* PM -- Privacy Message */ 2350 case 0x9f: /* APC -- Application Program Command */ 2351 tstrsequence(ascii); 2352 return; 2353 } 2354 /* only CAN, SUB, \a and C1 chars interrupt a sequence */ 2355 term.esc &= ~(ESC_STR_END|ESC_STR); 2356 } 2357 2358 /* 2359 * returns 1 when the sequence is finished and it hasn't to read 2360 * more characters for this sequence, otherwise 0 2361 */ 2362 int 2363 eschandle(uchar ascii) 2364 { 2365 switch (ascii) { 2366 case '[': 2367 term.esc |= ESC_CSI; 2368 return 0; 2369 case '#': 2370 term.esc |= ESC_TEST; 2371 return 0; 2372 case '%': 2373 term.esc |= ESC_UTF8; 2374 return 0; 2375 case 'P': /* DCS -- Device Control String */ 2376 case '_': /* APC -- Application Program Command */ 2377 case '^': /* PM -- Privacy Message */ 2378 case ']': /* OSC -- Operating System Command */ 2379 case 'k': /* old title set compatibility */ 2380 tstrsequence(ascii); 2381 return 0; 2382 case 'n': /* LS2 -- Locking shift 2 */ 2383 case 'o': /* LS3 -- Locking shift 3 */ 2384 term.charset = 2 + (ascii - 'n'); 2385 break; 2386 case '(': /* GZD4 -- set primary charset G0 */ 2387 case ')': /* G1D4 -- set secondary charset G1 */ 2388 case '*': /* G2D4 -- set tertiary charset G2 */ 2389 case '+': /* G3D4 -- set quaternary charset G3 */ 2390 term.icharset = ascii - '('; 2391 term.esc |= ESC_ALTCHARSET; 2392 return 0; 2393 case 'D': /* IND -- Linefeed */ 2394 if (term.c.y == term.bot) { 2395 tscrollup(term.top, 1, 1); 2396 } else { 2397 tmoveto(term.c.x, term.c.y+1); 2398 } 2399 break; 2400 case 'E': /* NEL -- Next line */ 2401 tnewline(1); /* always go to first col */ 2402 break; 2403 case 'H': /* HTS -- Horizontal tab stop */ 2404 term.tabs[term.c.x] = 1; 2405 break; 2406 case 'M': /* RI -- Reverse index */ 2407 if (term.c.y == term.top) { 2408 tscrolldown(term.top, 1, 1); 2409 } else { 2410 tmoveto(term.c.x, term.c.y-1); 2411 } 2412 break; 2413 case 'Z': /* DECID -- Identify Terminal */ 2414 ttywrite(vtiden, strlen(vtiden), 0); 2415 break; 2416 case 'c': /* RIS -- Reset to initial state */ 2417 treset(); 2418 resettitle(); 2419 xloadcols(); 2420 break; 2421 case '=': /* DECPAM -- Application keypad */ 2422 xsetmode(1, MODE_APPKEYPAD); 2423 break; 2424 case '>': /* DECPNM -- Normal keypad */ 2425 xsetmode(0, MODE_APPKEYPAD); 2426 break; 2427 case '7': /* DECSC -- Save Cursor */ 2428 tcursor(CURSOR_SAVE); 2429 break; 2430 case '8': /* DECRC -- Restore Cursor */ 2431 tcursor(CURSOR_LOAD); 2432 break; 2433 case '\\': /* ST -- String Terminator */ 2434 if (term.esc & ESC_STR_END) 2435 strhandle(); 2436 break; 2437 default: 2438 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n", 2439 (uchar) ascii, isprint(ascii)? ascii:'.'); 2440 break; 2441 } 2442 return 1; 2443 } 2444 2445 void 2446 tputc(Rune u) 2447 { 2448 char c[UTF_SIZ]; 2449 int control; 2450 int width, len; 2451 Glyph *gp; 2452 2453 control = ISCONTROL(u); 2454 if (u < 127 || !IS_SET(MODE_UTF8)) { 2455 c[0] = u; 2456 width = len = 1; 2457 } else { 2458 len = utf8encode(u, c); 2459 if (!control && (width = wcwidth(u)) == -1) 2460 width = 1; 2461 } 2462 2463 if (IS_SET(MODE_PRINT)) 2464 tprinter(c, len); 2465 2466 /* 2467 * STR sequence must be checked before anything else 2468 * because it uses all following characters until it 2469 * receives a ESC, a SUB, a ST or any other C1 control 2470 * character. 2471 */ 2472 if (term.esc & ESC_STR) { 2473 if (u == '\a' || u == 030 || u == 032 || u == 033 || 2474 ISCONTROLC1(u)) { 2475 term.esc &= ~(ESC_START|ESC_STR); 2476 term.esc |= ESC_STR_END; 2477 goto check_control_code; 2478 } 2479 2480 if (strescseq.len+len >= strescseq.siz) { 2481 /* 2482 * Here is a bug in terminals. If the user never sends 2483 * some code to stop the str or esc command, then st 2484 * will stop responding. But this is better than 2485 * silently failing with unknown characters. At least 2486 * then users will report back. 2487 * 2488 * In the case users ever get fixed, here is the code: 2489 */ 2490 /* 2491 * term.esc = 0; 2492 * strhandle(); 2493 */ 2494 if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2) 2495 return; 2496 strescseq.siz *= 2; 2497 strescseq.buf = xrealloc(strescseq.buf, strescseq.siz); 2498 } 2499 2500 memmove(&strescseq.buf[strescseq.len], c, len); 2501 strescseq.len += len; 2502 return; 2503 } 2504 2505 check_control_code: 2506 /* 2507 * Actions of control codes must be performed as soon they arrive 2508 * because they can be embedded inside a control sequence, and 2509 * they must not cause conflicts with sequences. 2510 */ 2511 if (control) { 2512 tcontrolcode(u); 2513 /* 2514 * control codes are not shown ever 2515 */ 2516 if (!term.esc) 2517 term.lastc = 0; 2518 return; 2519 } else if (term.esc & ESC_START) { 2520 if (term.esc & ESC_CSI) { 2521 csiescseq.buf[csiescseq.len++] = u; 2522 if (BETWEEN(u, 0x40, 0x7E) 2523 || csiescseq.len >= \ 2524 sizeof(csiescseq.buf)-1) { 2525 term.esc = 0; 2526 csiparse(); 2527 csihandle(); 2528 } 2529 return; 2530 } else if (term.esc & ESC_UTF8) { 2531 tdefutf8(u); 2532 } else if (term.esc & ESC_ALTCHARSET) { 2533 tdeftran(u); 2534 } else if (term.esc & ESC_TEST) { 2535 tdectest(u); 2536 } else { 2537 if (!eschandle(u)) 2538 return; 2539 /* sequence already finished */ 2540 } 2541 term.esc = 0; 2542 /* 2543 * All characters which form part of a sequence are not 2544 * printed 2545 */ 2546 return; 2547 } 2548 if (selected(term.c.x, term.c.y)) 2549 selclear(); 2550 2551 gp = &term.line[term.c.y][term.c.x]; 2552 if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) { 2553 gp->mode |= ATTR_WRAP; 2554 tnewline(1); 2555 gp = &term.line[term.c.y][term.c.x]; 2556 } 2557 2558 if (IS_SET(MODE_INSERT) && term.c.x+width < term.col) 2559 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph)); 2560 2561 if (term.c.x+width > term.col) { 2562 tnewline(1); 2563 gp = &term.line[term.c.y][term.c.x]; 2564 } 2565 2566 tsetchar(u, &term.c.attr, term.c.x, term.c.y); 2567 term.lastc = u; 2568 2569 if (width == 2) { 2570 gp->mode |= ATTR_WIDE; 2571 if (term.c.x+1 < term.col) { 2572 if (gp[1].mode == ATTR_WIDE && term.c.x+2 < term.col) { 2573 gp[2].u = ' '; 2574 gp[2].mode &= ~ATTR_WDUMMY; 2575 } 2576 gp[1].u = '\0'; 2577 gp[1].mode = ATTR_WDUMMY; 2578 } 2579 } 2580 if (term.c.x+width < term.col) { 2581 tmoveto(term.c.x+width, term.c.y); 2582 } else { 2583 term.c.state |= CURSOR_WRAPNEXT; 2584 } 2585 } 2586 2587 int 2588 twrite(const char *buf, int buflen, int show_ctrl) 2589 { 2590 int charsize; 2591 Rune u; 2592 int n; 2593 2594 for (n = 0; n < buflen; n += charsize) { 2595 if (IS_SET(MODE_UTF8)) { 2596 /* process a complete utf8 char */ 2597 charsize = utf8decode(buf + n, &u, buflen - n); 2598 if (charsize == 0) 2599 break; 2600 } else { 2601 u = buf[n] & 0xFF; 2602 charsize = 1; 2603 } 2604 if (show_ctrl && ISCONTROL(u)) { 2605 if (u & 0x80) { 2606 u &= 0x7f; 2607 tputc('^'); 2608 tputc('['); 2609 } else if (u != '\n' && u != '\r' && u != '\t') { 2610 u ^= 0x40; 2611 tputc('^'); 2612 } 2613 } 2614 tputc(u); 2615 } 2616 return n; 2617 } 2618 2619 void 2620 tresize(int col, int row) 2621 { 2622 int i, j; 2623 int minrow = MIN(row, term.row); 2624 int mincol = MIN(col, term.col); 2625 int *bp; 2626 TCursor c; 2627 2628 if (col < 1 || row < 1) { 2629 fprintf(stderr, 2630 "tresize: error resizing to %dx%d\n", col, row); 2631 return; 2632 } 2633 2634 /* 2635 * slide screen to keep cursor where we expect it - 2636 * tscrollup would work here, but we can optimize to 2637 * memmove because we're freeing the earlier lines 2638 */ 2639 for (i = 0; i <= term.c.y - row; i++) { 2640 free(term.line[i]); 2641 free(term.alt[i]); 2642 } 2643 /* ensure that both src and dst are not NULL */ 2644 if (i > 0) { 2645 memmove(term.line, term.line + i, row * sizeof(Line)); 2646 memmove(term.alt, term.alt + i, row * sizeof(Line)); 2647 } 2648 for (i += row; i < term.row; i++) { 2649 free(term.line[i]); 2650 free(term.alt[i]); 2651 } 2652 2653 /* resize to new height */ 2654 term.line = xrealloc(term.line, row * sizeof(Line)); 2655 term.alt = xrealloc(term.alt, row * sizeof(Line)); 2656 term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); 2657 term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); 2658 2659 for (i = 0; i < HISTSIZE; i++) { 2660 term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph)); 2661 for (j = mincol; j < col; j++) { 2662 term.hist[i][j] = term.c.attr; 2663 term.hist[i][j].u = ' '; 2664 } 2665 } 2666 2667 /* resize each row to new width, zero-pad if needed */ 2668 for (i = 0; i < minrow; i++) { 2669 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); 2670 term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph)); 2671 } 2672 2673 /* allocate any new rows */ 2674 for (/* i = minrow */; i < row; i++) { 2675 term.line[i] = xmalloc(col * sizeof(Glyph)); 2676 term.alt[i] = xmalloc(col * sizeof(Glyph)); 2677 } 2678 if (col > term.col) { 2679 bp = term.tabs + term.col; 2680 2681 memset(bp, 0, sizeof(*term.tabs) * (col - term.col)); 2682 while (--bp > term.tabs && !*bp) 2683 /* nothing */ ; 2684 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces) 2685 *bp = 1; 2686 } 2687 /* update terminal size */ 2688 term.col = col; 2689 term.row = row; 2690 /* reset scrolling region */ 2691 tsetscroll(0, row-1); 2692 /* make use of the LIMIT in tmoveto */ 2693 tmoveto(term.c.x, term.c.y); 2694 /* Clearing both screens (it makes dirty all lines) */ 2695 c = term.c; 2696 for (i = 0; i < 2; i++) { 2697 if (mincol < col && 0 < minrow) { 2698 tclearregion(mincol, 0, col - 1, minrow - 1); 2699 } 2700 if (0 < col && minrow < row) { 2701 tclearregion(0, minrow, col - 1, row - 1); 2702 } 2703 tswapscreen(); 2704 tcursor(CURSOR_LOAD); 2705 } 2706 term.c = c; 2707 } 2708 2709 void 2710 resettitle(void) 2711 { 2712 xsettitle(NULL); 2713 } 2714 2715 void 2716 drawregion(int x1, int y1, int x2, int y2) 2717 { 2718 int y; 2719 2720 for (y = y1; y < y2; y++) { 2721 if (!term.dirty[y]) 2722 continue; 2723 2724 term.dirty[y] = 0; 2725 xdrawline(TLINE(y), x1, y, x2); 2726 } 2727 } 2728 2729 void 2730 draw(void) 2731 { 2732 int cx = term.c.x, ocx = term.ocx, ocy = term.ocy; 2733 2734 if (!xstartdraw()) 2735 return; 2736 2737 /* adjust cursor position */ 2738 LIMIT(term.ocx, 0, term.col-1); 2739 LIMIT(term.ocy, 0, term.row-1); 2740 if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY) 2741 term.ocx--; 2742 if (term.line[term.c.y][cx].mode & ATTR_WDUMMY) 2743 cx--; 2744 2745 drawregion(0, 0, term.col, term.row); 2746 if (term.scr == 0) 2747 xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], 2748 term.ocx, term.ocy, term.line[term.ocy][term.ocx]); 2749 term.ocx = cx; 2750 term.ocy = term.c.y; 2751 xfinishdraw(); 2752 if (ocx != term.ocx || ocy != term.ocy) 2753 xximspot(term.ocx, term.ocy); 2754 } 2755 2756 void 2757 redraw(void) 2758 { 2759 tfulldirt(); 2760 draw(); 2761 }