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