1/* $OpenBSD: tty.c,v 1.40 2023/03/08 04:43:11 guenther Exp $ */ 2 3/* This file is in the public domain. */ 4 5/* 6 * Terminfo display driver 7 * 8 * Terminfo is a terminal information database and routines to describe 9 * terminals on most modern UNIX systems. Many other systems have adopted 10 * this as a reasonable way to allow for widely varying and ever changing 11 * varieties of terminal types. This should be used where practical. 12 */ 13/* 14 * Known problems: If you have a terminal with no clear to end of screen and 15 * memory of lines below the ones visible on the screen, display will be 16 * wrong in some cases. I doubt that any such terminal was ever made, but I 17 * thought everyone with delete line would have clear to end of screen too... 18 * 19 * Code for terminals without clear to end of screen and/or clear to end of line 20 * has not been extensively tested. 21 * 22 * Cost calculations are very rough. Costs of insert/delete line may be far 23 * from the truth. This is accentuated by display.c not knowing about 24 * multi-line insert/delete. 25 * 26 * Using scrolling region vs insert/delete line should probably be based on cost 27 * rather than the assumption that scrolling region operations look better. 28 */ 29 30#include <sys/ioctl.h> 31#include <sys/queue.h> 32#include <sys/types.h> 33#include <sys/time.h> 34#include <signal.h> 35#include <stdio.h> 36#include <term.h> 37#include <unistd.h> 38 39#include "def.h" 40 41static int charcost(const char *); 42 43static int cci; 44static int insdel; /* Do we have both insert & delete line? */ 45static char *scroll_fwd; /* How to scroll forward. */ 46 47static void winchhandler(int); 48 49volatile sig_atomic_t winch_flag; 50int tceeol; 51int tcinsl; 52int tcdell; 53 54static void 55winchhandler(int sig) 56{ 57 winch_flag = 1; 58} 59 60/* 61 * Initialize the terminal when the editor 62 * gets started up. 63 */ 64void 65ttinit(void) 66{ 67 char *tty; 68 int errret; 69 70 if (batch == 1) 71 tty = "pty"; 72 else 73 tty = NULL; 74 75 if (setupterm(tty, STDOUT_FILENO, &errret)) 76 panic("Terminal setup failed"); 77 78 signal(SIGWINCH, winchhandler); 79 signal(SIGCONT, winchhandler); 80 siginterrupt(SIGWINCH, 1); 81 82 scroll_fwd = scroll_forward; 83 if (scroll_fwd == NULL || *scroll_fwd == '\0') { 84 /* this is what GNU Emacs does */ 85 scroll_fwd = parm_down_cursor; 86 if (scroll_fwd == NULL || *scroll_fwd == '\0') 87 scroll_fwd = curbp->b_nlchr; 88 } 89 90 if (cursor_address == NULL || cursor_up == NULL) 91 panic("This terminal is too stupid to run mg"); 92 93 /* set nrow & ncol */ 94 ttresize(); 95 96 if (!clr_eol) 97 tceeol = ncol; 98 else 99 tceeol = charcost(clr_eol); 100 101 /* Estimate cost of inserting a line */ 102 if (change_scroll_region && scroll_reverse) 103 tcinsl = charcost(change_scroll_region) * 2 + 104 charcost(scroll_reverse); 105 else if (parm_insert_line) 106 tcinsl = charcost(parm_insert_line); 107 else if (insert_line) 108 tcinsl = charcost(insert_line); 109 else 110 /* make this cost high enough */ 111 tcinsl = nrow * ncol; 112 113 /* Estimate cost of deleting a line */ 114 if (change_scroll_region) 115 tcdell = charcost(change_scroll_region) * 2 + 116 charcost(scroll_fwd); 117 else if (parm_delete_line) 118 tcdell = charcost(parm_delete_line); 119 else if (delete_line) 120 tcdell = charcost(delete_line); 121 else 122 /* make this cost high enough */ 123 tcdell = nrow * ncol; 124 125 /* Flag to indicate that we can both insert and delete lines */ 126 insdel = (insert_line || parm_insert_line) && 127 (delete_line || parm_delete_line); 128 129 if (enter_ca_mode) 130 /* enter application mode */ 131 putpad(enter_ca_mode, 1); 132 133 ttresize(); 134} 135 136/* 137 * Re-initialize the terminal when the editor is resumed. 138 * The keypad_xmit doesn't really belong here but... 139 */ 140void 141ttreinit(void) 142{ 143 /* check if file was modified while we were gone */ 144 if (fchecktime(curbp) != TRUE) { 145 curbp->b_flag |= BFDIRTY; 146 } 147 148 if (enter_ca_mode) 149 /* enter application mode */ 150 putpad(enter_ca_mode, 1); 151 152 if (keypad_xmit) 153 /* turn on keypad */ 154 putpad(keypad_xmit, 1); 155 156 ttresize(); 157} 158 159/* 160 * Clean up the terminal, in anticipation of a return to the command 161 * interpreter. This is a no-op on the ANSI display. On the SCALD display, 162 * it sets the window back to half screen scrolling. Perhaps it should 163 * query the display for the increment, and put it back to what it was. 164 */ 165void 166tttidy(void) 167{ 168 ttykeymaptidy(); 169 170 /* set the term back to normal mode */ 171 if (exit_ca_mode) 172 putpad(exit_ca_mode, 1); 173} 174 175/* 176 * Move the cursor to the specified origin 0 row and column position. Try to 177 * optimize out extra moves; redisplay may have left the cursor in the right 178 * location last time! 179 */ 180void 181ttmove(int row, int col) 182{ 183 if (ttrow != row || ttcol != col) { 184 putpad(tgoto(cursor_address, col, row), 1); 185 ttrow = row; 186 ttcol = col; 187 } 188} 189 190/* 191 * Erase to end of line. 192 */ 193void 194tteeol(void) 195{ 196 int i; 197 198 if (clr_eol) 199 putpad(clr_eol, 1); 200 else { 201 i = ncol - ttcol; 202 while (i--) 203 ttputc(' '); 204 ttrow = ttcol = HUGE; 205 } 206} 207 208/* 209 * Erase to end of page. 210 */ 211void 212tteeop(void) 213{ 214 int line; 215 216 if (clr_eos) 217 putpad(clr_eos, nrow - ttrow); 218 else { 219 putpad(clr_eol, 1); 220 if (insdel) 221 ttdell(ttrow + 1, lines, lines - ttrow - 1); 222 else { 223 /* do it by hand */ 224 for (line = ttrow + 1; line <= lines; ++line) { 225 ttmove(line, 0); 226 tteeol(); 227 } 228 } 229 ttrow = ttcol = HUGE; 230 } 231} 232 233/* 234 * Make a noise. 235 */ 236void 237ttbeep(void) 238{ 239 putpad(bell, 1); 240 ttflush(); 241} 242 243/* 244 * Insert nchunk blank line(s) onto the screen, scrolling the last line on 245 * the screen off the bottom. Use the scrolling region if possible for a 246 * smoother display. If there is no scrolling region, use a set of insert 247 * and delete line sequences. 248 */ 249void 250ttinsl(int row, int bot, int nchunk) 251{ 252 int i, nl; 253 254 /* One line special cases */ 255 if (row == bot) { 256 ttmove(row, 0); 257 tteeol(); 258 return; 259 } 260 /* Use scroll region and back index */ 261 if (change_scroll_region && scroll_reverse) { 262 nl = bot - row; 263 ttwindow(row, bot); 264 ttmove(row, 0); 265 while (nchunk--) 266 putpad(scroll_reverse, nl); 267 ttnowindow(); 268 return; 269 /* else use insert/delete line */ 270 } else if (insdel) { 271 ttmove(1 + bot - nchunk, 0); 272 nl = nrow - ttrow; 273 if (parm_delete_line) 274 putpad(tgoto(parm_delete_line, 0, nchunk), nl); 275 else 276 /* For all lines in the chunk */ 277 for (i = 0; i < nchunk; i++) 278 putpad(delete_line, nl); 279 ttmove(row, 0); 280 281 /* ttmove() changes ttrow */ 282 nl = nrow - ttrow; 283 284 if (parm_insert_line) 285 putpad(tgoto(parm_insert_line, 0, nchunk), nl); 286 else 287 /* For all lines in the chunk */ 288 for (i = 0; i < nchunk; i++) 289 putpad(insert_line, nl); 290 ttrow = HUGE; 291 ttcol = HUGE; 292 } else 293 panic("ttinsl: Can't insert/delete line"); 294} 295 296/* 297 * Delete nchunk line(s) from "row", replacing the bottom line on the 298 * screen with a blank line. Unless we're using the scrolling region, 299 * this is done with crafty sequences of insert and delete lines. The 300 * presence of the echo area makes a boundary condition go away. 301 */ 302void 303ttdell(int row, int bot, int nchunk) 304{ 305 int i, nl; 306 307 /* One line special cases */ 308 if (row == bot) { 309 ttmove(row, 0); 310 tteeol(); 311 return; 312 } 313 /* scrolling region */ 314 if (change_scroll_region) { 315 nl = bot - row; 316 ttwindow(row, bot); 317 ttmove(bot, 0); 318 while (nchunk--) 319 putpad(scroll_fwd, nl); 320 ttnowindow(); 321 /* else use insert/delete line */ 322 } else if (insdel) { 323 ttmove(row, 0); 324 nl = nrow - ttrow; 325 if (parm_delete_line) 326 putpad(tgoto(parm_delete_line, 0, nchunk), nl); 327 else 328 /* For all lines in the chunk */ 329 for (i = 0; i < nchunk; i++) 330 putpad(delete_line, nl); 331 ttmove(1 + bot - nchunk, 0); 332 333 /* ttmove() changes ttrow */ 334 nl = nrow - ttrow; 335 336 if (parm_insert_line) 337 putpad(tgoto(parm_insert_line, 0, nchunk), nl); 338 else 339 /* For all lines in the chunk */ 340 for (i = 0; i < nchunk; i++) 341 putpad(insert_line, nl); 342 ttrow = HUGE; 343 ttcol = HUGE; 344 } else 345 panic("ttdell: Can't insert/delete line"); 346} 347 348/* 349 * This routine sets the scrolling window on the display to go from line 350 * "top" to line "bot" (origin 0, inclusive). The caller checks for the 351 * pathological 1-line scroll window which doesn't work right and avoids 352 * it. The "ttrow" and "ttcol" variables are set to a crazy value to 353 * ensure that the next call to "ttmove" does not turn into a no-op (the 354 * window adjustment moves the cursor). 355 */ 356void 357ttwindow(int top, int bot) 358{ 359 if (change_scroll_region && (tttop != top || ttbot != bot)) { 360 putpad(tgoto(change_scroll_region, bot, top), nrow - ttrow); 361 ttrow = HUGE; /* Unknown. */ 362 ttcol = HUGE; 363 tttop = top; /* Remember region. */ 364 ttbot = bot; 365 } 366} 367 368/* 369 * Switch to full screen scroll. This is used by "spawn.c" just before it 370 * suspends the editor and by "display.c" when it is getting ready to 371 * exit. This function does a full screen scroll by telling the terminal 372 * to set a scrolling region that is lines or nrow rows high, whichever is 373 * larger. This behavior seems to work right on systems where you can set 374 * your terminal size. 375 */ 376void 377ttnowindow(void) 378{ 379 if (change_scroll_region) { 380 putpad(tgoto(change_scroll_region, 381 (nrow > lines ? nrow : lines) - 1, 0), nrow - ttrow); 382 ttrow = HUGE; /* Unknown. */ 383 ttcol = HUGE; 384 tttop = HUGE; /* No scroll region. */ 385 ttbot = HUGE; 386 } 387} 388 389/* 390 * Set the current writing color to the specified color. Watch for color 391 * changes that are not going to do anything (the color is already right) 392 * and don't send anything to the display. The rainbow version does this 393 * in putline.s on a line by line basis, so don't bother sending out the 394 * color shift. 395 */ 396void 397ttcolor(int color) 398{ 399 if (color != tthue) { 400 if (color == CTEXT) 401 /* normal video */ 402 putpad(exit_standout_mode, 1); 403 else if (color == CMODE) 404 /* reverse video */ 405 putpad(enter_standout_mode, 1); 406 /* save the color */ 407 tthue = color; 408 } 409} 410 411/* 412 * This routine is called by the "refresh the screen" command to try 413 * to resize the display. Look in "window.c" to see how 414 * the caller deals with a change. 415 * 416 * We use `newrow' and `newcol' so vtresize() know the difference between the 417 * new and old settings. 418 */ 419void 420ttresize(void) 421{ 422 int newrow = 0, newcol = 0; 423 424 struct winsize winsize; 425 426 if (ioctl(0, TIOCGWINSZ, &winsize) == 0) { 427 newrow = winsize.ws_row; 428 newcol = winsize.ws_col; 429 } 430 if ((newrow <= 0 || newcol <= 0) && 431 ((newrow = lines) <= 0 || (newcol = columns) <= 0)) { 432 newrow = 24; 433 newcol = 80; 434 } 435 if (vtresize(1, newrow, newcol) != TRUE) 436 panic("vtresize failed"); 437} 438 439/* 440 * fake char output for charcost() 441 */ 442static int 443fakec(int c) 444{ 445 cci++; 446 return (0); 447} 448 449/* calculate the cost of doing string s */ 450static int 451charcost(const char *s) 452{ 453 cci = 0; 454 455 tputs(s, nrow, fakec); 456 return (cci); 457} 458