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