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