1 /* $OpenBSD: tty.c,v 1.35 2015/03/19 21:48:05 bcallah 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 /* Case of one line insert is special. */ 244 if (row == bot) { 245 ttmove(row, 0); 246 tteeol(); 247 return; 248 } 249 if (change_scroll_region && scroll_reverse) { 250 /* Use scroll region and back index */ 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 if (insdel) { 259 ttmove(1 + bot - nchunk, 0); 260 nl = nrow - ttrow; 261 if (parm_delete_line) 262 putpad(tgoto(parm_delete_line, 0, nchunk), nl); 263 else 264 /* For all lines in the chunk... */ 265 for (i = 0; i < nchunk; i++) 266 putpad(delete_line, nl); 267 ttmove(row, 0); 268 269 /* ttmove() changes ttrow */ 270 nl = nrow - ttrow; 271 272 if (parm_insert_line) 273 putpad(tgoto(parm_insert_line, 0, nchunk), nl); 274 else 275 /* For all lines in the chunk */ 276 for (i = 0; i < nchunk; i++) 277 putpad(insert_line, nl); 278 ttrow = HUGE; 279 ttcol = HUGE; 280 } else 281 panic("ttinsl: Can't insert/delete line"); 282 } 283 284 /* 285 * Delete nchunk line(s) from "row", replacing the bottom line on the 286 * screen with a blank line. Unless we're using the scrolling region, 287 * this is done with crafty sequences of insert and delete lines. The 288 * presence of the echo area makes a boundary condition go away. 289 */ 290 void 291 ttdell(int row, int bot, int nchunk) 292 { 293 int i, nl; 294 295 /* One line special cases */ 296 if (row == bot) { 297 ttmove(row, 0); 298 tteeol(); 299 return; 300 } 301 /* scrolling region */ 302 if (change_scroll_region) { 303 nl = bot - row; 304 ttwindow(row, bot); 305 ttmove(bot, 0); 306 while (nchunk--) 307 putpad(scroll_fwd, nl); 308 ttnowindow(); 309 /* else use insert/delete line */ 310 } else if (insdel) { 311 ttmove(row, 0); 312 nl = nrow - ttrow; 313 if (parm_delete_line) 314 putpad(tgoto(parm_delete_line, 0, nchunk), nl); 315 else 316 /* For all lines in the chunk */ 317 for (i = 0; i < nchunk; i++) 318 putpad(delete_line, nl); 319 ttmove(1 + bot - nchunk, 0); 320 321 /* ttmove() changes ttrow */ 322 nl = nrow - ttrow; 323 if (parm_insert_line) 324 putpad(tgoto(parm_insert_line, 0, nchunk), nl); 325 else 326 /* For all lines in the chunk */ 327 for (i = 0; i < nchunk; i++) 328 putpad(insert_line, nl); 329 ttrow = HUGE; 330 ttcol = HUGE; 331 } else 332 panic("ttdell: Can't insert/delete line"); 333 } 334 335 /* 336 * This routine sets the scrolling window on the display to go from line 337 * "top" to line "bot" (origin 0, inclusive). The caller checks for the 338 * pathological 1-line scroll window which doesn't work right and avoids 339 * it. The "ttrow" and "ttcol" variables are set to a crazy value to 340 * ensure that the next call to "ttmove" does not turn into a no-op (the 341 * window adjustment moves the cursor). 342 */ 343 void 344 ttwindow(int top, int bot) 345 { 346 if (change_scroll_region && (tttop != top || ttbot != bot)) { 347 putpad(tgoto(change_scroll_region, bot, top), nrow - ttrow); 348 ttrow = HUGE; /* Unknown. */ 349 ttcol = HUGE; 350 tttop = top; /* Remember region. */ 351 ttbot = bot; 352 } 353 } 354 355 /* 356 * Switch to full screen scroll. This is used by "spawn.c" just before it 357 * suspends the editor and by "display.c" when it is getting ready to 358 * exit. This function does a full screen scroll by telling the terminal 359 * to set a scrolling region that is lines or nrow rows high, whichever is 360 * larger. This behavior seems to work right on systems where you can set 361 * your terminal size. 362 */ 363 void 364 ttnowindow(void) 365 { 366 if (change_scroll_region) { 367 putpad(tgoto(change_scroll_region, 368 (nrow > lines ? nrow : lines) - 1, 0), nrow - ttrow); 369 ttrow = HUGE; /* Unknown. */ 370 ttcol = HUGE; 371 tttop = HUGE; /* No scroll region. */ 372 ttbot = HUGE; 373 } 374 } 375 376 /* 377 * Set the current writing color to the specified color. Watch for color 378 * changes that are not going to do anything (the color is already right) 379 * and don't send anything to the display. The rainbow version does this 380 * in putline.s on a line by line basis, so don't bother sending out the 381 * color shift. 382 */ 383 void 384 ttcolor(int color) 385 { 386 if (color != tthue) { 387 if (color == CTEXT) 388 /* normal video */ 389 putpad(exit_standout_mode, 1); 390 else if (color == CMODE) 391 /* reverse video */ 392 putpad(enter_standout_mode, 1); 393 /* save the color */ 394 tthue = color; 395 } 396 } 397 398 /* 399 * This routine is called by the "refresh the screen" command to try 400 * to resize the display. Look in "window.c" to see how 401 * the caller deals with a change. 402 * 403 * We use `newrow' and `newcol' so vtresize() know the difference between the 404 * new and old settings. 405 */ 406 void 407 ttresize(void) 408 { 409 int newrow = 0, newcol = 0; 410 411 struct winsize winsize; 412 413 if (ioctl(0, TIOCGWINSZ, &winsize) == 0) { 414 newrow = winsize.ws_row; 415 newcol = winsize.ws_col; 416 } 417 if ((newrow <= 0 || newcol <= 0) && 418 ((newrow = lines) <= 0 || (newcol = columns) <= 0)) { 419 newrow = 24; 420 newcol = 80; 421 } 422 if (vtresize(1, newrow, newcol) != TRUE) 423 panic("vtresize failed"); 424 } 425 426 /* 427 * fake char output for charcost() 428 */ 429 /* ARGSUSED */ 430 static int 431 fakec(int c) 432 { 433 cci++; 434 return (0); 435 } 436 437 /* calculate the cost of doing string s */ 438 static int 439 charcost(const char *s) 440 { 441 cci = 0; 442 443 tputs(s, nrow, fakec); 444 return (cci); 445 } 446