xref: /openbsd/lib/libcurses/tty/tty_update.c (revision 49da2d28)
1 /*	$OpenBSD: tty_update.c,v 1.15 2001/02/22 04:16:35 millert Exp $	*/
2 
3 /****************************************************************************
4  * Copyright (c) 1998,1999,2000,2001 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.151 2001/02/03 23:41:55 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     char *s;
118     int y, x;
119 
120     if (!_nc_tracing || (expected_y < 0 && expected_x < 0))
121 	return;
122 
123     _nc_flush();
124     memset(buf, '\0', sizeof(buf));
125     putp("\033[6n");		/* only works on ANSI-compatibles */
126     _nc_flush();
127     *(s = buf) = 0;
128     do {
129 	int ask = sizeof(buf) - 1 - (s - buf);
130 	int got = read(0, s, ask);
131 	if (got == 0)
132 	    break;
133 	s += got;
134     } while (strchr(buf, 'R') == 0);
135     _tracef("probe returned %s", _nc_visbuf(buf));
136 
137     /* try to interpret as a position report */
138     if (sscanf(buf, "\033[%d;%dR", &y, &x) != 2) {
139 	_tracef("position probe failed in %s", legend);
140     } else {
141 	if (expected_x < 0)
142 	    expected_x = x - 1;
143 	if (expected_y < 0)
144 	    expected_y = y - 1;
145 	if (y - 1 != expected_y || x - 1 != expected_x) {
146 	    beep();
147 	    tputs(tparm("\033[%d;%dH", expected_y + 1, expected_x + 1), 1, _nc_outch);
148 	    _tracef("position seen (%d, %d) doesn't match expected one (%d, %d) in %s",
149 		    y - 1, x - 1, expected_y, expected_x, legend);
150 	} else {
151 	    _tracef("position matches OK in %s", legend);
152 	}
153     }
154 }
155 #else
156 #define position_check(expected_y, expected_x, legend)	/* nothing */
157 #endif /* POSITION_DEBUG */
158 
159 /****************************************************************************
160  *
161  * Optimized update code
162  *
163  ****************************************************************************/
164 
165 static inline void
166 GoTo(int const row, int const col)
167 {
168     chtype oldattr = SP->_current_attr;
169 
170     TR(TRACE_MOVE, ("GoTo(%d, %d) from (%d, %d)",
171 		    row, col, SP->_cursrow, SP->_curscol));
172 
173     position_check(SP->_cursrow, SP->_curscol, "GoTo");
174 
175     /*
176      * Force restore even if msgr is on when we're in an alternate
177      * character set -- these have a strong tendency to screw up the
178      * CR & LF used for local character motions!
179      */
180     if ((oldattr & A_ALTCHARSET)
181 	|| (oldattr && !move_standout_mode)) {
182 	TR(TRACE_CHARPUT, ("turning off (%#lx) %s before move",
183 			   oldattr, _traceattr(oldattr)));
184 	vidattr(A_NORMAL);
185     }
186 
187     mvcur(SP->_cursrow, SP->_curscol, row, col);
188     SP->_cursrow = row;
189     SP->_curscol = col;
190     position_check(SP->_cursrow, SP->_curscol, "GoTo2");
191 }
192 
193 static inline void
194 PutAttrChar(chtype ch)
195 {
196     int data;
197 
198     if (tilde_glitch && (TextOf(ch) == '~'))
199 	ch = ('`' | AttrOf(ch));
200 
201     TR(TRACE_CHARPUT, ("PutAttrChar(%s) at (%d, %d)",
202 		       _tracechtype(ch),
203 		       SP->_cursrow, SP->_curscol));
204     UpdateAttrs(ch);
205     data = TextOf(ch);
206     if (SP->_outch != 0) {
207 	SP->_outch(data);
208     } else {
209 	putc(data, SP->_ofp);	/* macro's fastest... */
210 #ifdef TRACE
211 	_nc_outchars++;
212 #endif /* TRACE */
213     }
214     SP->_curscol++;
215     if (char_padding) {
216 	TPUTS_TRACE("char_padding");
217 	putp(char_padding);
218     }
219 }
220 
221 static bool
222 check_pending(void)
223 /* check for pending input */
224 {
225     bool have_pending = FALSE;
226 
227     /*
228      * Only carry out this check when the flag is zero, otherwise we'll
229      * have the refreshing slow down drastically (or stop) if there's an
230      * unread character available.
231      */
232     if (SP->_fifohold != 0)
233 	return FALSE;
234 
235     if (SP->_checkfd >= 0) {
236 #if USE_FUNC_POLL
237 	struct pollfd fds[1];
238 	fds[0].fd = SP->_checkfd;
239 	fds[0].events = POLLIN;
240 	if (poll(fds, 1, 0) > 0) {
241 	    have_pending = TRUE;
242 	}
243 #elif defined(__BEOS__)
244 	/*
245 	 * BeOS's select() is declared in socket.h, so the configure script does
246 	 * not see it.  That's just as well, since that function works only for
247 	 * sockets.  This (using snooze and ioctl) was distilled from Be's patch
248 	 * for ncurses which uses a separate thread to simulate select().
249 	 *
250 	 * FIXME: the return values from the ioctl aren't very clear if we get
251 	 * interrupted.
252 	 */
253 	int n = 0;
254 	int howmany = ioctl(0, 'ichr', &n);
255 	if (howmany >= 0 && n > 0) {
256 	    have_pending = TRUE;
257 	}
258 #elif HAVE_SELECT
259 	fd_set *fdset;
260 	struct timeval ktimeout;
261 
262 	ktimeout.tv_sec =
263 	    ktimeout.tv_usec = 0;
264 
265 	fdset = calloc(howmany(SP->_checkfd + 1, NFDBITS), sizeof(fd_mask));
266 	if (fdset != NULL) {
267 	    FD_SET(SP->_checkfd, fdset);
268 	    if (select(SP->_checkfd + 1, fdset, NULL, NULL, &ktimeout) != 0) {
269 		have_pending = TRUE;
270 	    }
271 	    free(fdset);
272 	}
273 #endif
274     }
275     if (have_pending) {
276 	SP->_fifohold = 5;
277 	_nc_flush();
278     }
279     return FALSE;
280 }
281 
282 /* put char at lower right corner */
283 static void
284 PutCharLR(chtype const ch)
285 {
286     if (!auto_right_margin) {
287 	/* we can put the char directly */
288 	PutAttrChar(ch);
289     } else if (enter_am_mode && exit_am_mode) {
290 	/* we can suppress automargin */
291 	TPUTS_TRACE("exit_am_mode");
292 	putp(exit_am_mode);
293 
294 	PutAttrChar(ch);
295 	SP->_curscol--;
296 	position_check(SP->_cursrow, SP->_curscol, "exit_am_mode");
297 
298 	TPUTS_TRACE("enter_am_mode");
299 	putp(enter_am_mode);
300     } else if ((enter_insert_mode && exit_insert_mode)
301 	       || insert_character || parm_ich) {
302 	GoTo(screen_lines - 1, screen_columns - 2);
303 	PutAttrChar(ch);
304 	GoTo(screen_lines - 1, screen_columns - 2);
305 	InsStr(newscr->_line[screen_lines - 1].text + screen_columns - 2, 1);
306     }
307 }
308 
309 static void
310 wrap_cursor(void)
311 {
312     if (eat_newline_glitch) {
313 	/*
314 	 * xenl can manifest two different ways.  The vt100
315 	 * way is that, when you'd expect the cursor to wrap,
316 	 * it stays hung at the right margin (on top of the
317 	 * character just emitted) and doesn't wrap until the
318 	 * *next* graphic char is emitted.  The c100 way is
319 	 * to ignore LF received just after an am wrap.
320 	 *
321 	 * An aggressive way to handle this would be to
322 	 * emit CR/LF after the char and then assume the wrap
323 	 * is done, you're on the first position of the next
324 	 * line, and the terminal out of its weird state.
325 	 * Here it's safe to just tell the code that the
326 	 * cursor is in hyperspace and let the next mvcur()
327 	 * call straighten things out.
328 	 */
329 	SP->_curscol = -1;
330 	SP->_cursrow = -1;
331     } else if (auto_right_margin) {
332 	SP->_curscol = 0;
333 	SP->_cursrow++;
334     } else {
335 	SP->_curscol--;
336     }
337     position_check(SP->_cursrow, SP->_curscol, "wrap_cursor");
338 }
339 
340 static inline void
341 PutChar(chtype const ch)
342 /* insert character, handling automargin stuff */
343 {
344     if (SP->_cursrow == screen_lines - 1 && SP->_curscol == screen_columns - 1)
345 	PutCharLR(ch);
346     else
347 	PutAttrChar(ch);
348 
349     if (SP->_curscol >= screen_columns)
350 	wrap_cursor();
351 
352     position_check(SP->_cursrow, SP->_curscol, "PutChar");
353 }
354 
355 /*
356  * Check whether the given character can be output by clearing commands.  This
357  * includes test for being a space and not including any 'bad' attributes, such
358  * as A_REVERSE.  All attribute flags which don't affect appearance of a space
359  * or can be output by clearing (A_COLOR in case of bce-terminal) are excluded.
360  */
361 static inline bool
362 can_clear_with(chtype ch)
363 {
364     if (!back_color_erase && SP->_coloron) {
365 	if (ch & A_COLOR)
366 	    return FALSE;
367 #if NCURSES_EXT_FUNCS
368 	if (!SP->_default_color)
369 	    return FALSE;
370 	if (SP->_default_fg != C_MASK || SP->_default_bg != C_MASK)
371 	    return FALSE;
372 #endif
373     }
374     return ((ch & ~(NONBLANK_ATTR | A_COLOR)) == BLANK);
375 }
376 
377 /*
378  * Issue a given span of characters from an array.
379  * Must be functionally equivalent to:
380  *	for (i = 0; i < num; i++)
381  *	    PutChar(ntext[i]);
382  * but can leave the cursor positioned at the middle of the interval.
383  *
384  * Returns: 0 - cursor is at the end of interval
385  *	    1 - cursor is somewhere in the middle
386  *
387  * This code is optimized using ech and rep.
388  */
389 static int
390 EmitRange(const chtype * ntext, int num)
391 {
392     int i;
393 
394     if (erase_chars || repeat_char) {
395 	while (num > 0) {
396 	    int runcount;
397 	    chtype ntext0;
398 
399 	    while (num > 1 && ntext[0] != ntext[1]) {
400 		PutChar(ntext[0]);
401 		ntext++;
402 		num--;
403 	    }
404 	    ntext0 = ntext[0];
405 	    if (num == 1) {
406 		PutChar(ntext0);
407 		return 0;
408 	    }
409 	    runcount = 2;
410 
411 	    while (runcount < num && ntext[runcount] == ntext0)
412 		runcount++;
413 
414 	    /*
415 	     * The cost expression in the middle isn't exactly right.
416 	     * _cup_ch_cost is an upper bound on the cost for moving to the
417 	     * end of the erased area, but not the cost itself (which we
418 	     * can't compute without emitting the move).  This may result
419 	     * in erase_chars not getting used in some situations for
420 	     * which it would be marginally advantageous.
421 	     */
422 	    if (erase_chars
423 		&& runcount > SP->_ech_cost + SP->_cup_ch_cost
424 		&& can_clear_with(ntext0)) {
425 		UpdateAttrs(ntext0);
426 		putp(tparm(erase_chars, runcount));
427 
428 		/*
429 		 * If this is the last part of the given interval,
430 		 * don't bother moving cursor, since it can be the
431 		 * last update on the line.
432 		 */
433 		if (runcount < num) {
434 		    GoTo(SP->_cursrow, SP->_curscol + runcount);
435 		} else {
436 		    return 1;	/* cursor stays in the middle */
437 		}
438 	    } else if (repeat_char && runcount > SP->_rep_cost) {
439 		bool wrap_possible = (SP->_curscol + runcount >= screen_columns);
440 		int rep_count = runcount;
441 
442 		if (wrap_possible)
443 		    rep_count--;
444 
445 		UpdateAttrs(ntext0);
446 		putp(tparm(repeat_char, TextOf(ntext0), rep_count));
447 		SP->_curscol += rep_count;
448 
449 		if (wrap_possible)
450 		    PutChar(ntext0);
451 	    } else {
452 		for (i = 0; i < runcount; i++)
453 		    PutChar(ntext[i]);
454 	    }
455 	    ntext += runcount;
456 	    num -= runcount;
457 	}
458 	return 0;
459     }
460 
461     for (i = 0; i < num; i++)
462 	PutChar(ntext[i]);
463     return 0;
464 }
465 
466 /*
467  * Output the line in the given range [first .. last]
468  *
469  * If there's a run of identical characters that's long enough to justify
470  * cursor movement, use that also.
471  *
472  * Returns: same as EmitRange
473  */
474 static int
475 PutRange(
476 	    const chtype * otext,
477 	    const chtype * ntext,
478 	    int row,
479 	    int first, int last)
480 {
481     int j, run;
482 
483     TR(TRACE_CHARPUT, ("PutRange(%p, %p, %d, %d, %d)",
484 		       otext, ntext, row, first, last));
485 
486     if (otext != ntext
487 	&& (last - first + 1) > SP->_inline_cost) {
488 	for (j = first, run = 0; j <= last; j++) {
489 	    if (otext[j] == ntext[j]) {
490 		run++;
491 	    } else {
492 		if (run > SP->_inline_cost) {
493 		    int before_run = (j - run);
494 		    EmitRange(ntext + first, before_run - first);
495 		    GoTo(row, first = j);
496 		}
497 		run = 0;
498 	    }
499 	}
500     }
501     return EmitRange(ntext + first, last - first + 1);
502 }
503 
504 /* leave unbracketed here so 'indent' works */
505 #define MARK_NOCHANGE(win,row) \
506 		win->_line[row].firstchar = _NOCHANGE; \
507 		win->_line[row].lastchar = _NOCHANGE; \
508 		if_USE_SCROLL_HINTS(win->_line[row].oldindex = row)
509 
510 NCURSES_EXPORT(int)
511 doupdate(void)
512 {
513     int i;
514     int nonempty;
515 #if USE_TRACE_TIMES
516     struct tms before, after;
517 #endif /* USE_TRACE_TIMES */
518 
519     T((T_CALLED("doupdate()")));
520 
521 #ifdef TRACE
522     if (_nc_tracing & TRACE_UPDATE) {
523 	if (curscr->_clear)
524 	    _tracef("curscr is clear");
525 	else
526 	    _tracedump("curscr", curscr);
527 	_tracedump("newscr", newscr);
528     }
529 #endif /* TRACE */
530 
531     _nc_signal_handler(FALSE);
532 
533     if (SP->_fifohold)
534 	SP->_fifohold--;
535 
536 #if USE_SIZECHANGE
537     if (SP->_endwin || SP->_sig_winch) {
538 	/*
539 	 * This is a transparent extension:  XSI does not address it,
540 	 * and applications need not know that ncurses can do it.
541 	 *
542 	 * Check if the terminal size has changed while curses was off
543 	 * (this can happen in an xterm, for example), and resize the
544 	 * ncurses data structures accordingly.
545 	 */
546 	_nc_update_screensize();
547     }
548 #endif
549 
550     if (SP->_endwin) {
551 
552 	T(("coming back from shell mode"));
553 	reset_prog_mode();
554 
555 	_nc_mvcur_resume();
556 	_nc_screen_resume();
557 	SP->_mouse_resume(SP);
558 
559 	SP->_endwin = FALSE;
560     }
561 #if USE_TRACE_TIMES
562     /* zero the metering machinery */
563     _nc_outchars = 0;
564     (void) times(&before);
565 #endif /* USE_TRACE_TIMES */
566 
567     /*
568      * This is the support for magic-cookie terminals.  The
569      * theory: we scan the virtual screen looking for attribute
570      * turnons.  Where we find one, check to make sure it's
571      * realizable by seeing if the required number of
572      * un-attributed blanks are present before and after the
573      * attributed range; try to shift the range boundaries over
574      * blanks (not changing the screen display) so this becomes
575      * true.  If it is, shift the beginning attribute change
576      * appropriately (the end one, if we've gotten this far, is
577      * guaranteed room for its cookie). If not, nuke the added
578      * attributes out of the span.
579      */
580 #if USE_XMC_SUPPORT
581     if (magic_cookie_glitch > 0) {
582 	int j, k;
583 	attr_t rattr = A_NORMAL;
584 
585 	for (i = 0; i < screen_lines; i++) {
586 	    for (j = 0; j < screen_columns; j++) {
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 		TR(TRACE_ATTRS, ("At (%d, %d): from %s...", i, j, _traceattr(rattr)));
597 		TR(TRACE_ATTRS, ("...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 		    newscr->_line[i].text[j] &= ~turnon;
608 		    continue;
609 		}
610 
611 		/* check that there's enough room at start of span */
612 		for (k = 1; k <= magic_cookie_glitch; k++) {
613 		    if (j - k < 0
614 			|| TextOf(newscr->_line[i].text[j - k]) != ' '
615 			|| !SAFE(AttrOf(newscr->_line[i].text[j - k])))
616 			failed = TRUE;
617 		}
618 		if (!failed) {
619 		    bool end_onscreen = FALSE;
620 		    int m, n = j;
621 
622 		    /* find end of span, if it's onscreen */
623 		    for (m = i; m < screen_lines; m++) {
624 			for (; n < screen_columns; n++) {
625 			    if (AttrOf(newscr->_line[m].text[n]) == rattr) {
626 				end_onscreen = TRUE;
627 				TR(TRACE_ATTRS,
628 				   ("Range attributed with %s ends at (%d, %d)",
629 				    _traceattr(turnon), m, n));
630 				goto foundit;
631 			    }
632 			}
633 			n = 0;
634 		    }
635 		    TR(TRACE_ATTRS,
636 		       ("Range attributed with %s ends offscreen",
637 			_traceattr(turnon)));
638 		  foundit:;
639 
640 		    if (end_onscreen) {
641 			chtype *lastline = newscr->_line[m].text;
642 
643 			/*
644 			 * If there are safely-attributed blanks at the
645 			 * end of the range, shorten the range.  This will
646 			 * help ensure that there is enough room at end
647 			 * of span.
648 			 */
649 			while (n >= 0
650 			       && TextOf(lastline[n]) == ' '
651 			       && SAFE(AttrOf(lastline[n])))
652 			    lastline[n--] &= ~turnon;
653 
654 			/* check that there's enough room at end of span */
655 			for (k = 1; k <= magic_cookie_glitch; k++)
656 			    if (n + k >= screen_columns
657 				|| TextOf(lastline[n + k]) != ' '
658 				|| !SAFE(AttrOf(lastline[n + k])))
659 				failed = TRUE;
660 		    }
661 		}
662 
663 		if (failed) {
664 		    int p, q = j;
665 
666 		    TR(TRACE_ATTRS,
667 		       ("Clearing %s beginning at (%d, %d)",
668 			_traceattr(turnon), i, j));
669 
670 		    /* turn off new attributes over span */
671 		    for (p = i; p < screen_lines; p++) {
672 			for (; q < screen_columns; q++) {
673 			    if (AttrOf(newscr->_line[p].text[q]) == rattr)
674 				goto foundend;
675 			    newscr->_line[p].text[q] &= ~turnon;
676 			}
677 			q = 0;
678 		    }
679 		  foundend:;
680 		} else {
681 		    TR(TRACE_ATTRS,
682 		       ("Cookie space for %s found before (%d, %d)",
683 			_traceattr(turnon), i, j));
684 
685 		    /*
686 		     * back up the start of range so there's room
687 		     * for cookies before the first nonblank character
688 		     */
689 		    for (k = 1; k <= magic_cookie_glitch; k++)
690 			newscr->_line[i].text[j - k] |= turnon;
691 		}
692 
693 		rattr = AttrOf(newscr->_line[i].text[j]);
694 	    }
695 	}
696 
697 #ifdef TRACE
698 	/* show altered highlights after magic-cookie check */
699 	if (_nc_tracing & TRACE_UPDATE) {
700 	    _tracef("After magic-cookie check...");
701 	    _tracedump("newscr", newscr);
702 	}
703 #endif /* TRACE */
704     }
705 #endif /* USE_XMC_SUPPORT */
706 
707     nonempty = 0;
708     if (curscr->_clear || newscr->_clear) {	/* force refresh ? */
709 	TR(TRACE_UPDATE, ("clearing and updating from scratch"));
710 	ClrUpdate();
711 	curscr->_clear = FALSE;	/* reset flag */
712 	newscr->_clear = FALSE;	/* reset flag */
713     } else {
714 	int changedlines = CHECK_INTERVAL;
715 
716 	if (check_pending())
717 	    goto cleanup;
718 
719 	nonempty = min(screen_lines, newscr->_maxy + 1);
720 
721 	if (SP->_scrolling) {
722 	    _nc_scroll_optimize();
723 	}
724 
725 	nonempty = ClrBottom(nonempty);
726 
727 	TR(TRACE_UPDATE, ("Transforming lines, nonempty %d", nonempty));
728 	for (i = 0; i < nonempty; i++) {
729 	    /*
730 	     * Here is our line-breakout optimization.
731 	     */
732 	    if (changedlines == CHECK_INTERVAL) {
733 		if (check_pending())
734 		    goto cleanup;
735 		changedlines = 0;
736 	    }
737 
738 	    /*
739 	     * newscr->line[i].firstchar is normally set
740 	     * by wnoutrefresh.  curscr->line[i].firstchar
741 	     * is normally set by _nc_scroll_window in the
742 	     * vertical-movement optimization code,
743 	     */
744 	    if (newscr->_line[i].firstchar != _NOCHANGE
745 		|| curscr->_line[i].firstchar != _NOCHANGE) {
746 		TransformLine(i);
747 		changedlines++;
748 	    }
749 
750 	    /* mark line changed successfully */
751 	    if (i <= newscr->_maxy) {
752 		MARK_NOCHANGE(newscr, i);
753 	    }
754 	    if (i <= curscr->_maxy) {
755 		MARK_NOCHANGE(curscr, i);
756 	    }
757 	}
758     }
759 
760     /* put everything back in sync */
761     for (i = nonempty; i <= newscr->_maxy; i++) {
762 	MARK_NOCHANGE(newscr, i);
763     }
764     for (i = nonempty; i <= curscr->_maxy; i++) {
765 	MARK_NOCHANGE(curscr, i);
766     }
767 
768     if (!newscr->_leaveok) {
769 	curscr->_curx = newscr->_curx;
770 	curscr->_cury = newscr->_cury;
771 
772 	GoTo(curscr->_cury, curscr->_curx);
773     }
774 
775   cleanup:
776     /*
777      * Keep the physical screen in normal mode in case we get other
778      * processes writing to the screen.
779      */
780     UpdateAttrs(A_NORMAL);
781 
782     _nc_flush();
783     curscr->_attrs = newscr->_attrs;
784 
785 #if USE_TRACE_TIMES
786     (void) times(&after);
787     TR(TRACE_TIMES,
788        ("Update cost: %ld chars, %ld clocks system time, %ld clocks user time",
789 	_nc_outchars,
790 	after.tms_stime - before.tms_stime,
791 	after.tms_utime - before.tms_utime));
792 #endif /* USE_TRACE_TIMES */
793 
794     _nc_signal_handler(TRUE);
795 
796     returnCode(OK);
797 }
798 
799 /*
800  *	ClrBlank(win)
801  *
802  *	Returns the attributed character that corresponds to the "cleared"
803  *	screen.  If the terminal has the back-color-erase feature, this will be
804  *	colored according to the wbkgd() call.
805  *
806  *	We treat 'curscr' specially because it isn't supposed to be set directly
807  *	in the wbkgd() call.  Assume 'stdscr' for this case.
808  */
809 #define BCE_ATTRS (A_NORMAL|A_COLOR)
810 #define BCE_BKGD(win) (((win) == curscr ? stdscr : (win))->_bkgd)
811 
812 static inline chtype
813 ClrBlank(WINDOW *win)
814 {
815     chtype blank = BLANK;
816     if (back_color_erase)
817 	blank |= (BCE_BKGD(win) & BCE_ATTRS);
818     return blank;
819 }
820 
821 /*
822 **	ClrUpdate()
823 **
824 **	Update by clearing and redrawing the entire screen.
825 **
826 */
827 
828 static void
829 ClrUpdate(void)
830 {
831     int i;
832     chtype blank = ClrBlank(stdscr);
833     int nonempty = min(screen_lines, newscr->_maxy + 1);
834 
835     TR(TRACE_UPDATE, ("ClrUpdate() called"));
836 
837     ClearScreen(blank);
838 
839     TR(TRACE_UPDATE, ("updating screen from scratch"));
840 
841     nonempty = ClrBottom(nonempty);
842 
843     for (i = 0; i < nonempty; i++)
844 	TransformLine(i);
845 }
846 
847 /*
848 **	ClrToEOL(blank)
849 **
850 **	Clear to end of current line, starting at the cursor position
851 */
852 
853 static void
854 ClrToEOL(chtype blank, bool needclear)
855 {
856     int j;
857 
858     if (curscr != 0
859 	&& SP->_cursrow >= 0) {
860 	for (j = SP->_curscol; j < screen_columns; j++) {
861 	    if (j >= 0) {
862 		chtype *cp = &(curscr->_line[SP->_cursrow].text[j]);
863 
864 		if (*cp != blank) {
865 		    *cp = blank;
866 		    needclear = TRUE;
867 		}
868 	    }
869 	}
870     } else {
871 	needclear = TRUE;
872     }
873 
874     if (needclear) {
875 	UpdateAttrs(blank);
876 	TPUTS_TRACE("clr_eol");
877 	if (SP->_el_cost > (screen_columns - SP->_curscol)) {
878 	    int count = (screen_columns - SP->_curscol);
879 	    while (count-- > 0)
880 		PutChar(blank);
881 	} else {
882 	    putp(clr_eol);
883 	}
884     }
885 }
886 
887 /*
888 **	ClrToEOS(blank)
889 **
890 **	Clear to end of screen, starting at the cursor position
891 */
892 
893 static void
894 ClrToEOS(chtype blank)
895 {
896     int row, col;
897 
898     row = SP->_cursrow;
899     col = SP->_curscol;
900 
901     UpdateAttrs(blank);
902     TPUTS_TRACE("clr_eos");
903     tputs(clr_eos, screen_lines - row, _nc_outch);
904 
905     while (col < screen_columns)
906 	curscr->_line[row].text[col++] = blank;
907 
908     for (row++; row < screen_lines; row++) {
909 	for (col = 0; col < screen_columns; col++)
910 	    curscr->_line[row].text[col] = blank;
911     }
912 }
913 
914 /*
915  *	ClrBottom(total)
916  *
917  *	Test if clearing the end of the screen would satisfy part of the
918  *	screen-update.  Do this by scanning backwards through the lines in the
919  *	screen, checking if each is blank, and one or more are changed.
920  */
921 static int
922 ClrBottom(int total)
923 {
924     int row;
925     int col;
926     int top = total;
927     int last = min(screen_columns, newscr->_maxx + 1);
928     chtype blank = ClrBlank(stdscr);
929     bool ok;
930 
931     if (clr_eos && can_clear_with(blank)) {
932 
933 	for (row = total - 1; row >= 0; row--) {
934 	    for (col = 0, ok = TRUE; ok && col < last; col++) {
935 		ok = (newscr->_line[row].text[col] == blank);
936 	    }
937 	    if (!ok)
938 		break;
939 
940 	    for (col = 0; ok && col < last; col++) {
941 		ok = (curscr->_line[row].text[col] == blank);
942 	    }
943 	    if (!ok)
944 		top = row;
945 	}
946 
947 	/* don't use clr_eos for just one line if clr_eol available */
948 	if (top < total - 1 || (top < total && !clr_eol && !clr_bol)) {
949 	    GoTo(top, 0);
950 	    ClrToEOS(blank);
951 	    total = top;
952 	    if (SP->oldhash && SP->newhash) {
953 		for (row = top; row < screen_lines; row++)
954 		    SP->oldhash[row] = SP->newhash[row];
955 	    }
956 	}
957     }
958     return total;
959 }
960 
961 /*
962 **	TransformLine(lineno)
963 **
964 **	Transform the given line in curscr to the one in newscr, using
965 **	Insert/Delete Character if _nc_idcok && has_ic().
966 **
967 **		firstChar = position of first different character in line
968 **		oLastChar = position of last different character in old line
969 **		nLastChar = position of last different character in new line
970 **
971 **		move to firstChar
972 **		overwrite chars up to min(oLastChar, nLastChar)
973 **		if oLastChar < nLastChar
974 **			insert newLine[oLastChar+1..nLastChar]
975 **		else
976 **			delete oLastChar - nLastChar spaces
977 */
978 
979 static void
980 TransformLine(int const lineno)
981 {
982     int firstChar, oLastChar, nLastChar;
983     chtype *newLine = newscr->_line[lineno].text;
984     chtype *oldLine = curscr->_line[lineno].text;
985     int n;
986     bool attrchanged = FALSE;
987 
988     TR(TRACE_UPDATE, ("TransformLine(%d) called", lineno));
989 
990     /* copy new hash value to old one */
991     if (SP->oldhash && SP->newhash)
992 	SP->oldhash[lineno] = SP->newhash[lineno];
993 
994 #define ColorOf(n) ((n) & A_COLOR)
995 #define unColor(n) ((n) & ALL_BUT_COLOR)
996     /*
997      * If we have colors, there is the possibility of having two color pairs
998      * that display as the same colors.  For instance, Lynx does this.  Check
999      * for this case, and update the old line with the new line's colors when
1000      * they are equivalent.
1001      */
1002     if (SP->_coloron) {
1003 	chtype oldColor;
1004 	chtype newColor;
1005 	int oldPair;
1006 	int newPair;
1007 
1008 	for (n = 0; n < screen_columns; n++) {
1009 	    if (newLine[n] != oldLine[n]) {
1010 		oldColor = ColorOf(oldLine[n]);
1011 		newColor = ColorOf(newLine[n]);
1012 		if (oldColor != newColor
1013 		    && unColor(oldLine[n]) == unColor(newLine[n])) {
1014 		    oldPair = PAIR_NUMBER(oldColor);
1015 		    newPair = PAIR_NUMBER(newColor);
1016 		    if (oldPair < COLOR_PAIRS
1017 			&& newPair < COLOR_PAIRS
1018 			&& SP->_color_pairs[oldPair] == SP->_color_pairs[newPair]) {
1019 			oldLine[n] &= ~A_COLOR;
1020 			oldLine[n] |= ColorOf(newLine[n]);
1021 		    }
1022 		}
1023 	    }
1024 	}
1025     }
1026 
1027     if (ceol_standout_glitch && clr_eol) {
1028 	firstChar = 0;
1029 	while (firstChar < screen_columns) {
1030 	    if (AttrOf(newLine[firstChar]) != AttrOf(oldLine[firstChar]))
1031 		attrchanged = TRUE;
1032 	    firstChar++;
1033 	}
1034     }
1035 
1036     firstChar = 0;
1037 
1038     if (attrchanged) {		/* we may have to disregard the whole line */
1039 	GoTo(lineno, firstChar);
1040 	ClrToEOL(ClrBlank(curscr), FALSE);
1041 	PutRange(oldLine, newLine, lineno, 0, (screen_columns - 1));
1042 #if USE_XMC_SUPPORT
1043 
1044 #define NEW(r,c) newscr->_line[r].text[c]
1045 #define xmc_turn_on(a,b) ((((a)^(b)) & ~(a) & SP->_xmc_triggers) != 0)
1046 #define xmc_turn_off(a,b) xmc_turn_on(b,a)
1047 
1048 	/*
1049 	 * This is a very simple loop to paint characters which may have the
1050 	 * magic cookie glitch embedded.  It doesn't know much about video
1051 	 * attributes which are continued from one line to the next.  It
1052 	 * assumes that we have filtered out requests for attribute changes
1053 	 * that do not get mapped to blank positions.
1054 	 *
1055 	 * FIXME: we are not keeping track of where we put the cookies, so this
1056 	 * will work properly only once, since we may overwrite a cookie in a
1057 	 * following operation.
1058 	 */
1059     } else if (magic_cookie_glitch > 0) {
1060 	GoTo(lineno, firstChar);
1061 	for (n = 0; n < screen_columns; n++) {
1062 	    int m = n + magic_cookie_glitch;
1063 
1064 	    /* check for turn-on:
1065 	     * If we are writing an attributed blank, where the
1066 	     * previous cell is not attributed.
1067 	     */
1068 	    if (TextOf(newLine[n]) == ' '
1069 		&& ((n > 0
1070 		     && xmc_turn_on(newLine[n - 1], newLine[n]))
1071 		    || (n == 0
1072 			&& lineno > 0
1073 			&& xmc_turn_on(NEW(lineno - 1, screen_columns - 1),
1074 				       newLine[n])))) {
1075 		n = m;
1076 	    }
1077 
1078 	    PutChar(newLine[n]);
1079 
1080 	    /* check for turn-off:
1081 	     * If we are writing an attributed non-blank, where the
1082 	     * next cell is blank, and not attributed.
1083 	     */
1084 	    if (TextOf(newLine[n]) != ' '
1085 		&& ((n + 1 < screen_columns
1086 		     && xmc_turn_off(newLine[n], newLine[n + 1]))
1087 		    || (n + 1 >= screen_columns
1088 			&& lineno + 1 < screen_lines
1089 			&& xmc_turn_off(newLine[n], NEW(lineno + 1, 0))))) {
1090 		n = m;
1091 	    }
1092 
1093 	}
1094 #undef NEW
1095 #endif
1096     } else {
1097 	chtype blank;
1098 
1099 	/* find the first differing character */
1100 	while (firstChar < screen_columns &&
1101 	       newLine[firstChar] == oldLine[firstChar])
1102 	    firstChar++;
1103 
1104 	/* if there wasn't one, we're done */
1105 	if (firstChar >= screen_columns)
1106 	    return;
1107 
1108 	/* it may be cheap to clear leading whitespace with clr_bol */
1109 	if (clr_bol && can_clear_with(blank = newLine[0])) {
1110 	    int oFirstChar, nFirstChar;
1111 
1112 	    for (oFirstChar = 0; oFirstChar < screen_columns; oFirstChar++)
1113 		if (oldLine[oFirstChar] != blank)
1114 		    break;
1115 	    for (nFirstChar = 0; nFirstChar < screen_columns; nFirstChar++)
1116 		if (newLine[nFirstChar] != blank)
1117 		    break;
1118 
1119 	    if (nFirstChar > oFirstChar + SP->_el1_cost) {
1120 		if (nFirstChar >= screen_columns && SP->_el_cost <= SP->_el1_cost) {
1121 		    GoTo(lineno, 0);
1122 		    UpdateAttrs(blank);
1123 		    TPUTS_TRACE("clr_eol");
1124 		    putp(clr_eol);
1125 		} else {
1126 		    GoTo(lineno, nFirstChar - 1);
1127 		    UpdateAttrs(blank);
1128 		    TPUTS_TRACE("clr_bol");
1129 		    putp(clr_bol);
1130 		}
1131 
1132 		while (firstChar < nFirstChar)
1133 		    oldLine[firstChar++] = blank;
1134 
1135 		if (firstChar >= screen_columns)
1136 		    return;
1137 	    }
1138 	}
1139 
1140 	blank = newLine[screen_columns - 1];
1141 
1142 	if (!can_clear_with(blank)) {
1143 	    /* find the last differing character */
1144 	    nLastChar = screen_columns - 1;
1145 
1146 	    while (nLastChar > firstChar
1147 		   && newLine[nLastChar] == oldLine[nLastChar])
1148 		nLastChar--;
1149 
1150 	    if (nLastChar >= firstChar) {
1151 		GoTo(lineno, firstChar);
1152 		PutRange(oldLine, newLine, lineno, firstChar, nLastChar);
1153 		memcpy(oldLine + firstChar,
1154 		       newLine + firstChar,
1155 		       (nLastChar - firstChar + 1) * sizeof(chtype));
1156 	    }
1157 	    return;
1158 	}
1159 
1160 	/* find last non-blank character on old line */
1161 	oLastChar = screen_columns - 1;
1162 	while (oLastChar > firstChar && oldLine[oLastChar] == blank)
1163 	    oLastChar--;
1164 
1165 	/* find last non-blank character on new line */
1166 	nLastChar = screen_columns - 1;
1167 	while (nLastChar > firstChar && newLine[nLastChar] == blank)
1168 	    nLastChar--;
1169 
1170 	if ((nLastChar == firstChar)
1171 	    && (SP->_el_cost < (oLastChar - nLastChar))) {
1172 	    GoTo(lineno, firstChar);
1173 	    if (newLine[firstChar] != blank)
1174 		PutChar(newLine[firstChar]);
1175 	    ClrToEOL(blank, FALSE);
1176 	} else if ((nLastChar != oLastChar)
1177 		   && (newLine[nLastChar] != oldLine[oLastChar]
1178 		       || !(_nc_idcok && has_ic()))) {
1179 	    GoTo(lineno, firstChar);
1180 	    if ((oLastChar - nLastChar) > SP->_el_cost) {
1181 		if (PutRange(oldLine, newLine, lineno, firstChar, nLastChar))
1182 		    GoTo(lineno, nLastChar + 1);
1183 		ClrToEOL(blank, FALSE);
1184 	    } else {
1185 		n = max(nLastChar, oLastChar);
1186 		PutRange(oldLine, newLine, lineno, firstChar, n);
1187 	    }
1188 	} else {
1189 	    int nLastNonblank = nLastChar;
1190 	    int oLastNonblank = oLastChar;
1191 
1192 	    /* find the last characters that really differ */
1193 	    while (newLine[nLastChar] == oldLine[oLastChar]) {
1194 		if (nLastChar != 0
1195 		    && oLastChar != 0) {
1196 		    nLastChar--;
1197 		    oLastChar--;
1198 		} else {
1199 		    break;
1200 		}
1201 	    }
1202 
1203 	    n = min(oLastChar, nLastChar);
1204 	    if (n >= firstChar) {
1205 		GoTo(lineno, firstChar);
1206 		PutRange(oldLine, newLine, lineno, firstChar, n);
1207 	    }
1208 
1209 	    if (oLastChar < nLastChar) {
1210 		int m = max(nLastNonblank, oLastNonblank);
1211 		GoTo(lineno, n + 1);
1212 		if (InsCharCost(nLastChar - oLastChar)
1213 		    > (m - n)) {
1214 		    PutRange(oldLine, newLine, lineno, n + 1, m);
1215 		} else {
1216 		    InsStr(&newLine[n + 1], nLastChar - oLastChar);
1217 		}
1218 	    } else if (oLastChar > nLastChar) {
1219 		GoTo(lineno, n + 1);
1220 		if (DelCharCost(oLastChar - nLastChar)
1221 		    > SP->_el_cost + nLastNonblank - (n + 1)) {
1222 		    if (PutRange(oldLine, newLine, lineno,
1223 				 n + 1, nLastNonblank))
1224 			GoTo(lineno, nLastNonblank + 1);
1225 		    ClrToEOL(blank, FALSE);
1226 		} else {
1227 		    /*
1228 		     * The delete-char sequence will
1229 		     * effectively shift in blanks from the
1230 		     * right margin of the screen.  Ensure
1231 		     * that they are the right color by
1232 		     * setting the video attributes from
1233 		     * the last character on the row.
1234 		     */
1235 		    UpdateAttrs(blank);
1236 		    DelChar(oLastChar - nLastChar);
1237 		}
1238 	    }
1239 	}
1240     }
1241 
1242     /* update the code's internal representation */
1243     if (screen_columns > firstChar)
1244 	memcpy(oldLine + firstChar,
1245 	       newLine + firstChar,
1246 	       (screen_columns - firstChar) * sizeof(chtype));
1247 }
1248 
1249 /*
1250 **	ClearScreen(blank)
1251 **
1252 **	Clear the physical screen and put cursor at home
1253 **
1254 */
1255 
1256 static void
1257 ClearScreen(chtype blank)
1258 {
1259     int i, j;
1260     bool fast_clear = (clear_screen || clr_eos || clr_eol);
1261 
1262     TR(TRACE_UPDATE, ("ClearScreen() called"));
1263 
1264 #if NCURSES_EXT_FUNCS
1265     if (SP->_coloron
1266 	&& !SP->_default_color) {
1267 	_nc_do_color((int) COLOR_PAIR(SP->_current_attr), 0, FALSE, _nc_outch);
1268 	if (!back_color_erase) {
1269 	    fast_clear = FALSE;
1270 	}
1271     }
1272 #endif
1273 
1274     if (fast_clear) {
1275 	if (clear_screen) {
1276 	    UpdateAttrs(blank);
1277 	    TPUTS_TRACE("clear_screen");
1278 	    putp(clear_screen);
1279 	    SP->_cursrow = SP->_curscol = 0;
1280 	    position_check(SP->_cursrow, SP->_curscol, "ClearScreen");
1281 	} else if (clr_eos) {
1282 	    SP->_cursrow = SP->_curscol = -1;
1283 	    GoTo(0, 0);
1284 
1285 	    UpdateAttrs(blank);
1286 	    TPUTS_TRACE("clr_eos");
1287 	    putp(clr_eos);
1288 	} else if (clr_eol) {
1289 	    SP->_cursrow = SP->_curscol = -1;
1290 
1291 	    UpdateAttrs(blank);
1292 	    for (i = 0; i < screen_lines; i++) {
1293 		GoTo(i, 0);
1294 		TPUTS_TRACE("clr_eol");
1295 		putp(clr_eol);
1296 	    }
1297 	    GoTo(0, 0);
1298 	}
1299     } else {
1300 	UpdateAttrs(blank);
1301 	for (i = 0; i < screen_lines; i++) {
1302 	    GoTo(i, 0);
1303 	    for (j = 0; j < screen_columns; j++)
1304 		PutChar(blank);
1305 	}
1306 	GoTo(0, 0);
1307     }
1308 
1309     for (i = 0; i < screen_lines; i++) {
1310 	for (j = 0; j < screen_columns; j++)
1311 	    curscr->_line[i].text[j] = blank;
1312     }
1313 
1314     TR(TRACE_UPDATE, ("screen cleared"));
1315 }
1316 
1317 /*
1318 **	InsStr(line, count)
1319 **
1320 **	Insert the count characters pointed to by line.
1321 **
1322 */
1323 
1324 static void
1325 InsStr(chtype * line, int count)
1326 {
1327     TR(TRACE_UPDATE, ("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 		TPUTS_TRACE("insert_padding");
1347 		putp(insert_padding);
1348 	    }
1349 	    line++;
1350 	    count--;
1351 	}
1352 	TPUTS_TRACE("exit_insert_mode");
1353 	putp(exit_insert_mode);
1354     } else {
1355 	while (count) {
1356 	    TPUTS_TRACE("insert_character");
1357 	    putp(insert_character);
1358 	    PutAttrChar(*line);
1359 	    if (insert_padding) {
1360 		TPUTS_TRACE("insert_padding");
1361 		putp(insert_padding);
1362 	    }
1363 	    line++;
1364 	    count--;
1365 	}
1366     }
1367     position_check(SP->_cursrow, SP->_curscol, "InsStr");
1368 }
1369 
1370 /*
1371 **	DelChar(count)
1372 **
1373 **	Delete count characters at current position
1374 **
1375 */
1376 
1377 static void
1378 DelChar(int count)
1379 {
1380     int n;
1381 
1382     TR(TRACE_UPDATE, ("DelChar(%d) called, position = (%d,%d)", count,
1383 		      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 NCURSES_EXPORT(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 #if 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 #if 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 NCURSES_EXPORT(int)
1596 _nc_scrolln
1597 (int n, int top, int bot, int maxy)
1598 /* scroll region from top to bot by n lines */
1599 {
1600     chtype blank = ClrBlank(stdscr);
1601     int i;
1602     bool cursor_saved = FALSE;
1603     int res;
1604 
1605     TR(TRACE_MOVE, ("mvcur_scrolln(%d, %d, %d, %d)", n, top, bot, maxy));
1606 
1607 #if USE_XMC_SUPPORT
1608     /*
1609      * If we scroll, we might remove a cookie.
1610      */
1611     if (magic_cookie_glitch > 0) {
1612 	return (ERR);
1613     }
1614 #endif
1615 
1616     if (n > 0) {		/* scroll up (forward) */
1617 	/*
1618 	 * Explicitly clear if stuff pushed off top of region might
1619 	 * be saved by the terminal.
1620 	 */
1621 	res = scroll_csr_forward(n, top, bot, 0, maxy, blank);
1622 
1623 	if (res == ERR && change_scroll_region) {
1624 	    if ((((n == 1 && scroll_forward) || parm_index)
1625 		 && (SP->_cursrow == bot || SP->_cursrow == bot - 1))
1626 		&& save_cursor && restore_cursor) {
1627 		cursor_saved = TRUE;
1628 		TPUTS_TRACE("save_cursor");
1629 		tputs(save_cursor, 0, _nc_outch);
1630 	    }
1631 	    TPUTS_TRACE("change_scroll_region");
1632 	    tputs(tparm(change_scroll_region, top, bot), 0, _nc_outch);
1633 	    if (cursor_saved) {
1634 		TPUTS_TRACE("restore_cursor");
1635 		tputs(restore_cursor, 0, _nc_outch);
1636 	    } else {
1637 		SP->_cursrow = SP->_curscol = -1;
1638 	    }
1639 
1640 	    res = scroll_csr_forward(n, top, bot, top, bot, blank);
1641 
1642 	    TPUTS_TRACE("change_scroll_region");
1643 	    tputs(tparm(change_scroll_region, 0, maxy), 0, _nc_outch);
1644 	    SP->_cursrow = SP->_curscol = -1;
1645 	}
1646 
1647 	if (res == ERR && _nc_idlok)
1648 	    res = scroll_idl(n, top, bot - n + 1, blank);
1649 
1650 	/*
1651 	 * Clear the newly shifted-in text.
1652 	 */
1653 	if (res != ERR
1654 	    && (non_dest_scroll_region || (memory_below && bot == maxy))) {
1655 	    if (bot == maxy && clr_eos) {
1656 		GoTo(bot - n, 0);
1657 		ClrToEOS(BLANK);
1658 	    } else {
1659 		for (i = 0; i < n; i++) {
1660 		    GoTo(bot - i, 0);
1661 		    ClrToEOL(BLANK, FALSE);
1662 		}
1663 	    }
1664 	}
1665 
1666     } else {			/* (n < 0) - scroll down (backward) */
1667 	res = scroll_csr_backward(-n, top, bot, 0, maxy, blank);
1668 
1669 	if (res == ERR && change_scroll_region) {
1670 	    if (top != 0 && (SP->_cursrow == top || SP->_cursrow == top - 1)
1671 		&& save_cursor && restore_cursor) {
1672 		cursor_saved = TRUE;
1673 		TPUTS_TRACE("save_cursor");
1674 		tputs(save_cursor, 0, _nc_outch);
1675 	    }
1676 	    TPUTS_TRACE("change_scroll_region");
1677 	    tputs(tparm(change_scroll_region, top, bot), 0, _nc_outch);
1678 	    if (cursor_saved) {
1679 		TPUTS_TRACE("restore_cursor");
1680 		tputs(restore_cursor, 0, _nc_outch);
1681 	    } else {
1682 		SP->_cursrow = SP->_curscol = -1;
1683 	    }
1684 
1685 	    res = scroll_csr_backward(-n, top, bot, top, bot, blank);
1686 
1687 	    TPUTS_TRACE("change_scroll_region");
1688 	    tputs(tparm(change_scroll_region, 0, maxy), 0, _nc_outch);
1689 	    SP->_cursrow = SP->_curscol = -1;
1690 	}
1691 
1692 	if (res == ERR && _nc_idlok)
1693 	    res = scroll_idl(-n, bot + n + 1, top, blank);
1694 
1695 	/*
1696 	 * Clear the newly shifted-in text.
1697 	 */
1698 	if (res != ERR
1699 	    && (non_dest_scroll_region || (memory_above && top == 0))) {
1700 	    for (i = 0; i < -n; i++) {
1701 		GoTo(i + top, 0);
1702 		ClrToEOL(BLANK, FALSE);
1703 	    }
1704 	}
1705     }
1706 
1707     if (res == ERR)
1708 	return (ERR);
1709 
1710     _nc_scroll_window(curscr, n, top, bot, blank);
1711 
1712     /* shift hash values too - they can be reused */
1713     _nc_scroll_oldhash(n, top, bot);
1714 
1715     return (OK);
1716 }
1717 
1718 NCURSES_EXPORT(void)
1719 _nc_screen_resume(void)
1720 {
1721     /* make sure terminal is in a sane known state */
1722     SP->_current_attr = A_NORMAL;
1723     newscr->_clear = TRUE;
1724 
1725     if (SP->_coloron == TRUE && orig_pair)
1726 	putp(orig_pair);
1727     if (exit_attribute_mode)
1728 	putp(exit_attribute_mode);
1729     else {
1730 	/* turn off attributes */
1731 	if (exit_alt_charset_mode)
1732 	    putp(exit_alt_charset_mode);
1733 	if (exit_standout_mode)
1734 	    putp(exit_standout_mode);
1735 	if (exit_underline_mode)
1736 	    putp(exit_underline_mode);
1737     }
1738     if (exit_insert_mode)
1739 	putp(exit_insert_mode);
1740     if (enter_am_mode && exit_am_mode)
1741 	putp(auto_right_margin ? enter_am_mode : exit_am_mode);
1742 }
1743 
1744 NCURSES_EXPORT(void)
1745 _nc_screen_init(void)
1746 {
1747     _nc_screen_resume();
1748 }
1749 
1750 /* wrap up screen handling */
1751 NCURSES_EXPORT(void)
1752 _nc_screen_wrap(void)
1753 {
1754     UpdateAttrs(A_NORMAL);
1755 #if NCURSES_EXT_FUNCS
1756     if (SP->_coloron
1757 	&& !SP->_default_color) {
1758 	SP->_default_color = TRUE;
1759 	_nc_do_color(-1, 0, FALSE, _nc_outch);
1760 	SP->_default_color = FALSE;
1761 
1762 	mvcur(SP->_cursrow, SP->_curscol, screen_lines - 1, 0);
1763 	SP->_cursrow = screen_lines - 1;
1764 	SP->_curscol = 0;
1765 
1766 	ClrToEOL(BLANK, TRUE);
1767     }
1768 #endif
1769 }
1770 
1771 #if USE_XMC_SUPPORT
1772 NCURSES_EXPORT(void)
1773 _nc_do_xmc_glitch(attr_t previous)
1774 {
1775     attr_t chg = XMC_CHANGES(previous ^ SP->_current_attr);
1776 
1777     while (chg != 0) {
1778 	if (chg & 1) {
1779 	    SP->_curscol += magic_cookie_glitch;
1780 	    if (SP->_curscol >= SP->_columns)
1781 		wrap_cursor();
1782 	    TR(TRACE_UPDATE, ("bumped to %d,%d after cookie", SP->_cursrow, SP->_curscol));
1783 	}
1784 	chg >>= 1;
1785     }
1786 }
1787 #endif /* USE_XMC_SUPPORT */
1788