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