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