1 /* $OpenBSD: tty_update.c,v 1.17 2020/05/29 20:46:13 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.17 2020/05/29 20:46:13 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 && 544 #if BSD_TPUTS 545 !isdigit(UChar(CharOf(ntext0))) && 546 #endif 547 runcount > SP->_rep_cost) { 548 bool wrap_possible = (SP->_curscol + runcount >= screen_columns); 549 int rep_count = runcount; 550 551 if (wrap_possible) 552 rep_count--; 553 554 UpdateAttrs(ntext0); 555 tputs(TPARM_2(repeat_char, CharOf(ntext0), rep_count), 556 rep_count, _nc_outch); 557 SP->_curscol += rep_count; 558 559 if (wrap_possible) 560 PutChar(CHREF(ntext0)); 561 } else { 562 for (i = 0; i < runcount; i++) 563 PutChar(CHREF(ntext[i])); 564 } 565 ntext += runcount; 566 num -= runcount; 567 } 568 return 0; 569 } 570 571 for (i = 0; i < num; i++) 572 PutChar(CHREF(ntext[i])); 573 return 0; 574 } 575 576 /* 577 * Output the line in the given range [first .. last] 578 * 579 * If there's a run of identical characters that's long enough to justify 580 * cursor movement, use that also. 581 * 582 * Returns: same as EmitRange 583 */ 584 static int 585 PutRange(const NCURSES_CH_T * otext, 586 const NCURSES_CH_T * ntext, 587 int row, 588 int first, int last) 589 { 590 int i, j, same; 591 592 TR(TRACE_CHARPUT, ("PutRange(%p, %p, %d, %d, %d)", 593 otext, ntext, row, first, last)); 594 595 if (otext != ntext 596 && (last - first + 1) > SP->_inline_cost) { 597 for (j = first, same = 0; j <= last; j++) { 598 if (!same && isWidecExt(otext[j])) 599 continue; 600 if (CharEq(otext[j], ntext[j])) { 601 same++; 602 } else { 603 if (same > SP->_inline_cost) { 604 EmitRange(ntext + first, j - same - first); 605 GoTo(row, first = j); 606 } 607 same = 0; 608 } 609 } 610 i = EmitRange(ntext + first, j - same - first); 611 /* 612 * Always return 1 for the next GoTo() after a PutRange() if we found 613 * identical characters at end of interval 614 */ 615 return (same == 0 ? i : 1); 616 } 617 return EmitRange(ntext + first, last - first + 1); 618 } 619 620 /* leave unbracketed here so 'indent' works */ 621 #define MARK_NOCHANGE(win,row) \ 622 win->_line[row].firstchar = _NOCHANGE; \ 623 win->_line[row].lastchar = _NOCHANGE; \ 624 if_USE_SCROLL_HINTS(win->_line[row].oldindex = row) 625 626 NCURSES_EXPORT(int) 627 doupdate(void) 628 { 629 int i; 630 int nonempty; 631 #if USE_TRACE_TIMES 632 struct tms before, after; 633 #endif /* USE_TRACE_TIMES */ 634 635 T((T_CALLED("doupdate()"))); 636 637 if (curscr == 0 638 || newscr == 0) 639 returnCode(ERR); 640 641 #ifdef TRACE 642 if (USE_TRACEF(TRACE_UPDATE)) { 643 if (curscr->_clear) 644 _tracef("curscr is clear"); 645 else 646 _tracedump("curscr", curscr); 647 _tracedump("newscr", newscr); 648 _nc_unlock_global(tracef); 649 } 650 #endif /* TRACE */ 651 652 _nc_signal_handler(FALSE); 653 654 if (SP->_fifohold) 655 SP->_fifohold--; 656 657 #if USE_SIZECHANGE 658 if (SP->_endwin || _nc_handle_sigwinch(SP)) { 659 /* 660 * This is a transparent extension: XSI does not address it, 661 * and applications need not know that ncurses can do it. 662 * 663 * Check if the terminal size has changed while curses was off 664 * (this can happen in an xterm, for example), and resize the 665 * ncurses data structures accordingly. 666 */ 667 _nc_update_screensize(SP); 668 } 669 #endif 670 671 if (SP->_endwin) { 672 673 T(("coming back from shell mode")); 674 reset_prog_mode(); 675 676 _nc_mvcur_resume(); 677 _nc_screen_resume(); 678 SP->_mouse_resume(SP); 679 680 SP->_endwin = FALSE; 681 } 682 #if USE_TRACE_TIMES 683 /* zero the metering machinery */ 684 RESET_OUTCHARS(); 685 (void) times(&before); 686 #endif /* USE_TRACE_TIMES */ 687 688 /* 689 * This is the support for magic-cookie terminals. The theory: we scan 690 * the virtual screen looking for attribute turnons. Where we find one, 691 * check to make sure it's realizable by seeing if the required number of 692 * un-attributed blanks are present before and after the attributed range; 693 * try to shift the range boundaries over blanks (not changing the screen 694 * display) so this becomes true. If it is, shift the beginning attribute 695 * change appropriately (the end one, if we've gotten this far, is 696 * guaranteed room for its cookie). If not, nuke the added attributes out 697 * of the span. 698 */ 699 #if USE_XMC_SUPPORT 700 if (magic_cookie_glitch > 0) { 701 int j, k; 702 attr_t rattr = A_NORMAL; 703 704 for (i = 0; i < screen_lines; i++) { 705 for (j = 0; j < screen_columns; j++) { 706 bool failed = FALSE; 707 NCURSES_CH_T *thisline = newscr->_line[i].text; 708 attr_t thisattr = AttrOf(thisline[j]) & SP->_xmc_triggers; 709 attr_t turnon = thisattr & ~rattr; 710 711 /* is an attribute turned on here? */ 712 if (turnon == 0) { 713 rattr = thisattr; 714 continue; 715 } 716 717 TR(TRACE_ATTRS, ("At (%d, %d): from %s...", i, j, _traceattr(rattr))); 718 TR(TRACE_ATTRS, ("...to %s", _traceattr(turnon))); 719 720 /* 721 * If the attribute change location is a blank with a "safe" 722 * attribute, undo the attribute turnon. This may ensure 723 * there's enough room to set the attribute before the first 724 * non-blank in the run. 725 */ 726 #define SAFE(a) (!((a) & SP->_xmc_triggers)) 727 if (ISBLANK(thisline[j]) && SAFE(turnon)) { 728 RemAttr(thisline[j], turnon); 729 continue; 730 } 731 732 /* check that there's enough room at start of span */ 733 for (k = 1; k <= magic_cookie_glitch; k++) { 734 if (j - k < 0 735 || !ISBLANK(thisline[j - k]) 736 || !SAFE(AttrOf(thisline[j - k]))) { 737 failed = TRUE; 738 TR(TRACE_ATTRS, ("No room at start in %d,%d%s%s", 739 i, j - k, 740 (ISBLANK(thisline[j - k]) 741 ? "" 742 : ":nonblank"), 743 (SAFE(AttrOf(thisline[j - k])) 744 ? "" 745 : ":unsafe"))); 746 break; 747 } 748 } 749 if (!failed) { 750 bool end_onscreen = FALSE; 751 int m, n = j; 752 753 /* find end of span, if it's onscreen */ 754 for (m = i; m < screen_lines; m++) { 755 for (; n < screen_columns; n++) { 756 attr_t testattr = AttrOf(newscr->_line[m].text[n]); 757 if ((testattr & SP->_xmc_triggers) == rattr) { 758 end_onscreen = TRUE; 759 TR(TRACE_ATTRS, 760 ("Range attributed with %s ends at (%d, %d)", 761 _traceattr(turnon), m, n)); 762 goto foundit; 763 } 764 } 765 n = 0; 766 } 767 TR(TRACE_ATTRS, 768 ("Range attributed with %s ends offscreen", 769 _traceattr(turnon))); 770 foundit:; 771 772 if (end_onscreen) { 773 NCURSES_CH_T *lastline = newscr->_line[m].text; 774 775 /* 776 * If there are safely-attributed blanks at the end of 777 * the range, shorten the range. This will help ensure 778 * that there is enough room at end of span. 779 */ 780 while (n >= 0 781 && ISBLANK(lastline[n]) 782 && SAFE(AttrOf(lastline[n]))) { 783 RemAttr(lastline[n--], turnon); 784 } 785 786 /* check that there's enough room at end of span */ 787 for (k = 1; k <= magic_cookie_glitch; k++) { 788 if (n + k >= screen_columns 789 || !ISBLANK(lastline[n + k]) 790 || !SAFE(AttrOf(lastline[n + k]))) { 791 failed = TRUE; 792 TR(TRACE_ATTRS, 793 ("No room at end in %d,%d%s%s", 794 i, j - k, 795 (ISBLANK(lastline[n + k]) 796 ? "" 797 : ":nonblank"), 798 (SAFE(AttrOf(lastline[n + k])) 799 ? "" 800 : ":unsafe"))); 801 break; 802 } 803 } 804 } 805 } 806 807 if (failed) { 808 int p, q = j; 809 810 TR(TRACE_ATTRS, 811 ("Clearing %s beginning at (%d, %d)", 812 _traceattr(turnon), i, j)); 813 814 /* turn off new attributes over span */ 815 for (p = i; p < screen_lines; p++) { 816 for (; q < screen_columns; q++) { 817 attr_t testattr = AttrOf(newscr->_line[p].text[q]); 818 if ((testattr & SP->_xmc_triggers) == rattr) 819 goto foundend; 820 RemAttr(newscr->_line[p].text[q], turnon); 821 } 822 q = 0; 823 } 824 foundend:; 825 } else { 826 TR(TRACE_ATTRS, 827 ("Cookie space for %s found before (%d, %d)", 828 _traceattr(turnon), i, j)); 829 830 /* 831 * Back up the start of range so there's room for cookies 832 * before the first nonblank character. 833 */ 834 for (k = 1; k <= magic_cookie_glitch; k++) 835 AddAttr(thisline[j - k], turnon); 836 } 837 838 rattr = thisattr; 839 } 840 } 841 842 #ifdef TRACE 843 /* show altered highlights after magic-cookie check */ 844 if (USE_TRACEF(TRACE_UPDATE)) { 845 _tracef("After magic-cookie check..."); 846 _tracedump("newscr", newscr); 847 _nc_unlock_global(tracef); 848 } 849 #endif /* TRACE */ 850 } 851 #endif /* USE_XMC_SUPPORT */ 852 853 nonempty = 0; 854 if (curscr->_clear || newscr->_clear) { /* force refresh ? */ 855 ClrUpdate(); 856 curscr->_clear = FALSE; /* reset flag */ 857 newscr->_clear = FALSE; /* reset flag */ 858 } else { 859 int changedlines = CHECK_INTERVAL; 860 861 if (check_pending()) 862 goto cleanup; 863 864 nonempty = min(screen_lines, newscr->_maxy + 1); 865 866 if (SP->_scrolling) { 867 _nc_scroll_optimize(); 868 } 869 870 nonempty = ClrBottom(nonempty); 871 872 TR(TRACE_UPDATE, ("Transforming lines, nonempty %d", nonempty)); 873 for (i = 0; i < nonempty; i++) { 874 /* 875 * Here is our line-breakout optimization. 876 */ 877 if (changedlines == CHECK_INTERVAL) { 878 if (check_pending()) 879 goto cleanup; 880 changedlines = 0; 881 } 882 883 /* 884 * newscr->line[i].firstchar is normally set 885 * by wnoutrefresh. curscr->line[i].firstchar 886 * is normally set by _nc_scroll_window in the 887 * vertical-movement optimization code, 888 */ 889 if (newscr->_line[i].firstchar != _NOCHANGE 890 || curscr->_line[i].firstchar != _NOCHANGE) { 891 TransformLine(i); 892 changedlines++; 893 } 894 895 /* mark line changed successfully */ 896 if (i <= newscr->_maxy) { 897 MARK_NOCHANGE(newscr, i); 898 } 899 if (i <= curscr->_maxy) { 900 MARK_NOCHANGE(curscr, i); 901 } 902 } 903 } 904 905 /* put everything back in sync */ 906 for (i = nonempty; i <= newscr->_maxy; i++) { 907 MARK_NOCHANGE(newscr, i); 908 } 909 for (i = nonempty; i <= curscr->_maxy; i++) { 910 MARK_NOCHANGE(curscr, i); 911 } 912 913 if (!newscr->_leaveok) { 914 curscr->_curx = newscr->_curx; 915 curscr->_cury = newscr->_cury; 916 917 GoTo(curscr->_cury, curscr->_curx); 918 } 919 920 cleanup: 921 /* 922 * We would like to keep the physical screen in normal mode in case we get 923 * other processes writing to the screen. This goal cannot be met for 924 * magic cookies since it interferes with attributes that may propagate 925 * past the current position. 926 */ 927 #if USE_XMC_SUPPORT 928 if (magic_cookie_glitch != 0) 929 #endif 930 UpdateAttrs(normal); 931 932 _nc_flush(); 933 WINDOW_ATTRS(curscr) = WINDOW_ATTRS(newscr); 934 935 #if USE_TRACE_TIMES 936 (void) times(&after); 937 TR(TRACE_TIMES, 938 ("Update cost: %ld chars, %ld clocks system time, %ld clocks user time", 939 _nc_outchars, 940 (long) (after.tms_stime - before.tms_stime), 941 (long) (after.tms_utime - before.tms_utime))); 942 #endif /* USE_TRACE_TIMES */ 943 944 _nc_signal_handler(TRUE); 945 946 returnCode(OK); 947 } 948 949 /* 950 * ClrBlank(win) 951 * 952 * Returns the attributed character that corresponds to the "cleared" 953 * screen. If the terminal has the back-color-erase feature, this will be 954 * colored according to the wbkgd() call. 955 * 956 * We treat 'curscr' specially because it isn't supposed to be set directly 957 * in the wbkgd() call. Assume 'stdscr' for this case. 958 */ 959 #define BCE_ATTRS (A_NORMAL|A_COLOR) 960 #define BCE_BKGD(win) (((win) == curscr ? stdscr : (win))->_nc_bkgd) 961 962 static NCURSES_INLINE NCURSES_CH_T 963 ClrBlank(WINDOW *win) 964 { 965 NCURSES_CH_T blank = blankchar; 966 if (back_color_erase) 967 AddAttr(blank, (AttrOf(BCE_BKGD(win)) & BCE_ATTRS)); 968 return blank; 969 } 970 971 /* 972 ** ClrUpdate() 973 ** 974 ** Update by clearing and redrawing the entire screen. 975 ** 976 */ 977 978 static void 979 ClrUpdate(void) 980 { 981 int i; 982 NCURSES_CH_T blank = ClrBlank(stdscr); 983 int nonempty = min(screen_lines, newscr->_maxy + 1); 984 985 TR(TRACE_UPDATE, (T_CALLED("ClrUpdate"))); 986 987 ClearScreen(blank); 988 989 TR(TRACE_UPDATE, ("updating screen from scratch")); 990 991 nonempty = ClrBottom(nonempty); 992 993 for (i = 0; i < nonempty; i++) 994 TransformLine(i); 995 996 TR(TRACE_UPDATE, (T_RETURN(""))); 997 } 998 999 /* 1000 ** ClrToEOL(blank) 1001 ** 1002 ** Clear to end of current line, starting at the cursor position 1003 */ 1004 1005 static void 1006 ClrToEOL(NCURSES_CH_T blank, bool needclear) 1007 { 1008 int j; 1009 1010 if (curscr != 0 1011 && SP->_cursrow >= 0) { 1012 for (j = SP->_curscol; j < screen_columns; j++) { 1013 if (j >= 0) { 1014 NCURSES_CH_T *cp = &(curscr->_line[SP->_cursrow].text[j]); 1015 1016 if (!CharEq(*cp, blank)) { 1017 *cp = blank; 1018 needclear = TRUE; 1019 } 1020 } 1021 } 1022 } else { 1023 needclear = TRUE; 1024 } 1025 1026 if (needclear) { 1027 UpdateAttrs(blank); 1028 TPUTS_TRACE("clr_eol"); 1029 if (clr_eol && SP->_el_cost <= (screen_columns - SP->_curscol)) { 1030 putp(clr_eol); 1031 } else { 1032 int count = (screen_columns - SP->_curscol); 1033 while (count-- > 0) 1034 PutChar(CHREF(blank)); 1035 } 1036 } 1037 } 1038 1039 /* 1040 ** ClrToEOS(blank) 1041 ** 1042 ** Clear to end of screen, starting at the cursor position 1043 */ 1044 1045 static void 1046 ClrToEOS(NCURSES_CH_T blank) 1047 { 1048 int row, col; 1049 1050 row = SP->_cursrow; 1051 col = SP->_curscol; 1052 1053 UpdateAttrs(blank); 1054 TPUTS_TRACE("clr_eos"); 1055 tputs(clr_eos, screen_lines - row, _nc_outch); 1056 1057 while (col < screen_columns) 1058 curscr->_line[row].text[col++] = blank; 1059 1060 for (row++; row < screen_lines; row++) { 1061 for (col = 0; col < screen_columns; col++) 1062 curscr->_line[row].text[col] = blank; 1063 } 1064 } 1065 1066 /* 1067 * ClrBottom(total) 1068 * 1069 * Test if clearing the end of the screen would satisfy part of the 1070 * screen-update. Do this by scanning backwards through the lines in the 1071 * screen, checking if each is blank, and one or more are changed. 1072 */ 1073 static int 1074 ClrBottom(int total) 1075 { 1076 int row; 1077 int col; 1078 int top = total; 1079 int last = min(screen_columns, newscr->_maxx + 1); 1080 NCURSES_CH_T blank = newscr->_line[total - 1].text[last - 1]; 1081 bool ok; 1082 1083 if (clr_eos && can_clear_with(CHREF(blank))) { 1084 1085 for (row = total - 1; row >= 0; row--) { 1086 for (col = 0, ok = TRUE; ok && col < last; col++) { 1087 ok = (CharEq(newscr->_line[row].text[col], blank)); 1088 } 1089 if (!ok) 1090 break; 1091 1092 for (col = 0; ok && col < last; col++) { 1093 ok = (CharEq(curscr->_line[row].text[col], blank)); 1094 } 1095 if (!ok) 1096 top = row; 1097 } 1098 1099 /* don't use clr_eos for just one line if clr_eol available */ 1100 if (top < total) { 1101 GoTo(top, 0); 1102 ClrToEOS(blank); 1103 if (SP->oldhash && SP->newhash) { 1104 for (row = top; row < screen_lines; row++) 1105 SP->oldhash[row] = SP->newhash[row]; 1106 } 1107 } 1108 } 1109 return top; 1110 } 1111 1112 #if USE_XMC_SUPPORT 1113 #if USE_WIDEC_SUPPORT 1114 #define check_xmc_transition(a, b) \ 1115 ((((a)->attr ^ (b)->attr) & ~((a)->attr) & SP->_xmc_triggers) != 0) 1116 #define xmc_turn_on(a,b) check_xmc_transition(&(a), &(b)) 1117 #else 1118 #define xmc_turn_on(a,b) ((((a)^(b)) & ~(a) & SP->_xmc_triggers) != 0) 1119 #endif 1120 1121 #define xmc_new(r,c) newscr->_line[r].text[c] 1122 #define xmc_turn_off(a,b) xmc_turn_on(b,a) 1123 #endif /* USE_XMC_SUPPORT */ 1124 1125 /* 1126 ** TransformLine(lineno) 1127 ** 1128 ** Transform the given line in curscr to the one in newscr, using 1129 ** Insert/Delete Character if _nc_idcok && has_ic(). 1130 ** 1131 ** firstChar = position of first different character in line 1132 ** oLastChar = position of last different character in old line 1133 ** nLastChar = position of last different character in new line 1134 ** 1135 ** move to firstChar 1136 ** overwrite chars up to min(oLastChar, nLastChar) 1137 ** if oLastChar < nLastChar 1138 ** insert newLine[oLastChar+1..nLastChar] 1139 ** else 1140 ** delete oLastChar - nLastChar spaces 1141 */ 1142 1143 static void 1144 TransformLine(int const lineno) 1145 { 1146 int firstChar, oLastChar, nLastChar; 1147 NCURSES_CH_T *newLine = newscr->_line[lineno].text; 1148 NCURSES_CH_T *oldLine = curscr->_line[lineno].text; 1149 int n; 1150 bool attrchanged = FALSE; 1151 1152 TR(TRACE_UPDATE, (T_CALLED("TransformLine(%d)"), lineno)); 1153 1154 /* copy new hash value to old one */ 1155 if (SP->oldhash && SP->newhash) 1156 SP->oldhash[lineno] = SP->newhash[lineno]; 1157 1158 /* 1159 * If we have colors, there is the possibility of having two color pairs 1160 * that display as the same colors. For instance, Lynx does this. Check 1161 * for this case, and update the old line with the new line's colors when 1162 * they are equivalent. 1163 */ 1164 if (SP->_coloron) { 1165 int oldPair; 1166 int newPair; 1167 1168 for (n = 0; n < screen_columns; n++) { 1169 if (!CharEq(newLine[n], oldLine[n])) { 1170 oldPair = GetPair(oldLine[n]); 1171 newPair = GetPair(newLine[n]); 1172 if (oldPair != newPair 1173 && unColor(oldLine[n]) == unColor(newLine[n])) { 1174 if (oldPair < COLOR_PAIRS 1175 && newPair < COLOR_PAIRS 1176 && SP->_color_pairs[oldPair] == SP->_color_pairs[newPair]) { 1177 SetPair(oldLine[n], GetPair(newLine[n])); 1178 } 1179 } 1180 } 1181 } 1182 } 1183 1184 if (ceol_standout_glitch && clr_eol) { 1185 firstChar = 0; 1186 while (firstChar < screen_columns) { 1187 if (!SameAttrOf(newLine[firstChar], oldLine[firstChar])) { 1188 attrchanged = TRUE; 1189 break; 1190 } 1191 firstChar++; 1192 } 1193 } 1194 1195 firstChar = 0; 1196 1197 if (attrchanged) { /* we may have to disregard the whole line */ 1198 GoTo(lineno, firstChar); 1199 ClrToEOL(ClrBlank(curscr), FALSE); 1200 PutRange(oldLine, newLine, lineno, 0, (screen_columns - 1)); 1201 #if USE_XMC_SUPPORT 1202 1203 /* 1204 * This is a very simple loop to paint characters which may have the 1205 * magic cookie glitch embedded. It doesn't know much about video 1206 * attributes which are continued from one line to the next. It 1207 * assumes that we have filtered out requests for attribute changes 1208 * that do not get mapped to blank positions. 1209 * 1210 * FIXME: we are not keeping track of where we put the cookies, so this 1211 * will work properly only once, since we may overwrite a cookie in a 1212 * following operation. 1213 */ 1214 } else if (magic_cookie_glitch > 0) { 1215 GoTo(lineno, firstChar); 1216 for (n = 0; n < screen_columns; n++) { 1217 int m = n + magic_cookie_glitch; 1218 1219 /* check for turn-on: 1220 * If we are writing an attributed blank, where the 1221 * previous cell is not attributed. 1222 */ 1223 if (ISBLANK(newLine[n]) 1224 && ((n > 0 1225 && xmc_turn_on(newLine[n - 1], newLine[n])) 1226 || (n == 0 1227 && lineno > 0 1228 && xmc_turn_on(xmc_new(lineno - 1, screen_columns - 1), 1229 newLine[n])))) { 1230 n = m; 1231 } 1232 1233 PutChar(CHREF(newLine[n])); 1234 1235 /* check for turn-off: 1236 * If we are writing an attributed non-blank, where the 1237 * next cell is blank, and not attributed. 1238 */ 1239 if (!ISBLANK(newLine[n]) 1240 && ((n + 1 < screen_columns 1241 && xmc_turn_off(newLine[n], newLine[n + 1])) 1242 || (n + 1 >= screen_columns 1243 && lineno + 1 < screen_lines 1244 && xmc_turn_off(newLine[n], xmc_new(lineno + 1, 0))))) { 1245 n = m; 1246 } 1247 1248 } 1249 #endif 1250 } else { 1251 NCURSES_CH_T blank; 1252 1253 /* it may be cheap to clear leading whitespace with clr_bol */ 1254 blank = newLine[0]; 1255 if (clr_bol && can_clear_with(CHREF(blank))) { 1256 int oFirstChar, nFirstChar; 1257 1258 for (oFirstChar = 0; oFirstChar < screen_columns; oFirstChar++) 1259 if (!CharEq(oldLine[oFirstChar], blank)) 1260 break; 1261 for (nFirstChar = 0; nFirstChar < screen_columns; nFirstChar++) 1262 if (!CharEq(newLine[nFirstChar], blank)) 1263 break; 1264 1265 if (nFirstChar == oFirstChar) { 1266 firstChar = nFirstChar; 1267 /* find the first differing character */ 1268 while (firstChar < screen_columns 1269 && CharEq(newLine[firstChar], oldLine[firstChar])) 1270 firstChar++; 1271 } else if (oFirstChar > nFirstChar) { 1272 firstChar = nFirstChar; 1273 } else { /* oFirstChar < nFirstChar */ 1274 firstChar = oFirstChar; 1275 if (SP->_el1_cost < nFirstChar - oFirstChar) { 1276 if (nFirstChar >= screen_columns 1277 && SP->_el_cost <= SP->_el1_cost) { 1278 GoTo(lineno, 0); 1279 UpdateAttrs(blank); 1280 TPUTS_TRACE("clr_eol"); 1281 putp(clr_eol); 1282 } else { 1283 GoTo(lineno, nFirstChar - 1); 1284 UpdateAttrs(blank); 1285 TPUTS_TRACE("clr_bol"); 1286 putp(clr_bol); 1287 } 1288 1289 while (firstChar < nFirstChar) 1290 oldLine[firstChar++] = blank; 1291 } 1292 } 1293 } else { 1294 /* find the first differing character */ 1295 while (firstChar < screen_columns 1296 && CharEq(newLine[firstChar], oldLine[firstChar])) 1297 firstChar++; 1298 } 1299 /* if there wasn't one, we're done */ 1300 if (firstChar >= screen_columns) { 1301 TR(TRACE_UPDATE, (T_RETURN(""))); 1302 return; 1303 } 1304 1305 blank = newLine[screen_columns - 1]; 1306 1307 if (!can_clear_with(CHREF(blank))) { 1308 /* find the last differing character */ 1309 nLastChar = screen_columns - 1; 1310 1311 while (nLastChar > firstChar 1312 && CharEq(newLine[nLastChar], oldLine[nLastChar])) 1313 nLastChar--; 1314 1315 if (nLastChar >= firstChar) { 1316 GoTo(lineno, firstChar); 1317 PutRange(oldLine, newLine, lineno, firstChar, nLastChar); 1318 memcpy(oldLine + firstChar, 1319 newLine + firstChar, 1320 (nLastChar - firstChar + 1) * sizeof(NCURSES_CH_T)); 1321 } 1322 TR(TRACE_UPDATE, (T_RETURN(""))); 1323 return; 1324 } 1325 1326 /* find last non-blank character on old line */ 1327 oLastChar = screen_columns - 1; 1328 while (oLastChar > firstChar && CharEq(oldLine[oLastChar], blank)) 1329 oLastChar--; 1330 1331 /* find last non-blank character on new line */ 1332 nLastChar = screen_columns - 1; 1333 while (nLastChar > firstChar && CharEq(newLine[nLastChar], blank)) 1334 nLastChar--; 1335 1336 if ((nLastChar == firstChar) 1337 && (SP->_el_cost < (oLastChar - nLastChar))) { 1338 GoTo(lineno, firstChar); 1339 if (!CharEq(newLine[firstChar], blank)) 1340 PutChar(CHREF(newLine[firstChar])); 1341 ClrToEOL(blank, FALSE); 1342 } else if ((nLastChar != oLastChar) 1343 && (!CharEq(newLine[nLastChar], oldLine[oLastChar]) 1344 || !(_nc_idcok && has_ic()))) { 1345 GoTo(lineno, firstChar); 1346 if ((oLastChar - nLastChar) > SP->_el_cost) { 1347 if (PutRange(oldLine, newLine, lineno, firstChar, nLastChar)) 1348 GoTo(lineno, nLastChar + 1); 1349 ClrToEOL(blank, FALSE); 1350 } else { 1351 n = max(nLastChar, oLastChar); 1352 PutRange(oldLine, newLine, lineno, firstChar, n); 1353 } 1354 } else { 1355 int nLastNonblank = nLastChar; 1356 int oLastNonblank = oLastChar; 1357 1358 /* find the last characters that really differ */ 1359 /* can be -1 if no characters differ */ 1360 while (CharEq(newLine[nLastChar], oldLine[oLastChar])) { 1361 /* don't split a wide char */ 1362 if (isWidecExt(newLine[nLastChar]) && 1363 !CharEq(newLine[nLastChar - 1], oldLine[oLastChar - 1])) 1364 break; 1365 nLastChar--; 1366 oLastChar--; 1367 if (nLastChar == -1 || oLastChar == -1) 1368 break; 1369 } 1370 1371 n = min(oLastChar, nLastChar); 1372 if (n >= firstChar) { 1373 GoTo(lineno, firstChar); 1374 PutRange(oldLine, newLine, lineno, firstChar, n); 1375 } 1376 1377 if (oLastChar < nLastChar) { 1378 int m = max(nLastNonblank, oLastNonblank); 1379 #if USE_WIDEC_SUPPORT 1380 while (isWidecExt(newLine[n + 1]) && n) { 1381 --n; 1382 --oLastChar; 1383 } 1384 #endif 1385 GoTo(lineno, n + 1); 1386 if ((nLastChar < nLastNonblank) 1387 || InsCharCost(nLastChar - oLastChar) > (m - n)) { 1388 PutRange(oldLine, newLine, lineno, n + 1, m); 1389 } else { 1390 InsStr(&newLine[n + 1], nLastChar - oLastChar); 1391 } 1392 } else if (oLastChar > nLastChar) { 1393 GoTo(lineno, n + 1); 1394 if (DelCharCost(oLastChar - nLastChar) 1395 > SP->_el_cost + nLastNonblank - (n + 1)) { 1396 if (PutRange(oldLine, newLine, lineno, 1397 n + 1, nLastNonblank)) 1398 GoTo(lineno, nLastNonblank + 1); 1399 ClrToEOL(blank, FALSE); 1400 } else { 1401 /* 1402 * The delete-char sequence will 1403 * effectively shift in blanks from the 1404 * right margin of the screen. Ensure 1405 * that they are the right color by 1406 * setting the video attributes from 1407 * the last character on the row. 1408 */ 1409 UpdateAttrs(blank); 1410 DelChar(oLastChar - nLastChar); 1411 } 1412 } 1413 } 1414 } 1415 1416 /* update the code's internal representation */ 1417 if (screen_columns > firstChar) 1418 memcpy(oldLine + firstChar, 1419 newLine + firstChar, 1420 (screen_columns - firstChar) * sizeof(NCURSES_CH_T)); 1421 TR(TRACE_UPDATE, (T_RETURN(""))); 1422 return; 1423 } 1424 1425 /* 1426 ** ClearScreen(blank) 1427 ** 1428 ** Clear the physical screen and put cursor at home 1429 ** 1430 */ 1431 1432 static void 1433 ClearScreen(NCURSES_CH_T blank) 1434 { 1435 int i, j; 1436 bool fast_clear = (clear_screen || clr_eos || clr_eol); 1437 1438 TR(TRACE_UPDATE, ("ClearScreen() called")); 1439 1440 #if NCURSES_EXT_FUNCS 1441 if (SP->_coloron 1442 && !SP->_default_color) { 1443 _nc_do_color(GET_SCREEN_PAIR(SP), 0, FALSE, _nc_outch); 1444 if (!back_color_erase) { 1445 fast_clear = FALSE; 1446 } 1447 } 1448 #endif 1449 1450 if (fast_clear) { 1451 if (clear_screen) { 1452 UpdateAttrs(blank); 1453 TPUTS_TRACE("clear_screen"); 1454 putp(clear_screen); 1455 SP->_cursrow = SP->_curscol = 0; 1456 position_check(SP->_cursrow, SP->_curscol, "ClearScreen"); 1457 } else if (clr_eos) { 1458 SP->_cursrow = SP->_curscol = -1; 1459 GoTo(0, 0); 1460 1461 UpdateAttrs(blank); 1462 TPUTS_TRACE("clr_eos"); 1463 tputs(clr_eos, screen_lines, _nc_outch); 1464 } else if (clr_eol) { 1465 SP->_cursrow = SP->_curscol = -1; 1466 1467 UpdateAttrs(blank); 1468 for (i = 0; i < screen_lines; i++) { 1469 GoTo(i, 0); 1470 TPUTS_TRACE("clr_eol"); 1471 putp(clr_eol); 1472 } 1473 GoTo(0, 0); 1474 } 1475 } else { 1476 UpdateAttrs(blank); 1477 for (i = 0; i < screen_lines; i++) { 1478 GoTo(i, 0); 1479 for (j = 0; j < screen_columns; j++) 1480 PutChar(CHREF(blank)); 1481 } 1482 GoTo(0, 0); 1483 } 1484 1485 for (i = 0; i < screen_lines; i++) { 1486 for (j = 0; j < screen_columns; j++) 1487 curscr->_line[i].text[j] = blank; 1488 } 1489 1490 TR(TRACE_UPDATE, ("screen cleared")); 1491 } 1492 1493 /* 1494 ** InsStr(line, count) 1495 ** 1496 ** Insert the count characters pointed to by line. 1497 ** 1498 */ 1499 1500 static void 1501 InsStr(NCURSES_CH_T * line, int count) 1502 { 1503 TR(TRACE_UPDATE, ("InsStr(%p,%d) called", line, count)); 1504 1505 /* Prefer parm_ich as it has the smallest cost - no need to shift 1506 * the whole line on each character. */ 1507 /* The order must match that of InsCharCost. */ 1508 if (parm_ich) { 1509 TPUTS_TRACE("parm_ich"); 1510 tputs(TPARM_1(parm_ich, count), count, _nc_outch); 1511 while (count) { 1512 PutAttrChar(CHREF(*line)); 1513 line++; 1514 count--; 1515 } 1516 } else if (enter_insert_mode && exit_insert_mode) { 1517 TPUTS_TRACE("enter_insert_mode"); 1518 putp(enter_insert_mode); 1519 while (count) { 1520 PutAttrChar(CHREF(*line)); 1521 if (insert_padding) { 1522 TPUTS_TRACE("insert_padding"); 1523 putp(insert_padding); 1524 } 1525 line++; 1526 count--; 1527 } 1528 TPUTS_TRACE("exit_insert_mode"); 1529 putp(exit_insert_mode); 1530 } else { 1531 while (count) { 1532 TPUTS_TRACE("insert_character"); 1533 putp(insert_character); 1534 PutAttrChar(CHREF(*line)); 1535 if (insert_padding) { 1536 TPUTS_TRACE("insert_padding"); 1537 putp(insert_padding); 1538 } 1539 line++; 1540 count--; 1541 } 1542 } 1543 position_check(SP->_cursrow, SP->_curscol, "InsStr"); 1544 } 1545 1546 /* 1547 ** DelChar(count) 1548 ** 1549 ** Delete count characters at current position 1550 ** 1551 */ 1552 1553 static void 1554 DelChar(int count) 1555 { 1556 int n; 1557 1558 TR(TRACE_UPDATE, ("DelChar(%d) called, position = (%ld,%ld)", 1559 count, 1560 (long) newscr->_cury, 1561 (long) newscr->_curx)); 1562 1563 if (parm_dch) { 1564 TPUTS_TRACE("parm_dch"); 1565 tputs(TPARM_1(parm_dch, count), count, _nc_outch); 1566 } else { 1567 for (n = 0; n < count; n++) { 1568 TPUTS_TRACE("delete_character"); 1569 putp(delete_character); 1570 } 1571 } 1572 } 1573 1574 /* 1575 * Physical-scrolling support 1576 * 1577 * This code was adapted from Keith Bostic's hardware scrolling 1578 * support for 4.4BSD curses. I (esr) translated it to use terminfo 1579 * capabilities, narrowed the call interface slightly, and cleaned 1580 * up some convoluted tests. I also added support for the memory_above 1581 * memory_below, and non_dest_scroll_region capabilities. 1582 * 1583 * For this code to work, we must have either 1584 * change_scroll_region and scroll forward/reverse commands, or 1585 * insert and delete line capabilities. 1586 * When the scrolling region has been set, the cursor has to 1587 * be at the last line of the region to make the scroll up 1588 * happen, or on the first line of region to scroll down. 1589 * 1590 * This code makes one aesthetic decision in the opposite way from 1591 * BSD curses. BSD curses preferred pairs of il/dl operations 1592 * over scrolls, allegedly because il/dl looked faster. We, on 1593 * the other hand, prefer scrolls because (a) they're just as fast 1594 * on many terminals and (b) using them avoids bouncing an 1595 * unchanged bottom section of the screen up and down, which is 1596 * visually nasty. 1597 * 1598 * (lav): added more cases, used dl/il when bot==maxy and in csr case. 1599 * 1600 * I used assumption that capabilities il/il1/dl/dl1 work inside 1601 * changed scroll region not shifting screen contents outside of it. 1602 * If there are any terminals behaving different way, it would be 1603 * necessary to add some conditions to scroll_csr_forward/backward. 1604 */ 1605 1606 /* Try to scroll up assuming given csr (miny, maxy). Returns ERR on failure */ 1607 static int 1608 scroll_csr_forward(int n, int top, int bot, int miny, int maxy, NCURSES_CH_T blank) 1609 { 1610 int i; 1611 1612 if (n == 1 && scroll_forward && top == miny && bot == maxy) { 1613 GoTo(bot, 0); 1614 UpdateAttrs(blank); 1615 TPUTS_TRACE("scroll_forward"); 1616 putp(scroll_forward); 1617 } else if (n == 1 && delete_line && bot == maxy) { 1618 GoTo(top, 0); 1619 UpdateAttrs(blank); 1620 TPUTS_TRACE("delete_line"); 1621 putp(delete_line); 1622 } else if (parm_index && top == miny && bot == maxy) { 1623 GoTo(bot, 0); 1624 UpdateAttrs(blank); 1625 TPUTS_TRACE("parm_index"); 1626 tputs(TPARM_2(parm_index, n, 0), n, _nc_outch); 1627 } else if (parm_delete_line && bot == maxy) { 1628 GoTo(top, 0); 1629 UpdateAttrs(blank); 1630 TPUTS_TRACE("parm_delete_line"); 1631 tputs(TPARM_2(parm_delete_line, n, 0), n, _nc_outch); 1632 } else if (scroll_forward && top == miny && bot == maxy) { 1633 GoTo(bot, 0); 1634 UpdateAttrs(blank); 1635 for (i = 0; i < n; i++) { 1636 TPUTS_TRACE("scroll_forward"); 1637 putp(scroll_forward); 1638 } 1639 } else if (delete_line && bot == maxy) { 1640 GoTo(top, 0); 1641 UpdateAttrs(blank); 1642 for (i = 0; i < n; i++) { 1643 TPUTS_TRACE("delete_line"); 1644 putp(delete_line); 1645 } 1646 } else 1647 return ERR; 1648 1649 #if NCURSES_EXT_FUNCS 1650 if (FILL_BCE()) { 1651 int j; 1652 for (i = 0; i < n; i++) { 1653 GoTo(bot - i, 0); 1654 for (j = 0; j < screen_columns; j++) 1655 PutChar(CHREF(blank)); 1656 } 1657 } 1658 #endif 1659 return OK; 1660 } 1661 1662 /* Try to scroll down assuming given csr (miny, maxy). Returns ERR on failure */ 1663 /* n > 0 */ 1664 static int 1665 scroll_csr_backward(int n, int top, int bot, int miny, int maxy, 1666 NCURSES_CH_T blank) 1667 { 1668 int i; 1669 1670 if (n == 1 && scroll_reverse && top == miny && bot == maxy) { 1671 GoTo(top, 0); 1672 UpdateAttrs(blank); 1673 TPUTS_TRACE("scroll_reverse"); 1674 putp(scroll_reverse); 1675 } else if (n == 1 && insert_line && bot == maxy) { 1676 GoTo(top, 0); 1677 UpdateAttrs(blank); 1678 TPUTS_TRACE("insert_line"); 1679 putp(insert_line); 1680 } else if (parm_rindex && top == miny && bot == maxy) { 1681 GoTo(top, 0); 1682 UpdateAttrs(blank); 1683 TPUTS_TRACE("parm_rindex"); 1684 tputs(TPARM_2(parm_rindex, n, 0), n, _nc_outch); 1685 } else if (parm_insert_line && bot == maxy) { 1686 GoTo(top, 0); 1687 UpdateAttrs(blank); 1688 TPUTS_TRACE("parm_insert_line"); 1689 tputs(TPARM_2(parm_insert_line, n, 0), n, _nc_outch); 1690 } else if (scroll_reverse && top == miny && bot == maxy) { 1691 GoTo(top, 0); 1692 UpdateAttrs(blank); 1693 for (i = 0; i < n; i++) { 1694 TPUTS_TRACE("scroll_reverse"); 1695 putp(scroll_reverse); 1696 } 1697 } else if (insert_line && bot == maxy) { 1698 GoTo(top, 0); 1699 UpdateAttrs(blank); 1700 for (i = 0; i < n; i++) { 1701 TPUTS_TRACE("insert_line"); 1702 putp(insert_line); 1703 } 1704 } else 1705 return ERR; 1706 1707 #if NCURSES_EXT_FUNCS 1708 if (FILL_BCE()) { 1709 int j; 1710 for (i = 0; i < n; i++) { 1711 GoTo(top + i, 0); 1712 for (j = 0; j < screen_columns; j++) 1713 PutChar(CHREF(blank)); 1714 } 1715 } 1716 #endif 1717 return OK; 1718 } 1719 1720 /* scroll by using delete_line at del and insert_line at ins */ 1721 /* n > 0 */ 1722 static int 1723 scroll_idl(int n, int del, int ins, NCURSES_CH_T blank) 1724 { 1725 int i; 1726 1727 if (!((parm_delete_line || delete_line) && (parm_insert_line || insert_line))) 1728 return ERR; 1729 1730 GoTo(del, 0); 1731 UpdateAttrs(blank); 1732 if (n == 1 && delete_line) { 1733 TPUTS_TRACE("delete_line"); 1734 putp(delete_line); 1735 } else if (parm_delete_line) { 1736 TPUTS_TRACE("parm_delete_line"); 1737 tputs(TPARM_2(parm_delete_line, n, 0), n, _nc_outch); 1738 } else { /* if (delete_line) */ 1739 for (i = 0; i < n; i++) { 1740 TPUTS_TRACE("delete_line"); 1741 putp(delete_line); 1742 } 1743 } 1744 1745 GoTo(ins, 0); 1746 UpdateAttrs(blank); 1747 if (n == 1 && insert_line) { 1748 TPUTS_TRACE("insert_line"); 1749 putp(insert_line); 1750 } else if (parm_insert_line) { 1751 TPUTS_TRACE("parm_insert_line"); 1752 tputs(TPARM_2(parm_insert_line, n, 0), n, _nc_outch); 1753 } else { /* if (insert_line) */ 1754 for (i = 0; i < n; i++) { 1755 TPUTS_TRACE("insert_line"); 1756 putp(insert_line); 1757 } 1758 } 1759 1760 return OK; 1761 } 1762 1763 /* 1764 * Note: some terminals require the cursor to be within the scrolling margins 1765 * before setting them. Generally, the cursor must be at the appropriate end 1766 * of the scrolling margins when issuing an indexing operation (it is not 1767 * apparent whether it must also be at the left margin; we do this just to be 1768 * safe). To make the related cursor movement a little faster, we use the 1769 * save/restore cursor capabilities if the terminal has them. 1770 */ 1771 NCURSES_EXPORT(int) 1772 _nc_scrolln(int n, int top, int bot, int maxy) 1773 /* scroll region from top to bot by n lines */ 1774 { 1775 NCURSES_CH_T blank = ClrBlank(stdscr); 1776 int i; 1777 bool cursor_saved = FALSE; 1778 int res; 1779 1780 TR(TRACE_MOVE, ("mvcur_scrolln(%d, %d, %d, %d)", n, top, bot, maxy)); 1781 1782 #if USE_XMC_SUPPORT 1783 /* 1784 * If we scroll, we might remove a cookie. 1785 */ 1786 if (magic_cookie_glitch > 0) { 1787 return (ERR); 1788 } 1789 #endif 1790 1791 if (n > 0) { /* scroll up (forward) */ 1792 /* 1793 * Explicitly clear if stuff pushed off top of region might 1794 * be saved by the terminal. 1795 */ 1796 res = scroll_csr_forward(n, top, bot, 0, maxy, blank); 1797 1798 if (res == ERR && change_scroll_region) { 1799 if ((((n == 1 && scroll_forward) || parm_index) 1800 && (SP->_cursrow == bot || SP->_cursrow == bot - 1)) 1801 && save_cursor && restore_cursor) { 1802 cursor_saved = TRUE; 1803 TPUTS_TRACE("save_cursor"); 1804 putp(save_cursor); 1805 } 1806 TPUTS_TRACE("change_scroll_region"); 1807 putp(TPARM_2(change_scroll_region, top, bot)); 1808 if (cursor_saved) { 1809 TPUTS_TRACE("restore_cursor"); 1810 putp(restore_cursor); 1811 } else { 1812 SP->_cursrow = SP->_curscol = -1; 1813 } 1814 1815 res = scroll_csr_forward(n, top, bot, top, bot, blank); 1816 1817 TPUTS_TRACE("change_scroll_region"); 1818 putp(TPARM_2(change_scroll_region, 0, maxy)); 1819 SP->_cursrow = SP->_curscol = -1; 1820 } 1821 1822 if (res == ERR && _nc_idlok) 1823 res = scroll_idl(n, top, bot - n + 1, blank); 1824 1825 /* 1826 * Clear the newly shifted-in text. 1827 */ 1828 if (res != ERR 1829 && (non_dest_scroll_region || (memory_below && bot == maxy))) { 1830 static const NCURSES_CH_T blank2 = NewChar(BLANK_TEXT); 1831 if (bot == maxy && clr_eos) { 1832 GoTo(bot - n + 1, 0); 1833 ClrToEOS(blank2); 1834 } else { 1835 for (i = 0; i < n; i++) { 1836 GoTo(bot - i, 0); 1837 ClrToEOL(blank2, FALSE); 1838 } 1839 } 1840 } 1841 1842 } else { /* (n < 0) - scroll down (backward) */ 1843 res = scroll_csr_backward(-n, top, bot, 0, maxy, blank); 1844 1845 if (res == ERR && change_scroll_region) { 1846 if (top != 0 && (SP->_cursrow == top || SP->_cursrow == top - 1) 1847 && save_cursor && restore_cursor) { 1848 cursor_saved = TRUE; 1849 TPUTS_TRACE("save_cursor"); 1850 putp(save_cursor); 1851 } 1852 TPUTS_TRACE("change_scroll_region"); 1853 putp(TPARM_2(change_scroll_region, top, bot)); 1854 if (cursor_saved) { 1855 TPUTS_TRACE("restore_cursor"); 1856 putp(restore_cursor); 1857 } else { 1858 SP->_cursrow = SP->_curscol = -1; 1859 } 1860 1861 res = scroll_csr_backward(-n, top, bot, top, bot, blank); 1862 1863 TPUTS_TRACE("change_scroll_region"); 1864 putp(TPARM_2(change_scroll_region, 0, maxy)); 1865 SP->_cursrow = SP->_curscol = -1; 1866 } 1867 1868 if (res == ERR && _nc_idlok) 1869 res = scroll_idl(-n, bot + n + 1, top, blank); 1870 1871 /* 1872 * Clear the newly shifted-in text. 1873 */ 1874 if (res != ERR 1875 && (non_dest_scroll_region || (memory_above && top == 0))) { 1876 static const NCURSES_CH_T blank2 = NewChar(BLANK_TEXT); 1877 for (i = 0; i < -n; i++) { 1878 GoTo(i + top, 0); 1879 ClrToEOL(blank2, FALSE); 1880 } 1881 } 1882 } 1883 1884 if (res == ERR) 1885 return (ERR); 1886 1887 _nc_scroll_window(curscr, n, top, bot, blank); 1888 1889 /* shift hash values too - they can be reused */ 1890 _nc_scroll_oldhash(n, top, bot); 1891 1892 return (OK); 1893 } 1894 1895 NCURSES_EXPORT(void) 1896 _nc_screen_resume(void) 1897 { 1898 /* make sure terminal is in a sane known state */ 1899 SetAttr(SCREEN_ATTRS(SP), A_NORMAL); 1900 newscr->_clear = TRUE; 1901 1902 /* reset color pairs and definitions */ 1903 if (SP->_coloron || SP->_color_defs) 1904 _nc_reset_colors(); 1905 1906 /* restore user-defined colors, if any */ 1907 if (SP->_color_defs < 0) { 1908 int n; 1909 SP->_color_defs = -(SP->_color_defs); 1910 for (n = 0; n < SP->_color_defs; ++n) { 1911 if (SP->_color_table[n].init) { 1912 init_color(n, 1913 SP->_color_table[n].r, 1914 SP->_color_table[n].g, 1915 SP->_color_table[n].b); 1916 } 1917 } 1918 } 1919 1920 if (exit_attribute_mode) 1921 putp(exit_attribute_mode); 1922 else { 1923 /* turn off attributes */ 1924 if (exit_alt_charset_mode) 1925 putp(exit_alt_charset_mode); 1926 if (exit_standout_mode) 1927 putp(exit_standout_mode); 1928 if (exit_underline_mode) 1929 putp(exit_underline_mode); 1930 } 1931 if (exit_insert_mode) 1932 putp(exit_insert_mode); 1933 if (enter_am_mode && exit_am_mode) 1934 putp(auto_right_margin ? enter_am_mode : exit_am_mode); 1935 } 1936 1937 NCURSES_EXPORT(void) 1938 _nc_screen_init(void) 1939 { 1940 _nc_screen_resume(); 1941 } 1942 1943 /* wrap up screen handling */ 1944 NCURSES_EXPORT(void) 1945 _nc_screen_wrap(void) 1946 { 1947 UpdateAttrs(normal); 1948 #if NCURSES_EXT_FUNCS 1949 if (SP->_coloron 1950 && !SP->_default_color) { 1951 static const NCURSES_CH_T blank = NewChar(BLANK_TEXT); 1952 SP->_default_color = TRUE; 1953 _nc_do_color(-1, 0, FALSE, _nc_outch); 1954 SP->_default_color = FALSE; 1955 1956 mvcur(SP->_cursrow, SP->_curscol, screen_lines - 1, 0); 1957 1958 ClrToEOL(blank, TRUE); 1959 } 1960 #endif 1961 if (SP->_color_defs) { 1962 _nc_reset_colors(); 1963 } 1964 } 1965 1966 #if USE_XMC_SUPPORT 1967 NCURSES_EXPORT(void) 1968 _nc_do_xmc_glitch(attr_t previous) 1969 { 1970 attr_t chg = XMC_CHANGES(previous ^ AttrOf(SCREEN_ATTRS(SP))); 1971 1972 while (chg != 0) { 1973 if (chg & 1) { 1974 SP->_curscol += magic_cookie_glitch; 1975 if (SP->_curscol >= SP->_columns) 1976 wrap_cursor(); 1977 TR(TRACE_UPDATE, ("bumped to %d,%d after cookie", SP->_cursrow, SP->_curscol)); 1978 } 1979 chg >>= 1; 1980 } 1981 } 1982 #endif /* USE_XMC_SUPPORT */ 1983