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