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