1 /* 2 * Copyright (C) 1984-2012 Mark Nudelman 3 * Modified for use with illumos by Garrett D'Amore. 4 * Copyright 2014 Garrett D'Amore <garrett@damore.org> 5 * 6 * You may distribute under the terms of either the GNU General Public 7 * License or the Less License, as specified in the README file. 8 * 9 * For more information, see the README file. 10 */ 11 12 /* 13 * Routines which deal with the characteristics of the terminal. 14 * Uses termcap to be as terminal-independent as possible. 15 */ 16 17 #include <sys/ioctl.h> 18 19 #include <err.h> 20 #include <term.h> 21 #include <termios.h> 22 23 #include "cmd.h" 24 #include "less.h" 25 26 #define DEFAULT_TERM "unknown" 27 28 /* 29 * Strings passed to tputs() to do various terminal functions. 30 */ 31 static char 32 *sc_home, /* Cursor home */ 33 *sc_addline, /* Add line, scroll down following lines */ 34 *sc_lower_left, /* Cursor to last line, first column */ 35 *sc_return, /* Cursor to beginning of current line */ 36 *sc_move, /* General cursor positioning */ 37 *sc_clear, /* Clear screen */ 38 *sc_eol_clear, /* Clear to end of line */ 39 *sc_eos_clear, /* Clear to end of screen */ 40 *sc_s_in, /* Enter standout (highlighted) mode */ 41 *sc_s_out, /* Exit standout mode */ 42 *sc_u_in, /* Enter underline mode */ 43 *sc_u_out, /* Exit underline mode */ 44 *sc_b_in, /* Enter bold mode */ 45 *sc_b_out, /* Exit bold mode */ 46 *sc_bl_in, /* Enter blink mode */ 47 *sc_bl_out, /* Exit blink mode */ 48 *sc_visual_bell, /* Visual bell (flash screen) sequence */ 49 *sc_backspace, /* Backspace cursor */ 50 *sc_s_keypad, /* Start keypad mode */ 51 *sc_e_keypad, /* End keypad mode */ 52 *sc_init, /* Startup terminal initialization */ 53 *sc_deinit; /* Exit terminal de-initialization */ 54 55 static int init_done = 0; 56 57 int auto_wrap; /* Terminal does \r\n when write past margin */ 58 int ignaw; /* Terminal ignores \n immediately after wrap */ 59 int erase_char; /* The user's erase char */ 60 int erase2_char; /* The user's other erase char */ 61 int kill_char; /* The user's line-kill char */ 62 int werase_char; /* The user's word-erase char */ 63 int sc_width, sc_height; /* Height & width of screen */ 64 int bo_s_width, bo_e_width; /* Printing width of boldface seq */ 65 int ul_s_width, ul_e_width; /* Printing width of underline seq */ 66 int so_s_width, so_e_width; /* Printing width of standout seq */ 67 int bl_s_width, bl_e_width; /* Printing width of blink seq */ 68 int can_goto_line; /* Can move cursor to any line */ 69 int missing_cap = 0; /* Some capability is missing */ 70 static int above_mem; /* Memory retained above screen */ 71 static int below_mem; /* Memory retained below screen */ 72 73 static int attrmode = AT_NORMAL; 74 extern int binattr; 75 76 static char *cheaper(char *, char *, char *); 77 static void tmodes(char *, char *, char **, char **, char *, char *); 78 79 extern int quiet; /* If VERY_QUIET, use visual bell for bell */ 80 extern int no_back_scroll; 81 extern int swindow; 82 extern int no_init; 83 extern int no_keypad; 84 extern volatile sig_atomic_t sigs; 85 extern int wscroll; 86 extern int screen_trashed; 87 extern int tty; 88 extern int top_scroll; 89 extern int oldbot; 90 extern int hilite_search; 91 92 /* 93 * Change terminal to "raw mode", or restore to "normal" mode. 94 * "Raw mode" means 95 * 1. An outstanding read will complete on receipt of a single keystroke. 96 * 2. Input is not echoed. 97 * 3. On output, \n is mapped to \r\n. 98 * 4. \t is NOT expanded into spaces. 99 * 5. Signal-causing characters such as ctrl-C (interrupt), 100 * etc. are NOT disabled. 101 * It doesn't matter whether an input \n is mapped to \r, or vice versa. 102 */ 103 void 104 raw_mode(int on) 105 { 106 static int curr_on = 0; 107 struct termios s; 108 static struct termios save_term; 109 static int saved_term = 0; 110 111 if (on == curr_on) 112 return; 113 erase2_char = '\b'; /* in case OS doesn't know about erase2 */ 114 115 if (on) { 116 /* 117 * Get terminal modes. 118 */ 119 (void) tcgetattr(tty, &s); 120 121 /* 122 * Save modes and set certain variables dependent on modes. 123 */ 124 if (!saved_term) { 125 save_term = s; 126 saved_term = 1; 127 } 128 129 erase_char = s.c_cc[VERASE]; 130 #ifdef VERASE2 131 erase2_char = s.c_cc[VERASE2]; 132 #endif 133 kill_char = s.c_cc[VKILL]; 134 #ifdef VWERASE 135 werase_char = s.c_cc[VWERASE]; 136 #endif 137 138 /* 139 * Set the modes to the way we want them. 140 */ 141 s.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHOK | ECHONL); 142 143 #ifndef TAB3 144 #define TAB3 0 /* Its a lie, but TAB3 isn't defined by POSIX. */ 145 #endif 146 s.c_oflag |= (TAB3 | OPOST | ONLCR); 147 s.c_oflag &= ~(OCRNL | ONOCR | ONLRET); 148 s.c_cc[VMIN] = 1; 149 s.c_cc[VTIME] = 0; 150 #ifdef VLNEXT 151 s.c_cc[VLNEXT] = 0; 152 #endif 153 #ifdef VDSUSP 154 s.c_cc[VDSUSP] = 0; 155 #endif 156 } else { 157 /* 158 * Restore saved modes. 159 */ 160 s = save_term; 161 } 162 (void) tcsetattr(tty, TCSASOFT | TCSADRAIN, &s); 163 curr_on = on; 164 } 165 166 /* 167 * Some glue to prevent calling termcap functions if tgetent() failed. 168 */ 169 static int hardcopy; 170 171 /* 172 * Get size of the output screen. 173 */ 174 static void 175 scrsize(void) 176 { 177 int sys_height = 0, sys_width = 0, n; 178 struct winsize w; 179 char *s; 180 181 #define DEF_SC_WIDTH 80 182 #define DEF_SC_HEIGHT 24 183 184 if (ioctl(2, TIOCGWINSZ, &w) == 0) { 185 if (w.ws_row > 0) 186 sys_height = w.ws_row; 187 if (w.ws_col > 0) 188 sys_width = w.ws_col; 189 } 190 191 if (sys_height > 0) 192 sc_height = sys_height; 193 else if ((s = lgetenv("LINES")) != NULL) 194 sc_height = atoi(s); 195 else if (!hardcopy && (n = lines) > 0) 196 sc_height = n; 197 if (sc_height <= 0) 198 sc_height = DEF_SC_HEIGHT; 199 200 if (sys_width > 0) 201 sc_width = sys_width; 202 else if ((s = lgetenv("COLUMNS")) != NULL) 203 sc_width = atoi(s); 204 else if (!hardcopy && (n = columns) > 0) 205 sc_width = n; 206 if (sc_width <= 0) 207 sc_width = DEF_SC_WIDTH; 208 } 209 210 /* 211 * Return the characters actually input by a "special" key. 212 */ 213 char * 214 special_key_str(int key) 215 { 216 char *s; 217 static char ctrlk[] = { CONTROL('K'), 0 }; 218 219 if (hardcopy) 220 return (NULL); 221 222 switch (key) { 223 case SK_RIGHT_ARROW: 224 s = key_right; 225 break; 226 case SK_LEFT_ARROW: 227 s = key_left; 228 break; 229 case SK_UP_ARROW: 230 s = key_up; 231 break; 232 case SK_DOWN_ARROW: 233 s = key_down; 234 break; 235 case SK_PAGE_UP: 236 s = key_ppage; 237 break; 238 case SK_PAGE_DOWN: 239 s = key_npage; 240 break; 241 case SK_HOME: 242 s = key_home; 243 break; 244 case SK_END: 245 s = key_end; 246 break; 247 case SK_DELETE: 248 s = key_dc; 249 if (s == NULL) { 250 s = "\177\0"; 251 } 252 break; 253 case SK_CONTROL_K: 254 s = ctrlk; 255 break; 256 default: 257 return (NULL); 258 } 259 return (s); 260 } 261 262 /* 263 * Get terminal capabilities via termcap. 264 */ 265 void 266 get_term(void) 267 { 268 char *t1, *t2; 269 char *term; 270 int err; 271 272 /* 273 * Find out what kind of terminal this is. 274 */ 275 if ((term = lgetenv("TERM")) == NULL) 276 term = DEFAULT_TERM; 277 hardcopy = 0; 278 279 if (setupterm(term, 1, &err) < 0) { 280 if (err == 1) 281 hardcopy = 1; 282 else 283 errx(1, "%s: unknown terminal type", term); 284 } 285 if (hard_copy == 1) 286 hardcopy = 1; 287 288 /* 289 * Get size of the screen. 290 */ 291 scrsize(); 292 pos_init(); 293 294 auto_wrap = hardcopy ? 0 : auto_right_margin; 295 ignaw = hardcopy ? 0 : eat_newline_glitch; 296 above_mem = hardcopy ? 0 : memory_above; 297 below_mem = hardcopy ? 0 : memory_below; 298 299 /* 300 * Assumes termcap variable "sg" is the printing width of: 301 * the standout sequence, the end standout sequence, 302 * the underline sequence, the end underline sequence, 303 * the boldface sequence, and the end boldface sequence. 304 */ 305 if (hardcopy || (so_s_width = magic_cookie_glitch) < 0) 306 so_s_width = 0; 307 so_e_width = so_s_width; 308 309 bo_s_width = bo_e_width = so_s_width; 310 ul_s_width = ul_e_width = so_s_width; 311 bl_s_width = bl_e_width = so_s_width; 312 313 if (so_s_width > 0 || so_e_width > 0) 314 /* 315 * Disable highlighting by default on magic cookie terminals. 316 * Turning on highlighting might change the displayed width 317 * of a line, causing the display to get messed up. 318 * The user can turn it back on with -g, 319 * but she won't like the results. 320 */ 321 hilite_search = 0; 322 323 /* 324 * Get various string-valued capabilities. 325 */ 326 327 sc_s_keypad = keypad_xmit; 328 if (hardcopy || sc_s_keypad == NULL) 329 sc_s_keypad = ""; 330 sc_e_keypad = keypad_local; 331 if (hardcopy || sc_e_keypad == NULL) 332 sc_e_keypad = ""; 333 334 sc_init = enter_ca_mode; 335 if (hardcopy || sc_init == NULL) 336 sc_init = ""; 337 338 sc_deinit = exit_ca_mode; 339 if (hardcopy || sc_deinit == NULL) 340 sc_deinit = ""; 341 342 sc_eol_clear = clr_eol; 343 if (hardcopy || sc_eol_clear == NULL || *sc_eol_clear == '\0') { 344 missing_cap = 1; 345 sc_eol_clear = ""; 346 } 347 348 sc_eos_clear = clr_eos; 349 if (below_mem && 350 (hardcopy || sc_eos_clear == NULL || *sc_eos_clear == '\0')) { 351 missing_cap = 1; 352 sc_eos_clear = ""; 353 } 354 355 sc_clear = clear_screen; 356 if (hardcopy || sc_clear == NULL || *sc_clear == '\0') { 357 missing_cap = 1; 358 sc_clear = "\n\n"; 359 } 360 361 sc_move = cursor_address; 362 if (hardcopy || sc_move == NULL || *sc_move == '\0') { 363 /* 364 * This is not an error here, because we don't 365 * always need sc_move. 366 * We need it only if we don't have home or lower-left. 367 */ 368 sc_move = ""; 369 can_goto_line = 0; 370 } else { 371 can_goto_line = 1; 372 } 373 374 tmodes(enter_standout_mode, exit_standout_mode, &sc_s_in, &sc_s_out, 375 "", ""); 376 tmodes(enter_underline_mode, exit_underline_mode, &sc_u_in, &sc_u_out, 377 sc_s_in, sc_s_out); 378 tmodes(enter_bold_mode, exit_attribute_mode, &sc_b_in, &sc_b_out, 379 sc_s_in, sc_s_out); 380 tmodes(enter_blink_mode, exit_attribute_mode, &sc_bl_in, &sc_bl_out, 381 sc_s_in, sc_s_out); 382 383 sc_visual_bell = flash_screen; 384 if (hardcopy || sc_visual_bell == NULL) 385 sc_visual_bell = ""; 386 387 sc_backspace = "\b"; 388 389 /* 390 * Choose between using "ho" and "cm" ("home" and "cursor move") 391 * to move the cursor to the upper left corner of the screen. 392 */ 393 t1 = cursor_home; 394 if (hardcopy || t1 == NULL) 395 t1 = ""; 396 if (*sc_move == '\0') { 397 t2 = ""; 398 } else { 399 t2 = estrdup(tparm(sc_move, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 400 } 401 sc_home = cheaper(t1, t2, "|\b^"); 402 403 /* 404 * Choose between using "ll" and "cm" ("lower left" and "cursor move") 405 * to move the cursor to the lower left corner of the screen. 406 */ 407 t1 = cursor_to_ll; 408 if (hardcopy || t1 == NULL) 409 t1 = ""; 410 if (*sc_move == '\0') { 411 t2 = ""; 412 } else { 413 t2 = estrdup(tparm(sc_move, sc_height-1, 414 0, 0, 0, 0, 0, 0, 0, 0)); 415 } 416 sc_lower_left = cheaper(t1, t2, "\r"); 417 418 /* 419 * Get carriage return string. 420 */ 421 sc_return = carriage_return; 422 if (hardcopy || sc_return == NULL) 423 sc_return = "\r"; 424 425 /* 426 * Choose between using insert_line or scroll_reverse 427 * to add a line at the top of the screen. 428 */ 429 t1 = insert_line; 430 if (hardcopy || t1 == NULL) 431 t1 = ""; 432 t2 = scroll_reverse; 433 if (hardcopy || t2 == NULL) 434 t2 = ""; 435 if (above_mem) 436 sc_addline = t1; 437 else 438 sc_addline = cheaper(t1, t2, ""); 439 if (*sc_addline == '\0') { 440 /* 441 * Force repaint on any backward movement. 442 */ 443 no_back_scroll = 1; 444 } 445 } 446 447 /* 448 * Return the cost of displaying a termcap string. 449 * We use the trick of calling tputs, but as a char printing function 450 * we give it inc_costcount, which just increments "costcount". 451 * This tells us how many chars would be printed by using this string. 452 * {{ Couldn't we just use strlen? }} 453 */ 454 static int costcount; 455 456 static int 457 inc_costcount(int c) 458 { 459 costcount++; 460 return (c); 461 } 462 463 static int 464 cost(char *t) 465 { 466 costcount = 0; 467 (void) tputs(t, sc_height, inc_costcount); 468 return (costcount); 469 } 470 471 /* 472 * Return the "best" of the two given termcap strings. 473 * The best, if both exist, is the one with the lower 474 * cost (see cost() function). 475 */ 476 static char * 477 cheaper(char *t1, char *t2, char *def) 478 { 479 if (*t1 == '\0' && *t2 == '\0') { 480 missing_cap = 1; 481 return (def); 482 } 483 if (*t1 == '\0') 484 return (t2); 485 if (*t2 == '\0') 486 return (t1); 487 if (cost(t1) < cost(t2)) 488 return (t1); 489 return (t2); 490 } 491 492 static void 493 tmodes(char *incap, char *outcap, char **instr, char **outstr, 494 char *def_instr, char *def_outstr) 495 { 496 if (hardcopy) { 497 *instr = ""; 498 *outstr = ""; 499 return; 500 } 501 502 *instr = incap; 503 *outstr = outcap; 504 505 if (*instr == NULL) { 506 /* Use defaults. */ 507 *instr = def_instr; 508 *outstr = def_outstr; 509 return; 510 } 511 512 if (*outstr == NULL) 513 /* No specific out capability; use exit_attribute_mode. */ 514 *outstr = exit_attribute_mode; 515 if (*outstr == NULL) 516 /* Don't even have that, use an empty string */ 517 *outstr = ""; 518 } 519 520 /* 521 * Below are the functions which perform all the 522 * terminal-specific screen manipulation. 523 */ 524 525 /* 526 * Initialize terminal 527 */ 528 void 529 init(void) 530 { 531 if (!no_init) 532 (void) tputs(sc_init, sc_height, putchr); 533 if (!no_keypad) 534 (void) tputs(sc_s_keypad, sc_height, putchr); 535 if (top_scroll) { 536 int i; 537 538 /* 539 * This is nice to terminals with no alternate screen, 540 * but with saved scrolled-off-the-top lines. This way, 541 * no previous line is lost, but we start with a whole 542 * screen to ourself. 543 */ 544 for (i = 1; i < sc_height; i++) 545 (void) putchr('\n'); 546 } else 547 line_left(); 548 init_done = 1; 549 } 550 551 /* 552 * Deinitialize terminal 553 */ 554 void 555 deinit(void) 556 { 557 if (!init_done) 558 return; 559 if (!no_keypad) 560 (void) tputs(sc_e_keypad, sc_height, putchr); 561 if (!no_init) 562 (void) tputs(sc_deinit, sc_height, putchr); 563 init_done = 0; 564 } 565 566 /* 567 * Home cursor (move to upper left corner of screen). 568 */ 569 void 570 home(void) 571 { 572 (void) tputs(sc_home, 1, putchr); 573 } 574 575 /* 576 * Add a blank line (called with cursor at home). 577 * Should scroll the display down. 578 */ 579 void 580 add_line(void) 581 { 582 (void) tputs(sc_addline, sc_height, putchr); 583 } 584 585 /* 586 * Move cursor to lower left corner of screen. 587 */ 588 void 589 lower_left(void) 590 { 591 (void) tputs(sc_lower_left, 1, putchr); 592 } 593 594 /* 595 * Move cursor to left position of current line. 596 */ 597 void 598 line_left(void) 599 { 600 (void) tputs(sc_return, 1, putchr); 601 } 602 603 /* 604 * Goto a specific line on the screen. 605 */ 606 void 607 goto_line(int slinenum) 608 { 609 (void) tputs(tparm(sc_move, slinenum, 0, 0, 0, 0, 0, 0, 0, 0), 1, 610 putchr); 611 } 612 613 /* 614 * Output the "visual bell", if there is one. 615 */ 616 void 617 vbell(void) 618 { 619 if (*sc_visual_bell == '\0') 620 return; 621 (void) tputs(sc_visual_bell, sc_height, putchr); 622 } 623 624 /* 625 * Make a noise. 626 */ 627 static void 628 beep(void) 629 { 630 (void) putchr(CONTROL('G')); 631 } 632 633 /* 634 * Ring the terminal bell. 635 */ 636 void 637 ring_bell(void) 638 { 639 if (quiet == VERY_QUIET) 640 vbell(); 641 else 642 beep(); 643 } 644 645 /* 646 * Clear the screen. 647 */ 648 void 649 do_clear(void) 650 { 651 (void) tputs(sc_clear, sc_height, putchr); 652 } 653 654 /* 655 * Clear from the cursor to the end of the cursor's line. 656 * {{ This must not move the cursor. }} 657 */ 658 void 659 clear_eol(void) 660 { 661 (void) tputs(sc_eol_clear, 1, putchr); 662 } 663 664 /* 665 * Clear the current line. 666 * Clear the screen if there's off-screen memory below the display. 667 */ 668 static void 669 clear_eol_bot(void) 670 { 671 if (below_mem) 672 (void) tputs(sc_eos_clear, 1, putchr); 673 else 674 (void) tputs(sc_eol_clear, 1, putchr); 675 } 676 677 /* 678 * Clear the bottom line of the display. 679 * Leave the cursor at the beginning of the bottom line. 680 */ 681 void 682 clear_bot(void) 683 { 684 /* 685 * If we're in a non-normal attribute mode, temporarily exit 686 * the mode while we do the clear. Some terminals fill the 687 * cleared area with the current attribute. 688 */ 689 if (oldbot) 690 lower_left(); 691 else 692 line_left(); 693 694 if (attrmode == AT_NORMAL) 695 clear_eol_bot(); 696 else 697 { 698 int saved_attrmode = attrmode; 699 700 at_exit(); 701 clear_eol_bot(); 702 at_enter(saved_attrmode); 703 } 704 } 705 706 void 707 at_enter(int attr) 708 { 709 attr = apply_at_specials(attr); 710 711 /* The one with the most priority is last. */ 712 if (attr & AT_UNDERLINE) 713 (void) tputs(sc_u_in, 1, putchr); 714 if (attr & AT_BOLD) 715 (void) tputs(sc_b_in, 1, putchr); 716 if (attr & AT_BLINK) 717 (void) tputs(sc_bl_in, 1, putchr); 718 if (attr & AT_STANDOUT) 719 (void) tputs(sc_s_in, 1, putchr); 720 721 attrmode = attr; 722 } 723 724 void 725 at_exit(void) 726 { 727 /* Undo things in the reverse order we did them. */ 728 if (attrmode & AT_STANDOUT) 729 (void) tputs(sc_s_out, 1, putchr); 730 if (attrmode & AT_BLINK) 731 (void) tputs(sc_bl_out, 1, putchr); 732 if (attrmode & AT_BOLD) 733 (void) tputs(sc_b_out, 1, putchr); 734 if (attrmode & AT_UNDERLINE) 735 (void) tputs(sc_u_out, 1, putchr); 736 737 attrmode = AT_NORMAL; 738 } 739 740 void 741 at_switch(int attr) 742 { 743 int new_attrmode = apply_at_specials(attr); 744 int ignore_modes = AT_ANSI; 745 746 if ((new_attrmode & ~ignore_modes) != (attrmode & ~ignore_modes)) { 747 at_exit(); 748 at_enter(attr); 749 } 750 } 751 752 int 753 is_at_equiv(int attr1, int attr2) 754 { 755 attr1 = apply_at_specials(attr1); 756 attr2 = apply_at_specials(attr2); 757 758 return (attr1 == attr2); 759 } 760 761 int 762 apply_at_specials(int attr) 763 { 764 if (attr & AT_BINARY) 765 attr |= binattr; 766 if (attr & AT_HILITE) 767 attr |= AT_STANDOUT; 768 attr &= ~(AT_BINARY|AT_HILITE); 769 770 return (attr); 771 } 772 773 /* 774 * Output a plain backspace, without erasing the previous char. 775 */ 776 void 777 putbs(void) 778 { 779 (void) tputs(sc_backspace, 1, putchr); 780 } 781