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