]> ncurses.scripts.mit.edu Git - ncurses.git/blob - ncurses/tty/tty_update.c
ncurses 6.4 - patch 20240420
[ncurses.git] / ncurses / tty / tty_update.c
1 /****************************************************************************
2  * Copyright 2018-2021,2022 Thomas E. Dickey                                *
3  * Copyright 1998-2016,2017 Free Software Foundation, Inc.                  *
4  *                                                                          *
5  * Permission is hereby granted, free of charge, to any person obtaining a  *
6  * copy of this software and associated documentation files (the            *
7  * "Software"), to deal in the Software without restriction, including      *
8  * without limitation the rights to use, copy, modify, merge, publish,      *
9  * distribute, distribute with modifications, sublicense, and/or sell       *
10  * copies of the Software, and to permit persons to whom the Software is    *
11  * furnished to do so, subject to the following conditions:                 *
12  *                                                                          *
13  * The above copyright notice and this permission notice shall be included  *
14  * in all copies or substantial portions of the Software.                   *
15  *                                                                          *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
17  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
18  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
19  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
20  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
21  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
22  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
23  *                                                                          *
24  * Except as contained in this notice, the name(s) of the above copyright   *
25  * holders shall not be used in advertising or otherwise to promote the     *
26  * sale, use or other dealings in this Software without prior written       *
27  * authorization.                                                           *
28  ****************************************************************************/
29
30 /****************************************************************************
31  *  Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995               *
32  *     and: Eric S. Raymond <esr@snark.thyrsus.com>                         *
33  *     and: Thomas E. Dickey                        1996-on                 *
34  *     and: Juergen Pfeifer                         2009                    *
35  ****************************************************************************/
36
37 /*-----------------------------------------------------------------
38  *
39  *      lib_doupdate.c
40  *
41  *      The routine doupdate() and its dependents.
42  *      All physical output is concentrated here (except _nc_outch()
43  *      in lib_tputs.c).
44  *
45  *-----------------------------------------------------------------*/
46
47 #define NEW_PAIR_INTERNAL 1
48
49 #include <curses.priv.h>
50
51 #ifndef CUR
52 #define CUR SP_TERMTYPE
53 #endif
54
55 #if defined __HAIKU__ && defined __BEOS__
56 #undef __BEOS__
57 #endif
58
59 #ifdef __BEOS__
60 #undef false
61 #undef true
62 #include <OS.h>
63 #endif
64
65 #if defined(TRACE) && HAVE_SYS_TIMES_H && HAVE_TIMES
66 #define USE_TRACE_TIMES 1
67 #else
68 #define USE_TRACE_TIMES 0
69 #endif
70
71 #if HAVE_SYS_TIME_H && HAVE_SYS_TIME_SELECT
72 #include <sys/time.h>
73 #endif
74
75 #if USE_TRACE_TIMES
76 #include <sys/times.h>
77 #endif
78
79 #if USE_FUNC_POLL
80 #elif HAVE_SELECT
81 #if HAVE_SYS_SELECT_H
82 #include <sys/select.h>
83 #endif
84 #endif
85
86 #include <ctype.h>
87
88 MODULE_ID("$Id: tty_update.c,v 1.314 2022/07/23 22:12:59 tom Exp $")
89
90 /*
91  * This define controls the line-breakout optimization.  Every once in a
92  * while during screen refresh, we want to check for input and abort the
93  * update if there's some waiting.  CHECK_INTERVAL controls the number of
94  * changed lines to be emitted between input checks.
95  *
96  * Note: Input-check-and-abort is no longer done if the screen is being
97  * updated from scratch.  This is a feature, not a bug.
98  */
99 #define CHECK_INTERVAL  5
100
101 #define FILL_BCE(sp) (sp->_coloron && !sp->_default_color && !back_color_erase)
102
103 static const NCURSES_CH_T blankchar = NewChar(BLANK_TEXT);
104 static NCURSES_CH_T normal = NewChar(BLANK_TEXT);
105
106 /*
107  * Enable checking to see if doupdate and friends are tracking the true
108  * cursor position correctly.  NOTE: this is a debugging hack which will
109  * work ONLY on ANSI-compatible terminals!
110  */
111 /* #define POSITION_DEBUG */
112
113 static NCURSES_INLINE NCURSES_CH_T ClrBlank(NCURSES_SP_DCLx WINDOW *win);
114
115 #if NCURSES_SP_FUNCS
116 static int ClrBottom(SCREEN *, int total);
117 static void ClearScreen(SCREEN *, NCURSES_CH_T blank);
118 static void ClrUpdate(SCREEN *);
119 static void DelChar(SCREEN *, int count);
120 static void InsStr(SCREEN *, NCURSES_CH_T *line, int count);
121 static void TransformLine(SCREEN *, int const lineno);
122 #else
123 static int ClrBottom(int total);
124 static void ClearScreen(NCURSES_CH_T blank);
125 static void ClrUpdate(void);
126 static void DelChar(int count);
127 static void InsStr(NCURSES_CH_T *line, int count);
128 static void TransformLine(int const lineno);
129 #endif
130
131 #ifdef POSITION_DEBUG
132 /****************************************************************************
133  *
134  * Debugging code.  Only works on ANSI-standard terminals.
135  *
136  ****************************************************************************/
137
138 static void
139 position_check(NCURSES_SP_DCLx int expected_y, int expected_x, const char *legend)
140 /* check to see if the real cursor position matches the virtual */
141 {
142     char buf[20];
143     char *s;
144     int y, x;
145
146     if (!_nc_tracing || (expected_y < 0 && expected_x < 0))
147         return;
148
149     NCURSES_SP_NAME(_nc_flush) (NCURSES_SP_ARG);
150     memset(buf, '\0', sizeof(buf));
151     NCURSES_PUTP2_FLUSH("cpr", "\033[6n");      /* only works on ANSI-compatibles */
152     *(s = buf) = 0;
153     do {
154         int ask = sizeof(buf) - 1 - (s - buf);
155         int got = read(0, s, ask);
156         if (got == 0)
157             break;
158         s += got;
159     } while (strchr(buf, 'R') == 0);
160     _tracef("probe returned %s", _nc_visbuf(buf));
161
162     /* try to interpret as a position report */
163     if (sscanf(buf, "\033[%d;%dR", &y, &x) != 2) {
164         _tracef("position probe failed in %s", legend);
165     } else {
166         if (expected_x < 0)
167             expected_x = x - 1;
168         if (expected_y < 0)
169             expected_y = y - 1;
170         if (y - 1 != expected_y || x - 1 != expected_x) {
171             NCURSES_SP_NAME(beep) (NCURSES_SP_ARG);
172             NCURSES_SP_NAME(tputs) (NCURSES_SP_ARGx
173                                     TIPARM_2("\033[%d;%dH",
174                                              expected_y + 1,
175                                              expected_x + 1),
176                                     1, NCURSES_SP_NAME(_nc_outch));
177             _tracef("position seen (%d, %d) doesn't match expected one (%d, %d) in %s",
178                     y - 1, x - 1, expected_y, expected_x, legend);
179         } else {
180             _tracef("position matches OK in %s", legend);
181         }
182     }
183 }
184 #else
185 #define position_check(expected_y, expected_x, legend)  /* nothing */
186 #endif /* POSITION_DEBUG */
187
188 /****************************************************************************
189  *
190  * Optimized update code
191  *
192  ****************************************************************************/
193
194 static NCURSES_INLINE void
195 GoTo(NCURSES_SP_DCLx int const row, int const col)
196 {
197     TR(TRACE_MOVE, ("GoTo(%p, %d, %d) from (%d, %d)",
198                     (void *) SP_PARM, row, col, SP_PARM->_cursrow, SP_PARM->_curscol));
199
200     position_check(NCURSES_SP_ARGx
201                    SP_PARM->_cursrow,
202                    SP_PARM->_curscol, "GoTo");
203
204     TINFO_MVCUR(NCURSES_SP_ARGx
205                 SP_PARM->_cursrow,
206                 SP_PARM->_curscol,
207                 row, col);
208     position_check(NCURSES_SP_ARGx
209                    SP_PARM->_cursrow,
210                    SP_PARM->_curscol, "GoTo2");
211 }
212
213 #if !NCURSES_WCWIDTH_GRAPHICS
214 #define is_wacs_value(ch) (_nc_wacs_width(ch) == 1 && wcwidth(ch) > 1)
215 #endif /* !NCURSES_WCWIDTH_GRAPHICS */
216
217 static NCURSES_INLINE void
218 PutAttrChar(NCURSES_SP_DCLx CARG_CH_T ch)
219 {
220     int chlen = 1;
221     NCURSES_CH_T my_ch;
222 #if USE_WIDEC_SUPPORT
223     PUTC_DATA;
224 #endif
225     NCURSES_CH_T tilde;
226     NCURSES_CH_T attr = CHDEREF(ch);
227
228     TR(TRACE_CHARPUT, ("PutAttrChar(%s) at (%d, %d)",
229                        _tracech_t(ch),
230                        SP_PARM->_cursrow, SP_PARM->_curscol));
231 #if USE_WIDEC_SUPPORT
232     /*
233      * If this is not a valid character, there is nothing more to do.
234      */
235     if (isWidecExt(CHDEREF(ch))) {
236         TR(TRACE_CHARPUT, ("...skip"));
237         return;
238     }
239     /*
240      * Determine the number of character cells which the 'ch' value will use
241      * on the screen.  It should be at least one.
242      */
243     if ((chlen = _nc_wacs_width(CharOf(CHDEREF(ch)))) <= 0) {
244         static const NCURSES_CH_T blank = NewChar(BLANK_TEXT);
245
246         /*
247          * If the character falls into any of these special cases, do
248          * not force the result to a blank:
249          *
250          * a) it is printable (this works around a bug in wcwidth()).
251          * b) use_legacy_coding() has been called to modify the treatment
252          *    of codes 128-255.
253          * c) the acs_map[] has been initialized to allow codes 0-31
254          *    to be rendered.  This supports Linux console's "PC"
255          *    characters.  Codes 128-255 are allowed though this is
256          *    not checked.
257          */
258         if (is8bits(CharOf(CHDEREF(ch)))
259             && (isprint(CharOf(CHDEREF(ch)))
260                 || (SP_PARM->_legacy_coding > 0 && CharOf(CHDEREF(ch)) >= 160)
261                 || (SP_PARM->_legacy_coding > 1 && CharOf(CHDEREF(ch)) >= 128)
262                 || (AttrOf(attr) & A_ALTCHARSET
263                     && ((CharOfD(ch) < ACS_LEN
264                          && SP_PARM->_acs_map != 0
265                          && SP_PARM->_acs_map[CharOfD(ch)] != 0)
266                         || (CharOfD(ch) >= 128))))) {
267             ;
268         } else {
269             ch = CHREF(blank);
270             TR(TRACE_CHARPUT, ("forced to blank"));
271         }
272         chlen = 1;
273     }
274 #endif
275
276     if ((AttrOf(attr) & A_ALTCHARSET)
277         && SP_PARM->_acs_map != 0
278         && ((CharOfD(ch) < ACS_LEN)
279 #if !NCURSES_WCWIDTH_GRAPHICS
280             || is_wacs_value(CharOfD(ch))
281 #endif
282         )) {
283         int c8;
284         my_ch = CHDEREF(ch);    /* work around const param */
285         c8 = CharOf(my_ch);
286 #if USE_WIDEC_SUPPORT
287         /*
288          * This is crude & ugly, but works most of the time.  It checks if the
289          * acs_chars string specified that we have a mapping for this
290          * character, and uses the wide-character mapping when we expect the
291          * normal one to be broken (by mis-design ;-).
292          */
293         if (SP_PARM->_screen_unicode
294             && _nc_wacs[CharOf(my_ch)].chars[0]) {
295             if (SP_PARM->_screen_acs_map[CharOf(my_ch)]) {
296                 if (SP_PARM->_screen_acs_fix) {
297                     RemAttr(attr, A_ALTCHARSET);
298                     my_ch = _nc_wacs[CharOf(my_ch)];
299                 }
300             } else {
301                 RemAttr(attr, A_ALTCHARSET);
302                 my_ch = _nc_wacs[CharOf(my_ch)];
303             }
304 #if !NCURSES_WCWIDTH_GRAPHICS
305             if (!(AttrOf(attr) & A_ALTCHARSET)) {
306                 chlen = 1;
307             }
308 #endif /* !NCURSES_WCWIDTH_GRAPHICS */
309         } else
310 #endif
311         if (!SP_PARM->_screen_acs_map[c8]) {
312             /*
313              * If we found no mapping for a given alternate-character set item
314              * in the terminal description, attempt to use the ASCII fallback
315              * code which is populated in the _acs_map[] array.  If that did
316              * not correspond to a line-drawing, etc., graphics character, the
317              * array entry would be empty.
318              */
319             chtype temp = UChar(SP_PARM->_acs_map[c8]);
320             if (temp) {
321                 RemAttr(attr, A_ALTCHARSET);
322                 SetChar(my_ch, temp, AttrOf(attr));
323             }
324         }
325
326         /*
327          * If we (still) have alternate character set, it is the normal 8bit
328          * flavor.  The _screen_acs_map[] array tells if the character was
329          * really in acs_chars, needed because of the way wide/normal line
330          * drawing flavors are integrated.
331          */
332         if (AttrOf(attr) & A_ALTCHARSET) {
333             int j = CharOfD(ch);
334             chtype temp = UChar(SP_PARM->_acs_map[j]);
335
336             if (temp != 0) {
337                 SetChar(my_ch, temp, AttrOf(attr));
338             } else {
339                 my_ch = CHDEREF(ch);
340                 RemAttr(attr, A_ALTCHARSET);
341             }
342         }
343         ch = CHREF(my_ch);
344     }
345 #if USE_WIDEC_SUPPORT && !NCURSES_WCWIDTH_GRAPHICS
346     else if (chlen > 1 && is_wacs_value(CharOfD(ch))) {
347         chlen = 1;
348     }
349 #endif
350     if (tilde_glitch && (CharOfD(ch) == L('~'))) {
351         SetChar(tilde, L('`'), AttrOf(attr));
352         ch = CHREF(tilde);
353     }
354
355     UpdateAttrs(SP_PARM, attr);
356     PUTC(CHDEREF(ch));
357 #if !USE_WIDEC_SUPPORT
358     COUNT_OUTCHARS(1);
359 #endif
360     SP_PARM->_curscol += chlen;
361     if (char_padding) {
362         NCURSES_PUTP2("char_padding", char_padding);
363     }
364 }
365
366 static bool
367 check_pending(NCURSES_SP_DCL0)
368 /* check for pending input */
369 {
370     bool have_pending = FALSE;
371
372     /*
373      * Only carry out this check when the flag is zero, otherwise we'll
374      * have the refreshing slow down drastically (or stop) if there's an
375      * unread character available.
376      */
377     if (SP_PARM->_fifohold != 0)
378         return FALSE;
379
380     if (SP_PARM->_checkfd >= 0) {
381 #if USE_FUNC_POLL
382         struct pollfd fds[1];
383         fds[0].fd = SP_PARM->_checkfd;
384         fds[0].events = POLLIN;
385         if (poll(fds, (size_t) 1, 0) > 0) {
386             have_pending = TRUE;
387         }
388 #elif defined(__BEOS__)
389         /*
390          * BeOS's select() is declared in socket.h, so the configure script does
391          * not see it.  That's just as well, since that function works only for
392          * sockets.  This (using snooze and ioctl) was distilled from Be's patch
393          * for ncurses which uses a separate thread to simulate select().
394          *
395          * FIXME: the return values from the ioctl aren't very clear if we get
396          * interrupted.
397          */
398         int n = 0;
399         int howmany = ioctl(0, 'ichr', &n);
400         if (howmany >= 0 && n > 0) {
401             have_pending = TRUE;
402         }
403 #elif HAVE_SELECT
404         fd_set fdset;
405         struct timeval ktimeout;
406
407         ktimeout.tv_sec =
408             ktimeout.tv_usec = 0;
409
410         FD_ZERO(&fdset);
411         FD_SET(SP_PARM->_checkfd, &fdset);
412         if (select(SP_PARM->_checkfd + 1, &fdset, NULL, NULL, &ktimeout) != 0) {
413             have_pending = TRUE;
414         }
415 #endif
416     }
417     if (have_pending) {
418         SP_PARM->_fifohold = 5;
419         NCURSES_SP_NAME(_nc_flush) (NCURSES_SP_ARG);
420     }
421     return FALSE;
422 }
423
424 /* put char at lower right corner */
425 static void
426 PutCharLR(NCURSES_SP_DCLx const ARG_CH_T ch)
427 {
428     if (!auto_right_margin) {
429         /* we can put the char directly */
430         PutAttrChar(NCURSES_SP_ARGx ch);
431     } else if (enter_am_mode && exit_am_mode) {
432         int oldcol = SP_PARM->_curscol;
433         /* we can suppress automargin */
434         NCURSES_PUTP2("exit_am_mode", exit_am_mode);
435
436         PutAttrChar(NCURSES_SP_ARGx ch);
437         SP_PARM->_curscol = oldcol;
438         position_check(NCURSES_SP_ARGx
439                        SP_PARM->_cursrow,
440                        SP_PARM->_curscol,
441                        "exit_am_mode");
442
443         NCURSES_PUTP2("enter_am_mode", enter_am_mode);
444     } else if ((enter_insert_mode && exit_insert_mode)
445                || insert_character || parm_ich) {
446         GoTo(NCURSES_SP_ARGx
447              screen_lines(SP_PARM) - 1,
448              screen_columns(SP_PARM) - 2);
449         PutAttrChar(NCURSES_SP_ARGx ch);
450         GoTo(NCURSES_SP_ARGx
451              screen_lines(SP_PARM) - 1,
452              screen_columns(SP_PARM) - 2);
453         InsStr(NCURSES_SP_ARGx
454                NewScreen(SP_PARM)->_line[screen_lines(SP_PARM) - 1].text +
455                screen_columns(SP_PARM) - 2, 1);
456     }
457 }
458
459 /*
460  * Wrap the cursor position, i.e., advance to the beginning of the next line.
461  */
462 static void
463 wrap_cursor(NCURSES_SP_DCL0)
464 {
465     if (eat_newline_glitch) {
466         /*
467          * xenl can manifest two different ways.  The vt100 way is that, when
468          * you'd expect the cursor to wrap, it stays hung at the right margin
469          * (on top of the character just emitted) and doesn't wrap until the
470          * *next* graphic char is emitted.  The c100 way is to ignore LF
471          * received just after an am wrap.
472          *
473          * An aggressive way to handle this would be to emit CR/LF after the
474          * char and then assume the wrap is done, you're on the first position
475          * of the next line, and the terminal out of its weird state.  Here
476          * it is safe to just tell the code that the cursor is in hyperspace and
477          * let the next mvcur() call straighten things out.
478          */
479         SP_PARM->_curscol = -1;
480         SP_PARM->_cursrow = -1;
481     } else if (auto_right_margin) {
482         SP_PARM->_curscol = 0;
483         SP_PARM->_cursrow++;
484         /*
485          * We've actually moved - but may have to work around problems with
486          * video attributes not working.
487          */
488         if (!move_standout_mode && AttrOf(SCREEN_ATTRS(SP_PARM))) {
489             TR(TRACE_CHARPUT, ("turning off (%#lx) %s before wrapping",
490                                (unsigned long) AttrOf(SCREEN_ATTRS(SP_PARM)),
491                                _traceattr(AttrOf(SCREEN_ATTRS(SP_PARM)))));
492             VIDPUTS(SP_PARM, A_NORMAL, 0);
493         }
494     } else {
495         SP_PARM->_curscol--;
496     }
497     position_check(NCURSES_SP_ARGx
498                    SP_PARM->_cursrow,
499                    SP_PARM->_curscol,
500                    "wrap_cursor");
501 }
502
503 static NCURSES_INLINE void
504 PutChar(NCURSES_SP_DCLx const ARG_CH_T ch)
505 /* insert character, handling automargin stuff */
506 {
507     if (SP_PARM->_cursrow == screen_lines(SP_PARM) - 1 &&
508         SP_PARM->_curscol == screen_columns(SP_PARM) - 1) {
509         PutCharLR(NCURSES_SP_ARGx ch);
510     } else {
511         PutAttrChar(NCURSES_SP_ARGx ch);
512     }
513
514     if (SP_PARM->_curscol >= screen_columns(SP_PARM))
515         wrap_cursor(NCURSES_SP_ARG);
516
517     position_check(NCURSES_SP_ARGx
518                    SP_PARM->_cursrow,
519                    SP_PARM->_curscol, "PutChar");
520 }
521
522 /*
523  * Check whether the given character can be output by clearing commands.  This
524  * includes test for being a space and not including any 'bad' attributes, such
525  * as A_REVERSE.  All attribute flags which don't affect appearance of a space
526  * or can be output by clearing (A_COLOR in case of bce-terminal) are excluded.
527  */
528 static NCURSES_INLINE bool
529 can_clear_with(NCURSES_SP_DCLx ARG_CH_T ch)
530 {
531     if (!back_color_erase && SP_PARM->_coloron) {
532 #if NCURSES_EXT_FUNCS
533         int pair;
534
535         if (!SP_PARM->_default_color)
536             return FALSE;
537         if (!(isDefaultColor(SP_PARM->_default_fg) &&
538               isDefaultColor(SP_PARM->_default_bg)))
539             return FALSE;
540         if ((pair = GetPair(CHDEREF(ch))) != 0) {
541             NCURSES_COLOR_T fg, bg;
542             if (NCURSES_SP_NAME(pair_content) (NCURSES_SP_ARGx
543                                                (short) pair,
544                                                &fg, &bg) == ERR
545                 || !(isDefaultColor(fg) && isDefaultColor(bg))) {
546                 return FALSE;
547             }
548         }
549 #else
550         if (AttrOfD(ch) & A_COLOR)
551             return FALSE;
552 #endif
553     }
554     return (ISBLANK(CHDEREF(ch)) &&
555             (AttrOfD(ch) & ~(NONBLANK_ATTR | A_COLOR)) == BLANK_ATTR);
556 }
557
558 /*
559  * Issue a given span of characters from an array.
560  * Must be functionally equivalent to:
561  *      for (i = 0; i < num; i++)
562  *          PutChar(ntext[i]);
563  * but can leave the cursor positioned at the middle of the interval.
564  *
565  * Returns: 0 - cursor is at the end of interval
566  *          1 - cursor is somewhere in the middle
567  *
568  * This code is optimized using ech and rep.
569  */
570 static int
571 EmitRange(NCURSES_SP_DCLx const NCURSES_CH_T *ntext, int num)
572 {
573     int i;
574
575     TR(TRACE_CHARPUT, ("EmitRange %d:%s", num, _nc_viscbuf(ntext, num)));
576
577     if (erase_chars || repeat_char) {
578         while (num > 0) {
579             int runcount;
580             NCURSES_CH_T ntext0;
581
582             while (num > 1 && !CharEq(ntext[0], ntext[1])) {
583                 PutChar(NCURSES_SP_ARGx CHREF(ntext[0]));
584                 ntext++;
585                 num--;
586             }
587             ntext0 = ntext[0];
588             if (num == 1) {
589                 PutChar(NCURSES_SP_ARGx CHREF(ntext0));
590                 return 0;
591             }
592             runcount = 2;
593
594             while (runcount < num && CharEq(ntext[runcount], ntext0))
595                 runcount++;
596
597             /*
598              * The cost expression in the middle isn't exactly right.
599              * _cup_ch_cost is an upper bound on the cost for moving to the
600              * end of the erased area, but not the cost itself (which we
601              * can't compute without emitting the move).  This may result
602              * in erase_chars not getting used in some situations for
603              * which it would be marginally advantageous.
604              */
605             if (erase_chars
606                 && runcount > SP_PARM->_ech_cost + SP_PARM->_cup_ch_cost
607                 && can_clear_with(NCURSES_SP_ARGx CHREF(ntext0))) {
608                 UpdateAttrs(SP_PARM, ntext0);
609                 NCURSES_PUTP2("erase_chars", TIPARM_1(erase_chars, runcount));
610
611                 /*
612                  * If this is the last part of the given interval,
613                  * don't bother moving cursor, since it can be the
614                  * last update on the line.
615                  */
616                 if (runcount < num) {
617                     GoTo(NCURSES_SP_ARGx
618                          SP_PARM->_cursrow,
619                          SP_PARM->_curscol + runcount);
620                 } else {
621                     return 1;   /* cursor stays in the middle */
622                 }
623             } else if (repeat_char != 0 &&
624 #if BSD_TPUTS
625                        !isdigit(UChar(CharOf(ntext0))) &&
626 #endif
627 #if USE_WIDEC_SUPPORT
628                        (!SP_PARM->_screen_unicode &&
629                         (CharOf(ntext0) < ((AttrOf(ntext0) & A_ALTCHARSET)
630                                            ? ACS_LEN
631                                            : 256))) &&
632 #endif
633                        runcount > SP_PARM->_rep_cost) {
634                 NCURSES_CH_T temp;
635                 bool wrap_possible = (SP_PARM->_curscol + runcount >=
636                                       screen_columns(SP_PARM));
637                 int rep_count = runcount;
638
639                 if (wrap_possible)
640                     rep_count--;
641
642                 UpdateAttrs(SP_PARM, ntext0);
643                 temp = ntext0;
644                 if ((AttrOf(temp) & A_ALTCHARSET) &&
645                     SP_PARM->_acs_map != 0 &&
646                     (SP_PARM->_acs_map[CharOf(temp)] & A_CHARTEXT) != 0) {
647                     SetChar(temp,
648                             (SP_PARM->_acs_map[CharOf(ntext0)] & A_CHARTEXT),
649                             AttrOf(ntext0) | A_ALTCHARSET);
650                 }
651                 NCURSES_SP_NAME(tputs) (NCURSES_SP_ARGx
652                                         TIPARM_2(repeat_char,
653                                                  CharOf(temp),
654                                                  rep_count),
655                                         1,
656                                         NCURSES_SP_NAME(_nc_outch));
657                 SP_PARM->_curscol += rep_count;
658
659                 if (wrap_possible)
660                     PutChar(NCURSES_SP_ARGx CHREF(ntext0));
661             } else {
662                 for (i = 0; i < runcount; i++)
663                     PutChar(NCURSES_SP_ARGx CHREF(ntext[i]));
664             }
665             ntext += runcount;
666             num -= runcount;
667         }
668         return 0;
669     }
670
671     for (i = 0; i < num; i++)
672         PutChar(NCURSES_SP_ARGx CHREF(ntext[i]));
673     return 0;
674 }
675
676 /*
677  * Output the line in the given range [first .. last]
678  *
679  * If there's a run of identical characters that's long enough to justify
680  * cursor movement, use that also.
681  *
682  * Returns: same as EmitRange
683  */
684 static int
685 PutRange(NCURSES_SP_DCLx
686          const NCURSES_CH_T *otext,
687          const NCURSES_CH_T *ntext,
688          int row,
689          int first, int last)
690 {
691     int rc;
692
693     TR(TRACE_CHARPUT, ("PutRange(%p, %p, %p, %d, %d, %d)",
694                        (void *) SP_PARM,
695                        (const void *) otext,
696                        (const void *) ntext,
697                        row, first, last));
698
699     if (otext != ntext
700         && (last - first + 1) > SP_PARM->_inline_cost) {
701         int i, j, same;
702
703         for (j = first, same = 0; j <= last; j++) {
704             if (!same && isWidecExt(otext[j]))
705                 continue;
706             if (CharEq(otext[j], ntext[j])) {
707                 same++;
708             } else {
709                 if (same > SP_PARM->_inline_cost) {
710                     EmitRange(NCURSES_SP_ARGx ntext + first, j - same - first);
711                     GoTo(NCURSES_SP_ARGx row, first = j);
712                 }
713                 same = 0;
714             }
715         }
716         i = EmitRange(NCURSES_SP_ARGx ntext + first, j - same - first);
717         /*
718          * Always return 1 for the next GoTo() after a PutRange() if we found
719          * identical characters at end of interval
720          */
721         rc = (same == 0 ? i : 1);
722     } else {
723         rc = EmitRange(NCURSES_SP_ARGx ntext + first, last - first + 1);
724     }
725     return rc;
726 }
727
728 /* leave unbracketed here so 'indent' works */
729 #define MARK_NOCHANGE(win,row) \
730                 win->_line[row].firstchar = _NOCHANGE; \
731                 win->_line[row].lastchar = _NOCHANGE; \
732                 if_USE_SCROLL_HINTS(win->_line[row].oldindex = row)
733
734 NCURSES_EXPORT(int)
735 TINFO_DOUPDATE(NCURSES_SP_DCL0)
736 {
737     int i;
738     int nonempty;
739 #if USE_TRACE_TIMES
740     struct tms before, after;
741 #endif /* USE_TRACE_TIMES */
742
743     T((T_CALLED("_nc_tinfo:doupdate(%p)"), (void *) SP_PARM));
744
745     _nc_lock_global(update);
746
747     if (SP_PARM == 0) {
748         _nc_unlock_global(update);
749         returnCode(ERR);
750     }
751 #if !USE_REENTRANT
752     /*
753      * It is "legal" but unlikely that an application could assign a new
754      * value to one of the standard windows.  Check for that possibility
755      * and try to recover.
756      *
757      * We do not allow applications to assign new values in the reentrant
758      * model.
759      */
760 #if NCURSES_SP_FUNCS
761     if (SP_PARM == CURRENT_SCREEN) {
762 #endif
763 #define SyncScreens(internal,exported) \
764         if (internal == 0) internal = exported; \
765         if (internal != exported) exported = internal
766
767         SyncScreens(CurScreen(SP_PARM), curscr);
768         SyncScreens(NewScreen(SP_PARM), newscr);
769         SyncScreens(StdScreen(SP_PARM), stdscr);
770 #if NCURSES_SP_FUNCS
771     }
772 #endif
773 #endif /* !USE_REENTRANT */
774
775     if (CurScreen(SP_PARM) == 0
776         || NewScreen(SP_PARM) == 0
777         || StdScreen(SP_PARM) == 0) {
778         _nc_unlock_global(update);
779         returnCode(ERR);
780     }
781 #ifdef TRACE
782     if (USE_TRACEF(TRACE_UPDATE)) {
783         if (CurScreen(SP_PARM)->_clear)
784             _tracef("curscr is clear");
785         else
786             _tracedump("curscr", CurScreen(SP_PARM));
787         _tracedump("newscr", NewScreen(SP_PARM));
788         _nc_unlock_global(tracef);
789     }
790 #endif /* TRACE */
791
792     _nc_signal_handler(FALSE);
793
794     if (SP_PARM->_fifohold)
795         SP_PARM->_fifohold--;
796
797 #if USE_SIZECHANGE
798     if ((SP_PARM->_endwin == ewSuspend)
799         || _nc_handle_sigwinch(SP_PARM)) {
800         /*
801          * This is a transparent extension:  XSI does not address it,
802          * and applications need not know that ncurses can do it.
803          *
804          * Check if the terminal size has changed while curses was off
805          * (this can happen in an xterm, for example), and resize the
806          * ncurses data structures accordingly.
807          */
808         _nc_update_screensize(SP_PARM);
809     }
810 #endif
811
812     if (SP_PARM->_endwin == ewSuspend) {
813
814         T(("coming back from shell mode"));
815         NCURSES_SP_NAME(reset_prog_mode) (NCURSES_SP_ARG);
816
817         NCURSES_SP_NAME(_nc_mvcur_resume) (NCURSES_SP_ARG);
818         NCURSES_SP_NAME(_nc_screen_resume) (NCURSES_SP_ARG);
819         SP_PARM->_mouse_resume(SP_PARM);
820
821         SP_PARM->_endwin = ewRunning;
822     }
823 #if USE_TRACE_TIMES
824     /* zero the metering machinery */
825     RESET_OUTCHARS();
826     (void) times(&before);
827 #endif /* USE_TRACE_TIMES */
828
829     /*
830      * This is the support for magic-cookie terminals.  The theory:  we scan
831      * the virtual screen looking for attribute turnons.  Where we find one,
832      * check to make sure it is realizable by seeing if the required number of
833      * un-attributed blanks are present before and after the attributed range;
834      * try to shift the range boundaries over blanks (not changing the screen
835      * display) so this becomes true.  If it is, shift the beginning attribute
836      * change appropriately (the end one, if we've gotten this far, is
837      * guaranteed room for its cookie).  If not, nuke the added attributes out
838      * of the span.
839      */
840 #if USE_XMC_SUPPORT
841     if (magic_cookie_glitch > 0) {
842         int j, k;
843         attr_t rattr = A_NORMAL;
844
845         for (i = 0; i < screen_lines(SP_PARM); i++) {
846             for (j = 0; j < screen_columns(SP_PARM); j++) {
847                 bool failed = FALSE;
848                 NCURSES_CH_T *thisline = NewScreen(SP_PARM)->_line[i].text;
849                 attr_t thisattr = AttrOf(thisline[j]) & SP_PARM->_xmc_triggers;
850                 attr_t turnon = thisattr & ~rattr;
851
852                 /* is an attribute turned on here? */
853                 if (turnon == 0) {
854                     rattr = thisattr;
855                     continue;
856                 }
857
858                 TR(TRACE_ATTRS, ("At (%d, %d): from %s...", i, j, _traceattr(rattr)));
859                 TR(TRACE_ATTRS, ("...to %s", _traceattr(turnon)));
860
861                 /*
862                  * If the attribute change location is a blank with a "safe"
863                  * attribute, undo the attribute turnon.  This may ensure
864                  * there's enough room to set the attribute before the first
865                  * non-blank in the run.
866                  */
867 #define SAFE(scr,a)     (!((a) & (scr)->_xmc_triggers))
868                 if (ISBLANK(thisline[j]) && SAFE(SP_PARM, turnon)) {
869                     RemAttr(thisline[j], turnon);
870                     continue;
871                 }
872
873                 /* check that there's enough room at start of span */
874                 for (k = 1; k <= magic_cookie_glitch; k++) {
875                     if (j - k < 0
876                         || !ISBLANK(thisline[j - k])
877                         || !SAFE(SP_PARM, AttrOf(thisline[j - k]))) {
878                         failed = TRUE;
879                         TR(TRACE_ATTRS, ("No room at start in %d,%d%s%s",
880                                          i, j - k,
881                                          (ISBLANK(thisline[j - k])
882                                           ? ""
883                                           : ":nonblank"),
884                                          (SAFE(SP_PARM, AttrOf(thisline[j - k]))
885                                           ? ""
886                                           : ":unsafe")));
887                         break;
888                     }
889                 }
890                 if (!failed) {
891                     bool end_onscreen = FALSE;
892                     int m, n = j;
893
894                     /* find end of span, if it is onscreen */
895                     for (m = i; m < screen_lines(SP_PARM); m++) {
896                         for (; n < screen_columns(SP_PARM); n++) {
897                             attr_t testattr =
898                             AttrOf(NewScreen(SP_PARM)->_line[m].text[n]);
899                             if ((testattr & SP_PARM->_xmc_triggers) == rattr) {
900                                 end_onscreen = TRUE;
901                                 TR(TRACE_ATTRS,
902                                    ("Range attributed with %s ends at (%d, %d)",
903                                     _traceattr(turnon), m, n));
904                                 goto foundit;
905                             }
906                         }
907                         n = 0;
908                     }
909                     TR(TRACE_ATTRS,
910                        ("Range attributed with %s ends offscreen",
911                         _traceattr(turnon)));
912                   foundit:;
913
914                     if (end_onscreen) {
915                         NCURSES_CH_T *lastline =
916                         NewScreen(SP_PARM)->_line[m].text;
917
918                         /*
919                          * If there are safely-attributed blanks at the end of
920                          * the range, shorten the range.  This will help ensure
921                          * that there is enough room at end of span.
922                          */
923                         while (n >= 0
924                                && ISBLANK(lastline[n])
925                                && SAFE(SP_PARM, AttrOf(lastline[n]))) {
926                             RemAttr(lastline[n--], turnon);
927                         }
928
929                         /* check that there's enough room at end of span */
930                         for (k = 1; k <= magic_cookie_glitch; k++) {
931                             if (n + k >= screen_columns(SP_PARM)
932                                 || !ISBLANK(lastline[n + k])
933                                 || !SAFE(SP_PARM, AttrOf(lastline[n + k]))) {
934                                 failed = TRUE;
935                                 TR(TRACE_ATTRS,
936                                    ("No room at end in %d,%d%s%s",
937                                     i, j - k,
938                                     (ISBLANK(lastline[n + k])
939                                      ? ""
940                                      : ":nonblank"),
941                                     (SAFE(SP_PARM, AttrOf(lastline[n + k]))
942                                      ? ""
943                                      : ":unsafe")));
944                                 break;
945                             }
946                         }
947                     }
948                 }
949
950                 if (failed) {
951                     int p, q = j;
952
953                     TR(TRACE_ATTRS,
954                        ("Clearing %s beginning at (%d, %d)",
955                         _traceattr(turnon), i, j));
956
957                     /* turn off new attributes over span */
958                     for (p = i; p < screen_lines(SP_PARM); p++) {
959                         for (; q < screen_columns(SP_PARM); q++) {
960                             attr_t testattr = AttrOf(newscr->_line[p].text[q]);
961                             if ((testattr & SP_PARM->_xmc_triggers) == rattr)
962                                 goto foundend;
963                             RemAttr(NewScreen(SP_PARM)->_line[p].text[q], turnon);
964                         }
965                         q = 0;
966                     }
967                   foundend:;
968                 } else {
969                     TR(TRACE_ATTRS,
970                        ("Cookie space for %s found before (%d, %d)",
971                         _traceattr(turnon), i, j));
972
973                     /*
974                      * Back up the start of range so there's room for cookies
975                      * before the first nonblank character.
976                      */
977                     for (k = 1; k <= magic_cookie_glitch; k++)
978                         AddAttr(thisline[j - k], turnon);
979                 }
980
981                 rattr = thisattr;
982             }
983         }
984
985 #ifdef TRACE
986         /* show altered highlights after magic-cookie check */
987         if (USE_TRACEF(TRACE_UPDATE)) {
988             _tracef("After magic-cookie check...");
989             _tracedump("newscr", NewScreen(SP_PARM));
990             _nc_unlock_global(tracef);
991         }
992 #endif /* TRACE */
993     }
994 #endif /* USE_XMC_SUPPORT */
995
996     nonempty = 0;
997     if (CurScreen(SP_PARM)->_clear || NewScreen(SP_PARM)->_clear) {     /* force refresh ? */
998         ClrUpdate(NCURSES_SP_ARG);
999         CurScreen(SP_PARM)->_clear = FALSE;     /* reset flag */
1000         NewScreen(SP_PARM)->_clear = FALSE;     /* reset flag */
1001     } else {
1002         int changedlines = CHECK_INTERVAL;
1003
1004         if (check_pending(NCURSES_SP_ARG))
1005             goto cleanup;
1006
1007         nonempty = min(screen_lines(SP_PARM), NewScreen(SP_PARM)->_maxy + 1);
1008
1009         if (SP_PARM->_scrolling) {
1010             NCURSES_SP_NAME(_nc_scroll_optimize) (NCURSES_SP_ARG);
1011         }
1012
1013         nonempty = ClrBottom(NCURSES_SP_ARGx nonempty);
1014
1015         TR(TRACE_UPDATE, ("Transforming lines, nonempty %d", nonempty));
1016         for (i = 0; i < nonempty; i++) {
1017             /*
1018              * Here is our line-breakout optimization.
1019              */
1020             if (changedlines == CHECK_INTERVAL) {
1021                 if (check_pending(NCURSES_SP_ARG))
1022                     goto cleanup;
1023                 changedlines = 0;
1024             }
1025
1026             /*
1027              * newscr->line[i].firstchar is normally set
1028              * by wnoutrefresh.  curscr->line[i].firstchar
1029              * is normally set by _nc_scroll_window in the
1030              * vertical-movement optimization code,
1031              */
1032             if (NewScreen(SP_PARM)->_line[i].firstchar != _NOCHANGE
1033                 || CurScreen(SP_PARM)->_line[i].firstchar != _NOCHANGE) {
1034                 TransformLine(NCURSES_SP_ARGx i);
1035                 changedlines++;
1036             }
1037
1038             /* mark line changed successfully */
1039             if (i <= NewScreen(SP_PARM)->_maxy) {
1040                 MARK_NOCHANGE(NewScreen(SP_PARM), i);
1041             }
1042             if (i <= CurScreen(SP_PARM)->_maxy) {
1043                 MARK_NOCHANGE(CurScreen(SP_PARM), i);
1044             }
1045         }
1046     }
1047
1048     /* put everything back in sync */
1049     for (i = nonempty; i <= NewScreen(SP_PARM)->_maxy; i++) {
1050         MARK_NOCHANGE(NewScreen(SP_PARM), i);
1051     }
1052     for (i = nonempty; i <= CurScreen(SP_PARM)->_maxy; i++) {
1053         MARK_NOCHANGE(CurScreen(SP_PARM), i);
1054     }
1055
1056     if (!NewScreen(SP_PARM)->_leaveok) {
1057         CurScreen(SP_PARM)->_curx = NewScreen(SP_PARM)->_curx;
1058         CurScreen(SP_PARM)->_cury = NewScreen(SP_PARM)->_cury;
1059
1060         GoTo(NCURSES_SP_ARGx CurScreen(SP_PARM)->_cury, CurScreen(SP_PARM)->_curx);
1061     }
1062
1063   cleanup:
1064     /*
1065      * We would like to keep the physical screen in normal mode in case we get
1066      * other processes writing to the screen.  This goal cannot be met for
1067      * magic cookies since it interferes with attributes that may propagate
1068      * past the current position.
1069      */
1070 #if USE_XMC_SUPPORT
1071     if (magic_cookie_glitch != 0)
1072 #endif
1073         UpdateAttrs(SP_PARM, normal);
1074
1075     NCURSES_SP_NAME(_nc_flush) (NCURSES_SP_ARG);
1076     WINDOW_ATTRS(CurScreen(SP_PARM)) = WINDOW_ATTRS(NewScreen(SP_PARM));
1077
1078 #if USE_TRACE_TIMES
1079     (void) times(&after);
1080     TR(TRACE_TIMES,
1081        ("Update cost: %ld chars, %ld clocks system time, %ld clocks user time",
1082         _nc_outchars,
1083         (long) (after.tms_stime - before.tms_stime),
1084         (long) (after.tms_utime - before.tms_utime)));
1085 #endif /* USE_TRACE_TIMES */
1086
1087     _nc_signal_handler(TRUE);
1088
1089     _nc_unlock_global(update);
1090     returnCode(OK);
1091 }
1092
1093 #if NCURSES_SP_FUNCS && !defined(USE_TERM_DRIVER)
1094 NCURSES_EXPORT(int)
1095 doupdate(void)
1096 {
1097     return TINFO_DOUPDATE(CURRENT_SCREEN);
1098 }
1099 #endif
1100
1101 /*
1102  *      ClrBlank(win)
1103  *
1104  *      Returns the attributed character that corresponds to the "cleared"
1105  *      screen.  If the terminal has the back-color-erase feature, this will be
1106  *      colored according to the wbkgd() call.
1107  *
1108  *      We treat 'curscr' specially because it isn't supposed to be set directly
1109  *      in the wbkgd() call.  Assume 'stdscr' for this case.
1110  */
1111 #define BCE_ATTRS (A_NORMAL|A_COLOR)
1112 #define BCE_BKGD(sp,win) (((win) == CurScreen(sp) ? StdScreen(sp) : (win))->_nc_bkgd)
1113
1114 static NCURSES_INLINE NCURSES_CH_T
1115 ClrBlank(NCURSES_SP_DCLx WINDOW *win)
1116 {
1117     NCURSES_CH_T blank = blankchar;
1118     if (back_color_erase)
1119         AddAttr(blank, (AttrOf(BCE_BKGD(SP_PARM, win)) & BCE_ATTRS));
1120     return blank;
1121 }
1122
1123 /*
1124 **      ClrUpdate()
1125 **
1126 **      Update by clearing and redrawing the entire screen.
1127 **
1128 */
1129
1130 static void
1131 ClrUpdate(NCURSES_SP_DCL0)
1132 {
1133     TR(TRACE_UPDATE, (T_CALLED("ClrUpdate")));
1134     if (0 != SP_PARM) {
1135         int i;
1136         NCURSES_CH_T blank = ClrBlank(NCURSES_SP_ARGx StdScreen(SP_PARM));
1137         int nonempty = min(screen_lines(SP_PARM),
1138                            NewScreen(SP_PARM)->_maxy + 1);
1139
1140         ClearScreen(NCURSES_SP_ARGx blank);
1141
1142         TR(TRACE_UPDATE, ("updating screen from scratch"));
1143
1144         nonempty = ClrBottom(NCURSES_SP_ARGx nonempty);
1145
1146         for (i = 0; i < nonempty; i++)
1147             TransformLine(NCURSES_SP_ARGx i);
1148     }
1149     TR(TRACE_UPDATE, (T_RETURN("")));
1150 }
1151
1152 /*
1153 **      ClrToEOL(blank)
1154 **
1155 **      Clear to end of current line, starting at the cursor position
1156 */
1157
1158 static void
1159 ClrToEOL(NCURSES_SP_DCLx NCURSES_CH_T blank, int needclear)
1160 {
1161     if (CurScreen(SP_PARM) != 0
1162         && SP_PARM->_cursrow >= 0) {
1163         int j;
1164
1165         for (j = SP_PARM->_curscol; j < screen_columns(SP_PARM); j++) {
1166             if (j >= 0) {
1167                 NCURSES_CH_T *cp =
1168                 &(CurScreen(SP_PARM)->_line[SP_PARM->_cursrow].text[j]);
1169
1170                 if (!CharEq(*cp, blank)) {
1171                     *cp = blank;
1172                     needclear = TRUE;
1173                 }
1174             }
1175         }
1176     }
1177
1178     if (needclear) {
1179         UpdateAttrs(SP_PARM, blank);
1180         if (clr_eol && SP_PARM->_el_cost <= (screen_columns(SP_PARM) - SP_PARM->_curscol)) {
1181             NCURSES_PUTP2("clr_eol", clr_eol);
1182         } else {
1183             int count = (screen_columns(SP_PARM) - SP_PARM->_curscol);
1184             while (count-- > 0)
1185                 PutChar(NCURSES_SP_ARGx CHREF(blank));
1186         }
1187     }
1188 }
1189
1190 /*
1191 **      ClrToEOS(blank)
1192 **
1193 **      Clear to end of screen, starting at the cursor position
1194 */
1195
1196 static void
1197 ClrToEOS(NCURSES_SP_DCLx NCURSES_CH_T blank)
1198 {
1199     int row, col;
1200
1201     row = SP_PARM->_cursrow;
1202     col = SP_PARM->_curscol;
1203
1204     if (row < 0)
1205         row = 0;
1206     if (col < 0)
1207         col = 0;
1208
1209     UpdateAttrs(SP_PARM, blank);
1210     TPUTS_TRACE("clr_eos");
1211     NCURSES_SP_NAME(tputs) (NCURSES_SP_ARGx
1212                             clr_eos,
1213                             screen_lines(SP_PARM) - row,
1214                             NCURSES_SP_NAME(_nc_outch));
1215
1216     while (col < screen_columns(SP_PARM))
1217         CurScreen(SP_PARM)->_line[row].text[col++] = blank;
1218
1219     for (row++; row < screen_lines(SP_PARM); row++) {
1220         for (col = 0; col < screen_columns(SP_PARM); col++)
1221             CurScreen(SP_PARM)->_line[row].text[col] = blank;
1222     }
1223 }
1224
1225 /*
1226  *      ClrBottom(total)
1227  *
1228  *      Test if clearing the end of the screen would satisfy part of the
1229  *      screen-update.  Do this by scanning backwards through the lines in the
1230  *      screen, checking if each is blank, and one or more are changed.
1231  */
1232 static int
1233 ClrBottom(NCURSES_SP_DCLx int total)
1234 {
1235     int top = total;
1236     int last = min(screen_columns(SP_PARM), NewScreen(SP_PARM)->_maxx + 1);
1237     NCURSES_CH_T blank = NewScreen(SP_PARM)->_line[total - 1].text[last - 1];
1238
1239     if (clr_eos && can_clear_with(NCURSES_SP_ARGx CHREF(blank))) {
1240         int row;
1241
1242         for (row = total - 1; row >= 0; row--) {
1243             int col;
1244             bool ok;
1245
1246             for (col = 0, ok = TRUE; ok && col < last; col++) {
1247                 ok = (CharEq(NewScreen(SP_PARM)->_line[row].text[col], blank));
1248             }
1249             if (!ok)
1250                 break;
1251
1252             for (col = 0; ok && col < last; col++) {
1253                 ok = (CharEq(CurScreen(SP_PARM)->_line[row].text[col], blank));
1254             }
1255             if (!ok)
1256                 top = row;
1257         }
1258
1259         /* don't use clr_eos for just one line if clr_eol available */
1260         if (top < total) {
1261             GoTo(NCURSES_SP_ARGx top, 0);
1262             ClrToEOS(NCURSES_SP_ARGx blank);
1263             if (SP_PARM->oldhash && SP_PARM->newhash) {
1264                 for (row = top; row < screen_lines(SP_PARM); row++)
1265                     SP_PARM->oldhash[row] = SP_PARM->newhash[row];
1266             }
1267         }
1268     }
1269     return top;
1270 }
1271
1272 #if USE_XMC_SUPPORT
1273 #if USE_WIDEC_SUPPORT
1274 #define check_xmc_transition(sp, a, b)                                  \
1275     ((((a)->attr ^ (b)->attr) & ~((a)->attr) & (sp)->_xmc_triggers) != 0)
1276 #define xmc_turn_on(sp,a,b) check_xmc_transition(sp,&(a), &(b))
1277 #else
1278 #define xmc_turn_on(sp,a,b) ((((a)^(b)) & ~(a) & (sp)->_xmc_triggers) != 0)
1279 #endif
1280
1281 #define xmc_new(sp,r,c) NewScreen(sp)->_line[r].text[c]
1282 #define xmc_turn_off(sp,a,b) xmc_turn_on(sp,b,a)
1283 #endif /* USE_XMC_SUPPORT */
1284
1285 /*
1286 **      TransformLine(lineno)
1287 **
1288 **      Transform the given line in curscr to the one in newscr, using
1289 **      Insert/Delete Character if idcok && has_ic().
1290 **
1291 **              firstChar = position of first different character in line
1292 **              oLastChar = position of last different character in old line
1293 **              nLastChar = position of last different character in new line
1294 **
1295 **              move to firstChar
1296 **              overwrite chars up to min(oLastChar, nLastChar)
1297 **              if oLastChar < nLastChar
1298 **                      insert newLine[oLastChar+1..nLastChar]
1299 **              else
1300 **                      delete oLastChar - nLastChar spaces
1301 */
1302
1303 static void
1304 TransformLine(NCURSES_SP_DCLx int const lineno)
1305 {
1306     int firstChar, oLastChar, nLastChar;
1307     NCURSES_CH_T *newLine = NewScreen(SP_PARM)->_line[lineno].text;
1308     NCURSES_CH_T *oldLine = CurScreen(SP_PARM)->_line[lineno].text;
1309     int n;
1310     bool attrchanged = FALSE;
1311
1312     TR(TRACE_UPDATE, (T_CALLED("TransformLine(%p, %d)"), (void *) SP_PARM, lineno));
1313
1314     /* copy new hash value to old one */
1315     if (SP_PARM->oldhash && SP_PARM->newhash)
1316         SP_PARM->oldhash[lineno] = SP_PARM->newhash[lineno];
1317
1318     /*
1319      * If we have colors, there is the possibility of having two color pairs
1320      * that display as the same colors.  For instance, Lynx does this.  Check
1321      * for this case, and update the old line with the new line's colors when
1322      * they are equivalent.
1323      */
1324     if (SP_PARM->_coloron) {
1325         int oldPair;
1326         int newPair;
1327
1328         for (n = 0; n < screen_columns(SP_PARM); n++) {
1329             if (!CharEq(newLine[n], oldLine[n])) {
1330                 oldPair = GetPair(oldLine[n]);
1331                 newPair = GetPair(newLine[n]);
1332                 if (oldPair != newPair
1333                     && unColor(oldLine[n]) == unColor(newLine[n])) {
1334                     if (oldPair < SP_PARM->_pair_alloc
1335                         && newPair < SP_PARM->_pair_alloc
1336                         && (isSamePair(SP_PARM->_color_pairs[oldPair],
1337                                        SP_PARM->_color_pairs[newPair]))) {
1338                         SetPair(oldLine[n], GetPair(newLine[n]));
1339                     }
1340                 }
1341             }
1342         }
1343     }
1344
1345     if (ceol_standout_glitch && clr_eol) {
1346         firstChar = 0;
1347         while (firstChar < screen_columns(SP_PARM)) {
1348             if (!SameAttrOf(newLine[firstChar], oldLine[firstChar])) {
1349                 attrchanged = TRUE;
1350                 break;
1351             }
1352             firstChar++;
1353         }
1354     }
1355
1356     firstChar = 0;
1357
1358     if (attrchanged) {          /* we may have to disregard the whole line */
1359         GoTo(NCURSES_SP_ARGx lineno, firstChar);
1360         ClrToEOL(NCURSES_SP_ARGx
1361                  ClrBlank(NCURSES_SP_ARGx
1362                           CurScreen(SP_PARM)), FALSE);
1363         PutRange(NCURSES_SP_ARGx
1364                  oldLine, newLine, lineno, 0,
1365                  screen_columns(SP_PARM) - 1);
1366 #if USE_XMC_SUPPORT
1367
1368         /*
1369          * This is a very simple loop to paint characters which may have the
1370          * magic cookie glitch embedded.  It doesn't know much about video
1371          * attributes which are continued from one line to the next.  It
1372          * assumes that we have filtered out requests for attribute changes
1373          * that do not get mapped to blank positions.
1374          *
1375          * FIXME: we are not keeping track of where we put the cookies, so this
1376          * will work properly only once, since we may overwrite a cookie in a
1377          * following operation.
1378          */
1379     } else if (magic_cookie_glitch > 0) {
1380         GoTo(NCURSES_SP_ARGx lineno, firstChar);
1381         for (n = 0; n < screen_columns(SP_PARM); n++) {
1382             int m = n + magic_cookie_glitch;
1383
1384             /* check for turn-on:
1385              * If we are writing an attributed blank, where the
1386              * previous cell is not attributed.
1387              */
1388             if (ISBLANK(newLine[n])
1389                 && ((n > 0
1390                      && xmc_turn_on(SP_PARM, newLine[n - 1], newLine[n]))
1391                     || (n == 0
1392                         && lineno > 0
1393                         && xmc_turn_on(SP_PARM,
1394                                        xmc_new(SP_PARM, lineno - 1,
1395                                                screen_columns(SP_PARM) - 1),
1396                                        newLine[n])))) {
1397                 n = m;
1398             }
1399
1400             PutChar(NCURSES_SP_ARGx CHREF(newLine[n]));
1401
1402             /* check for turn-off:
1403              * If we are writing an attributed non-blank, where the
1404              * next cell is blank, and not attributed.
1405              */
1406             if (!ISBLANK(newLine[n])
1407                 && ((n + 1 < screen_columns(SP_PARM)
1408                      && xmc_turn_off(SP_PARM, newLine[n], newLine[n + 1]))
1409                     || (n + 1 >= screen_columns(SP_PARM)
1410                         && lineno + 1 < screen_lines(SP_PARM)
1411                         && xmc_turn_off(SP_PARM,
1412                                         newLine[n],
1413                                         xmc_new(SP_PARM, lineno + 1, 0))))) {
1414                 n = m;
1415             }
1416
1417         }
1418 #endif
1419     } else {
1420         NCURSES_CH_T blank;
1421
1422         /* it may be cheap to clear leading whitespace with clr_bol */
1423         blank = newLine[0];
1424         if (clr_bol && can_clear_with(NCURSES_SP_ARGx CHREF(blank))) {
1425             int oFirstChar, nFirstChar;
1426
1427             for (oFirstChar = 0;
1428                  oFirstChar < screen_columns(SP_PARM);
1429                  oFirstChar++)
1430                 if (!CharEq(oldLine[oFirstChar], blank))
1431                     break;
1432             for (nFirstChar = 0;
1433                  nFirstChar < screen_columns(SP_PARM);
1434                  nFirstChar++)
1435                 if (!CharEq(newLine[nFirstChar], blank))
1436                     break;
1437
1438             if (nFirstChar == oFirstChar) {
1439                 firstChar = nFirstChar;
1440                 /* find the first differing character */
1441                 while (firstChar < screen_columns(SP_PARM)
1442                        && CharEq(newLine[firstChar], oldLine[firstChar]))
1443                     firstChar++;
1444             } else if (oFirstChar > nFirstChar) {
1445                 firstChar = nFirstChar;
1446             } else {            /* oFirstChar < nFirstChar */
1447                 firstChar = oFirstChar;
1448                 if (SP_PARM->_el1_cost < nFirstChar - oFirstChar) {
1449                     if (nFirstChar >= screen_columns(SP_PARM)
1450                         && SP_PARM->_el_cost <= SP_PARM->_el1_cost) {
1451                         GoTo(NCURSES_SP_ARGx lineno, 0);
1452                         UpdateAttrs(SP_PARM, blank);
1453                         NCURSES_PUTP2("clr_eol", clr_eol);
1454                     } else {
1455                         GoTo(NCURSES_SP_ARGx lineno, nFirstChar - 1);
1456                         UpdateAttrs(SP_PARM, blank);
1457                         NCURSES_PUTP2("clr_bol", clr_bol);
1458                     }
1459
1460                     while (firstChar < nFirstChar)
1461                         oldLine[firstChar++] = blank;
1462                 }
1463             }
1464         } else {
1465             /* find the first differing character */
1466             while (firstChar < screen_columns(SP_PARM)
1467                    && CharEq(newLine[firstChar], oldLine[firstChar]))
1468                 firstChar++;
1469         }
1470         /* if there wasn't one, we're done */
1471         if (firstChar >= screen_columns(SP_PARM)) {
1472             TR(TRACE_UPDATE, (T_RETURN("")));
1473             return;
1474         }
1475
1476         blank = newLine[screen_columns(SP_PARM) - 1];
1477
1478         if (!can_clear_with(NCURSES_SP_ARGx CHREF(blank))) {
1479             /* find the last differing character */
1480             nLastChar = screen_columns(SP_PARM) - 1;
1481
1482             while (nLastChar > firstChar
1483                    && CharEq(newLine[nLastChar], oldLine[nLastChar]))
1484                 nLastChar--;
1485
1486             if (nLastChar >= firstChar) {
1487                 GoTo(NCURSES_SP_ARGx lineno, firstChar);
1488                 PutRange(NCURSES_SP_ARGx
1489                          oldLine,
1490                          newLine,
1491                          lineno,
1492                          firstChar,
1493                          nLastChar);
1494                 memcpy(oldLine + firstChar,
1495                        newLine + firstChar,
1496                        (unsigned) (nLastChar - firstChar + 1) * sizeof(NCURSES_CH_T));
1497             }
1498             TR(TRACE_UPDATE, (T_RETURN("")));
1499             return;
1500         }
1501
1502         /* find last non-blank character on old line */
1503         oLastChar = screen_columns(SP_PARM) - 1;
1504         while (oLastChar > firstChar && CharEq(oldLine[oLastChar], blank))
1505             oLastChar--;
1506
1507         /* find last non-blank character on new line */
1508         nLastChar = screen_columns(SP_PARM) - 1;
1509         while (nLastChar > firstChar && CharEq(newLine[nLastChar], blank))
1510             nLastChar--;
1511
1512         if ((nLastChar == firstChar)
1513             && (SP_PARM->_el_cost < (oLastChar - nLastChar))) {
1514             GoTo(NCURSES_SP_ARGx lineno, firstChar);
1515             if (!CharEq(newLine[firstChar], blank))
1516                 PutChar(NCURSES_SP_ARGx CHREF(newLine[firstChar]));
1517             ClrToEOL(NCURSES_SP_ARGx blank, FALSE);
1518         } else if ((nLastChar != oLastChar)
1519                    && (!CharEq(newLine[nLastChar], oldLine[oLastChar])
1520                        || !(SP_PARM->_nc_sp_idcok
1521                             && NCURSES_SP_NAME(has_ic) (NCURSES_SP_ARG)))) {
1522             GoTo(NCURSES_SP_ARGx lineno, firstChar);
1523             if ((oLastChar - nLastChar) > SP_PARM->_el_cost) {
1524                 if (PutRange(NCURSES_SP_ARGx
1525                              oldLine,
1526                              newLine,
1527                              lineno,
1528                              firstChar,
1529                              nLastChar)) {
1530                     GoTo(NCURSES_SP_ARGx lineno, nLastChar + 1);
1531                 }
1532                 ClrToEOL(NCURSES_SP_ARGx blank, FALSE);
1533             } else {
1534                 n = max(nLastChar, oLastChar);
1535                 PutRange(NCURSES_SP_ARGx
1536                          oldLine,
1537                          newLine,
1538                          lineno,
1539                          firstChar,
1540                          n);
1541             }
1542         } else {
1543             int nLastNonblank = nLastChar;
1544             int oLastNonblank = oLastChar;
1545
1546             /* find the last characters that really differ */
1547             /* can be -1 if no characters differ */
1548             while (CharEq(newLine[nLastChar], oldLine[oLastChar])) {
1549                 /* don't split a wide char */
1550                 if (isWidecExt(newLine[nLastChar]) &&
1551                     !CharEq(newLine[nLastChar - 1], oldLine[oLastChar - 1]))
1552                     break;
1553                 nLastChar--;
1554                 oLastChar--;
1555                 if (nLastChar == -1 || oLastChar == -1)
1556                     break;
1557             }
1558
1559             n = min(oLastChar, nLastChar);
1560             if (n >= firstChar) {
1561                 GoTo(NCURSES_SP_ARGx lineno, firstChar);
1562                 PutRange(NCURSES_SP_ARGx
1563                          oldLine,
1564                          newLine,
1565                          lineno,
1566                          firstChar,
1567                          n);
1568             }
1569
1570             if (oLastChar < nLastChar) {
1571                 int m = max(nLastNonblank, oLastNonblank);
1572 #if USE_WIDEC_SUPPORT
1573                 if (n) {
1574                     while (isWidecExt(newLine[n + 1]) && n) {
1575                         --n;
1576                         --oLastChar;    /* increase cost */
1577                     }
1578                 } else if (n >= firstChar &&
1579                            isWidecBase(newLine[n])) {
1580                     while (isWidecExt(newLine[n + 1])) {
1581                         ++n;
1582                         ++oLastChar;    /* decrease cost */
1583                     }
1584                 }
1585 #endif
1586                 GoTo(NCURSES_SP_ARGx lineno, n + 1);
1587                 if ((nLastChar < nLastNonblank)
1588                     || InsCharCost(SP_PARM, nLastChar - oLastChar) > (m - n)) {
1589                     PutRange(NCURSES_SP_ARGx
1590                              oldLine,
1591                              newLine,
1592                              lineno,
1593                              n + 1,
1594                              m);
1595                 } else {
1596                     InsStr(NCURSES_SP_ARGx &newLine[n + 1], nLastChar - oLastChar);
1597                 }
1598             } else if (oLastChar > nLastChar) {
1599                 GoTo(NCURSES_SP_ARGx lineno, n + 1);
1600                 if (DelCharCost(SP_PARM, oLastChar - nLastChar)
1601                     > SP_PARM->_el_cost + nLastNonblank - (n + 1)) {
1602                     if (PutRange(NCURSES_SP_ARGx oldLine, newLine, lineno,
1603                                  n + 1, nLastNonblank)) {
1604                         GoTo(NCURSES_SP_ARGx lineno, nLastNonblank + 1);
1605                     }
1606                     ClrToEOL(NCURSES_SP_ARGx blank, FALSE);
1607                 } else {
1608                     /*
1609                      * The delete-char sequence will
1610                      * effectively shift in blanks from the
1611                      * right margin of the screen.  Ensure
1612                      * that they are the right color by
1613                      * setting the video attributes from
1614                      * the last character on the row.
1615                      */
1616                     UpdateAttrs(SP_PARM, blank);
1617                     DelChar(NCURSES_SP_ARGx oLastChar - nLastChar);
1618                 }
1619             }
1620         }
1621     }
1622
1623     /* update the code's internal representation */
1624     if (screen_columns(SP_PARM) > firstChar)
1625         memcpy(oldLine + firstChar,
1626                newLine + firstChar,
1627                (unsigned) (screen_columns(SP_PARM) - firstChar) * sizeof(NCURSES_CH_T));
1628     TR(TRACE_UPDATE, (T_RETURN("")));
1629     return;
1630 }
1631
1632 /*
1633 **      ClearScreen(blank)
1634 **
1635 **      Clear the physical screen and put cursor at home
1636 **
1637 */
1638
1639 static void
1640 ClearScreen(NCURSES_SP_DCLx NCURSES_CH_T blank)
1641 {
1642     int i, j;
1643     bool fast_clear = (clear_screen || clr_eos || clr_eol);
1644
1645     TR(TRACE_UPDATE, ("ClearScreen() called"));
1646
1647 #if NCURSES_EXT_FUNCS
1648     if (SP_PARM->_coloron
1649         && !SP_PARM->_default_color) {
1650         NCURSES_SP_NAME(_nc_do_color) (NCURSES_SP_ARGx
1651                                        (short) GET_SCREEN_PAIR(SP_PARM),
1652                                        0,
1653                                        FALSE,
1654                                        NCURSES_SP_NAME(_nc_outch));
1655         if (!back_color_erase) {
1656             fast_clear = FALSE;
1657         }
1658     }
1659 #endif
1660
1661     if (fast_clear) {
1662         if (clear_screen) {
1663             UpdateAttrs(SP_PARM, blank);
1664             NCURSES_PUTP2("clear_screen", clear_screen);
1665             SP_PARM->_cursrow = SP_PARM->_curscol = 0;
1666             position_check(NCURSES_SP_ARGx
1667                            SP_PARM->_cursrow,
1668                            SP_PARM->_curscol,
1669                            "ClearScreen");
1670         } else if (clr_eos) {
1671             SP_PARM->_cursrow = SP_PARM->_curscol = -1;
1672             GoTo(NCURSES_SP_ARGx 0, 0);
1673             UpdateAttrs(SP_PARM, blank);
1674             TPUTS_TRACE("clr_eos");
1675             NCURSES_SP_NAME(tputs) (NCURSES_SP_ARGx
1676                                     clr_eos,
1677                                     screen_lines(SP_PARM),
1678                                     NCURSES_SP_NAME(_nc_outch));
1679         } else if (clr_eol) {
1680             SP_PARM->_cursrow = SP_PARM->_curscol = -1;
1681             UpdateAttrs(SP_PARM, blank);
1682             for (i = 0; i < screen_lines(SP_PARM); i++) {
1683                 GoTo(NCURSES_SP_ARGx i, 0);
1684                 NCURSES_PUTP2("clr_eol", clr_eol);
1685             }
1686             GoTo(NCURSES_SP_ARGx 0, 0);
1687         }
1688     } else {
1689         UpdateAttrs(SP_PARM, blank);
1690         for (i = 0; i < screen_lines(SP_PARM); i++) {
1691             GoTo(NCURSES_SP_ARGx i, 0);
1692             for (j = 0; j < screen_columns(SP_PARM); j++)
1693                 PutChar(NCURSES_SP_ARGx CHREF(blank));
1694         }
1695         GoTo(NCURSES_SP_ARGx 0, 0);
1696     }
1697
1698     for (i = 0; i < screen_lines(SP_PARM); i++) {
1699         for (j = 0; j < screen_columns(SP_PARM); j++)
1700             CurScreen(SP_PARM)->_line[i].text[j] = blank;
1701     }
1702
1703     TR(TRACE_UPDATE, ("screen cleared"));
1704 }
1705
1706 /*
1707 **      InsStr(line, count)
1708 **
1709 **      Insert the count characters pointed to by line.
1710 **
1711 */
1712
1713 static void
1714 InsStr(NCURSES_SP_DCLx NCURSES_CH_T *line, int count)
1715 {
1716     TR(TRACE_UPDATE, ("InsStr(%p, %p,%d) called",
1717                       (void *) SP_PARM,
1718                       (void *) line, count));
1719
1720     /* Prefer parm_ich as it has the smallest cost - no need to shift
1721      * the whole line on each character. */
1722     /* The order must match that of InsCharCost. */
1723     if (parm_ich) {
1724         TPUTS_TRACE("parm_ich");
1725         NCURSES_SP_NAME(tputs) (NCURSES_SP_ARGx
1726                                 TIPARM_1(parm_ich, count),
1727                                 1,
1728                                 NCURSES_SP_NAME(_nc_outch));
1729         while (count > 0) {
1730             PutAttrChar(NCURSES_SP_ARGx CHREF(*line));
1731             line++;
1732             count--;
1733         }
1734     } else if (enter_insert_mode && exit_insert_mode) {
1735         NCURSES_PUTP2("enter_insert_mode", enter_insert_mode);
1736         while (count > 0) {
1737             PutAttrChar(NCURSES_SP_ARGx CHREF(*line));
1738             if (insert_padding) {
1739                 NCURSES_PUTP2("insert_padding", insert_padding);
1740             }
1741             line++;
1742             count--;
1743         }
1744         NCURSES_PUTP2("exit_insert_mode", exit_insert_mode);
1745     } else {
1746         while (count > 0) {
1747             NCURSES_PUTP2("insert_character", insert_character);
1748             PutAttrChar(NCURSES_SP_ARGx CHREF(*line));
1749             if (insert_padding) {
1750                 NCURSES_PUTP2("insert_padding", insert_padding);
1751             }
1752             line++;
1753             count--;
1754         }
1755     }
1756     position_check(NCURSES_SP_ARGx
1757                    SP_PARM->_cursrow,
1758                    SP_PARM->_curscol, "InsStr");
1759 }
1760
1761 /*
1762 **      DelChar(count)
1763 **
1764 **      Delete count characters at current position
1765 **
1766 */
1767
1768 static void
1769 DelChar(NCURSES_SP_DCLx int count)
1770 {
1771     TR(TRACE_UPDATE, ("DelChar(%p, %d) called, position = (%ld,%ld)",
1772                       (void *) SP_PARM, count,
1773                       (long) NewScreen(SP_PARM)->_cury,
1774                       (long) NewScreen(SP_PARM)->_curx));
1775
1776     if (parm_dch) {
1777         TPUTS_TRACE("parm_dch");
1778         NCURSES_SP_NAME(tputs) (NCURSES_SP_ARGx
1779                                 TIPARM_1(parm_dch, count),
1780                                 1,
1781                                 NCURSES_SP_NAME(_nc_outch));
1782     } else {
1783         int n;
1784
1785         for (n = 0; n < count; n++) {
1786             NCURSES_PUTP2("delete_character", delete_character);
1787         }
1788     }
1789 }
1790
1791 /*
1792  * Physical-scrolling support
1793  *
1794  * This code was adapted from Keith Bostic's hardware scrolling
1795  * support for 4.4BSD curses.  I (esr) translated it to use terminfo
1796  * capabilities, narrowed the call interface slightly, and cleaned
1797  * up some convoluted tests.  I also added support for the memory_above
1798  * memory_below, and non_dest_scroll_region capabilities.
1799  *
1800  * For this code to work, we must have either
1801  * change_scroll_region and scroll forward/reverse commands, or
1802  * insert and delete line capabilities.
1803  * When the scrolling region has been set, the cursor has to
1804  * be at the last line of the region to make the scroll up
1805  * happen, or on the first line of region to scroll down.
1806  *
1807  * This code makes one aesthetic decision in the opposite way from
1808  * BSD curses.  BSD curses preferred pairs of il/dl operations
1809  * over scrolls, allegedly because il/dl looked faster.  We, on
1810  * the other hand, prefer scrolls because (a) they're just as fast
1811  * on many terminals and (b) using them avoids bouncing an
1812  * unchanged bottom section of the screen up and down, which is
1813  * visually nasty.
1814  *
1815  * (lav): added more cases, used dl/il when bot==maxy and in csr case.
1816  *
1817  * I used assumption that capabilities il/il1/dl/dl1 work inside
1818  * changed scroll region not shifting screen contents outside of it.
1819  * If there are any terminals behaving different way, it would be
1820  * necessary to add some conditions to scroll_csr_forward/backward.
1821  */
1822
1823 /* Try to scroll up assuming given csr (miny, maxy). Returns ERR on failure */
1824 static int
1825 scroll_csr_forward(NCURSES_SP_DCLx
1826                    int n,
1827                    int top,
1828                    int bot,
1829                    int miny,
1830                    int maxy,
1831                    NCURSES_CH_T blank)
1832 {
1833     int i;
1834
1835     if (n == 1 && scroll_forward && top == miny && bot == maxy) {
1836         GoTo(NCURSES_SP_ARGx bot, 0);
1837         UpdateAttrs(SP_PARM, blank);
1838         NCURSES_PUTP2("scroll_forward", scroll_forward);
1839     } else if (n == 1 && delete_line && bot == maxy) {
1840         GoTo(NCURSES_SP_ARGx top, 0);
1841         UpdateAttrs(SP_PARM, blank);
1842         NCURSES_PUTP2("delete_line", delete_line);
1843     } else if (parm_index && top == miny && bot == maxy) {
1844         GoTo(NCURSES_SP_ARGx bot, 0);
1845         UpdateAttrs(SP_PARM, blank);
1846         TPUTS_TRACE("parm_index");
1847         NCURSES_SP_NAME(tputs) (NCURSES_SP_ARGx
1848                                 TIPARM_1(parm_index, n),
1849                                 n,
1850                                 NCURSES_SP_NAME(_nc_outch));
1851     } else if (parm_delete_line && bot == maxy) {
1852         GoTo(NCURSES_SP_ARGx top, 0);
1853         UpdateAttrs(SP_PARM, blank);
1854         TPUTS_TRACE("parm_delete_line");
1855         NCURSES_SP_NAME(tputs) (NCURSES_SP_ARGx
1856                                 TIPARM_1(parm_delete_line, n),
1857                                 n,
1858                                 NCURSES_SP_NAME(_nc_outch));
1859     } else if (scroll_forward && top == miny && bot == maxy) {
1860         GoTo(NCURSES_SP_ARGx bot, 0);
1861         UpdateAttrs(SP_PARM, blank);
1862         for (i = 0; i < n; i++) {
1863             NCURSES_PUTP2("scroll_forward", scroll_forward);
1864         }
1865     } else if (delete_line && bot == maxy) {
1866         GoTo(NCURSES_SP_ARGx top, 0);
1867         UpdateAttrs(SP_PARM, blank);
1868         for (i = 0; i < n; i++) {
1869             NCURSES_PUTP2("delete_line", delete_line);
1870         }
1871     } else
1872         return ERR;
1873
1874 #if NCURSES_EXT_FUNCS
1875     if (FILL_BCE(SP_PARM)) {
1876         int j;
1877         for (i = 0; i < n; i++) {
1878             GoTo(NCURSES_SP_ARGx bot - i, 0);
1879             for (j = 0; j < screen_columns(SP_PARM); j++)
1880                 PutChar(NCURSES_SP_ARGx CHREF(blank));
1881         }
1882     }
1883 #endif
1884     return OK;
1885 }
1886
1887 /* Try to scroll down assuming given csr (miny, maxy). Returns ERR on failure */
1888 /* n > 0 */
1889 static int
1890 scroll_csr_backward(NCURSES_SP_DCLx
1891                     int n,
1892                     int top,
1893                     int bot,
1894                     int miny,
1895                     int maxy,
1896                     NCURSES_CH_T blank)
1897 {
1898     int i;
1899
1900     if (n == 1 && scroll_reverse && top == miny && bot == maxy) {
1901         GoTo(NCURSES_SP_ARGx top, 0);
1902         UpdateAttrs(SP_PARM, blank);
1903         NCURSES_PUTP2("scroll_reverse", scroll_reverse);
1904     } else if (n == 1 && insert_line && bot == maxy) {
1905         GoTo(NCURSES_SP_ARGx top, 0);
1906         UpdateAttrs(SP_PARM, blank);
1907         NCURSES_PUTP2("insert_line", insert_line);
1908     } else if (parm_rindex && top == miny && bot == maxy) {
1909         GoTo(NCURSES_SP_ARGx top, 0);
1910         UpdateAttrs(SP_PARM, blank);
1911         TPUTS_TRACE("parm_rindex");
1912         NCURSES_SP_NAME(tputs) (NCURSES_SP_ARGx
1913                                 TIPARM_1(parm_rindex, n),
1914                                 n,
1915                                 NCURSES_SP_NAME(_nc_outch));
1916     } else if (parm_insert_line && bot == maxy) {
1917         GoTo(NCURSES_SP_ARGx top, 0);
1918         UpdateAttrs(SP_PARM, blank);
1919         TPUTS_TRACE("parm_insert_line");
1920         NCURSES_SP_NAME(tputs) (NCURSES_SP_ARGx
1921                                 TIPARM_1(parm_insert_line, n),
1922                                 n,
1923                                 NCURSES_SP_NAME(_nc_outch));
1924     } else if (scroll_reverse && top == miny && bot == maxy) {
1925         GoTo(NCURSES_SP_ARGx top, 0);
1926         UpdateAttrs(SP_PARM, blank);
1927         for (i = 0; i < n; i++) {
1928             NCURSES_PUTP2("scroll_reverse", scroll_reverse);
1929         }
1930     } else if (insert_line && bot == maxy) {
1931         GoTo(NCURSES_SP_ARGx top, 0);
1932         UpdateAttrs(SP_PARM, blank);
1933         for (i = 0; i < n; i++) {
1934             NCURSES_PUTP2("insert_line", insert_line);
1935         }
1936     } else
1937         return ERR;
1938
1939 #if NCURSES_EXT_FUNCS
1940     if (FILL_BCE(SP_PARM)) {
1941         int j;
1942         for (i = 0; i < n; i++) {
1943             GoTo(NCURSES_SP_ARGx top + i, 0);
1944             for (j = 0; j < screen_columns(SP_PARM); j++)
1945                 PutChar(NCURSES_SP_ARGx CHREF(blank));
1946         }
1947     }
1948 #endif
1949     return OK;
1950 }
1951
1952 /* scroll by using delete_line at del and insert_line at ins */
1953 /* n > 0 */
1954 static int
1955 scroll_idl(NCURSES_SP_DCLx int n, int del, int ins, NCURSES_CH_T blank)
1956 {
1957     int i;
1958
1959     if (!((parm_delete_line || delete_line) && (parm_insert_line || insert_line)))
1960         return ERR;
1961
1962     GoTo(NCURSES_SP_ARGx del, 0);
1963     UpdateAttrs(SP_PARM, blank);
1964     if (n == 1 && delete_line) {
1965         NCURSES_PUTP2("delete_line", delete_line);
1966     } else if (parm_delete_line) {
1967         TPUTS_TRACE("parm_delete_line");
1968         NCURSES_SP_NAME(tputs) (NCURSES_SP_ARGx
1969                                 TIPARM_1(parm_delete_line, n),
1970                                 n,
1971                                 NCURSES_SP_NAME(_nc_outch));
1972     } else {                    /* if (delete_line) */
1973         for (i = 0; i < n; i++) {
1974             NCURSES_PUTP2("delete_line", delete_line);
1975         }
1976     }
1977
1978     GoTo(NCURSES_SP_ARGx ins, 0);
1979     UpdateAttrs(SP_PARM, blank);
1980     if (n == 1 && insert_line) {
1981         NCURSES_PUTP2("insert_line", insert_line);
1982     } else if (parm_insert_line) {
1983         TPUTS_TRACE("parm_insert_line");
1984         NCURSES_SP_NAME(tputs) (NCURSES_SP_ARGx
1985                                 TIPARM_1(parm_insert_line, n),
1986                                 n,
1987                                 NCURSES_SP_NAME(_nc_outch));
1988     } else {                    /* if (insert_line) */
1989         for (i = 0; i < n; i++) {
1990             NCURSES_PUTP2("insert_line", insert_line);
1991         }
1992     }
1993
1994     return OK;
1995 }
1996
1997 /*
1998  * Note:  some terminals require the cursor to be within the scrolling margins
1999  * before setting them.  Generally, the cursor must be at the appropriate end
2000  * of the scrolling margins when issuing an indexing operation (it is not
2001  * apparent whether it must also be at the left margin; we do this just to be
2002  * safe).  To make the related cursor movement a little faster, we use the
2003  * save/restore cursor capabilities if the terminal has them.
2004  */
2005 NCURSES_EXPORT(int)
2006 NCURSES_SP_NAME(_nc_scrolln) (NCURSES_SP_DCLx
2007                               int n,
2008                               int top,
2009                               int bot,
2010                               int maxy)
2011 /* scroll region from top to bot by n lines */
2012 {
2013     NCURSES_CH_T blank;
2014     int i;
2015     bool cursor_saved = FALSE;
2016     int res;
2017
2018     TR(TRACE_MOVE, ("_nc_scrolln(%p, %d, %d, %d, %d)",
2019                     (void *) SP_PARM, n, top, bot, maxy));
2020
2021     if (!IsValidScreen(SP_PARM))
2022         return (ERR);
2023
2024     blank = ClrBlank(NCURSES_SP_ARGx StdScreen(SP_PARM));
2025
2026 #if USE_XMC_SUPPORT
2027     /*
2028      * If we scroll, we might remove a cookie.
2029      */
2030     if (magic_cookie_glitch > 0) {
2031         return (ERR);
2032     }
2033 #endif
2034
2035     if (n > 0) {                /* scroll up (forward) */
2036         /*
2037          * Explicitly clear if stuff pushed off top of region might
2038          * be saved by the terminal.
2039          */
2040         res = scroll_csr_forward(NCURSES_SP_ARGx n, top, bot, 0, maxy, blank);
2041
2042         if (res == ERR && change_scroll_region) {
2043             if ((((n == 1 && scroll_forward) || parm_index)
2044                  && (SP_PARM->_cursrow == bot || SP_PARM->_cursrow == bot - 1))
2045                 && save_cursor && restore_cursor) {
2046                 cursor_saved = TRUE;
2047                 NCURSES_PUTP2("save_cursor", save_cursor);
2048             }
2049             NCURSES_PUTP2("change_scroll_region",
2050                           TIPARM_2(change_scroll_region, top, bot));
2051             if (cursor_saved) {
2052                 NCURSES_PUTP2("restore_cursor", restore_cursor);
2053             } else {
2054                 SP_PARM->_cursrow = SP_PARM->_curscol = -1;
2055             }
2056
2057             res = scroll_csr_forward(NCURSES_SP_ARGx n, top, bot, top, bot, blank);
2058
2059             NCURSES_PUTP2("change_scroll_region",
2060                           TIPARM_2(change_scroll_region, 0, maxy));
2061             SP_PARM->_cursrow = SP_PARM->_curscol = -1;
2062         }
2063
2064         if (res == ERR && SP_PARM->_nc_sp_idlok)
2065             res = scroll_idl(NCURSES_SP_ARGx n, top, bot - n + 1, blank);
2066
2067         /*
2068          * Clear the newly shifted-in text.
2069          */
2070         if (res != ERR
2071             && (non_dest_scroll_region || (memory_below && bot == maxy))) {
2072             static const NCURSES_CH_T blank2 = NewChar(BLANK_TEXT);
2073             if (bot == maxy && clr_eos) {
2074                 GoTo(NCURSES_SP_ARGx bot - n + 1, 0);
2075                 ClrToEOS(NCURSES_SP_ARGx blank2);
2076             } else {
2077                 for (i = 0; i < n; i++) {
2078                     GoTo(NCURSES_SP_ARGx bot - i, 0);
2079                     ClrToEOL(NCURSES_SP_ARGx blank2, FALSE);
2080                 }
2081             }
2082         }
2083
2084     } else {                    /* (n < 0) - scroll down (backward) */
2085         res = scroll_csr_backward(NCURSES_SP_ARGx -n, top, bot, 0, maxy, blank);
2086
2087         if (res == ERR && change_scroll_region) {
2088             if (top != 0
2089                 && (SP_PARM->_cursrow == top ||
2090                     SP_PARM->_cursrow == top - 1)
2091                 && save_cursor && restore_cursor) {
2092                 cursor_saved = TRUE;
2093                 NCURSES_PUTP2("save_cursor", save_cursor);
2094             }
2095             NCURSES_PUTP2("change_scroll_region",
2096                           TIPARM_2(change_scroll_region, top, bot));
2097             if (cursor_saved) {
2098                 NCURSES_PUTP2("restore_cursor", restore_cursor);
2099             } else {
2100                 SP_PARM->_cursrow = SP_PARM->_curscol = -1;
2101             }
2102
2103             res = scroll_csr_backward(NCURSES_SP_ARGx
2104                                       -n, top, bot, top, bot, blank);
2105
2106             NCURSES_PUTP2("change_scroll_region",
2107                           TIPARM_2(change_scroll_region, 0, maxy));
2108             SP_PARM->_cursrow = SP_PARM->_curscol = -1;
2109         }
2110
2111         if (res == ERR && SP_PARM->_nc_sp_idlok)
2112             res = scroll_idl(NCURSES_SP_ARGx -n, bot + n + 1, top, blank);
2113
2114         /*
2115          * Clear the newly shifted-in text.
2116          */
2117         if (res != ERR
2118             && (non_dest_scroll_region || (memory_above && top == 0))) {
2119             static const NCURSES_CH_T blank2 = NewChar(BLANK_TEXT);
2120             for (i = 0; i < -n; i++) {
2121                 GoTo(NCURSES_SP_ARGx i + top, 0);
2122                 ClrToEOL(NCURSES_SP_ARGx blank2, FALSE);
2123             }
2124         }
2125     }
2126
2127     if (res == ERR)
2128         return (ERR);
2129
2130     _nc_scroll_window(CurScreen(SP_PARM), n,
2131                       (NCURSES_SIZE_T) top,
2132                       (NCURSES_SIZE_T) bot,
2133                       blank);
2134
2135     /* shift hash values too - they can be reused */
2136     NCURSES_SP_NAME(_nc_scroll_oldhash) (NCURSES_SP_ARGx n, top, bot);
2137
2138     return (OK);
2139 }
2140
2141 #if NCURSES_SP_FUNCS
2142 NCURSES_EXPORT(int)
2143 _nc_scrolln(int n, int top, int bot, int maxy)
2144 {
2145     return NCURSES_SP_NAME(_nc_scrolln) (CURRENT_SCREEN, n, top, bot, maxy);
2146 }
2147 #endif
2148
2149 NCURSES_EXPORT(void)
2150 NCURSES_SP_NAME(_nc_screen_resume) (NCURSES_SP_DCL0)
2151 {
2152     assert(SP_PARM);
2153
2154     /* make sure terminal is in a sane known state */
2155     SetAttr(SCREEN_ATTRS(SP_PARM), A_NORMAL);
2156     NewScreen(SP_PARM)->_clear = TRUE;
2157
2158     /* reset color pairs and definitions */
2159     if (SP_PARM->_coloron || SP_PARM->_color_defs)
2160         NCURSES_SP_NAME(_nc_reset_colors) (NCURSES_SP_ARG);
2161
2162     /* restore user-defined colors, if any */
2163     if (SP_PARM->_color_defs < 0 && !SP_PARM->_direct_color.value) {
2164         int n;
2165         SP_PARM->_color_defs = -(SP_PARM->_color_defs);
2166         for (n = 0; n < SP_PARM->_color_defs; ++n) {
2167             if (SP_PARM->_color_table[n].init) {
2168                 _nc_init_color(SP_PARM,
2169                                n,
2170                                SP_PARM->_color_table[n].r,
2171                                SP_PARM->_color_table[n].g,
2172                                SP_PARM->_color_table[n].b);
2173             }
2174         }
2175     }
2176
2177     if (exit_attribute_mode)
2178         NCURSES_PUTP2("exit_attribute_mode", exit_attribute_mode);
2179     else {
2180         /* turn off attributes */
2181         if (exit_alt_charset_mode)
2182             NCURSES_PUTP2("exit_alt_charset_mode", exit_alt_charset_mode);
2183         if (exit_standout_mode)
2184             NCURSES_PUTP2("exit_standout_mode", exit_standout_mode);
2185         if (exit_underline_mode)
2186             NCURSES_PUTP2("exit_underline_mode", exit_underline_mode);
2187     }
2188     if (exit_insert_mode)
2189         NCURSES_PUTP2("exit_insert_mode", exit_insert_mode);
2190     if (enter_am_mode && exit_am_mode) {
2191         if (auto_right_margin) {
2192             NCURSES_PUTP2("enter_am_mode", enter_am_mode);
2193         } else {
2194             NCURSES_PUTP2("exit_am_mode", exit_am_mode);
2195         }
2196     }
2197 }
2198
2199 #if NCURSES_SP_FUNCS
2200 NCURSES_EXPORT(void)
2201 _nc_screen_resume(void)
2202 {
2203     NCURSES_SP_NAME(_nc_screen_resume) (CURRENT_SCREEN);
2204 }
2205 #endif
2206
2207 NCURSES_EXPORT(void)
2208 NCURSES_SP_NAME(_nc_screen_init) (NCURSES_SP_DCL0)
2209 {
2210     NCURSES_SP_NAME(_nc_screen_resume) (NCURSES_SP_ARG);
2211 }
2212
2213 #if NCURSES_SP_FUNCS
2214 NCURSES_EXPORT(void)
2215 _nc_screen_init(void)
2216 {
2217     NCURSES_SP_NAME(_nc_screen_init) (CURRENT_SCREEN);
2218 }
2219 #endif
2220
2221 /* wrap up screen handling */
2222 NCURSES_EXPORT(void)
2223 NCURSES_SP_NAME(_nc_screen_wrap) (NCURSES_SP_DCL0)
2224 {
2225     if (SP_PARM != 0) {
2226
2227         UpdateAttrs(SP_PARM, normal);
2228 #if NCURSES_EXT_FUNCS
2229         if (SP_PARM->_coloron
2230             && !SP_PARM->_default_color) {
2231             static const NCURSES_CH_T blank = NewChar(BLANK_TEXT);
2232             SP_PARM->_default_color = TRUE;
2233             NCURSES_SP_NAME(_nc_do_color) (NCURSES_SP_ARGx
2234                                            -1,
2235                                            0,
2236                                            FALSE,
2237                                            NCURSES_SP_NAME(_nc_outch));
2238             SP_PARM->_default_color = FALSE;
2239
2240             TINFO_MVCUR(NCURSES_SP_ARGx
2241                         SP_PARM->_cursrow,
2242                         SP_PARM->_curscol,
2243                         screen_lines(SP_PARM) - 1,
2244                         0);
2245
2246             ClrToEOL(NCURSES_SP_ARGx blank, TRUE);
2247         }
2248 #endif
2249         if (SP_PARM->_color_defs) {
2250             NCURSES_SP_NAME(_nc_reset_colors) (NCURSES_SP_ARG);
2251         }
2252     }
2253 }
2254
2255 #if NCURSES_SP_FUNCS
2256 NCURSES_EXPORT(void)
2257 _nc_screen_wrap(void)
2258 {
2259     NCURSES_SP_NAME(_nc_screen_wrap) (CURRENT_SCREEN);
2260 }
2261 #endif
2262
2263 #if USE_XMC_SUPPORT
2264 NCURSES_EXPORT(void)
2265 NCURSES_SP_NAME(_nc_do_xmc_glitch) (NCURSES_SP_DCLx attr_t previous)
2266 {
2267     if (SP_PARM != 0) {
2268         attr_t chg = XMC_CHANGES(previous ^ AttrOf(SCREEN_ATTRS(SP_PARM)));
2269
2270         while (chg != 0) {
2271             if (chg & 1) {
2272                 SP_PARM->_curscol += magic_cookie_glitch;
2273                 if (SP_PARM->_curscol >= SP_PARM->_columns)
2274                     wrap_cursor(NCURSES_SP_ARG);
2275                 TR(TRACE_UPDATE, ("bumped to %d,%d after cookie",
2276                                   SP_PARM->_cursrow, SP_PARM->_curscol));
2277             }
2278             chg >>= 1;
2279         }
2280     }
2281 }
2282
2283 #if NCURSES_SP_FUNCS
2284 NCURSES_EXPORT(void)
2285 _nc_do_xmc_glitch(attr_t previous)
2286 {
2287     NCURSES_SP_NAME(_nc_do_xmc_glitch) (CURRENT_SCREEN, previous);
2288 }
2289 #endif
2290
2291 #endif /* USE_XMC_SUPPORT */