1 /* $OpenBSD: tty_update.c,v 1.16 2010/01/12 23:22:07 nicm Exp $ */ 2 3 /**************************************************************************** 4 * Copyright (c) 1998-2007,2008 Free Software Foundation, Inc. * 5 * * 6 * Permission is hereby granted, free of charge, to any person obtaining a * 7 * copy of this software and associated documentation files (the * 8 * "Software"), to deal in the Software without restriction, including * 9 * without limitation the rights to use, copy, modify, merge, publish, * 10 * distribute, distribute with modifications, sublicense, and/or sell * 11 * copies of the Software, and to permit persons to whom the Software is * 12 * furnished to do so, subject to the following conditions: * 13 * * 14 * The above copyright notice and this permission notice shall be included * 15 * in all copies or substantial portions of the Software. * 16 * * 17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * 18 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * 19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * 20 * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * 21 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * 22 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR * 23 * THE USE OR OTHER DEALINGS IN THE SOFTWARE. * 24 * * 25 * Except as contained in this notice, the name(s) of the above copyright * 26 * holders shall not be used in advertising or otherwise to promote the * 27 * sale, use or other dealings in this Software without prior written * 28 * authorization. * 29 ****************************************************************************/ 30 31 /**************************************************************************** 32 * Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995 * 33 * and: Eric S. Raymond <esr@snark.thyrsus.com> * 34 * and: Thomas E. Dickey 1996-on * 35 ****************************************************************************/ 36 37 /*----------------------------------------------------------------- 38 * 39 * lib_doupdate.c 40 * 41 * The routine doupdate() and its dependents. 42 * All physical output is concentrated here (except _nc_outch() 43 * in lib_tputs.c). 44 * 45 *-----------------------------------------------------------------*/ 46 47 #include <curses.priv.h> 48 49 #if defined __HAIKU__ && defined __BEOS__ 50 #undef __BEOS__ 51 #endif 52 53 #ifdef __BEOS__ 54 #undef false 55 #undef true 56 #include <OS.h> 57 #endif 58 59 #if defined(TRACE) && HAVE_SYS_TIMES_H && HAVE_TIMES 60 #define USE_TRACE_TIMES 1 61 #else 62 #define USE_TRACE_TIMES 0 63 #endif 64 65 #if HAVE_SYS_TIME_H && HAVE_SYS_TIME_SELECT 66 #include <sys/time.h> 67 #endif 68 69 #if USE_TRACE_TIMES 70 #include <sys/times.h> 71 #endif 72 73 #if USE_FUNC_POLL 74 #elif HAVE_SELECT 75 #if HAVE_SYS_SELECT_H 76 #include <sys/select.h> 77 #endif 78 #endif 79 80 #include <ctype.h> 81 #include <term.h> 82 83 MODULE_ID("$Id: tty_update.c,v 1.16 2010/01/12 23:22:07 nicm Exp $") 84 85 /* 86 * This define controls the line-breakout optimization. Every once in a 87 * while during screen refresh, we want to check for input and abort the 88 * update if there's some waiting. CHECK_INTERVAL controls the number of 89 * changed lines to be emitted between input checks. 90 * 91 * Note: Input-check-and-abort is no longer done if the screen is being 92 * updated from scratch. This is a feature, not a bug. 93 */ 94 #define CHECK_INTERVAL 5 95 96 #define FILL_BCE() (SP->_coloron && !SP->_default_color && !back_color_erase) 97 98 static const NCURSES_CH_T blankchar = NewChar(BLANK_TEXT); 99 static NCURSES_CH_T normal = NewChar(BLANK_TEXT); 100 101 /* 102 * Enable checking to see if doupdate and friends are tracking the true 103 * cursor position correctly. NOTE: this is a debugging hack which will 104 * work ONLY on ANSI-compatible terminals! 105 */ 106 /* #define POSITION_DEBUG */ 107 108 static NCURSES_INLINE NCURSES_CH_T ClrBlank(WINDOW *win); 109 static int ClrBottom(int total); 110 static void ClearScreen(NCURSES_CH_T blank); 111 static void ClrUpdate(void); 112 static void DelChar(int count); 113 static void InsStr(NCURSES_CH_T * line, int count); 114 static void TransformLine(int const lineno); 115 116 #ifdef POSITION_DEBUG 117 /**************************************************************************** 118 * 119 * Debugging code. Only works on ANSI-standard terminals. 120 * 121 ****************************************************************************/ 122 123 static void 124 position_check(int expected_y, int expected_x, char *legend) 125 /* check to see if the real cursor position matches the virtual */ 126 { 127 char buf[20]; 128 char *s; 129 int y, x; 130 131 if (!_nc_tracing || (expected_y < 0 && expected_x < 0)) 132 return; 133 134 _nc_flush(); 135 memset(buf, '\0', sizeof(buf)); 136 putp("\033[6n"); /* only works on ANSI-compatibles */ 137 _nc_flush(); 138 *(s = buf) = 0; 139 do { 140 int ask = sizeof(buf) - 1 - (s - buf); 141 int got = read(0, s, ask); 142 if (got == 0) 143 break; 144 s += got; 145 } while (strchr(buf, 'R') == 0); 146 _tracef("probe returned %s", _nc_visbuf(buf)); 147 148 /* try to interpret as a position report */ 149 if (sscanf(buf, "\033[%d;%dR", &y, &x) != 2) { 150 _tracef("position probe failed in %s", legend); 151 } else { 152 if (expected_x < 0) 153 expected_x = x - 1; 154 if (expected_y < 0) 155 expected_y = y - 1; 156 if (y - 1 != expected_y || x - 1 != expected_x) { 157 beep(); 158 tputs(tparm("\033[%d;%dH", expected_y + 1, expected_x + 1), 1, _nc_outch); 159 _tracef("position seen (%d, %d) doesn't match expected one (%d, %d) in %s", 160 y - 1, x - 1, expected_y, expected_x, legend); 161 } else { 162 _tracef("position matches OK in %s", legend); 163 } 164 } 165 } 166 #else 167 #define position_check(expected_y, expected_x, legend) /* nothing */ 168 #endif /* POSITION_DEBUG */ 169 170 /**************************************************************************** 171 * 172 * Optimized update code 173 * 174 ****************************************************************************/ 175 176 static NCURSES_INLINE void 177 GoTo(int const row, int const col) 178 { 179 TR(TRACE_MOVE, ("GoTo(%d, %d) from (%d, %d)", 180 row, col, SP->_cursrow, SP->_curscol)); 181 182 position_check(SP->_cursrow, SP->_curscol, "GoTo"); 183 184 mvcur(SP->_cursrow, SP->_curscol, row, col); 185 position_check(SP->_cursrow, SP->_curscol, "GoTo2"); 186 } 187 188 static NCURSES_INLINE void 189 PutAttrChar(CARG_CH_T ch) 190 { 191 int chlen = 1; 192 NCURSES_CH_T my_ch; 193 PUTC_DATA; 194 NCURSES_CH_T tilde; 195 NCURSES_CH_T attr = CHDEREF(ch); 196 197 TR(TRACE_CHARPUT, ("PutAttrChar(%s) at (%d, %d)", 198 _tracech_t(ch), 199 SP->_cursrow, SP->_curscol)); 200 #if USE_WIDEC_SUPPORT 201 /* 202 * If this is not a valid character, there is nothing more to do. 203 */ 204 if (isWidecExt(CHDEREF(ch))) { 205 TR(TRACE_CHARPUT, ("...skip")); 206 return; 207 } 208 /* 209 * Determine the number of character cells which the 'ch' value will use 210 * on the screen. It should be at least one. 211 */ 212 if ((chlen = wcwidth(CharOf(CHDEREF(ch)))) <= 0) { 213 static const NCURSES_CH_T blank = NewChar(BLANK_TEXT); 214 215 /* 216 * If the character falls into any of these special cases, do 217 * not force the result to a blank: 218 * 219 * a) it is printable (this works around a bug in wcwidth()). 220 * b) use_legacy_coding() has been called to modify the treatment 221 * of codes 128-255. 222 * c) the acs_map[] has been initialized to allow codes 0-31 223 * to be rendered. This supports Linux console's "PC" 224 * characters. Codes 128-255 are allowed though this is 225 * not checked. 226 */ 227 if (is8bits(CharOf(CHDEREF(ch))) 228 && (isprint(CharOf(CHDEREF(ch))) 229 || (SP->_legacy_coding > 0 && CharOf(CHDEREF(ch)) >= 160) 230 || (SP->_legacy_coding > 1 && CharOf(CHDEREF(ch)) >= 128) 231 || (AttrOf(attr) & A_ALTCHARSET 232 && ((CharOfD(ch) < ACS_LEN 233 && SP->_acs_map != 0 234 && SP->_acs_map[CharOfD(ch)] != 0) 235 || (CharOfD(ch) >= 128))))) { 236 ; 237 } else { 238 ch = CHREF(blank); 239 TR(TRACE_CHARPUT, ("forced to blank")); 240 } 241 chlen = 1; 242 } 243 #endif 244 245 if ((AttrOf(attr) & A_ALTCHARSET) 246 && SP->_acs_map != 0 247 && CharOfD(ch) < ACS_LEN) { 248 my_ch = CHDEREF(ch); /* work around const param */ 249 #if USE_WIDEC_SUPPORT 250 /* 251 * This is crude & ugly, but works most of the time. It checks if the 252 * acs_chars string specified that we have a mapping for this 253 * character, and uses the wide-character mapping when we expect the 254 * normal one to be broken (by mis-design ;-). 255 */ 256 if (SP->_screen_acs_fix 257 && SP->_screen_acs_map[CharOf(my_ch)]) { 258 RemAttr(attr, A_ALTCHARSET); 259 my_ch = _nc_wacs[CharOf(my_ch)]; 260 } 261 #endif 262 /* 263 * If we (still) have alternate character set, it is the normal 8bit 264 * flavor. The _screen_acs_map[] array tells if the character was 265 * really in acs_chars, needed because of the way wide/normal line 266 * drawing flavors are integrated. 267 */ 268 if (AttrOf(attr) & A_ALTCHARSET) { 269 int j = CharOfD(ch); 270 chtype temp = UChar(SP->_acs_map[j]); 271 272 if (!(SP->_screen_acs_map[j])) { 273 RemAttr(attr, A_ALTCHARSET); 274 if (temp == 0) 275 temp = ' '; 276 } 277 if (temp != 0) 278 SetChar(my_ch, temp, AttrOf(attr)); 279 } 280 ch = CHREF(my_ch); 281 } 282 if (tilde_glitch && (CharOfD(ch) == L('~'))) { 283 SetChar(tilde, L('`'), AttrOf(attr)); 284 ch = CHREF(tilde); 285 } 286 287 UpdateAttrs(attr); 288 #if !USE_WIDEC_SUPPORT 289 /* FIXME - we do this special case for signal handling, should see how to 290 * make it work for wide characters. 291 */ 292 if (SP->_outch != 0) { 293 SP->_outch(UChar(ch)); 294 } else 295 #endif 296 { 297 PUTC(CHDEREF(ch), SP->_ofp); /* macro's fastest... */ 298 COUNT_OUTCHARS(1); 299 } 300 SP->_curscol += chlen; 301 if (char_padding) { 302 TPUTS_TRACE("char_padding"); 303 putp(char_padding); 304 } 305 } 306 307 static bool 308 check_pending(void) 309 /* check for pending input */ 310 { 311 bool have_pending = FALSE; 312 313 /* 314 * Only carry out this check when the flag is zero, otherwise we'll 315 * have the refreshing slow down drastically (or stop) if there's an 316 * unread character available. 317 */ 318 if (SP->_fifohold != 0) 319 return FALSE; 320 321 if (SP->_checkfd >= 0) { 322 #if USE_FUNC_POLL 323 struct pollfd fds[1]; 324 fds[0].fd = SP->_checkfd; 325 fds[0].events = POLLIN; 326 if (poll(fds, 1, 0) > 0) { 327 have_pending = TRUE; 328 } 329 #elif defined(__BEOS__) 330 /* 331 * BeOS's select() is declared in socket.h, so the configure script does 332 * not see it. That's just as well, since that function works only for 333 * sockets. This (using snooze and ioctl) was distilled from Be's patch 334 * for ncurses which uses a separate thread to simulate select(). 335 * 336 * FIXME: the return values from the ioctl aren't very clear if we get 337 * interrupted. 338 */ 339 int n = 0; 340 int howmany = ioctl(0, 'ichr', &n); 341 if (howmany >= 0 && n > 0) { 342 have_pending = TRUE; 343 } 344 #elif HAVE_SELECT 345 fd_set fdset; 346 struct timeval ktimeout; 347 348 ktimeout.tv_sec = 349 ktimeout.tv_usec = 0; 350 351 FD_ZERO(&fdset); 352 FD_SET(SP->_checkfd, &fdset); 353 if (select(SP->_checkfd + 1, &fdset, NULL, NULL, &ktimeout) != 0) { 354 have_pending = TRUE; 355 } 356 #endif 357 } 358 if (have_pending) { 359 SP->_fifohold = 5; 360 _nc_flush(); 361 } 362 return FALSE; 363 } 364 365 /* put char at lower right corner */ 366 static void 367 PutCharLR(const ARG_CH_T ch) 368 { 369 if (!auto_right_margin) { 370 /* we can put the char directly */ 371 PutAttrChar(ch); 372 } else if (enter_am_mode && exit_am_mode) { 373 /* we can suppress automargin */ 374 TPUTS_TRACE("exit_am_mode"); 375 putp(exit_am_mode); 376 377 PutAttrChar(ch); 378 SP->_curscol--; 379 position_check(SP->_cursrow, SP->_curscol, "exit_am_mode"); 380 381 TPUTS_TRACE("enter_am_mode"); 382 putp(enter_am_mode); 383 } else if ((enter_insert_mode && exit_insert_mode) 384 || insert_character || parm_ich) { 385 GoTo(screen_lines - 1, screen_columns - 2); 386 PutAttrChar(ch); 387 GoTo(screen_lines - 1, screen_columns - 2); 388 InsStr(newscr->_line[screen_lines - 1].text + screen_columns - 2, 1); 389 } 390 } 391 392 /* 393 * Wrap the cursor position, i.e., advance to the beginning of the next line. 394 */ 395 static void 396 wrap_cursor(void) 397 { 398 if (eat_newline_glitch) { 399 /* 400 * xenl can manifest two different ways. The vt100 way is that, when 401 * you'd expect the cursor to wrap, it stays hung at the right margin 402 * (on top of the character just emitted) and doesn't wrap until the 403 * *next* graphic char is emitted. The c100 way is to ignore LF 404 * received just after an am wrap. 405 * 406 * An aggressive way to handle this would be to emit CR/LF after the 407 * char and then assume the wrap is done, you're on the first position 408 * of the next line, and the terminal out of its weird state. Here 409 * it's safe to just tell the code that the cursor is in hyperspace and 410 * let the next mvcur() call straighten things out. 411 */ 412 SP->_curscol = -1; 413 SP->_cursrow = -1; 414 } else if (auto_right_margin) { 415 SP->_curscol = 0; 416 SP->_cursrow++; 417 /* 418 * We've actually moved - but may have to work around problems with 419 * video attributes not working. 420 */ 421 if (!move_standout_mode && AttrOf(SCREEN_ATTRS(SP))) { 422 TR(TRACE_CHARPUT, ("turning off (%#lx) %s before wrapping", 423 (unsigned long) AttrOf(SCREEN_ATTRS(SP)), 424 _traceattr(AttrOf(SCREEN_ATTRS(SP))))); 425 (void) VIDATTR(A_NORMAL, 0); 426 } 427 } else { 428 SP->_curscol--; 429 } 430 position_check(SP->_cursrow, SP->_curscol, "wrap_cursor"); 431 } 432 433 static NCURSES_INLINE void 434 PutChar(const ARG_CH_T ch) 435 /* insert character, handling automargin stuff */ 436 { 437 if (SP->_cursrow == screen_lines - 1 && SP->_curscol == screen_columns - 1) 438 PutCharLR(ch); 439 else 440 PutAttrChar(ch); 441 442 if (SP->_curscol >= screen_columns) 443 wrap_cursor(); 444 445 position_check(SP->_cursrow, SP->_curscol, "PutChar"); 446 } 447 448 /* 449 * Check whether the given character can be output by clearing commands. This 450 * includes test for being a space and not including any 'bad' attributes, such 451 * as A_REVERSE. All attribute flags which don't affect appearance of a space 452 * or can be output by clearing (A_COLOR in case of bce-terminal) are excluded. 453 */ 454 static NCURSES_INLINE bool 455 can_clear_with(ARG_CH_T ch) 456 { 457 if (!back_color_erase && SP->_coloron) { 458 #if NCURSES_EXT_FUNCS 459 int pair; 460 461 if (!SP->_default_color) 462 return FALSE; 463 if (SP->_default_fg != C_MASK || SP->_default_bg != C_MASK) 464 return FALSE; 465 if ((pair = GetPair(CHDEREF(ch))) != 0) { 466 short fg, bg; 467 pair_content(pair, &fg, &bg); 468 if (fg != C_MASK || bg != C_MASK) 469 return FALSE; 470 } 471 #else 472 if (AttrOfD(ch) & A_COLOR) 473 return FALSE; 474 #endif 475 } 476 return (ISBLANK(CHDEREF(ch)) && 477 (AttrOfD(ch) & ~(NONBLANK_ATTR | A_COLOR)) == BLANK_ATTR); 478 } 479 480 /* 481 * Issue a given span of characters from an array. 482 * Must be functionally equivalent to: 483 * for (i = 0; i < num; i++) 484 * PutChar(ntext[i]); 485 * but can leave the cursor positioned at the middle of the interval. 486 * 487 * Returns: 0 - cursor is at the end of interval 488 * 1 - cursor is somewhere in the middle 489 * 490 * This code is optimized using ech and rep. 491 */ 492 static int 493 EmitRange(const NCURSES_CH_T * ntext, int num) 494 { 495 int i; 496 497 TR(TRACE_CHARPUT, ("EmitRange %d:%s", num, _nc_viscbuf(ntext, num))); 498 499 if (erase_chars || repeat_char) { 500 while (num > 0) { 501 int runcount; 502 NCURSES_CH_T ntext0; 503 504 while (num > 1 && !CharEq(ntext[0], ntext[1])) { 505 PutChar(CHREF(ntext[0])); 506 ntext++; 507 num--; 508 } 509 ntext0 = ntext[0]; 510 if (num == 1) { 511 PutChar(CHREF(ntext0)); 512 return 0; 513 } 514 runcount = 2; 515 516 while (runcount < num && CharEq(ntext[runcount], ntext0)) 517 runcount++; 518 519 /* 520 * The cost expression in the middle isn't exactly right. 521 * _cup_ch_cost is an upper bound on the cost for moving to the 522 * end of the erased area, but not the cost itself (which we 523 * can't compute without emitting the move). This may result 524 * in erase_chars not getting used in some situations for 525 * which it would be marginally advantageous. 526 */ 527 if (erase_chars 528 && runcount > SP->_ech_cost + SP->_cup_ch_cost 529 && can_clear_with(CHREF(ntext0))) { 530 UpdateAttrs(ntext0); 531 putp(TPARM_1(erase_chars, runcount)); 532 533 /* 534 * If this is the last part of the given interval, 535 * don't bother moving cursor, since it can be the 536 * last update on the line. 537 */ 538 if (runcount < num) { 539 GoTo(SP->_cursrow, SP->_curscol + runcount); 540 } else { 541 return 1; /* cursor stays in the middle */ 542 } 543 } else if (repeat_char && runcount > SP->_rep_cost) { 544 bool wrap_possible = (SP->_curscol + runcount >= screen_columns); 545 int rep_count = runcount; 546 547 if (wrap_possible) 548 rep_count--; 549 550 UpdateAttrs(ntext0); 551 tputs(TPARM_2(repeat_char, CharOf(ntext0), rep_count), 552 rep_count, _nc_outch); 553 SP->_curscol += rep_count; 554 555 if (wrap_possible) 556 PutChar(CHREF(ntext0)); 557 } else { 558 for (i = 0; i < runcount; i++) 559 PutChar(CHREF(ntext[i])); 560 } 561 ntext += runcount; 562 num -= runcount; 563 } 564 return 0; 565 } 566 567 for (i = 0; i < num; i++) 568 PutChar(CHREF(ntext[i])); 569 return 0; 570 } 571 572 /* 573 * Output the line in the given range [first .. last] 574 * 575 * If there's a run of identical characters that's long enough to justify 576 * cursor movement, use that also. 577 * 578 * Returns: same as EmitRange 579 */ 580 static int 581 PutRange(const NCURSES_CH_T * otext, 582 const NCURSES_CH_T * ntext, 583 int row, 584 int first, int last) 585 { 586 int i, j, same; 587 588 TR(TRACE_CHARPUT, ("PutRange(%p, %p, %d, %d, %d)", 589 otext, ntext, row, first, last)); 590 591 if (otext != ntext 592 && (last - first + 1) > SP->_inline_cost) { 593 for (j = first, same = 0; j <= last; j++) { 594 if (!same && isWidecExt(otext[j])) 595 continue; 596 if (CharEq(otext[j], ntext[j])) { 597 same++; 598 } else { 599 if (same > SP->_inline_cost) { 600 EmitRange(ntext + first, j - same - first); 601 GoTo(row, first = j); 602 } 603 same = 0; 604 } 605 } 606 i = EmitRange(ntext + first, j - same - first); 607 /* 608 * Always return 1 for the next GoTo() after a PutRange() if we found 609 * identical characters at end of interval 610 */ 611 return (same == 0 ? i : 1); 612 } 613 return EmitRange(ntext + first, last - first + 1); 614 } 615 616 /* leave unbracketed here so 'indent' works */ 617 #define MARK_NOCHANGE(win,row) \ 618 win->_line[row].firstchar = _NOCHANGE; \ 619 win->_line[row].lastchar = _NOCHANGE; \ 620 if_USE_SCROLL_HINTS(win->_line[row].oldindex = row) 621 622 NCURSES_EXPORT(int) 623 doupdate(void) 624 { 625 int i; 626 int nonempty; 627 #if USE_TRACE_TIMES 628 struct tms before, after; 629 #endif /* USE_TRACE_TIMES */ 630 631 T((T_CALLED("doupdate()"))); 632 633 if (curscr == 0 634 || newscr == 0) 635 returnCode(ERR); 636 637 #ifdef TRACE 638 if (USE_TRACEF(TRACE_UPDATE)) { 639 if (curscr->_clear) 640 _tracef("curscr is clear"); 641 else 642 _tracedump("curscr", curscr); 643 _tracedump("newscr", newscr); 644 _nc_unlock_global(tracef); 645 } 646 #endif /* TRACE */ 647 648 _nc_signal_handler(FALSE); 649 650 if (SP->_fifohold) 651 SP->_fifohold--; 652 653 #if USE_SIZECHANGE 654 if (SP->_endwin || _nc_handle_sigwinch(SP)) { 655 /* 656 * This is a transparent extension: XSI does not address it, 657 * and applications need not know that ncurses can do it. 658 * 659 * Check if the terminal size has changed while curses was off 660 * (this can happen in an xterm, for example), and resize the 661 * ncurses data structures accordingly. 662 */ 663 _nc_update_screensize(SP); 664 } 665 #endif 666 667 if (SP->_endwin) { 668 669 T(("coming back from shell mode")); 670 reset_prog_mode(); 671 672 _nc_mvcur_resume(); 673 _nc_screen_resume(); 674 SP->_mouse_resume(SP); 675 676 SP->_endwin = FALSE; 677 } 678 #if USE_TRACE_TIMES 679 /* zero the metering machinery */ 680 RESET_OUTCHARS(); 681 (void) times(&before); 682 #endif /* USE_TRACE_TIMES */ 683 684 /* 685 * This is the support for magic-cookie terminals. The theory: we scan 686 * the virtual screen looking for attribute turnons. Where we find one, 687 * check to make sure it's realizable by seeing if the required number of 688 * un-attributed blanks are present before and after the attributed range; 689 * try to shift the range boundaries over blanks (not changing the screen 690 * display) so this becomes true. If it is, shift the beginning attribute 691 * change appropriately (the end one, if we've gotten this far, is 692 * guaranteed room for its cookie). If not, nuke the added attributes out 693 * of the span. 694 */ 695 #if USE_XMC_SUPPORT 696 if (magic_cookie_glitch > 0) { 697 int j, k; 698 attr_t rattr = A_NORMAL; 699 700 for (i = 0; i < screen_lines; i++) { 701 for (j = 0; j < screen_columns; j++) { 702 bool failed = FALSE; 703 NCURSES_CH_T *thisline = newscr->_line[i].text; 704 attr_t thisattr = AttrOf(thisline[j]) & SP->_xmc_triggers; 705 attr_t turnon = thisattr & ~rattr; 706 707 /* is an attribute turned on here? */ 708 if (turnon == 0) { 709 rattr = thisattr; 710 continue; 711 } 712 713 TR(TRACE_ATTRS, ("At (%d, %d): from %s...", i, j, _traceattr(rattr))); 714 TR(TRACE_ATTRS, ("...to %s", _traceattr(turnon))); 715 716 /* 717 * If the attribute change location is a blank with a "safe" 718 * attribute, undo the attribute turnon. This may ensure 719 * there's enough room to set the attribute before the first 720 * non-blank in the run. 721 */ 722 #define SAFE(a) (!((a) & SP->_xmc_triggers)) 723 if (ISBLANK(thisline[j]) && SAFE(turnon)) { 724 RemAttr(thisline[j], turnon); 725 continue; 726 } 727 728 /* check that there's enough room at start of span */ 729 for (k = 1; k <= magic_cookie_glitch; k++) { 730 if (j - k < 0 731 || !ISBLANK(thisline[j - k]) 732 || !SAFE(AttrOf(thisline[j - k]))) { 733 failed = TRUE; 734 TR(TRACE_ATTRS, ("No room at start in %d,%d%s%s", 735 i, j - k, 736 (ISBLANK(thisline[j - k]) 737 ? "" 738 : ":nonblank"), 739 (SAFE(AttrOf(thisline[j - k])) 740 ? "" 741 : ":unsafe"))); 742 break; 743 } 744 } 745 if (!failed) { 746 bool end_onscreen = FALSE; 747 int m, n = j; 748 749 /* find end of span, if it's onscreen */ 750 for (m = i; m < screen_lines; m++) { 751 for (; n < screen_columns; n++) { 752 attr_t testattr = AttrOf(newscr->_line[m].text[n]); 753 if ((testattr & SP->_xmc_triggers) == rattr) { 754 end_onscreen = TRUE; 755 TR(TRACE_ATTRS, 756 ("Range attributed with %s ends at (%d, %d)", 757 _traceattr(turnon), m, n)); 758 goto foundit; 759 } 760 } 761 n = 0; 762 } 763 TR(TRACE_ATTRS, 764 ("Range attributed with %s ends offscreen", 765 _traceattr(turnon))); 766 foundit:; 767 768 if (end_onscreen) { 769 NCURSES_CH_T *lastline = newscr->_line[m].text; 770 771 /* 772 * If there are safely-attributed blanks at the end of 773 * the range, shorten the range. This will help ensure 774 * that there is enough room at end of span. 775 */ 776 while (n >= 0 777 && ISBLANK(lastline[n]) 778 && SAFE(AttrOf(lastline[n]))) { 779 RemAttr(lastline[n--], turnon); 780 } 781 782 /* check that there's enough room at end of span */ 783 for (k = 1; k <= magic_cookie_glitch; k++) { 784 if (n + k >= screen_columns 785 || !ISBLANK(lastline[n + k]) 786 || !SAFE(AttrOf(lastline[n + k]))) { 787 failed = TRUE; 788 TR(TRACE_ATTRS, 789 ("No room at end in %d,%d%s%s", 790 i, j - k, 791 (ISBLANK(lastline[n + k]) 792 ? "" 793 : ":nonblank"), 794 (SAFE(AttrOf(lastline[n + k])) 795 ? "" 796 : ":unsafe"))); 797 break; 798 } 799 } 800 } 801 } 802 803 if (failed) { 804 int p, q = j; 805 806 TR(TRACE_ATTRS, 807 ("Clearing %s beginning at (%d, %d)", 808 _traceattr(turnon), i, j)); 809 810 /* turn off new attributes over span */ 811 for (p = i; p < screen_lines; p++) { 812 for (; q < screen_columns; q++) { 813 attr_t testattr = AttrOf(newscr->_line[p].text[q]); 814 if ((testattr & SP->_xmc_triggers) == rattr) 815 goto foundend; 816 RemAttr(newscr->_line[p].text[q], turnon); 817 } 818 q = 0; 819 } 820 foundend:; 821 } else { 822 TR(TRACE_ATTRS, 823 ("Cookie space for %s found before (%d, %d)", 824 _traceattr(turnon), i, j)); 825 826 /* 827 * Back up the start of range so there's room for cookies 828 * before the first nonblank character. 829 */ 830 for (k = 1; k <= magic_cookie_glitch; k++) 831 AddAttr(thisline[j - k], turnon); 832 } 833 834 rattr = thisattr; 835 } 836 } 837 838 #ifdef TRACE 839 /* show altered highlights after magic-cookie check */ 840 if (USE_TRACEF(TRACE_UPDATE)) { 841 _tracef("After magic-cookie check..."); 842 _tracedump("newscr", newscr); 843 _nc_unlock_global(tracef); 844 } 845 #endif /* TRACE */ 846 } 847 #endif /* USE_XMC_SUPPORT */ 848 849 nonempty = 0; 850 if (curscr->_clear || newscr->_clear) { /* force refresh ? */ 851 ClrUpdate(); 852 curscr->_clear = FALSE; /* reset flag */ 853 newscr->_clear = FALSE; /* reset flag */ 854 } else { 855 int changedlines = CHECK_INTERVAL; 856 857 if (check_pending()) 858 goto cleanup; 859 860 nonempty = min(screen_lines, newscr->_maxy + 1); 861 862 if (SP->_scrolling) { 863 _nc_scroll_optimize(); 864 } 865 866 nonempty = ClrBottom(nonempty); 867 868 TR(TRACE_UPDATE, ("Transforming lines, nonempty %d", nonempty)); 869 for (i = 0; i < nonempty; i++) { 870 /* 871 * Here is our line-breakout optimization. 872 */ 873 if (changedlines == CHECK_INTERVAL) { 874 if (check_pending()) 875 goto cleanup; 876 changedlines = 0; 877 } 878 879 /* 880 * newscr->line[i].firstchar is normally set 881 * by wnoutrefresh. curscr->line[i].firstchar 882 * is normally set by _nc_scroll_window in the 883 * vertical-movement optimization code, 884 */ 885 if (newscr->_line[i].firstchar != _NOCHANGE 886 || curscr->_line[i].firstchar != _NOCHANGE) { 887 TransformLine(i); 888 changedlines++; 889 } 890 891 /* mark line changed successfully */ 892 if (i <= newscr->_maxy) { 893 MARK_NOCHANGE(newscr, i); 894 } 895 if (i <= curscr->_maxy) { 896 MARK_NOCHANGE(curscr, i); 897 } 898 } 899 } 900 901 /* put everything back in sync */ 902 for (i = nonempty; i <= newscr->_maxy; i++) { 903 MARK_NOCHANGE(newscr, i); 904 } 905 for (i = nonempty; i <= curscr->_maxy; i++) { 906 MARK_NOCHANGE(curscr, i); 907 } 908 909 if (!newscr->_leaveok) { 910 curscr->_curx = newscr->_curx; 911 curscr->_cury = newscr->_cury; 912 913 GoTo(curscr->_cury, curscr->_curx); 914 } 915 916 cleanup: 917 /* 918 * We would like to keep the physical screen in normal mode in case we get 919 * other processes writing to the screen. This goal cannot be met for 920 * magic cookies since it interferes with attributes that may propagate 921 * past the current position. 922 */ 923 #if USE_XMC_SUPPORT 924 if (magic_cookie_glitch != 0) 925 #endif 926 UpdateAttrs(normal); 927 928 _nc_flush(); 929 WINDOW_ATTRS(curscr) = WINDOW_ATTRS(newscr); 930 931 #if USE_TRACE_TIMES 932 (void) times(&after); 933 TR(TRACE_TIMES, 934 ("Update cost: %ld chars, %ld clocks system time, %ld clocks user time", 935 _nc_outchars, 936 (long) (after.tms_stime - before.tms_stime), 937 (long) (after.tms_utime - before.tms_utime))); 938 #endif /* USE_TRACE_TIMES */ 939 940 _nc_signal_handler(TRUE); 941 942 returnCode(OK); 943 } 944 945 /* 946 * ClrBlank(win) 947 * 948 * Returns the attributed character that corresponds to the "cleared" 949 * screen. If the terminal has the back-color-erase feature, this will be 950 * colored according to the wbkgd() call. 951 * 952 * We treat 'curscr' specially because it isn't supposed to be set directly 953 * in the wbkgd() call. Assume 'stdscr' for this case. 954 */ 955 #define BCE_ATTRS (A_NORMAL|A_COLOR) 956 #define BCE_BKGD(win) (((win) == curscr ? stdscr : (win))->_nc_bkgd) 957 958 static NCURSES_INLINE NCURSES_CH_T 959 ClrBlank(WINDOW *win) 960 { 961 NCURSES_CH_T blank = blankchar; 962 if (back_color_erase) 963 AddAttr(blank, (AttrOf(BCE_BKGD(win)) & BCE_ATTRS)); 964 return blank; 965 } 966 967 /* 968 ** ClrUpdate() 969 ** 970 ** Update by clearing and redrawing the entire screen. 971 ** 972 */ 973 974 static void 975 ClrUpdate(void) 976 { 977 int i; 978 NCURSES_CH_T blank = ClrBlank(stdscr); 979 int nonempty = min(screen_lines, newscr->_maxy + 1); 980 981 TR(TRACE_UPDATE, (T_CALLED("ClrUpdate"))); 982 983 ClearScreen(blank); 984 985 TR(TRACE_UPDATE, ("updating screen from scratch")); 986 987 nonempty = ClrBottom(nonempty); 988 989 for (i = 0; i < nonempty; i++) 990 TransformLine(i); 991 992 TR(TRACE_UPDATE, (T_RETURN(""))); 993 } 994 995 /* 996 ** ClrToEOL(blank) 997 ** 998 ** Clear to end of current line, starting at the cursor position 999 */ 1000 1001 static void 1002 ClrToEOL(NCURSES_CH_T blank, bool needclear) 1003 { 1004 int j; 1005 1006 if (curscr != 0 1007 && SP->_cursrow >= 0) { 1008 for (j = SP->_curscol; j < screen_columns; j++) { 1009 if (j >= 0) { 1010 NCURSES_CH_T *cp = &(curscr->_line[SP->_cursrow].text[j]); 1011 1012 if (!CharEq(*cp, blank)) { 1013 *cp = blank; 1014 needclear = TRUE; 1015 } 1016 } 1017 } 1018 } else { 1019 needclear = TRUE; 1020 } 1021 1022 if (needclear) { 1023 UpdateAttrs(blank); 1024 TPUTS_TRACE("clr_eol"); 1025 if (clr_eol && SP->_el_cost <= (screen_columns - SP->_curscol)) { 1026 putp(clr_eol); 1027 } else { 1028 int count = (screen_columns - SP->_curscol); 1029 while (count-- > 0) 1030 PutChar(CHREF(blank)); 1031 } 1032 } 1033 } 1034 1035 /* 1036 ** ClrToEOS(blank) 1037 ** 1038 ** Clear to end of screen, starting at the cursor position 1039 */ 1040 1041 static void 1042 ClrToEOS(NCURSES_CH_T blank) 1043 { 1044 int row, col; 1045 1046 row = SP->_cursrow; 1047 col = SP->_curscol; 1048 1049 UpdateAttrs(blank); 1050 TPUTS_TRACE("clr_eos"); 1051 tputs(clr_eos, screen_lines - row, _nc_outch); 1052 1053 while (col < screen_columns) 1054 curscr->_line[row].text[col++] = blank; 1055 1056 for (row++; row < screen_lines; row++) { 1057 for (col = 0; col < screen_columns; col++) 1058 curscr->_line[row].text[col] = blank; 1059 } 1060 } 1061 1062 /* 1063 * ClrBottom(total) 1064 * 1065 * Test if clearing the end of the screen would satisfy part of the 1066 * screen-update. Do this by scanning backwards through the lines in the 1067 * screen, checking if each is blank, and one or more are changed. 1068 */ 1069 static int 1070 ClrBottom(int total) 1071 { 1072 int row; 1073 int col; 1074 int top = total; 1075 int last = min(screen_columns, newscr->_maxx + 1); 1076 NCURSES_CH_T blank = newscr->_line[total - 1].text[last - 1]; 1077 bool ok; 1078 1079 if (clr_eos && can_clear_with(CHREF(blank))) { 1080 1081 for (row = total - 1; row >= 0; row--) { 1082 for (col = 0, ok = TRUE; ok && col < last; col++) { 1083 ok = (CharEq(newscr->_line[row].text[col], blank)); 1084 } 1085 if (!ok) 1086 break; 1087 1088 for (col = 0; ok && col < last; col++) { 1089 ok = (CharEq(curscr->_line[row].text[col], blank)); 1090 } 1091 if (!ok) 1092 top = row; 1093 } 1094 1095 /* don't use clr_eos for just one line if clr_eol available */ 1096 if (top < total) { 1097 GoTo(top, 0); 1098 ClrToEOS(blank); 1099 if (SP->oldhash && SP->newhash) { 1100 for (row = top; row < screen_lines; row++) 1101 SP->oldhash[row] = SP->newhash[row]; 1102 } 1103 } 1104 } 1105 return top; 1106 } 1107 1108 #if USE_XMC_SUPPORT 1109 #if USE_WIDEC_SUPPORT 1110 #define check_xmc_transition(a, b) \ 1111 ((((a)->attr ^ (b)->attr) & ~((a)->attr) & SP->_xmc_triggers) != 0) 1112 #define xmc_turn_on(a,b) check_xmc_transition(&(a), &(b)) 1113 #else 1114 #define xmc_turn_on(a,b) ((((a)^(b)) & ~(a) & SP->_xmc_triggers) != 0) 1115 #endif 1116 1117 #define xmc_new(r,c) newscr->_line[r].text[c] 1118 #define xmc_turn_off(a,b) xmc_turn_on(b,a) 1119 #endif /* USE_XMC_SUPPORT */ 1120 1121 /* 1122 ** TransformLine(lineno) 1123 ** 1124 ** Transform the given line in curscr to the one in newscr, using 1125 ** Insert/Delete Character if _nc_idcok && has_ic(). 1126 ** 1127 ** firstChar = position of first different character in line 1128 ** oLastChar = position of last different character in old line 1129 ** nLastChar = position of last different character in new line 1130 ** 1131 ** move to firstChar 1132 ** overwrite chars up to min(oLastChar, nLastChar) 1133 ** if oLastChar < nLastChar 1134 ** insert newLine[oLastChar+1..nLastChar] 1135 ** else 1136 ** delete oLastChar - nLastChar spaces 1137 */ 1138 1139 static void 1140 TransformLine(int const lineno) 1141 { 1142 int firstChar, oLastChar, nLastChar; 1143 NCURSES_CH_T *newLine = newscr->_line[lineno].text; 1144 NCURSES_CH_T *oldLine = curscr->_line[lineno].text; 1145 int n; 1146 bool attrchanged = FALSE; 1147 1148 TR(TRACE_UPDATE, (T_CALLED("TransformLine(%d)"), lineno)); 1149 1150 /* copy new hash value to old one */ 1151 if (SP->oldhash && SP->newhash) 1152 SP->oldhash[lineno] = SP->newhash[lineno]; 1153 1154 /* 1155 * If we have colors, there is the possibility of having two color pairs 1156 * that display as the same colors. For instance, Lynx does this. Check 1157 * for this case, and update the old line with the new line's colors when 1158 * they are equivalent. 1159 */ 1160 if (SP->_coloron) { 1161 int oldPair; 1162 int newPair; 1163 1164 for (n = 0; n < screen_columns; n++) { 1165 if (!CharEq(newLine[n], oldLine[n])) { 1166 oldPair = GetPair(oldLine[n]); 1167 newPair = GetPair(newLine[n]); 1168 if (oldPair != newPair 1169 && unColor(oldLine[n]) == unColor(newLine[n])) { 1170 if (oldPair < COLOR_PAIRS 1171 && newPair < COLOR_PAIRS 1172 && SP->_color_pairs[oldPair] == SP->_color_pairs[newPair]) { 1173 SetPair(oldLine[n], GetPair(newLine[n])); 1174 } 1175 } 1176 } 1177 } 1178 } 1179 1180 if (ceol_standout_glitch && clr_eol) { 1181 firstChar = 0; 1182 while (firstChar < screen_columns) { 1183 if (!SameAttrOf(newLine[firstChar], oldLine[firstChar])) { 1184 attrchanged = TRUE; 1185 break; 1186 } 1187 firstChar++; 1188 } 1189 } 1190 1191 firstChar = 0; 1192 1193 if (attrchanged) { /* we may have to disregard the whole line */ 1194 GoTo(lineno, firstChar); 1195 ClrToEOL(ClrBlank(curscr), FALSE); 1196 PutRange(oldLine, newLine, lineno, 0, (screen_columns - 1)); 1197 #if USE_XMC_SUPPORT 1198 1199 /* 1200 * This is a very simple loop to paint characters which may have the 1201 * magic cookie glitch embedded. It doesn't know much about video 1202 * attributes which are continued from one line to the next. It 1203 * assumes that we have filtered out requests for attribute changes 1204 * that do not get mapped to blank positions. 1205 * 1206 * FIXME: we are not keeping track of where we put the cookies, so this 1207 * will work properly only once, since we may overwrite a cookie in a 1208 * following operation. 1209 */ 1210 } else if (magic_cookie_glitch > 0) { 1211 GoTo(lineno, firstChar); 1212 for (n = 0; n < screen_columns; n++) { 1213 int m = n + magic_cookie_glitch; 1214 1215 /* check for turn-on: 1216 * If we are writing an attributed blank, where the 1217 * previous cell is not attributed. 1218 */ 1219 if (ISBLANK(newLine[n]) 1220 && ((n > 0 1221 && xmc_turn_on(newLine[n - 1], newLine[n])) 1222 || (n == 0 1223 && lineno > 0 1224 && xmc_turn_on(xmc_new(lineno - 1, screen_columns - 1), 1225 newLine[n])))) { 1226 n = m; 1227 } 1228 1229 PutChar(CHREF(newLine[n])); 1230 1231 /* check for turn-off: 1232 * If we are writing an attributed non-blank, where the 1233 * next cell is blank, and not attributed. 1234 */ 1235 if (!ISBLANK(newLine[n]) 1236 && ((n + 1 < screen_columns 1237 && xmc_turn_off(newLine[n], newLine[n + 1])) 1238 || (n + 1 >= screen_columns 1239 && lineno + 1 < screen_lines 1240 && xmc_turn_off(newLine[n], xmc_new(lineno + 1, 0))))) { 1241 n = m; 1242 } 1243 1244 } 1245 #endif 1246 } else { 1247 NCURSES_CH_T blank; 1248 1249 /* it may be cheap to clear leading whitespace with clr_bol */ 1250 blank = newLine[0]; 1251 if (clr_bol && can_clear_with(CHREF(blank))) { 1252 int oFirstChar, nFirstChar; 1253 1254 for (oFirstChar = 0; oFirstChar < screen_columns; oFirstChar++) 1255 if (!CharEq(oldLine[oFirstChar], blank)) 1256 break; 1257 for (nFirstChar = 0; nFirstChar < screen_columns; nFirstChar++) 1258 if (!CharEq(newLine[nFirstChar], blank)) 1259 break; 1260 1261 if (nFirstChar == oFirstChar) { 1262 firstChar = nFirstChar; 1263 /* find the first differing character */ 1264 while (firstChar < screen_columns 1265 && CharEq(newLine[firstChar], oldLine[firstChar])) 1266 firstChar++; 1267 } else if (oFirstChar > nFirstChar) { 1268 firstChar = nFirstChar; 1269 } else { /* oFirstChar < nFirstChar */ 1270 firstChar = oFirstChar; 1271 if (SP->_el1_cost < nFirstChar - oFirstChar) { 1272 if (nFirstChar >= screen_columns 1273 && SP->_el_cost <= SP->_el1_cost) { 1274 GoTo(lineno, 0); 1275 UpdateAttrs(blank); 1276 TPUTS_TRACE("clr_eol"); 1277 putp(clr_eol); 1278 } else { 1279 GoTo(lineno, nFirstChar - 1); 1280 UpdateAttrs(blank); 1281 TPUTS_TRACE("clr_bol"); 1282 putp(clr_bol); 1283 } 1284 1285 while (firstChar < nFirstChar) 1286 oldLine[firstChar++] = blank; 1287 } 1288 } 1289 } else { 1290 /* find the first differing character */ 1291 while (firstChar < screen_columns 1292 && CharEq(newLine[firstChar], oldLine[firstChar])) 1293 firstChar++; 1294 } 1295 /* if there wasn't one, we're done */ 1296 if (firstChar >= screen_columns) { 1297 TR(TRACE_UPDATE, (T_RETURN(""))); 1298 return; 1299 } 1300 1301 blank = newLine[screen_columns - 1]; 1302 1303 if (!can_clear_with(CHREF(blank))) { 1304 /* find the last differing character */ 1305 nLastChar = screen_columns - 1; 1306 1307 while (nLastChar > firstChar 1308 && CharEq(newLine[nLastChar], oldLine[nLastChar])) 1309 nLastChar--; 1310 1311 if (nLastChar >= firstChar) { 1312 GoTo(lineno, firstChar); 1313 PutRange(oldLine, newLine, lineno, firstChar, nLastChar); 1314 memcpy(oldLine + firstChar, 1315 newLine + firstChar, 1316 (nLastChar - firstChar + 1) * sizeof(NCURSES_CH_T)); 1317 } 1318 TR(TRACE_UPDATE, (T_RETURN(""))); 1319 return; 1320 } 1321 1322 /* find last non-blank character on old line */ 1323 oLastChar = screen_columns - 1; 1324 while (oLastChar > firstChar && CharEq(oldLine[oLastChar], blank)) 1325 oLastChar--; 1326 1327 /* find last non-blank character on new line */ 1328 nLastChar = screen_columns - 1; 1329 while (nLastChar > firstChar && CharEq(newLine[nLastChar], blank)) 1330 nLastChar--; 1331 1332 if ((nLastChar == firstChar) 1333 && (SP->_el_cost < (oLastChar - nLastChar))) { 1334 GoTo(lineno, firstChar); 1335 if (!CharEq(newLine[firstChar], blank)) 1336 PutChar(CHREF(newLine[firstChar])); 1337 ClrToEOL(blank, FALSE); 1338 } else if ((nLastChar != oLastChar) 1339 && (!CharEq(newLine[nLastChar], oldLine[oLastChar]) 1340 || !(_nc_idcok && has_ic()))) { 1341 GoTo(lineno, firstChar); 1342 if ((oLastChar - nLastChar) > SP->_el_cost) { 1343 if (PutRange(oldLine, newLine, lineno, firstChar, nLastChar)) 1344 GoTo(lineno, nLastChar + 1); 1345 ClrToEOL(blank, FALSE); 1346 } else { 1347 n = max(nLastChar, oLastChar); 1348 PutRange(oldLine, newLine, lineno, firstChar, n); 1349 } 1350 } else { 1351 int nLastNonblank = nLastChar; 1352 int oLastNonblank = oLastChar; 1353 1354 /* find the last characters that really differ */ 1355 /* can be -1 if no characters differ */ 1356 while (CharEq(newLine[nLastChar], oldLine[oLastChar])) { 1357 /* don't split a wide char */ 1358 if (isWidecExt(newLine[nLastChar]) && 1359 !CharEq(newLine[nLastChar - 1], oldLine[oLastChar - 1])) 1360 break; 1361 nLastChar--; 1362 oLastChar--; 1363 if (nLastChar == -1 || oLastChar == -1) 1364 break; 1365 } 1366 1367 n = min(oLastChar, nLastChar); 1368 if (n >= firstChar) { 1369 GoTo(lineno, firstChar); 1370 PutRange(oldLine, newLine, lineno, firstChar, n); 1371 } 1372 1373 if (oLastChar < nLastChar) { 1374 int m = max(nLastNonblank, oLastNonblank); 1375 #if USE_WIDEC_SUPPORT 1376 while (isWidecExt(newLine[n + 1]) && n) { 1377 --n; 1378 --oLastChar; 1379 } 1380 #endif 1381 GoTo(lineno, n + 1); 1382 if ((nLastChar < nLastNonblank) 1383 || InsCharCost(nLastChar - oLastChar) > (m - n)) { 1384 PutRange(oldLine, newLine, lineno, n + 1, m); 1385 } else { 1386 InsStr(&newLine[n + 1], nLastChar - oLastChar); 1387 } 1388 } else if (oLastChar > nLastChar) { 1389 GoTo(lineno, n + 1); 1390 if (DelCharCost(oLastChar - nLastChar) 1391 > SP->_el_cost + nLastNonblank - (n + 1)) { 1392 if (PutRange(oldLine, newLine, lineno, 1393 n + 1, nLastNonblank)) 1394 GoTo(lineno, nLastNonblank + 1); 1395 ClrToEOL(blank, FALSE); 1396 } else { 1397 /* 1398 * The delete-char sequence will 1399 * effectively shift in blanks from the 1400 * right margin of the screen. Ensure 1401 * that they are the right color by 1402 * setting the video attributes from 1403 * the last character on the row. 1404 */ 1405 UpdateAttrs(blank); 1406 DelChar(oLastChar - nLastChar); 1407 } 1408 } 1409 } 1410 } 1411 1412 /* update the code's internal representation */ 1413 if (screen_columns > firstChar) 1414 memcpy(oldLine + firstChar, 1415 newLine + firstChar, 1416 (screen_columns - firstChar) * sizeof(NCURSES_CH_T)); 1417 TR(TRACE_UPDATE, (T_RETURN(""))); 1418 return; 1419 } 1420 1421 /* 1422 ** ClearScreen(blank) 1423 ** 1424 ** Clear the physical screen and put cursor at home 1425 ** 1426 */ 1427 1428 static void 1429 ClearScreen(NCURSES_CH_T blank) 1430 { 1431 int i, j; 1432 bool fast_clear = (clear_screen || clr_eos || clr_eol); 1433 1434 TR(TRACE_UPDATE, ("ClearScreen() called")); 1435 1436 #if NCURSES_EXT_FUNCS 1437 if (SP->_coloron 1438 && !SP->_default_color) { 1439 _nc_do_color(GET_SCREEN_PAIR(SP), 0, FALSE, _nc_outch); 1440 if (!back_color_erase) { 1441 fast_clear = FALSE; 1442 } 1443 } 1444 #endif 1445 1446 if (fast_clear) { 1447 if (clear_screen) { 1448 UpdateAttrs(blank); 1449 TPUTS_TRACE("clear_screen"); 1450 putp(clear_screen); 1451 SP->_cursrow = SP->_curscol = 0; 1452 position_check(SP->_cursrow, SP->_curscol, "ClearScreen"); 1453 } else if (clr_eos) { 1454 SP->_cursrow = SP->_curscol = -1; 1455 GoTo(0, 0); 1456 1457 UpdateAttrs(blank); 1458 TPUTS_TRACE("clr_eos"); 1459 tputs(clr_eos, screen_lines, _nc_outch); 1460 } else if (clr_eol) { 1461 SP->_cursrow = SP->_curscol = -1; 1462 1463 UpdateAttrs(blank); 1464 for (i = 0; i < screen_lines; i++) { 1465 GoTo(i, 0); 1466 TPUTS_TRACE("clr_eol"); 1467 putp(clr_eol); 1468 } 1469 GoTo(0, 0); 1470 } 1471 } else { 1472 UpdateAttrs(blank); 1473 for (i = 0; i < screen_lines; i++) { 1474 GoTo(i, 0); 1475 for (j = 0; j < screen_columns; j++) 1476 PutChar(CHREF(blank)); 1477 } 1478 GoTo(0, 0); 1479 } 1480 1481 for (i = 0; i < screen_lines; i++) { 1482 for (j = 0; j < screen_columns; j++) 1483 curscr->_line[i].text[j] = blank; 1484 } 1485 1486 TR(TRACE_UPDATE, ("screen cleared")); 1487 } 1488 1489 /* 1490 ** InsStr(line, count) 1491 ** 1492 ** Insert the count characters pointed to by line. 1493 ** 1494 */ 1495 1496 static void 1497 InsStr(NCURSES_CH_T * line, int count) 1498 { 1499 TR(TRACE_UPDATE, ("InsStr(%p,%d) called", line, count)); 1500 1501 /* Prefer parm_ich as it has the smallest cost - no need to shift 1502 * the whole line on each character. */ 1503 /* The order must match that of InsCharCost. */ 1504 if (parm_ich) { 1505 TPUTS_TRACE("parm_ich"); 1506 tputs(TPARM_1(parm_ich, count), count, _nc_outch); 1507 while (count) { 1508 PutAttrChar(CHREF(*line)); 1509 line++; 1510 count--; 1511 } 1512 } else if (enter_insert_mode && exit_insert_mode) { 1513 TPUTS_TRACE("enter_insert_mode"); 1514 putp(enter_insert_mode); 1515 while (count) { 1516 PutAttrChar(CHREF(*line)); 1517 if (insert_padding) { 1518 TPUTS_TRACE("insert_padding"); 1519 putp(insert_padding); 1520 } 1521 line++; 1522 count--; 1523 } 1524 TPUTS_TRACE("exit_insert_mode"); 1525 putp(exit_insert_mode); 1526 } else { 1527 while (count) { 1528 TPUTS_TRACE("insert_character"); 1529 putp(insert_character); 1530 PutAttrChar(CHREF(*line)); 1531 if (insert_padding) { 1532 TPUTS_TRACE("insert_padding"); 1533 putp(insert_padding); 1534 } 1535 line++; 1536 count--; 1537 } 1538 } 1539 position_check(SP->_cursrow, SP->_curscol, "InsStr"); 1540 } 1541 1542 /* 1543 ** DelChar(count) 1544 ** 1545 ** Delete count characters at current position 1546 ** 1547 */ 1548 1549 static void 1550 DelChar(int count) 1551 { 1552 int n; 1553 1554 TR(TRACE_UPDATE, ("DelChar(%d) called, position = (%ld,%ld)", 1555 count, 1556 (long) newscr->_cury, 1557 (long) newscr->_curx)); 1558 1559 if (parm_dch) { 1560 TPUTS_TRACE("parm_dch"); 1561 tputs(TPARM_1(parm_dch, count), count, _nc_outch); 1562 } else { 1563 for (n = 0; n < count; n++) { 1564 TPUTS_TRACE("delete_character"); 1565 putp(delete_character); 1566 } 1567 } 1568 } 1569 1570 /* 1571 * Physical-scrolling support 1572 * 1573 * This code was adapted from Keith Bostic's hardware scrolling 1574 * support for 4.4BSD curses. I (esr) translated it to use terminfo 1575 * capabilities, narrowed the call interface slightly, and cleaned 1576 * up some convoluted tests. I also added support for the memory_above 1577 * memory_below, and non_dest_scroll_region capabilities. 1578 * 1579 * For this code to work, we must have either 1580 * change_scroll_region and scroll forward/reverse commands, or 1581 * insert and delete line capabilities. 1582 * When the scrolling region has been set, the cursor has to 1583 * be at the last line of the region to make the scroll up 1584 * happen, or on the first line of region to scroll down. 1585 * 1586 * This code makes one aesthetic decision in the opposite way from 1587 * BSD curses. BSD curses preferred pairs of il/dl operations 1588 * over scrolls, allegedly because il/dl looked faster. We, on 1589 * the other hand, prefer scrolls because (a) they're just as fast 1590 * on many terminals and (b) using them avoids bouncing an 1591 * unchanged bottom section of the screen up and down, which is 1592 * visually nasty. 1593 * 1594 * (lav): added more cases, used dl/il when bot==maxy and in csr case. 1595 * 1596 * I used assumption that capabilities il/il1/dl/dl1 work inside 1597 * changed scroll region not shifting screen contents outside of it. 1598 * If there are any terminals behaving different way, it would be 1599 * necessary to add some conditions to scroll_csr_forward/backward. 1600 */ 1601 1602 /* Try to scroll up assuming given csr (miny, maxy). Returns ERR on failure */ 1603 static int 1604 scroll_csr_forward(int n, int top, int bot, int miny, int maxy, NCURSES_CH_T blank) 1605 { 1606 int i; 1607 1608 if (n == 1 && scroll_forward && top == miny && bot == maxy) { 1609 GoTo(bot, 0); 1610 UpdateAttrs(blank); 1611 TPUTS_TRACE("scroll_forward"); 1612 putp(scroll_forward); 1613 } else if (n == 1 && delete_line && bot == maxy) { 1614 GoTo(top, 0); 1615 UpdateAttrs(blank); 1616 TPUTS_TRACE("delete_line"); 1617 putp(delete_line); 1618 } else if (parm_index && top == miny && bot == maxy) { 1619 GoTo(bot, 0); 1620 UpdateAttrs(blank); 1621 TPUTS_TRACE("parm_index"); 1622 tputs(TPARM_2(parm_index, n, 0), n, _nc_outch); 1623 } else if (parm_delete_line && bot == maxy) { 1624 GoTo(top, 0); 1625 UpdateAttrs(blank); 1626 TPUTS_TRACE("parm_delete_line"); 1627 tputs(TPARM_2(parm_delete_line, n, 0), n, _nc_outch); 1628 } else if (scroll_forward && top == miny && bot == maxy) { 1629 GoTo(bot, 0); 1630 UpdateAttrs(blank); 1631 for (i = 0; i < n; i++) { 1632 TPUTS_TRACE("scroll_forward"); 1633 putp(scroll_forward); 1634 } 1635 } else if (delete_line && bot == maxy) { 1636 GoTo(top, 0); 1637 UpdateAttrs(blank); 1638 for (i = 0; i < n; i++) { 1639 TPUTS_TRACE("delete_line"); 1640 putp(delete_line); 1641 } 1642 } else 1643 return ERR; 1644 1645 #if NCURSES_EXT_FUNCS 1646 if (FILL_BCE()) { 1647 int j; 1648 for (i = 0; i < n; i++) { 1649 GoTo(bot - i, 0); 1650 for (j = 0; j < screen_columns; j++) 1651 PutChar(CHREF(blank)); 1652 } 1653 } 1654 #endif 1655 return OK; 1656 } 1657 1658 /* Try to scroll down assuming given csr (miny, maxy). Returns ERR on failure */ 1659 /* n > 0 */ 1660 static int 1661 scroll_csr_backward(int n, int top, int bot, int miny, int maxy, 1662 NCURSES_CH_T blank) 1663 { 1664 int i; 1665 1666 if (n == 1 && scroll_reverse && top == miny && bot == maxy) { 1667 GoTo(top, 0); 1668 UpdateAttrs(blank); 1669 TPUTS_TRACE("scroll_reverse"); 1670 putp(scroll_reverse); 1671 } else if (n == 1 && insert_line && bot == maxy) { 1672 GoTo(top, 0); 1673 UpdateAttrs(blank); 1674 TPUTS_TRACE("insert_line"); 1675 putp(insert_line); 1676 } else if (parm_rindex && top == miny && bot == maxy) { 1677 GoTo(top, 0); 1678 UpdateAttrs(blank); 1679 TPUTS_TRACE("parm_rindex"); 1680 tputs(TPARM_2(parm_rindex, n, 0), n, _nc_outch); 1681 } else if (parm_insert_line && bot == maxy) { 1682 GoTo(top, 0); 1683 UpdateAttrs(blank); 1684 TPUTS_TRACE("parm_insert_line"); 1685 tputs(TPARM_2(parm_insert_line, n, 0), n, _nc_outch); 1686 } else if (scroll_reverse && top == miny && bot == maxy) { 1687 GoTo(top, 0); 1688 UpdateAttrs(blank); 1689 for (i = 0; i < n; i++) { 1690 TPUTS_TRACE("scroll_reverse"); 1691 putp(scroll_reverse); 1692 } 1693 } else if (insert_line && bot == maxy) { 1694 GoTo(top, 0); 1695 UpdateAttrs(blank); 1696 for (i = 0; i < n; i++) { 1697 TPUTS_TRACE("insert_line"); 1698 putp(insert_line); 1699 } 1700 } else 1701 return ERR; 1702 1703 #if NCURSES_EXT_FUNCS 1704 if (FILL_BCE()) { 1705 int j; 1706 for (i = 0; i < n; i++) { 1707 GoTo(top + i, 0); 1708 for (j = 0; j < screen_columns; j++) 1709 PutChar(CHREF(blank)); 1710 } 1711 } 1712 #endif 1713 return OK; 1714 } 1715 1716 /* scroll by using delete_line at del and insert_line at ins */ 1717 /* n > 0 */ 1718 static int 1719 scroll_idl(int n, int del, int ins, NCURSES_CH_T blank) 1720 { 1721 int i; 1722 1723 if (!((parm_delete_line || delete_line) && (parm_insert_line || insert_line))) 1724 return ERR; 1725 1726 GoTo(del, 0); 1727 UpdateAttrs(blank); 1728 if (n == 1 && delete_line) { 1729 TPUTS_TRACE("delete_line"); 1730 putp(delete_line); 1731 } else if (parm_delete_line) { 1732 TPUTS_TRACE("parm_delete_line"); 1733 tputs(TPARM_2(parm_delete_line, n, 0), n, _nc_outch); 1734 } else { /* if (delete_line) */ 1735 for (i = 0; i < n; i++) { 1736 TPUTS_TRACE("delete_line"); 1737 putp(delete_line); 1738 } 1739 } 1740 1741 GoTo(ins, 0); 1742 UpdateAttrs(blank); 1743 if (n == 1 && insert_line) { 1744 TPUTS_TRACE("insert_line"); 1745 putp(insert_line); 1746 } else if (parm_insert_line) { 1747 TPUTS_TRACE("parm_insert_line"); 1748 tputs(TPARM_2(parm_insert_line, n, 0), n, _nc_outch); 1749 } else { /* if (insert_line) */ 1750 for (i = 0; i < n; i++) { 1751 TPUTS_TRACE("insert_line"); 1752 putp(insert_line); 1753 } 1754 } 1755 1756 return OK; 1757 } 1758 1759 /* 1760 * Note: some terminals require the cursor to be within the scrolling margins 1761 * before setting them. Generally, the cursor must be at the appropriate end 1762 * of the scrolling margins when issuing an indexing operation (it is not 1763 * apparent whether it must also be at the left margin; we do this just to be 1764 * safe). To make the related cursor movement a little faster, we use the 1765 * save/restore cursor capabilities if the terminal has them. 1766 */ 1767 NCURSES_EXPORT(int) 1768 _nc_scrolln(int n, int top, int bot, int maxy) 1769 /* scroll region from top to bot by n lines */ 1770 { 1771 NCURSES_CH_T blank = ClrBlank(stdscr); 1772 int i; 1773 bool cursor_saved = FALSE; 1774 int res; 1775 1776 TR(TRACE_MOVE, ("mvcur_scrolln(%d, %d, %d, %d)", n, top, bot, maxy)); 1777 1778 #if USE_XMC_SUPPORT 1779 /* 1780 * If we scroll, we might remove a cookie. 1781 */ 1782 if (magic_cookie_glitch > 0) { 1783 return (ERR); 1784 } 1785 #endif 1786 1787 if (n > 0) { /* scroll up (forward) */ 1788 /* 1789 * Explicitly clear if stuff pushed off top of region might 1790 * be saved by the terminal. 1791 */ 1792 res = scroll_csr_forward(n, top, bot, 0, maxy, blank); 1793 1794 if (res == ERR && change_scroll_region) { 1795 if ((((n == 1 && scroll_forward) || parm_index) 1796 && (SP->_cursrow == bot || SP->_cursrow == bot - 1)) 1797 && save_cursor && restore_cursor) { 1798 cursor_saved = TRUE; 1799 TPUTS_TRACE("save_cursor"); 1800 putp(save_cursor); 1801 } 1802 TPUTS_TRACE("change_scroll_region"); 1803 putp(TPARM_2(change_scroll_region, top, bot)); 1804 if (cursor_saved) { 1805 TPUTS_TRACE("restore_cursor"); 1806 putp(restore_cursor); 1807 } else { 1808 SP->_cursrow = SP->_curscol = -1; 1809 } 1810 1811 res = scroll_csr_forward(n, top, bot, top, bot, blank); 1812 1813 TPUTS_TRACE("change_scroll_region"); 1814 putp(TPARM_2(change_scroll_region, 0, maxy)); 1815 SP->_cursrow = SP->_curscol = -1; 1816 } 1817 1818 if (res == ERR && _nc_idlok) 1819 res = scroll_idl(n, top, bot - n + 1, blank); 1820 1821 /* 1822 * Clear the newly shifted-in text. 1823 */ 1824 if (res != ERR 1825 && (non_dest_scroll_region || (memory_below && bot == maxy))) { 1826 static const NCURSES_CH_T blank2 = NewChar(BLANK_TEXT); 1827 if (bot == maxy && clr_eos) { 1828 GoTo(bot - n + 1, 0); 1829 ClrToEOS(blank2); 1830 } else { 1831 for (i = 0; i < n; i++) { 1832 GoTo(bot - i, 0); 1833 ClrToEOL(blank2, FALSE); 1834 } 1835 } 1836 } 1837 1838 } else { /* (n < 0) - scroll down (backward) */ 1839 res = scroll_csr_backward(-n, top, bot, 0, maxy, blank); 1840 1841 if (res == ERR && change_scroll_region) { 1842 if (top != 0 && (SP->_cursrow == top || SP->_cursrow == top - 1) 1843 && save_cursor && restore_cursor) { 1844 cursor_saved = TRUE; 1845 TPUTS_TRACE("save_cursor"); 1846 putp(save_cursor); 1847 } 1848 TPUTS_TRACE("change_scroll_region"); 1849 putp(TPARM_2(change_scroll_region, top, bot)); 1850 if (cursor_saved) { 1851 TPUTS_TRACE("restore_cursor"); 1852 putp(restore_cursor); 1853 } else { 1854 SP->_cursrow = SP->_curscol = -1; 1855 } 1856 1857 res = scroll_csr_backward(-n, top, bot, top, bot, blank); 1858 1859 TPUTS_TRACE("change_scroll_region"); 1860 putp(TPARM_2(change_scroll_region, 0, maxy)); 1861 SP->_cursrow = SP->_curscol = -1; 1862 } 1863 1864 if (res == ERR && _nc_idlok) 1865 res = scroll_idl(-n, bot + n + 1, top, blank); 1866 1867 /* 1868 * Clear the newly shifted-in text. 1869 */ 1870 if (res != ERR 1871 && (non_dest_scroll_region || (memory_above && top == 0))) { 1872 static const NCURSES_CH_T blank2 = NewChar(BLANK_TEXT); 1873 for (i = 0; i < -n; i++) { 1874 GoTo(i + top, 0); 1875 ClrToEOL(blank2, FALSE); 1876 } 1877 } 1878 } 1879 1880 if (res == ERR) 1881 return (ERR); 1882 1883 _nc_scroll_window(curscr, n, top, bot, blank); 1884 1885 /* shift hash values too - they can be reused */ 1886 _nc_scroll_oldhash(n, top, bot); 1887 1888 return (OK); 1889 } 1890 1891 NCURSES_EXPORT(void) 1892 _nc_screen_resume(void) 1893 { 1894 /* make sure terminal is in a sane known state */ 1895 SetAttr(SCREEN_ATTRS(SP), A_NORMAL); 1896 newscr->_clear = TRUE; 1897 1898 /* reset color pairs and definitions */ 1899 if (SP->_coloron || SP->_color_defs) 1900 _nc_reset_colors(); 1901 1902 /* restore user-defined colors, if any */ 1903 if (SP->_color_defs < 0) { 1904 int n; 1905 SP->_color_defs = -(SP->_color_defs); 1906 for (n = 0; n < SP->_color_defs; ++n) { 1907 if (SP->_color_table[n].init) { 1908 init_color(n, 1909 SP->_color_table[n].r, 1910 SP->_color_table[n].g, 1911 SP->_color_table[n].b); 1912 } 1913 } 1914 } 1915 1916 if (exit_attribute_mode) 1917 putp(exit_attribute_mode); 1918 else { 1919 /* turn off attributes */ 1920 if (exit_alt_charset_mode) 1921 putp(exit_alt_charset_mode); 1922 if (exit_standout_mode) 1923 putp(exit_standout_mode); 1924 if (exit_underline_mode) 1925 putp(exit_underline_mode); 1926 } 1927 if (exit_insert_mode) 1928 putp(exit_insert_mode); 1929 if (enter_am_mode && exit_am_mode) 1930 putp(auto_right_margin ? enter_am_mode : exit_am_mode); 1931 } 1932 1933 NCURSES_EXPORT(void) 1934 _nc_screen_init(void) 1935 { 1936 _nc_screen_resume(); 1937 } 1938 1939 /* wrap up screen handling */ 1940 NCURSES_EXPORT(void) 1941 _nc_screen_wrap(void) 1942 { 1943 UpdateAttrs(normal); 1944 #if NCURSES_EXT_FUNCS 1945 if (SP->_coloron 1946 && !SP->_default_color) { 1947 static const NCURSES_CH_T blank = NewChar(BLANK_TEXT); 1948 SP->_default_color = TRUE; 1949 _nc_do_color(-1, 0, FALSE, _nc_outch); 1950 SP->_default_color = FALSE; 1951 1952 mvcur(SP->_cursrow, SP->_curscol, screen_lines - 1, 0); 1953 1954 ClrToEOL(blank, TRUE); 1955 } 1956 #endif 1957 if (SP->_color_defs) { 1958 _nc_reset_colors(); 1959 } 1960 } 1961 1962 #if USE_XMC_SUPPORT 1963 NCURSES_EXPORT(void) 1964 _nc_do_xmc_glitch(attr_t previous) 1965 { 1966 attr_t chg = XMC_CHANGES(previous ^ AttrOf(SCREEN_ATTRS(SP))); 1967 1968 while (chg != 0) { 1969 if (chg & 1) { 1970 SP->_curscol += magic_cookie_glitch; 1971 if (SP->_curscol >= SP->_columns) 1972 wrap_cursor(); 1973 TR(TRACE_UPDATE, ("bumped to %d,%d after cookie", SP->_cursrow, SP->_curscol)); 1974 } 1975 chg >>= 1; 1976 } 1977 } 1978 #endif /* USE_XMC_SUPPORT */ 1979