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