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