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