st.c (55849B)
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(int *, int); 190 static void tsetchar(Rune, 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, 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(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 uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; 230 static uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; 231 static Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; 232 static 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(char *s) 273 { 274 if ((s = strdup(s)) == NULL) 275 die("strdup: %s\n", strerror(errno)); 276 277 return s; 278 } 279 280 size_t 281 utf8decode(const char *c, Rune *u, size_t clen) 282 { 283 size_t i, j, len, type; 284 Rune udecoded; 285 286 *u = UTF_INVALID; 287 if (!clen) 288 return 0; 289 udecoded = utf8decodebyte(c[0], &len); 290 if (!BETWEEN(len, 1, UTF_SIZ)) 291 return 1; 292 for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { 293 udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); 294 if (type != 0) 295 return j; 296 } 297 if (j < len) 298 return 0; 299 *u = udecoded; 300 utf8validate(u, len); 301 302 return len; 303 } 304 305 Rune 306 utf8decodebyte(char c, size_t *i) 307 { 308 for (*i = 0; *i < LEN(utfmask); ++(*i)) 309 if (((uchar)c & utfmask[*i]) == utfbyte[*i]) 310 return (uchar)c & ~utfmask[*i]; 311 312 return 0; 313 } 314 315 size_t 316 utf8encode(Rune u, char *c) 317 { 318 size_t len, i; 319 320 len = utf8validate(&u, 0); 321 if (len > UTF_SIZ) 322 return 0; 323 324 for (i = len - 1; i != 0; --i) { 325 c[i] = utf8encodebyte(u, 0); 326 u >>= 6; 327 } 328 c[0] = utf8encodebyte(u, len); 329 330 return len; 331 } 332 333 char 334 utf8encodebyte(Rune u, size_t i) 335 { 336 return utfbyte[i] | (u & ~utfmask[i]); 337 } 338 339 size_t 340 utf8validate(Rune *u, size_t i) 341 { 342 if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) 343 *u = UTF_INVALID; 344 for (i = 1; *u > utfmax[i]; ++i) 345 ; 346 347 return i; 348 } 349 350 static const char base64_digits[] = { 351 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 352 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0, 353 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1, 354 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 355 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 356 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0, 357 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 358 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 359 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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 363 }; 364 365 char 366 base64dec_getc(const char **src) 367 { 368 while (**src && !isprint(**src)) 369 (*src)++; 370 return **src ? *((*src)++) : '='; /* emulate padding if string ends */ 371 } 372 373 char * 374 base64dec(const char *src) 375 { 376 size_t in_len = strlen(src); 377 char *result, *dst; 378 379 if (in_len % 4) 380 in_len += 4 - (in_len % 4); 381 result = dst = xmalloc(in_len / 4 * 3 + 1); 382 while (*src) { 383 int a = base64_digits[(unsigned char) base64dec_getc(&src)]; 384 int b = base64_digits[(unsigned char) base64dec_getc(&src)]; 385 int c = base64_digits[(unsigned char) base64dec_getc(&src)]; 386 int d = base64_digits[(unsigned char) base64dec_getc(&src)]; 387 388 /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */ 389 if (a == -1 || b == -1) 390 break; 391 392 *dst++ = (a << 2) | ((b & 0x30) >> 4); 393 if (c == -1) 394 break; 395 *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2); 396 if (d == -1) 397 break; 398 *dst++ = ((c & 0x03) << 6) | d; 399 } 400 *dst = '\0'; 401 return result; 402 } 403 404 void 405 selinit(void) 406 { 407 sel.mode = SEL_IDLE; 408 sel.snap = 0; 409 sel.ob.x = -1; 410 } 411 412 int 413 tlinelen(int y) 414 { 415 int i = term.col; 416 417 if (term.line[y][i - 1].mode & ATTR_WRAP) 418 return i; 419 420 while (i > 0 && term.line[y][i - 1].u == ' ') 421 --i; 422 423 return i; 424 } 425 426 void 427 selstart(int col, int row, int snap) 428 { 429 selclear(); 430 sel.mode = SEL_EMPTY; 431 sel.type = SEL_REGULAR; 432 sel.alt = IS_SET(MODE_ALTSCREEN); 433 sel.snap = snap; 434 sel.oe.x = sel.ob.x = col; 435 sel.oe.y = sel.ob.y = row; 436 selnormalize(); 437 438 if (sel.snap != 0) 439 sel.mode = SEL_READY; 440 tsetdirt(sel.nb.y, sel.ne.y); 441 } 442 443 void 444 selextend(int col, int row, int type, int done) 445 { 446 int oldey, oldex, oldsby, oldsey, oldtype; 447 448 if (sel.mode == SEL_IDLE) 449 return; 450 if (done && sel.mode == SEL_EMPTY) { 451 selclear(); 452 return; 453 } 454 455 oldey = sel.oe.y; 456 oldex = sel.oe.x; 457 oldsby = sel.nb.y; 458 oldsey = sel.ne.y; 459 oldtype = sel.type; 460 461 sel.oe.x = col; 462 sel.oe.y = row; 463 selnormalize(); 464 sel.type = type; 465 466 if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY) 467 tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey)); 468 469 sel.mode = done ? SEL_IDLE : SEL_READY; 470 } 471 472 void 473 selnormalize(void) 474 { 475 int i; 476 477 if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) { 478 sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x; 479 sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x; 480 } else { 481 sel.nb.x = MIN(sel.ob.x, sel.oe.x); 482 sel.ne.x = MAX(sel.ob.x, sel.oe.x); 483 } 484 sel.nb.y = MIN(sel.ob.y, sel.oe.y); 485 sel.ne.y = MAX(sel.ob.y, sel.oe.y); 486 487 selsnap(&sel.nb.x, &sel.nb.y, -1); 488 selsnap(&sel.ne.x, &sel.ne.y, +1); 489 490 /* expand selection over line breaks */ 491 if (sel.type == SEL_RECTANGULAR) 492 return; 493 i = tlinelen(sel.nb.y); 494 if (i < sel.nb.x) 495 sel.nb.x = i; 496 if (tlinelen(sel.ne.y) <= sel.ne.x) 497 sel.ne.x = term.col - 1; 498 } 499 500 int 501 selected(int x, int y) 502 { 503 if (sel.mode == SEL_EMPTY || sel.ob.x == -1 || 504 sel.alt != IS_SET(MODE_ALTSCREEN)) 505 return 0; 506 507 if (sel.type == SEL_RECTANGULAR) 508 return BETWEEN(y, sel.nb.y, sel.ne.y) 509 && BETWEEN(x, sel.nb.x, sel.ne.x); 510 511 return BETWEEN(y, sel.nb.y, sel.ne.y) 512 && (y != sel.nb.y || x >= sel.nb.x) 513 && (y != sel.ne.y || x <= sel.ne.x); 514 } 515 516 void 517 selsnap(int *x, int *y, int direction) 518 { 519 int newx, newy, xt, yt; 520 int delim, prevdelim; 521 Glyph *gp, *prevgp; 522 523 switch (sel.snap) { 524 case SNAP_WORD: 525 /* 526 * Snap around if the word wraps around at the end or 527 * beginning of a line. 528 */ 529 prevgp = &term.line[*y][*x]; 530 prevdelim = ISDELIM(prevgp->u); 531 for (;;) { 532 newx = *x + direction; 533 newy = *y; 534 if (!BETWEEN(newx, 0, term.col - 1)) { 535 newy += direction; 536 newx = (newx + term.col) % term.col; 537 if (!BETWEEN(newy, 0, term.row - 1)) 538 break; 539 540 if (direction > 0) 541 yt = *y, xt = *x; 542 else 543 yt = newy, xt = newx; 544 if (!(term.line[yt][xt].mode & ATTR_WRAP)) 545 break; 546 } 547 548 if (newx >= tlinelen(newy)) 549 break; 550 551 gp = &term.line[newy][newx]; 552 delim = ISDELIM(gp->u); 553 if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim 554 || (delim && gp->u != prevgp->u))) 555 break; 556 557 *x = newx; 558 *y = newy; 559 prevgp = gp; 560 prevdelim = delim; 561 } 562 break; 563 case SNAP_LINE: 564 /* 565 * Snap around if the the previous line or the current one 566 * has set ATTR_WRAP at its end. Then the whole next or 567 * previous line will be selected. 568 */ 569 *x = (direction < 0) ? 0 : term.col - 1; 570 if (direction < 0) { 571 for (; *y > 0; *y += direction) { 572 if (!(term.line[*y-1][term.col-1].mode 573 & ATTR_WRAP)) { 574 break; 575 } 576 } 577 } else if (direction > 0) { 578 for (; *y < term.row-1; *y += direction) { 579 if (!(term.line[*y][term.col-1].mode 580 & ATTR_WRAP)) { 581 break; 582 } 583 } 584 } 585 break; 586 } 587 } 588 589 char * 590 getsel(void) 591 { 592 char *str, *ptr; 593 int y, bufsize, lastx, linelen; 594 Glyph *gp, *last; 595 596 if (sel.ob.x == -1) 597 return NULL; 598 599 bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ; 600 ptr = str = xmalloc(bufsize); 601 602 /* append every set & selected glyph to the selection */ 603 for (y = sel.nb.y; y <= sel.ne.y; y++) { 604 if ((linelen = tlinelen(y)) == 0) { 605 *ptr++ = '\n'; 606 continue; 607 } 608 609 if (sel.type == SEL_RECTANGULAR) { 610 gp = &term.line[y][sel.nb.x]; 611 lastx = sel.ne.x; 612 } else { 613 gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0]; 614 lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; 615 } 616 last = &term.line[y][MIN(lastx, linelen-1)]; 617 while (last >= gp && last->u == ' ') 618 --last; 619 620 for ( ; gp <= last; ++gp) { 621 if (gp->mode & ATTR_WDUMMY) 622 continue; 623 624 ptr += utf8encode(gp->u, ptr); 625 } 626 627 /* 628 * Copy and pasting of line endings is inconsistent 629 * in the inconsistent terminal and GUI world. 630 * The best solution seems like to produce '\n' when 631 * something is copied from st and convert '\n' to 632 * '\r', when something to be pasted is received by 633 * st. 634 * FIXME: Fix the computer world. 635 */ 636 if ((y < sel.ne.y || lastx >= linelen) && 637 (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR)) 638 *ptr++ = '\n'; 639 } 640 *ptr = 0; 641 return str; 642 } 643 644 void 645 selclear(void) 646 { 647 if (sel.ob.x == -1) 648 return; 649 sel.mode = SEL_IDLE; 650 sel.ob.x = -1; 651 tsetdirt(sel.nb.y, sel.ne.y); 652 } 653 654 void 655 die(const char *errstr, ...) 656 { 657 va_list ap; 658 659 va_start(ap, errstr); 660 vfprintf(stderr, errstr, ap); 661 va_end(ap); 662 exit(1); 663 } 664 665 void 666 execsh(char *cmd, char **args) 667 { 668 char *sh, *prog, *arg; 669 const struct passwd *pw; 670 671 errno = 0; 672 if ((pw = getpwuid(getuid())) == NULL) { 673 if (errno) 674 die("getpwuid: %s\n", strerror(errno)); 675 else 676 die("who are you?\n"); 677 } 678 679 if ((sh = getenv("SHELL")) == NULL) 680 sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd; 681 682 if (args) { 683 prog = args[0]; 684 arg = NULL; 685 } else if (scroll) { 686 prog = scroll; 687 arg = utmp ? utmp : sh; 688 } else if (utmp) { 689 prog = utmp; 690 arg = NULL; 691 } else { 692 prog = sh; 693 arg = NULL; 694 } 695 DEFAULT(args, ((char *[]) {prog, arg, NULL})); 696 697 unsetenv("COLUMNS"); 698 unsetenv("LINES"); 699 unsetenv("TERMCAP"); 700 setenv("LOGNAME", pw->pw_name, 1); 701 setenv("USER", pw->pw_name, 1); 702 setenv("SHELL", sh, 1); 703 setenv("HOME", pw->pw_dir, 1); 704 setenv("TERM", termname, 1); 705 706 signal(SIGCHLD, SIG_DFL); 707 signal(SIGHUP, SIG_DFL); 708 signal(SIGINT, SIG_DFL); 709 signal(SIGQUIT, SIG_DFL); 710 signal(SIGTERM, SIG_DFL); 711 signal(SIGALRM, SIG_DFL); 712 713 execvp(prog, args); 714 _exit(1); 715 } 716 717 void 718 sigchld(int a) 719 { 720 int stat; 721 pid_t p; 722 723 if ((p = waitpid(pid, &stat, WNOHANG)) < 0) 724 die("waiting for pid %hd failed: %s\n", pid, strerror(errno)); 725 726 if (pid != p) 727 return; 728 729 if (WIFEXITED(stat) && WEXITSTATUS(stat)) 730 die("child exited with status %d\n", WEXITSTATUS(stat)); 731 else if (WIFSIGNALED(stat)) 732 die("child terminated due to signal %d\n", WTERMSIG(stat)); 733 _exit(0); 734 } 735 736 void 737 stty(char **args) 738 { 739 char cmd[_POSIX_ARG_MAX], **p, *q, *s; 740 size_t n, siz; 741 742 if ((n = strlen(stty_args)) > sizeof(cmd)-1) 743 die("incorrect stty parameters\n"); 744 memcpy(cmd, stty_args, n); 745 q = cmd + n; 746 siz = sizeof(cmd) - n; 747 for (p = args; p && (s = *p); ++p) { 748 if ((n = strlen(s)) > siz-1) 749 die("stty parameter length too long\n"); 750 *q++ = ' '; 751 memcpy(q, s, n); 752 q += n; 753 siz -= n + 1; 754 } 755 *q = '\0'; 756 if (system(cmd) != 0) 757 perror("Couldn't call stty"); 758 } 759 760 int 761 ttynew(char *line, char *cmd, char *out, char **args) 762 { 763 int m, s; 764 765 if (out) { 766 term.mode |= MODE_PRINT; 767 iofd = (!strcmp(out, "-")) ? 768 1 : open(out, O_WRONLY | O_CREAT, 0666); 769 if (iofd < 0) { 770 fprintf(stderr, "Error opening %s:%s\n", 771 out, strerror(errno)); 772 } 773 } 774 775 if (line) { 776 if ((cmdfd = open(line, O_RDWR)) < 0) 777 die("open line '%s' failed: %s\n", 778 line, strerror(errno)); 779 dup2(cmdfd, 0); 780 stty(args); 781 return cmdfd; 782 } 783 784 /* seems to work fine on linux, openbsd and freebsd */ 785 if (openpty(&m, &s, NULL, NULL, NULL) < 0) 786 die("openpty failed: %s\n", strerror(errno)); 787 788 switch (pid = fork()) { 789 case -1: 790 die("fork failed: %s\n", strerror(errno)); 791 break; 792 case 0: 793 close(iofd); 794 setsid(); /* create a new process group */ 795 dup2(s, 0); 796 dup2(s, 1); 797 dup2(s, 2); 798 if (ioctl(s, TIOCSCTTY, NULL) < 0) 799 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno)); 800 close(s); 801 close(m); 802 #ifdef __OpenBSD__ 803 if (pledge("stdio getpw proc exec", NULL) == -1) 804 die("pledge\n"); 805 #endif 806 execsh(cmd, args); 807 break; 808 default: 809 #ifdef __OpenBSD__ 810 if (pledge("stdio rpath tty proc", NULL) == -1) 811 die("pledge\n"); 812 #endif 813 close(s); 814 cmdfd = m; 815 signal(SIGCHLD, sigchld); 816 break; 817 } 818 return cmdfd; 819 } 820 821 size_t 822 ttyread(void) 823 { 824 static char buf[BUFSIZ]; 825 static int buflen = 0; 826 int ret, written; 827 828 /* append read bytes to unprocessed bytes */ 829 ret = read(cmdfd, buf+buflen, LEN(buf)-buflen); 830 831 switch (ret) { 832 case 0: 833 exit(0); 834 case -1: 835 die("couldn't read from shell: %s\n", strerror(errno)); 836 default: 837 buflen += ret; 838 written = twrite(buf, buflen, 0); 839 buflen -= written; 840 /* keep any incomplete UTF-8 byte sequence for the next call */ 841 if (buflen > 0) 842 memmove(buf, buf + written, buflen); 843 return ret; 844 } 845 } 846 847 void 848 ttywrite(const char *s, size_t n, int may_echo) 849 { 850 const char *next; 851 852 if (may_echo && IS_SET(MODE_ECHO)) 853 twrite(s, n, 1); 854 855 if (!IS_SET(MODE_CRLF)) { 856 ttywriteraw(s, n); 857 return; 858 } 859 860 /* This is similar to how the kernel handles ONLCR for ttys */ 861 while (n > 0) { 862 if (*s == '\r') { 863 next = s + 1; 864 ttywriteraw("\r\n", 2); 865 } else { 866 next = memchr(s, '\r', n); 867 DEFAULT(next, s + n); 868 ttywriteraw(s, next - s); 869 } 870 n -= next - s; 871 s = next; 872 } 873 } 874 875 void 876 ttywriteraw(const char *s, size_t n) 877 { 878 fd_set wfd, rfd; 879 ssize_t r; 880 size_t lim = 256; 881 882 /* 883 * Remember that we are using a pty, which might be a modem line. 884 * Writing too much will clog the line. That's why we are doing this 885 * dance. 886 * FIXME: Migrate the world to Plan 9. 887 */ 888 while (n > 0) { 889 FD_ZERO(&wfd); 890 FD_ZERO(&rfd); 891 FD_SET(cmdfd, &wfd); 892 FD_SET(cmdfd, &rfd); 893 894 /* Check if we can write. */ 895 if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) { 896 if (errno == EINTR) 897 continue; 898 die("select failed: %s\n", strerror(errno)); 899 } 900 if (FD_ISSET(cmdfd, &wfd)) { 901 /* 902 * Only write the bytes written by ttywrite() or the 903 * default of 256. This seems to be a reasonable value 904 * for a serial line. Bigger values might clog the I/O. 905 */ 906 if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0) 907 goto write_error; 908 if (r < n) { 909 /* 910 * We weren't able to write out everything. 911 * This means the buffer is getting full 912 * again. Empty it. 913 */ 914 if (n < lim) 915 lim = ttyread(); 916 n -= r; 917 s += r; 918 } else { 919 /* All bytes have been written. */ 920 break; 921 } 922 } 923 if (FD_ISSET(cmdfd, &rfd)) 924 lim = ttyread(); 925 } 926 return; 927 928 write_error: 929 die("write error on tty: %s\n", strerror(errno)); 930 } 931 932 void 933 ttyresize(int tw, int th) 934 { 935 struct winsize w; 936 937 w.ws_row = term.row; 938 w.ws_col = term.col; 939 w.ws_xpixel = tw; 940 w.ws_ypixel = th; 941 if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0) 942 fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno)); 943 } 944 945 void 946 ttyhangup() 947 { 948 /* Send SIGHUP to shell */ 949 kill(pid, SIGHUP); 950 } 951 952 int 953 tattrset(int attr) 954 { 955 int i, j; 956 957 for (i = 0; i < term.row-1; i++) { 958 for (j = 0; j < term.col-1; j++) { 959 if (term.line[i][j].mode & attr) 960 return 1; 961 } 962 } 963 964 return 0; 965 } 966 967 void 968 tsetdirt(int top, int bot) 969 { 970 int i; 971 972 LIMIT(top, 0, term.row-1); 973 LIMIT(bot, 0, term.row-1); 974 975 for (i = top; i <= bot; i++) 976 term.dirty[i] = 1; 977 } 978 979 void 980 tsetdirtattr(int attr) 981 { 982 int i, j; 983 984 for (i = 0; i < term.row-1; i++) { 985 for (j = 0; j < term.col-1; j++) { 986 if (term.line[i][j].mode & attr) { 987 tsetdirt(i, i); 988 break; 989 } 990 } 991 } 992 } 993 994 void 995 tfulldirt(void) 996 { 997 tsetdirt(0, term.row-1); 998 } 999 1000 void 1001 tcursor(int mode) 1002 { 1003 static TCursor c[2]; 1004 int alt = IS_SET(MODE_ALTSCREEN); 1005 1006 if (mode == CURSOR_SAVE) { 1007 c[alt] = term.c; 1008 } else if (mode == CURSOR_LOAD) { 1009 term.c = c[alt]; 1010 tmoveto(c[alt].x, c[alt].y); 1011 } 1012 } 1013 1014 void 1015 treset(void) 1016 { 1017 uint i; 1018 1019 term.c = (TCursor){{ 1020 .mode = ATTR_NULL, 1021 .fg = defaultfg, 1022 .bg = defaultbg 1023 }, .x = 0, .y = 0, .state = CURSOR_DEFAULT}; 1024 1025 memset(term.tabs, 0, term.col * sizeof(*term.tabs)); 1026 for (i = tabspaces; i < term.col; i += tabspaces) 1027 term.tabs[i] = 1; 1028 term.top = 0; 1029 term.bot = term.row - 1; 1030 term.mode = MODE_WRAP|MODE_UTF8; 1031 memset(term.trantbl, CS_USA, sizeof(term.trantbl)); 1032 term.charset = 0; 1033 1034 for (i = 0; i < 2; i++) { 1035 tmoveto(0, 0); 1036 tcursor(CURSOR_SAVE); 1037 tclearregion(0, 0, term.col-1, term.row-1); 1038 tswapscreen(); 1039 } 1040 } 1041 1042 void 1043 tnew(int col, int row) 1044 { 1045 term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } }; 1046 tresize(col, row); 1047 treset(); 1048 } 1049 1050 void 1051 tswapscreen(void) 1052 { 1053 Line *tmp = term.line; 1054 1055 term.line = term.alt; 1056 term.alt = tmp; 1057 term.mode ^= MODE_ALTSCREEN; 1058 tfulldirt(); 1059 } 1060 1061 void 1062 tscrolldown(int orig, int n) 1063 { 1064 int i; 1065 Line temp; 1066 1067 LIMIT(n, 0, term.bot-orig+1); 1068 1069 tsetdirt(orig, term.bot-n); 1070 tclearregion(0, term.bot-n+1, term.col-1, term.bot); 1071 1072 for (i = term.bot; i >= orig+n; i--) { 1073 temp = term.line[i]; 1074 term.line[i] = term.line[i-n]; 1075 term.line[i-n] = temp; 1076 } 1077 1078 selscroll(orig, n); 1079 } 1080 1081 void 1082 tscrollup(int orig, int n) 1083 { 1084 int i; 1085 Line temp; 1086 1087 LIMIT(n, 0, term.bot-orig+1); 1088 1089 tclearregion(0, orig, term.col-1, orig+n-1); 1090 tsetdirt(orig+n, term.bot); 1091 1092 for (i = orig; i <= term.bot-n; i++) { 1093 temp = term.line[i]; 1094 term.line[i] = term.line[i+n]; 1095 term.line[i+n] = temp; 1096 } 1097 1098 selscroll(orig, -n); 1099 } 1100 1101 void 1102 selscroll(int orig, int n) 1103 { 1104 if (sel.ob.x == -1) 1105 return; 1106 1107 if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) { 1108 selclear(); 1109 } else if (BETWEEN(sel.nb.y, orig, term.bot)) { 1110 sel.ob.y += n; 1111 sel.oe.y += n; 1112 if (sel.ob.y < term.top || sel.ob.y > term.bot || 1113 sel.oe.y < term.top || sel.oe.y > term.bot) { 1114 selclear(); 1115 } else { 1116 selnormalize(); 1117 } 1118 } 1119 } 1120 1121 void 1122 tnewline(int first_col) 1123 { 1124 int y = term.c.y; 1125 1126 if (y == term.bot) { 1127 tscrollup(term.top, 1); 1128 } else { 1129 y++; 1130 } 1131 tmoveto(first_col ? 0 : term.c.x, y); 1132 } 1133 1134 void 1135 csiparse(void) 1136 { 1137 char *p = csiescseq.buf, *np; 1138 long int v; 1139 1140 csiescseq.narg = 0; 1141 if (*p == '?') { 1142 csiescseq.priv = 1; 1143 p++; 1144 } 1145 1146 csiescseq.buf[csiescseq.len] = '\0'; 1147 while (p < csiescseq.buf+csiescseq.len) { 1148 np = NULL; 1149 v = strtol(p, &np, 10); 1150 if (np == p) 1151 v = 0; 1152 if (v == LONG_MAX || v == LONG_MIN) 1153 v = -1; 1154 csiescseq.arg[csiescseq.narg++] = v; 1155 p = np; 1156 if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ) 1157 break; 1158 p++; 1159 } 1160 csiescseq.mode[0] = *p++; 1161 csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0'; 1162 } 1163 1164 /* for absolute user moves, when decom is set */ 1165 void 1166 tmoveato(int x, int y) 1167 { 1168 tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0)); 1169 } 1170 1171 void 1172 tmoveto(int x, int y) 1173 { 1174 int miny, maxy; 1175 1176 if (term.c.state & CURSOR_ORIGIN) { 1177 miny = term.top; 1178 maxy = term.bot; 1179 } else { 1180 miny = 0; 1181 maxy = term.row - 1; 1182 } 1183 term.c.state &= ~CURSOR_WRAPNEXT; 1184 term.c.x = LIMIT(x, 0, term.col-1); 1185 term.c.y = LIMIT(y, miny, maxy); 1186 } 1187 1188 void 1189 tsetchar(Rune u, Glyph *attr, int x, int y) 1190 { 1191 static char *vt100_0[62] = { /* 0x41 - 0x7e */ 1192 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */ 1193 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */ 1194 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */ 1195 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */ 1196 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */ 1197 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */ 1198 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */ 1199 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */ 1200 }; 1201 1202 /* 1203 * The table is proudly stolen from rxvt. 1204 */ 1205 if (term.trantbl[term.charset] == CS_GRAPHIC0 && 1206 BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41]) 1207 utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ); 1208 1209 if (term.line[y][x].mode & ATTR_WIDE) { 1210 if (x+1 < term.col) { 1211 term.line[y][x+1].u = ' '; 1212 term.line[y][x+1].mode &= ~ATTR_WDUMMY; 1213 } 1214 } else if (term.line[y][x].mode & ATTR_WDUMMY) { 1215 term.line[y][x-1].u = ' '; 1216 term.line[y][x-1].mode &= ~ATTR_WIDE; 1217 } 1218 1219 term.dirty[y] = 1; 1220 term.line[y][x] = *attr; 1221 term.line[y][x].u = u; 1222 1223 if (isboxdraw(u)) 1224 term.line[y][x].mode |= ATTR_BOXDRAW; 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(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(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, int *args, int narg) 1475 { 1476 int alt, *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 strhandle(void) 1847 { 1848 char *p = NULL, *dec; 1849 int j, narg, par; 1850 1851 term.esc &= ~(ESC_STR_END|ESC_STR); 1852 strparse(); 1853 par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0; 1854 1855 switch (strescseq.type) { 1856 case ']': /* OSC -- Operating System Command */ 1857 switch (par) { 1858 case 0: 1859 case 1: 1860 case 2: 1861 if (narg > 1) 1862 xsettitle(strescseq.args[1]); 1863 return; 1864 case 52: 1865 if (narg > 2 && allowwindowops) { 1866 dec = base64dec(strescseq.args[2]); 1867 if (dec) { 1868 xsetsel(dec); 1869 xclipcopy(); 1870 } else { 1871 fprintf(stderr, "erresc: invalid base64\n"); 1872 } 1873 } 1874 return; 1875 case 4: /* color set */ 1876 if (narg < 3) 1877 break; 1878 p = strescseq.args[2]; 1879 /* FALLTHROUGH */ 1880 case 104: /* color reset, here p = NULL */ 1881 j = (narg > 1) ? atoi(strescseq.args[1]) : -1; 1882 if (xsetcolorname(j, p)) { 1883 if (par == 104 && narg <= 1) 1884 return; /* color reset without parameter */ 1885 fprintf(stderr, "erresc: invalid color j=%d, p=%s\n", 1886 j, p ? p : "(null)"); 1887 } else { 1888 /* 1889 * TODO if defaultbg color is changed, borders 1890 * are dirty 1891 */ 1892 redraw(); 1893 } 1894 return; 1895 } 1896 break; 1897 case 'k': /* old title set compatibility */ 1898 xsettitle(strescseq.args[0]); 1899 return; 1900 case 'P': /* DCS -- Device Control String */ 1901 case '_': /* APC -- Application Program Command */ 1902 case '^': /* PM -- Privacy Message */ 1903 return; 1904 } 1905 1906 fprintf(stderr, "erresc: unknown str "); 1907 strdump(); 1908 } 1909 1910 void 1911 strparse(void) 1912 { 1913 int c; 1914 char *p = strescseq.buf; 1915 1916 strescseq.narg = 0; 1917 strescseq.buf[strescseq.len] = '\0'; 1918 1919 if (*p == '\0') 1920 return; 1921 1922 while (strescseq.narg < STR_ARG_SIZ) { 1923 strescseq.args[strescseq.narg++] = p; 1924 while ((c = *p) != ';' && c != '\0') 1925 ++p; 1926 if (c == '\0') 1927 return; 1928 *p++ = '\0'; 1929 } 1930 } 1931 1932 void 1933 strdump(void) 1934 { 1935 size_t i; 1936 uint c; 1937 1938 fprintf(stderr, "ESC%c", strescseq.type); 1939 for (i = 0; i < strescseq.len; i++) { 1940 c = strescseq.buf[i] & 0xff; 1941 if (c == '\0') { 1942 putc('\n', stderr); 1943 return; 1944 } else if (isprint(c)) { 1945 putc(c, stderr); 1946 } else if (c == '\n') { 1947 fprintf(stderr, "(\\n)"); 1948 } else if (c == '\r') { 1949 fprintf(stderr, "(\\r)"); 1950 } else if (c == 0x1b) { 1951 fprintf(stderr, "(\\e)"); 1952 } else { 1953 fprintf(stderr, "(%02x)", c); 1954 } 1955 } 1956 fprintf(stderr, "ESC\\\n"); 1957 } 1958 1959 void 1960 strreset(void) 1961 { 1962 strescseq = (STREscape){ 1963 .buf = xrealloc(strescseq.buf, STR_BUF_SIZ), 1964 .siz = STR_BUF_SIZ, 1965 }; 1966 } 1967 1968 void 1969 sendbreak(const Arg *arg) 1970 { 1971 if (tcsendbreak(cmdfd, 0)) 1972 perror("Error sending break"); 1973 } 1974 1975 void 1976 tprinter(char *s, size_t len) 1977 { 1978 if (iofd != -1 && xwrite(iofd, s, len) < 0) { 1979 perror("Error writing to output file"); 1980 close(iofd); 1981 iofd = -1; 1982 } 1983 } 1984 1985 void 1986 toggleprinter(const Arg *arg) 1987 { 1988 term.mode ^= MODE_PRINT; 1989 } 1990 1991 void 1992 printscreen(const Arg *arg) 1993 { 1994 tdump(); 1995 } 1996 1997 void 1998 printsel(const Arg *arg) 1999 { 2000 tdumpsel(); 2001 } 2002 2003 void 2004 tdumpsel(void) 2005 { 2006 char *ptr; 2007 2008 if ((ptr = getsel())) { 2009 tprinter(ptr, strlen(ptr)); 2010 free(ptr); 2011 } 2012 } 2013 2014 void 2015 tdumpline(int n) 2016 { 2017 char buf[UTF_SIZ]; 2018 Glyph *bp, *end; 2019 2020 bp = &term.line[n][0]; 2021 end = &bp[MIN(tlinelen(n), term.col) - 1]; 2022 if (bp != end || bp->u != ' ') { 2023 for ( ; bp <= end; ++bp) 2024 tprinter(buf, utf8encode(bp->u, buf)); 2025 } 2026 tprinter("\n", 1); 2027 } 2028 2029 void 2030 tdump(void) 2031 { 2032 int i; 2033 2034 for (i = 0; i < term.row; ++i) 2035 tdumpline(i); 2036 } 2037 2038 void 2039 tputtab(int n) 2040 { 2041 uint x = term.c.x; 2042 2043 if (n > 0) { 2044 while (x < term.col && n--) 2045 for (++x; x < term.col && !term.tabs[x]; ++x) 2046 /* nothing */ ; 2047 } else if (n < 0) { 2048 while (x > 0 && n++) 2049 for (--x; x > 0 && !term.tabs[x]; --x) 2050 /* nothing */ ; 2051 } 2052 term.c.x = LIMIT(x, 0, term.col-1); 2053 } 2054 2055 void 2056 tdefutf8(char ascii) 2057 { 2058 if (ascii == 'G') 2059 term.mode |= MODE_UTF8; 2060 else if (ascii == '@') 2061 term.mode &= ~MODE_UTF8; 2062 } 2063 2064 void 2065 tdeftran(char ascii) 2066 { 2067 static char cs[] = "0B"; 2068 static int vcs[] = {CS_GRAPHIC0, CS_USA}; 2069 char *p; 2070 2071 if ((p = strchr(cs, ascii)) == NULL) { 2072 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii); 2073 } else { 2074 term.trantbl[term.icharset] = vcs[p - cs]; 2075 } 2076 } 2077 2078 void 2079 tdectest(char c) 2080 { 2081 int x, y; 2082 2083 if (c == '8') { /* DEC screen alignment test. */ 2084 for (x = 0; x < term.col; ++x) { 2085 for (y = 0; y < term.row; ++y) 2086 tsetchar('E', &term.c.attr, x, y); 2087 } 2088 } 2089 } 2090 2091 void 2092 tstrsequence(uchar c) 2093 { 2094 switch (c) { 2095 case 0x90: /* DCS -- Device Control String */ 2096 c = 'P'; 2097 break; 2098 case 0x9f: /* APC -- Application Program Command */ 2099 c = '_'; 2100 break; 2101 case 0x9e: /* PM -- Privacy Message */ 2102 c = '^'; 2103 break; 2104 case 0x9d: /* OSC -- Operating System Command */ 2105 c = ']'; 2106 break; 2107 } 2108 strreset(); 2109 strescseq.type = c; 2110 term.esc |= ESC_STR; 2111 } 2112 2113 void 2114 tcontrolcode(uchar ascii) 2115 { 2116 switch (ascii) { 2117 case '\t': /* HT */ 2118 tputtab(1); 2119 return; 2120 case '\b': /* BS */ 2121 tmoveto(term.c.x-1, term.c.y); 2122 return; 2123 case '\r': /* CR */ 2124 tmoveto(0, term.c.y); 2125 return; 2126 case '\f': /* LF */ 2127 case '\v': /* VT */ 2128 case '\n': /* LF */ 2129 /* go to first col if the mode is set */ 2130 tnewline(IS_SET(MODE_CRLF)); 2131 return; 2132 case '\a': /* BEL */ 2133 if (term.esc & ESC_STR_END) { 2134 /* backwards compatibility to xterm */ 2135 strhandle(); 2136 } else { 2137 xbell(); 2138 } 2139 break; 2140 case '\033': /* ESC */ 2141 csireset(); 2142 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST); 2143 term.esc |= ESC_START; 2144 return; 2145 case '\016': /* SO (LS1 -- Locking shift 1) */ 2146 case '\017': /* SI (LS0 -- Locking shift 0) */ 2147 term.charset = 1 - (ascii - '\016'); 2148 return; 2149 case '\032': /* SUB */ 2150 tsetchar('?', &term.c.attr, term.c.x, term.c.y); 2151 /* FALLTHROUGH */ 2152 case '\030': /* CAN */ 2153 csireset(); 2154 break; 2155 case '\005': /* ENQ (IGNORED) */ 2156 case '\000': /* NUL (IGNORED) */ 2157 case '\021': /* XON (IGNORED) */ 2158 case '\023': /* XOFF (IGNORED) */ 2159 case 0177: /* DEL (IGNORED) */ 2160 return; 2161 case 0x80: /* TODO: PAD */ 2162 case 0x81: /* TODO: HOP */ 2163 case 0x82: /* TODO: BPH */ 2164 case 0x83: /* TODO: NBH */ 2165 case 0x84: /* TODO: IND */ 2166 break; 2167 case 0x85: /* NEL -- Next line */ 2168 tnewline(1); /* always go to first col */ 2169 break; 2170 case 0x86: /* TODO: SSA */ 2171 case 0x87: /* TODO: ESA */ 2172 break; 2173 case 0x88: /* HTS -- Horizontal tab stop */ 2174 term.tabs[term.c.x] = 1; 2175 break; 2176 case 0x89: /* TODO: HTJ */ 2177 case 0x8a: /* TODO: VTS */ 2178 case 0x8b: /* TODO: PLD */ 2179 case 0x8c: /* TODO: PLU */ 2180 case 0x8d: /* TODO: RI */ 2181 case 0x8e: /* TODO: SS2 */ 2182 case 0x8f: /* TODO: SS3 */ 2183 case 0x91: /* TODO: PU1 */ 2184 case 0x92: /* TODO: PU2 */ 2185 case 0x93: /* TODO: STS */ 2186 case 0x94: /* TODO: CCH */ 2187 case 0x95: /* TODO: MW */ 2188 case 0x96: /* TODO: SPA */ 2189 case 0x97: /* TODO: EPA */ 2190 case 0x98: /* TODO: SOS */ 2191 case 0x99: /* TODO: SGCI */ 2192 break; 2193 case 0x9a: /* DECID -- Identify Terminal */ 2194 ttywrite(vtiden, strlen(vtiden), 0); 2195 break; 2196 case 0x9b: /* TODO: CSI */ 2197 case 0x9c: /* TODO: ST */ 2198 break; 2199 case 0x90: /* DCS -- Device Control String */ 2200 case 0x9d: /* OSC -- Operating System Command */ 2201 case 0x9e: /* PM -- Privacy Message */ 2202 case 0x9f: /* APC -- Application Program Command */ 2203 tstrsequence(ascii); 2204 return; 2205 } 2206 /* only CAN, SUB, \a and C1 chars interrupt a sequence */ 2207 term.esc &= ~(ESC_STR_END|ESC_STR); 2208 } 2209 2210 /* 2211 * returns 1 when the sequence is finished and it hasn't to read 2212 * more characters for this sequence, otherwise 0 2213 */ 2214 int 2215 eschandle(uchar ascii) 2216 { 2217 switch (ascii) { 2218 case '[': 2219 term.esc |= ESC_CSI; 2220 return 0; 2221 case '#': 2222 term.esc |= ESC_TEST; 2223 return 0; 2224 case '%': 2225 term.esc |= ESC_UTF8; 2226 return 0; 2227 case 'P': /* DCS -- Device Control String */ 2228 case '_': /* APC -- Application Program Command */ 2229 case '^': /* PM -- Privacy Message */ 2230 case ']': /* OSC -- Operating System Command */ 2231 case 'k': /* old title set compatibility */ 2232 tstrsequence(ascii); 2233 return 0; 2234 case 'n': /* LS2 -- Locking shift 2 */ 2235 case 'o': /* LS3 -- Locking shift 3 */ 2236 term.charset = 2 + (ascii - 'n'); 2237 break; 2238 case '(': /* GZD4 -- set primary charset G0 */ 2239 case ')': /* G1D4 -- set secondary charset G1 */ 2240 case '*': /* G2D4 -- set tertiary charset G2 */ 2241 case '+': /* G3D4 -- set quaternary charset G3 */ 2242 term.icharset = ascii - '('; 2243 term.esc |= ESC_ALTCHARSET; 2244 return 0; 2245 case 'D': /* IND -- Linefeed */ 2246 if (term.c.y == term.bot) { 2247 tscrollup(term.top, 1); 2248 } else { 2249 tmoveto(term.c.x, term.c.y+1); 2250 } 2251 break; 2252 case 'E': /* NEL -- Next line */ 2253 tnewline(1); /* always go to first col */ 2254 break; 2255 case 'H': /* HTS -- Horizontal tab stop */ 2256 term.tabs[term.c.x] = 1; 2257 break; 2258 case 'M': /* RI -- Reverse index */ 2259 if (term.c.y == term.top) { 2260 tscrolldown(term.top, 1); 2261 } else { 2262 tmoveto(term.c.x, term.c.y-1); 2263 } 2264 break; 2265 case 'Z': /* DECID -- Identify Terminal */ 2266 ttywrite(vtiden, strlen(vtiden), 0); 2267 break; 2268 case 'c': /* RIS -- Reset to initial state */ 2269 treset(); 2270 resettitle(); 2271 xloadcols(); 2272 break; 2273 case '=': /* DECPAM -- Application keypad */ 2274 xsetmode(1, MODE_APPKEYPAD); 2275 break; 2276 case '>': /* DECPNM -- Normal keypad */ 2277 xsetmode(0, MODE_APPKEYPAD); 2278 break; 2279 case '7': /* DECSC -- Save Cursor */ 2280 tcursor(CURSOR_SAVE); 2281 break; 2282 case '8': /* DECRC -- Restore Cursor */ 2283 tcursor(CURSOR_LOAD); 2284 break; 2285 case '\\': /* ST -- String Terminator */ 2286 if (term.esc & ESC_STR_END) 2287 strhandle(); 2288 break; 2289 default: 2290 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n", 2291 (uchar) ascii, isprint(ascii)? ascii:'.'); 2292 break; 2293 } 2294 return 1; 2295 } 2296 2297 void 2298 tputc(Rune u) 2299 { 2300 char c[UTF_SIZ]; 2301 int control; 2302 int width, len; 2303 Glyph *gp; 2304 2305 control = ISCONTROL(u); 2306 if (u < 127 || !IS_SET(MODE_UTF8)) { 2307 c[0] = u; 2308 width = len = 1; 2309 } else { 2310 len = utf8encode(u, c); 2311 if (!control && (width = wcwidth(u)) == -1) 2312 width = 1; 2313 } 2314 2315 if (IS_SET(MODE_PRINT)) 2316 tprinter(c, len); 2317 2318 /* 2319 * STR sequence must be checked before anything else 2320 * because it uses all following characters until it 2321 * receives a ESC, a SUB, a ST or any other C1 control 2322 * character. 2323 */ 2324 if (term.esc & ESC_STR) { 2325 if (u == '\a' || u == 030 || u == 032 || u == 033 || 2326 ISCONTROLC1(u)) { 2327 term.esc &= ~(ESC_START|ESC_STR); 2328 term.esc |= ESC_STR_END; 2329 goto check_control_code; 2330 } 2331 2332 if (strescseq.len+len >= strescseq.siz) { 2333 /* 2334 * Here is a bug in terminals. If the user never sends 2335 * some code to stop the str or esc command, then st 2336 * will stop responding. But this is better than 2337 * silently failing with unknown characters. At least 2338 * then users will report back. 2339 * 2340 * In the case users ever get fixed, here is the code: 2341 */ 2342 /* 2343 * term.esc = 0; 2344 * strhandle(); 2345 */ 2346 if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2) 2347 return; 2348 strescseq.siz *= 2; 2349 strescseq.buf = xrealloc(strescseq.buf, strescseq.siz); 2350 } 2351 2352 memmove(&strescseq.buf[strescseq.len], c, len); 2353 strescseq.len += len; 2354 return; 2355 } 2356 2357 check_control_code: 2358 /* 2359 * Actions of control codes must be performed as soon they arrive 2360 * because they can be embedded inside a control sequence, and 2361 * they must not cause conflicts with sequences. 2362 */ 2363 if (control) { 2364 tcontrolcode(u); 2365 /* 2366 * control codes are not shown ever 2367 */ 2368 if (!term.esc) 2369 term.lastc = 0; 2370 return; 2371 } else if (term.esc & ESC_START) { 2372 if (term.esc & ESC_CSI) { 2373 csiescseq.buf[csiescseq.len++] = u; 2374 if (BETWEEN(u, 0x40, 0x7E) 2375 || csiescseq.len >= \ 2376 sizeof(csiescseq.buf)-1) { 2377 term.esc = 0; 2378 csiparse(); 2379 csihandle(); 2380 } 2381 return; 2382 } else if (term.esc & ESC_UTF8) { 2383 tdefutf8(u); 2384 } else if (term.esc & ESC_ALTCHARSET) { 2385 tdeftran(u); 2386 } else if (term.esc & ESC_TEST) { 2387 tdectest(u); 2388 } else { 2389 if (!eschandle(u)) 2390 return; 2391 /* sequence already finished */ 2392 } 2393 term.esc = 0; 2394 /* 2395 * All characters which form part of a sequence are not 2396 * printed 2397 */ 2398 return; 2399 } 2400 if (selected(term.c.x, term.c.y)) 2401 selclear(); 2402 2403 gp = &term.line[term.c.y][term.c.x]; 2404 if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) { 2405 gp->mode |= ATTR_WRAP; 2406 tnewline(1); 2407 gp = &term.line[term.c.y][term.c.x]; 2408 } 2409 2410 if (IS_SET(MODE_INSERT) && term.c.x+width < term.col) 2411 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph)); 2412 2413 if (term.c.x+width > term.col) { 2414 tnewline(1); 2415 gp = &term.line[term.c.y][term.c.x]; 2416 } 2417 2418 tsetchar(u, &term.c.attr, term.c.x, term.c.y); 2419 term.lastc = u; 2420 2421 if (width == 2) { 2422 gp->mode |= ATTR_WIDE; 2423 if (term.c.x+1 < term.col) { 2424 gp[1].u = '\0'; 2425 gp[1].mode = ATTR_WDUMMY; 2426 } 2427 } 2428 if (term.c.x+width < term.col) { 2429 tmoveto(term.c.x+width, term.c.y); 2430 } else { 2431 term.c.state |= CURSOR_WRAPNEXT; 2432 } 2433 } 2434 2435 int 2436 twrite(const char *buf, int buflen, int show_ctrl) 2437 { 2438 int charsize; 2439 Rune u; 2440 int n; 2441 2442 for (n = 0; n < buflen; n += charsize) { 2443 if (IS_SET(MODE_UTF8)) { 2444 /* process a complete utf8 char */ 2445 charsize = utf8decode(buf + n, &u, buflen - n); 2446 if (charsize == 0) 2447 break; 2448 } else { 2449 u = buf[n] & 0xFF; 2450 charsize = 1; 2451 } 2452 if (show_ctrl && ISCONTROL(u)) { 2453 if (u & 0x80) { 2454 u &= 0x7f; 2455 tputc('^'); 2456 tputc('['); 2457 } else if (u != '\n' && u != '\r' && u != '\t') { 2458 u ^= 0x40; 2459 tputc('^'); 2460 } 2461 } 2462 tputc(u); 2463 } 2464 return n; 2465 } 2466 2467 void 2468 tresize(int col, int row) 2469 { 2470 int i; 2471 int minrow = MIN(row, term.row); 2472 int mincol = MIN(col, term.col); 2473 int *bp; 2474 TCursor c; 2475 2476 if (col < 1 || row < 1) { 2477 fprintf(stderr, 2478 "tresize: error resizing to %dx%d\n", col, row); 2479 return; 2480 } 2481 2482 /* 2483 * slide screen to keep cursor where we expect it - 2484 * tscrollup would work here, but we can optimize to 2485 * memmove because we're freeing the earlier lines 2486 */ 2487 for (i = 0; i <= term.c.y - row; i++) { 2488 free(term.line[i]); 2489 free(term.alt[i]); 2490 } 2491 /* ensure that both src and dst are not NULL */ 2492 if (i > 0) { 2493 memmove(term.line, term.line + i, row * sizeof(Line)); 2494 memmove(term.alt, term.alt + i, row * sizeof(Line)); 2495 } 2496 for (i += row; i < term.row; i++) { 2497 free(term.line[i]); 2498 free(term.alt[i]); 2499 } 2500 2501 /* resize to new height */ 2502 term.line = xrealloc(term.line, row * sizeof(Line)); 2503 term.alt = xrealloc(term.alt, row * sizeof(Line)); 2504 term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); 2505 term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); 2506 2507 /* resize each row to new width, zero-pad if needed */ 2508 for (i = 0; i < minrow; i++) { 2509 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); 2510 term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph)); 2511 } 2512 2513 /* allocate any new rows */ 2514 for (/* i = minrow */; i < row; i++) { 2515 term.line[i] = xmalloc(col * sizeof(Glyph)); 2516 term.alt[i] = xmalloc(col * sizeof(Glyph)); 2517 } 2518 if (col > term.col) { 2519 bp = term.tabs + term.col; 2520 2521 memset(bp, 0, sizeof(*term.tabs) * (col - term.col)); 2522 while (--bp > term.tabs && !*bp) 2523 /* nothing */ ; 2524 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces) 2525 *bp = 1; 2526 } 2527 /* update terminal size */ 2528 term.col = col; 2529 term.row = row; 2530 /* reset scrolling region */ 2531 tsetscroll(0, row-1); 2532 /* make use of the LIMIT in tmoveto */ 2533 tmoveto(term.c.x, term.c.y); 2534 /* Clearing both screens (it makes dirty all lines) */ 2535 c = term.c; 2536 for (i = 0; i < 2; i++) { 2537 if (mincol < col && 0 < minrow) { 2538 tclearregion(mincol, 0, col - 1, minrow - 1); 2539 } 2540 if (0 < col && minrow < row) { 2541 tclearregion(0, minrow, col - 1, row - 1); 2542 } 2543 tswapscreen(); 2544 tcursor(CURSOR_LOAD); 2545 } 2546 term.c = c; 2547 } 2548 2549 void 2550 resettitle(void) 2551 { 2552 xsettitle(NULL); 2553 } 2554 2555 void 2556 drawregion(int x1, int y1, int x2, int y2) 2557 { 2558 int y; 2559 2560 for (y = y1; y < y2; y++) { 2561 if (!term.dirty[y]) 2562 continue; 2563 2564 term.dirty[y] = 0; 2565 xdrawline(term.line[y], x1, y, x2); 2566 } 2567 } 2568 2569 void 2570 draw(void) 2571 { 2572 int cx = term.c.x, ocx = term.ocx, ocy = term.ocy; 2573 2574 if (!xstartdraw()) 2575 return; 2576 2577 /* adjust cursor position */ 2578 LIMIT(term.ocx, 0, term.col-1); 2579 LIMIT(term.ocy, 0, term.row-1); 2580 if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY) 2581 term.ocx--; 2582 if (term.line[term.c.y][cx].mode & ATTR_WDUMMY) 2583 cx--; 2584 2585 drawregion(0, 0, term.col, term.row); 2586 xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], 2587 term.ocx, term.ocy, term.line[term.ocy][term.ocx]); 2588 term.ocx = cx; 2589 term.ocy = term.c.y; 2590 xfinishdraw(); 2591 if (ocx != term.ocx || ocy != term.ocy) 2592 xximspot(term.ocx, term.ocy); 2593 } 2594 2595 void 2596 redraw(void) 2597 { 2598 tfulldirt(); 2599 draw(); 2600 }