Consistently use a single notation to refer to SSH protocol versions, as
[u/mdw/putty] / terminal.c
index 0f0c9d8..eaaa04d 100644 (file)
@@ -271,7 +271,7 @@ static int termchars_equal_override(termchar *a, termchar *b,
     /* FULL-TERMCHAR */
     if (a->chr != bchr)
        return FALSE;
-    if (a->attr != battr)
+    if ((a->attr &~ DATTR_MASK) != (battr &~ DATTR_MASK))
        return FALSE;
     while (a->cc_next || b->cc_next) {
        if (!a->cc_next || !b->cc_next)
@@ -554,15 +554,39 @@ static void makeliteral_attr(struct buf *b, termchar *c, unsigned long *state)
      * store a two-byte value with the top bit clear (indicating
      * just that value), or a four-byte value with the top bit set
      * (indicating the same value with its top bit clear).
+     * 
+     * However, first I permute the bits of the attribute value, so
+     * that the eight bits of colour (four in each of fg and bg)
+     * which are never non-zero unless xterm 256-colour mode is in
+     * use are placed higher up the word than everything else. This
+     * ensures that attribute values remain 16-bit _unless_ the
+     * user uses extended colour.
      */
-    if (c->attr < 0x8000) {
-       add(b, (unsigned char)((c->attr >> 8) & 0xFF));
-       add(b, (unsigned char)(c->attr & 0xFF));
+    unsigned attr, colourbits;
+
+    attr = c->attr;
+
+    assert(ATTR_BGSHIFT > ATTR_FGSHIFT);
+
+    colourbits = (attr >> (ATTR_BGSHIFT + 4)) & 0xF;
+    colourbits <<= 4;
+    colourbits |= (attr >> (ATTR_FGSHIFT + 4)) & 0xF;
+
+    attr = (((attr >> (ATTR_BGSHIFT + 8)) << (ATTR_BGSHIFT + 4)) |
+           (attr & ((1 << (ATTR_BGSHIFT + 4))-1)));
+    attr = (((attr >> (ATTR_FGSHIFT + 8)) << (ATTR_FGSHIFT + 4)) |
+           (attr & ((1 << (ATTR_FGSHIFT + 4))-1)));
+
+    attr |= (colourbits << (32-9));
+
+    if (attr < 0x8000) {
+       add(b, (unsigned char)((attr >> 8) & 0xFF));
+       add(b, (unsigned char)(attr & 0xFF));
     } else {
-       add(b, (unsigned char)(((c->attr >> 24) & 0x7F) | 0x80));
-       add(b, (unsigned char)((c->attr >> 16) & 0xFF));
-       add(b, (unsigned char)((c->attr >> 8) & 0xFF));
-       add(b, (unsigned char)(c->attr & 0xFF));
+       add(b, (unsigned char)(((attr >> 24) & 0x7F) | 0x80));
+       add(b, (unsigned char)((attr >> 16) & 0xFF));
+       add(b, (unsigned char)((attr >> 8) & 0xFF));
+       add(b, (unsigned char)(attr & 0xFF));
     }
 }
 static void makeliteral_cc(struct buf *b, termchar *c, unsigned long *state)
@@ -758,18 +782,30 @@ static void readliteral_chr(struct buf *b, termchar *c, termline *ldata,
 static void readliteral_attr(struct buf *b, termchar *c, termline *ldata,
                             unsigned long *state)
 {
-    int val;
+    unsigned val, attr, colourbits;
 
     val = get(b) << 8;
     val |= get(b);
 
     if (val >= 0x8000) {
+       val &= ~0x8000;
        val <<= 16;
        val |= get(b) << 8;
        val |= get(b);
     }
 
-    c->attr = val;
+    colourbits = (val >> (32-9)) & 0xFF;
+    attr = (val & ((1<<(32-9))-1));
+
+    attr = (((attr >> (ATTR_FGSHIFT + 4)) << (ATTR_FGSHIFT + 8)) |
+           (attr & ((1 << (ATTR_FGSHIFT + 4))-1)));
+    attr = (((attr >> (ATTR_BGSHIFT + 4)) << (ATTR_BGSHIFT + 8)) |
+           (attr & ((1 << (ATTR_BGSHIFT + 4))-1)));
+
+    attr |= (colourbits >> 4) << (ATTR_BGSHIFT + 4);
+    attr |= (colourbits & 0xF) << (ATTR_FGSHIFT + 4);
+
+    c->attr = attr;
 }
 static void readliteral_cc(struct buf *b, termchar *c, termline *ldata,
                           unsigned long *state)
@@ -981,6 +1017,21 @@ static termline *lineptr(Terminal *term, int y, int lineno, int screen)
     }
 
     /* We assume that we don't screw up and retrieve something out of range. */
+    if (line == NULL) {
+       fatalbox("line==NULL in terminal.c\n"
+                "lineno=%d y=%d w=%d h=%d\n"
+                "count(scrollback=%p)=%d\n"
+                "count(screen=%p)=%d\n"
+                "count(alt=%p)=%d alt_sblines=%d\n"
+                "whichtree=%p treeindex=%d\n\n"
+                "Please contact <putty@projects.tartarus.org> "
+                "and pass on the above information.",
+                lineno, y, term->cols, term->rows,
+                term->scrollback, count234(term->scrollback),
+                term->screen, count234(term->screen),
+                term->alt_screen, count234(term->alt_screen), term->alt_sblines,
+                whichtree, treeindex);
+    }
     assert(line != NULL);
 
     resizeline(term, line, term->cols);
@@ -1024,6 +1075,14 @@ static void term_timer(void *ctx, long now)
        term_update(term);
 }
 
+static void term_schedule_update(Terminal *term)
+{
+    if (!term->window_update_pending) {
+       term->window_update_pending = TRUE;
+       term->next_update = schedule_timer(UPDATE_DELAY, term_timer, term);
+    }
+}
+
 /*
  * Call this whenever the terminal window state changes, to queue
  * an update.
@@ -1031,10 +1090,7 @@ static void term_timer(void *ctx, long now)
 static void seen_disp_event(Terminal *term)
 {
     term->seen_disp_event = TRUE;      /* for scrollback-reset-on-activity */
-    if (!term->window_update_pending) {
-       term->window_update_pending = TRUE;
-       term->next_update = schedule_timer(UPDATE_DELAY, term_timer, term);
-    }
+    term_schedule_update(term);
 }
 
 /*
@@ -1914,27 +1970,27 @@ static void scroll_display(Terminal *term, int topline, int botline, int lines)
     if (lines > 0) {
        for (i = 0; i < nlines; i++)
            for (j = 0; j < term->cols; j++)
-               copy_termchar(term->disptext[start+i], j,
-                             term->disptext[start+i+distance]->chars+j);
+               copy_termchar(term->disptext[i], j,
+                             term->disptext[i+distance]->chars+j);
        if (term->dispcursy >= 0 &&
            term->dispcursy >= topline + distance &&
            term->dispcursy < topline + distance + nlines)
            term->dispcursy -= distance;
        for (i = 0; i < distance; i++)
            for (j = 0; j < term->cols; j++)
-               term->disptext[start+nlines+i]->chars[j].attr |= ATTR_INVALID;
+               term->disptext[nlines+i]->chars[j].attr |= ATTR_INVALID;
     } else {
        for (i = nlines; i-- ;)
            for (j = 0; j < term->cols; j++)
-               copy_termchar(term->disptext[start+i+distance], j,
-                             term->disptext[start+i]->chars+j);
+               copy_termchar(term->disptext[i+distance], j,
+                             term->disptext[i]->chars+j);
        if (term->dispcursy >= 0 &&
            term->dispcursy >= topline &&
            term->dispcursy < topline + nlines)
            term->dispcursy += distance;
        for (i = 0; i < distance; i++)
            for (j = 0; j < term->cols; j++)
-               term->disptext[start+i]->chars[j].attr |= ATTR_INVALID;
+               term->disptext[i]->chars[j].attr |= ATTR_INVALID;
     }
     save_scroll(term, topline, botline, lines);
 }
@@ -2567,12 +2623,19 @@ static void term_out(Terminal *term)
            }
        }
 
-       /* How about C1 controls ? */
+       /*
+        * How about C1 controls? 
+        * Explicitly ignore SCI (0x9a), which we don't translate to DECID.
+        */
        if ((c & -32) == 0x80 && term->termstate < DO_CTRLS &&
            !term->vt52_mode && has_compat(VT220)) {
-           term->termstate = SEEN_ESC;
-           term->esc_query = FALSE;
-           c = '@' + (c & 0x1F);
+           if (c == 0x9a)
+               c = 0;
+           else {
+               term->termstate = SEEN_ESC;
+               term->esc_query = FALSE;
+               c = '@' + (c & 0x1F);
+           }
        }
 
        /* Or the GL control. */
@@ -2592,13 +2655,12 @@ static void term_out(Terminal *term)
        if ((c & ~0x1F) == 0 && term->termstate < DO_CTRLS) {
            switch (c) {
              case '\005':             /* ENQ: terminal type query */
-               /* Strictly speaking this is VT100 but a VT100 defaults to
+               /* 
+                * Strictly speaking this is VT100 but a VT100 defaults to
                 * no response. Other terminals respond at their option.
                 *
                 * Don't put a CR in the default string as this tends to
                 * upset some weird software.
-                *
-                * An xterm returns "xterm" (5 characters)
                 */
                compatibility(ANSIMIN);
                if (term->ldisc) {
@@ -2841,6 +2903,7 @@ static void term_out(Terminal *term)
                            else if (term->curs.y < term->rows - 1)
                                term->curs.y++;
                            term->curs.x = 0;
+                           cline = scrlineptr(term->curs.y);
                            /* Now we must check_boundary again, of course. */
                            check_boundary(term, term->curs.x, term->curs.y);
                            check_boundary(term, term->curs.x+2, term->curs.y);
@@ -3468,10 +3531,10 @@ static void term_out(Terminal *term)
                                  case 95:
                                  case 96:
                                  case 97:
-                                   /* xterm-style bright foreground */
+                                   /* aixterm-style bright foreground */
                                    term->curr_attr &= ~ATTR_FGMASK;
                                    term->curr_attr |=
-                                       ((term->esc_args[i] - 90 + 16)
+                                       ((term->esc_args[i] - 90 + 8)
                                          << ATTR_FGSHIFT);
                                    break;
                                  case 39:      /* default-foreground */
@@ -3499,16 +3562,36 @@ static void term_out(Terminal *term)
                                  case 105:
                                  case 106:
                                  case 107:
-                                   /* xterm-style bright background */
+                                   /* aixterm-style bright background */
                                    term->curr_attr &= ~ATTR_BGMASK;
                                    term->curr_attr |=
-                                       ((term->esc_args[i] - 100 + 16)
+                                       ((term->esc_args[i] - 100 + 8)
                                          << ATTR_BGSHIFT);
                                    break;
                                  case 49:      /* default-background */
                                    term->curr_attr &= ~ATTR_BGMASK;
                                    term->curr_attr |= ATTR_DEFBG;
                                    break;
+                                 case 38:   /* xterm 256-colour mode */
+                                   if (i+2 < term->esc_nargs &&
+                                       term->esc_args[i+1] == 5) {
+                                       term->curr_attr &= ~ATTR_FGMASK;
+                                       term->curr_attr |=
+                                           ((term->esc_args[i+2] & 0xFF)
+                                            << ATTR_FGSHIFT);
+                                       i += 2;
+                                   }
+                                   break;
+                                 case 48:   /* xterm 256-colour mode */
+                                   if (i+2 < term->esc_nargs &&
+                                       term->esc_args[i+1] == 5) {
+                                       term->curr_attr &= ~ATTR_BGMASK;
+                                       term->curr_attr |=
+                                           ((term->esc_args[i+2] & 0xFF)
+                                            << ATTR_BGSHIFT);
+                                       i += 2;
+                                   }
+                                   break;
                                }
                            }
                            set_erase_char(term);
@@ -3736,7 +3819,7 @@ static void term_out(Terminal *term)
                            }
                        }
                        break;
-                     case 'Z':         /* CBT: BackTab for xterm */
+                     case 'Z':         /* CBT */
                        compatibility(OTHER);
                        {
                            int i = def(term->esc_args[0], 1);
@@ -3801,7 +3884,7 @@ static void term_out(Terminal *term)
                        if (term->esc_args[0] >= 0 && term->esc_args[0] < 16) {
                            long colour =
                                (sco2ansicolour[term->esc_args[0] & 0x7] |
-                                ((term->esc_args[0] & 0x8) << 1)) <<
+                                (term->esc_args[0] & 0x8)) <<
                                ATTR_FGSHIFT;
                            term->curr_attr &= ~ATTR_FGMASK;
                            term->curr_attr |= colour;
@@ -3814,7 +3897,7 @@ static void term_out(Terminal *term)
                        if (term->esc_args[0] >= 0 && term->esc_args[0] < 16) {
                            long colour =
                                (sco2ansicolour[term->esc_args[0] & 0x7] |
-                                ((term->esc_args[0] & 0x8) << 1)) <<
+                                (term->esc_args[0] & 0x8)) <<
                                ATTR_BGSHIFT;
                            term->curr_attr &= ~ATTR_BGMASK;
                            term->curr_attr |= colour;
@@ -4255,22 +4338,14 @@ static void term_out(Terminal *term)
                term->termstate = TOPLEVEL;
                term->curr_attr &= ~ATTR_FGMASK;
                term->curr_attr &= ~ATTR_BOLD;
-               term->curr_attr |= (c & 0x7) << ATTR_FGSHIFT;
-               if ((c & 0x8) || term->vt52_bold)
-                   term->curr_attr |= ATTR_BOLD;
-
+               term->curr_attr |= (c & 0xF) << ATTR_FGSHIFT;
                set_erase_char(term);
                break;
              case VT52_BG:
                term->termstate = TOPLEVEL;
                term->curr_attr &= ~ATTR_BGMASK;
                term->curr_attr &= ~ATTR_BLINK;
-               term->curr_attr |= (c & 0x7) << ATTR_BGSHIFT;
-
-               /* Note: bold background */
-               if (c & 0x8)
-                   term->curr_attr |= ATTR_BLINK;
-
+               term->curr_attr |= (c & 0xF) << ATTR_BGSHIFT;
                set_erase_char(term);
                break;
 #endif
@@ -4284,7 +4359,8 @@ static void term_out(Terminal *term)
     }
 
     term_print_flush(term);
-    logflush(term->logctx);
+    if (term->cfg.logflush)
+       logflush(term->logctx);
 }
 
 /*
@@ -4318,7 +4394,7 @@ static int term_bidi_cache_hit(Terminal *term, int line,
 
 static void term_bidi_cache_store(Terminal *term, int line, termchar *lbefore,
                                  termchar *lafter, bidi_char *wcTo,
-                                 int width)
+                                 int width, int size)
 {
     int i;
 
@@ -4336,22 +4412,28 @@ static void term_bidi_cache_store(Terminal *term, int line, termchar *lbefore,
                term->post_bidi_cache[j].chars = NULL;
            term->pre_bidi_cache[j].width =
                term->post_bidi_cache[j].width = -1;
+           term->pre_bidi_cache[j].forward =
+               term->post_bidi_cache[j].forward = NULL;
+           term->pre_bidi_cache[j].backward =
+               term->post_bidi_cache[j].backward = NULL;
            j++;
        }
     }
 
     sfree(term->pre_bidi_cache[line].chars);
     sfree(term->post_bidi_cache[line].chars);
+    sfree(term->post_bidi_cache[line].forward);
+    sfree(term->post_bidi_cache[line].backward);
 
     term->pre_bidi_cache[line].width = width;
-    term->pre_bidi_cache[line].chars = snewn(width, termchar);
+    term->pre_bidi_cache[line].chars = snewn(size, termchar);
     term->post_bidi_cache[line].width = width;
-    term->post_bidi_cache[line].chars = snewn(width, termchar);
+    term->post_bidi_cache[line].chars = snewn(size, termchar);
     term->post_bidi_cache[line].forward = snewn(width, int);
     term->post_bidi_cache[line].backward = snewn(width, int);
 
-    memcpy(term->pre_bidi_cache[line].chars, lbefore, width * TSIZE);
-    memcpy(term->post_bidi_cache[line].chars, lafter, width * TSIZE);
+    memcpy(term->pre_bidi_cache[line].chars, lbefore, size * TSIZE);
+    memcpy(term->post_bidi_cache[line].chars, lafter, size * TSIZE);
     memset(term->post_bidi_cache[line].forward, 0, width * sizeof(int));
     memset(term->post_bidi_cache[line].backward, 0, width * sizeof(int));
 
@@ -4453,7 +4535,8 @@ static termchar *term_bidi_line(Terminal *term, struct termline *ldata,
                    term->ltemp[it].chr = term->wcTo[it].wc;
            }
            term_bidi_cache_store(term, scr_y, ldata->chars,
-                                 term->ltemp, term->wcTo, ldata->size);
+                                 term->ltemp, term->wcTo,
+                                  term->cols, ldata->size);
 
            lchars = term->ltemp;
        } else {
@@ -4477,16 +4560,16 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise)
     pos scrpos;
     wchar_t *ch;
     int chlen;
-    termchar cursor_background;
 #ifdef OPTIMISE_SCROLL
     struct scrollregion *sr;
 #endif /* OPTIMISE_SCROLL */
-
-    cursor_background = term->basic_erase_char;
+    termchar *newline;
 
     chlen = 1024;
     ch = snewn(chlen, wchar_t);
 
+    newline = snewn(term->cols, termchar);
+
     rv = (!term->rvideo ^ !term->in_vbell ? ATTR_REVERSE : 0);
 
     /* Depends on:
@@ -4581,15 +4664,12 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise)
        int start = 0;
        int ccount = 0;
        int last_run_dirty = 0;
+       int laststart, dirtyrect;
        int *backward;
 
        scrpos.y = i + term->disptop;
        ldata = lineptr(scrpos.y);
 
-       dirty_run = dirty_line = (ldata->lattr !=
-                                 term->disptext[i]->lattr);
-       term->disptext[i]->lattr = ldata->lattr;
-
        /* Do Arabic shaping and bidi. */
        lchars = term_bidi_line(term, ldata, i);
        if (lchars) {
@@ -4599,10 +4679,13 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise)
            backward = NULL;
        }
 
+       /*
+        * First loop: work along the line deciding what we want
+        * each character cell to look like.
+        */
        for (j = 0; j < term->cols; j++) {
            unsigned long tattr, tchar;
            termchar *d = lchars + j;
-           int break_run, do_copy;
            scrpos.x = backward ? backward[j] : j;
 
            tchar = d->chr;
@@ -4612,6 +4695,16 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise)
                 tattr = (tattr & ~(ATTR_FGMASK | ATTR_BGMASK)) | 
                 ATTR_DEFFG | ATTR_DEFBG;
 
+           if (!term->cfg.xterm_256_colour) {
+               int colour;
+               colour = (tattr & ATTR_FGMASK) >> ATTR_FGSHIFT;
+               if (colour >= 16 && colour < 256)
+                   tattr = (tattr &~ ATTR_FGMASK) | ATTR_DEFFG;
+               colour = (tattr & ATTR_BGMASK) >> ATTR_BGSHIFT;
+               if (colour >= 16 && colour < 256)
+                   tattr = (tattr &~ ATTR_BGMASK) | ATTR_DEFBG;
+           }
+
            switch (tchar & CSET_MASK) {
              case CSET_ASCII:
                tchar = term->ucsdata->unitab_line[tchar & 0xFF];
@@ -4653,26 +4746,78 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise)
             */
            if (tchar != term->disptext[i]->chars[j].chr ||
                tattr != (term->disptext[i]->chars[j].attr &~
-                         ATTR_NARROW)) {
+                         (ATTR_NARROW | DATTR_MASK))) {
                if ((tattr & ATTR_WIDE) == 0 && char_width(ctx, tchar) == 2)
                    tattr |= ATTR_NARROW;
            } else if (term->disptext[i]->chars[j].attr & ATTR_NARROW)
                tattr |= ATTR_NARROW;
 
-           /* Cursor here ? Save the 'background' */
            if (i == our_curs_y && j == our_curs_x) {
-               /* FULL-TERMCHAR */
-               cursor_background.chr = tchar;
-               cursor_background.attr = tattr;
-               /* For once, this cc_next field is an absolute index in lchars */
-               if (d->cc_next)
-                   cursor_background.cc_next = d->cc_next + j;
-               else
-                   cursor_background.cc_next = 0;
+               tattr |= cursor;
+               term->curstype = cursor;
                term->dispcursx = j;
                term->dispcursy = i;
            }
 
+           /* FULL-TERMCHAR */
+           newline[j].attr = tattr;
+           newline[j].chr = tchar;
+           /* Combining characters are still read from lchars */
+           newline[j].cc_next = 0;
+       }
+
+       /*
+        * Now loop over the line again, noting where things have
+        * changed.
+        * 
+        * During this loop, we keep track of where we last saw
+        * DATTR_STARTRUN. Any mismatch automatically invalidates
+        * _all_ of the containing run that was last printed: that
+        * is, any rectangle that was drawn in one go in the
+        * previous update should be either left completely alone
+        * or overwritten in its entirety. This, along with the
+        * expectation that front ends clip all text runs to their
+        * bounding rectangle, should solve any possible problems
+        * with fonts that overflow their character cells.
+        */
+       laststart = 0;
+       dirtyrect = FALSE;
+       for (j = 0; j < term->cols; j++) {
+           if (term->disptext[i]->chars[j].attr & DATTR_STARTRUN) {
+               laststart = j;
+               dirtyrect = FALSE;
+           }
+
+           if (term->disptext[i]->chars[j].chr != newline[j].chr ||
+               (term->disptext[i]->chars[j].attr &~ DATTR_MASK)
+               != newline[j].attr) {
+               int k;
+
+               for (k = laststart; k < j; k++)
+                   term->disptext[i]->chars[k].attr |= ATTR_INVALID;
+
+               dirtyrect = TRUE;
+           }
+
+           if (dirtyrect)
+               term->disptext[i]->chars[j].attr |= ATTR_INVALID;
+       }
+
+       /*
+        * Finally, loop once more and actually do the drawing.
+        */
+       dirty_run = dirty_line = (ldata->lattr !=
+                                 term->disptext[i]->lattr);
+       term->disptext[i]->lattr = ldata->lattr;
+
+       for (j = 0; j < term->cols; j++) {
+           unsigned long tattr, tchar;
+           int break_run, do_copy;
+           termchar *d = lchars + j;
+
+           tattr = newline[j].attr;
+           tchar = newline[j].chr;
+
            if ((term->disptext[i]->chars[j].attr ^ tattr) & ATTR_WIDE)
                dirty_line = TRUE;
 
@@ -4698,7 +4843,7 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise)
 
            if (!term->ucsdata->dbcs_screenfont && !dirty_line) {
                if (term->disptext[i]->chars[j].chr == tchar &&
-                   term->disptext[i]->chars[j].attr == tattr)
+                   (term->disptext[i]->chars[j].attr &~ DATTR_MASK) == tattr)
                    break_run = TRUE;
                else if (!dirty_run && ccount == 1)
                    break_run = TRUE;
@@ -4706,7 +4851,12 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise)
 
            if (break_run) {
                if ((dirty_run || last_run_dirty) && ccount > 0) {
-                   do_text(ctx, start, i, ch, ccount, attr, ldata->lattr);
+                   do_text(ctx, start, i, ch, ccount, attr,
+                           ldata->lattr);
+                   if (attr & (TATTR_ACTCURS | TATTR_PASCURS))
+                       do_cursor(ctx, start, i, ch, ccount, attr,
+                                 ldata->lattr);
+
                    updated_line = 1;
                }
                start = j;
@@ -4766,6 +4916,8 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise)
                copy_termchar(term->disptext[i], j, d);
                term->disptext[i]->chars[j].chr = tchar;
                term->disptext[i]->chars[j].attr = tattr;
+               if (start == j)
+                   term->disptext[i]->chars[j].attr |= DATTR_STARTRUN;
            }
 
            /* If it's a wide char step along to the next one. */
@@ -4785,57 +4937,19 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise)
            }
        }
        if (dirty_run && ccount > 0) {
-           do_text(ctx, start, i, ch, ccount, attr, ldata->lattr);
-           updated_line = 1;
-       }
-
-       /* Cursor on this line ? (and changed) */
-       if (i == our_curs_y && (term->curstype != cursor || updated_line)) {
-           ch[0] = (wchar_t) cursor_background.chr;
-           attr = cursor_background.attr | cursor;
-           ccount = 1;
+           do_text(ctx, start, i, ch, ccount, attr,
+                   ldata->lattr);
+           if (attr & (TATTR_ACTCURS | TATTR_PASCURS))
+               do_cursor(ctx, start, i, ch, ccount, attr,
+                         ldata->lattr);
 
-           if (cursor_background.cc_next) {
-               termchar *dd = ldata->chars + cursor_background.cc_next;
-
-               while (1) {
-                   unsigned long schar;
-
-                   schar = dd->chr;
-                   switch (schar & CSET_MASK) {
-                     case CSET_ASCII:
-                       schar = term->ucsdata->unitab_line[schar & 0xFF];
-                       break;
-                     case CSET_LINEDRW:
-                       schar = term->ucsdata->unitab_xterm[schar & 0xFF];
-                       break;
-                     case CSET_SCOACS:
-                       schar = term->ucsdata->unitab_scoacs[schar&0xFF];
-                       break;
-                   }
-
-                   if (ccount >= chlen) {
-                       chlen = ccount + 256;
-                       ch = sresize(ch, chlen, wchar_t);
-                   }
-                   ch[ccount++] = (wchar_t) schar;
-
-                   if (dd->cc_next)
-                       dd += dd->cc_next;
-                   else
-                       break;
-               }
-
-               attr |= TATTR_COMBINING;
-           }
-
-           do_cursor(ctx, our_curs_x, i, ch, ccount, attr, ldata->lattr);
-           term->curstype = cursor;
+           updated_line = 1;
        }
 
        unlineptr(ldata);
     }
 
+    sfree(newline);
     sfree(ch);
 }
 
@@ -4848,7 +4962,9 @@ void term_invalidate(Terminal *term)
 
     for (i = 0; i < term->rows; i++)
        for (j = 0; j < term->cols; j++)
-           term->disptext[i]->chars[j].attr = ATTR_INVALID;
+           term->disptext[i]->chars[j].attr |= ATTR_INVALID;
+
+    term_schedule_update(term);
 }
 
 /*
@@ -4866,19 +4982,16 @@ void term_paint(Terminal *term, Context ctx,
     for (i = top; i <= bottom && i < term->rows; i++) {
        if ((term->disptext[i]->lattr & LATTR_MODE) == LATTR_NORM)
            for (j = left; j <= right && j < term->cols; j++)
-               term->disptext[i]->chars[j].attr = ATTR_INVALID;
+               term->disptext[i]->chars[j].attr |= ATTR_INVALID;
        else
            for (j = left / 2; j <= right / 2 + 1 && j < term->cols; j++)
-               term->disptext[i]->chars[j].attr = ATTR_INVALID;
+               term->disptext[i]->chars[j].attr |= ATTR_INVALID;
     }
 
     if (immediately) {
         do_paint (term, ctx, FALSE);
     } else {
-       if (!term->window_update_pending) {
-           term->window_update_pending = TRUE;
-           term->next_update = schedule_timer(UPDATE_DELAY, term_timer, term);
-       }
+       term_schedule_update(term);
     }
 }
 
@@ -6118,13 +6231,13 @@ int term_data(Terminal *term, int is_stderr, const char *data, int len)
      * the remote side needing to wait until term_out() has cleared
      * a backlog.
      *
-     * This is a slightly suboptimal way to deal with SSH2 - in
+     * This is a slightly suboptimal way to deal with SSH-2 - in
      * principle, the window mechanism would allow us to continue
      * to accept data on forwarded ports and X connections even
      * while the terminal processing was going slowly - but we
      * can't do the 100% right thing without moving the terminal
      * processing into a separate thread, and that might hurt
-     * portability. So we manage stdout buffering the old SSH1 way:
+     * portability. So we manage stdout buffering the old SSH-1 way:
      * if the terminal processing goes slowly, the whole SSH
      * connection stops accepting data until it's ready.
      *