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