X-Git-Url: https://git.distorted.org.uk/u/mdw/putty/blobdiff_plain/6d60c7915f26898fb4468096ffbf9ddc843db73a..4e95a413c2909c807226e5efc09da4c8ba24740e:/terminal.c diff --git a/terminal.c b/terminal.c index 54f86f86..432ed936 100644 --- a/terminal.c +++ b/terminal.c @@ -133,14 +133,13 @@ static void unlineptr(termline *line) freeline(line); } +#ifdef TERM_CC_DIAGS /* * Diagnostic function: verify that a termline has a correct * combining character structure. * - * XXX-REMOVE-BEFORE-RELEASE: This is a performance-intensive - * check. Although it's currently really useful for getting all the - * bugs out of the new cc stuff, it will want to be absent when we - * make a proper release. + * This is a performance-intensive check, so it's no longer enabled + * by default. */ static void cc_check(termline *line) { @@ -185,6 +184,7 @@ static void cc_check(termline *line) sfree(flags); } +#endif /* * Add a combining character to a character cell. @@ -231,7 +231,9 @@ static void add_cc(termline *line, int col, unsigned long chr) line->chars[newcc].chr = chr; line->chars[col].cc_next = newcc - col; - cc_check(line); /* XXX-REMOVE-BEFORE-RELEASE */ +#ifdef TERM_CC_DIAGS + cc_check(line); +#endif } /* @@ -257,7 +259,9 @@ static void clear_cc(termline *line, int col) line->chars[origcol].cc_next = 0; - cc_check(line); /* XXX-REMOVE-BEFORE-RELEASE */ +#ifdef TERM_CC_DIAGS + cc_check(line); +#endif } /* @@ -271,7 +275,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) @@ -305,7 +309,9 @@ static void copy_termchar(termline *destline, int x, termchar *src) add_cc(destline, x, src->chr); } - cc_check(destline); /* XXX-REMOVE-BEFORE-RELEASE */ +#ifdef TERM_CC_DIAGS + cc_check(destline); +#endif } /* @@ -324,7 +330,9 @@ static void move_termchar(termline *line, termchar *dest, termchar *src) /* Ensure the original cell doesn't have a cc list. */ src->cc_next = 0; - cc_check(line); /* XXX-REMOVE-BEFORE-RELEASE */ +#ifdef TERM_CC_DIAGS + cc_check(line); +#endif } /* @@ -669,9 +677,9 @@ static unsigned char *compressline(termline *ldata) * Diagnostics: ensure that the compressed data really does * decompress to the right thing. * - * XXX-REMOVE-BEFORE-RELEASE: This is a bit performance-heavy - * to be leaving in production code. + * This is a bit performance-heavy for production code. */ +#ifdef TERM_CC_DIAGS #ifndef CHECK_SB_COMPRESSION { int dused; @@ -701,6 +709,7 @@ static unsigned char *compressline(termline *ldata) freeline(dcl); } #endif +#endif /* TERM_CC_DIAGS */ /* * Trim the allocated memory so we don't waste any, and return. @@ -960,7 +969,9 @@ static void resizeline(Terminal *term, termline *line, int cols) for (i = oldcols; i < cols; i++) line->chars[i] = term->basic_erase_char; - cc_check(line); /* XXX-REMOVE-BEFORE-RELEASE */ +#ifdef TERM_CC_DIAGS + cc_check(line); +#endif } } @@ -1017,6 +1028,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 " + "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); @@ -1820,7 +1846,9 @@ static void scroll(Terminal *term, int topline, int botline, int lines, int sb) } else { while (lines > 0) { line = delpos234(term->screen, topline); - cc_check(line); /* XXX-REMOVE-BEFORE-RELEASE */ +#ifdef TERM_CC_DIAGS + cc_check(line); +#endif if (sb && term->savelines > 0) { int sblen = count234(term->scrollback); /* @@ -1955,27 +1983,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); } @@ -2608,12 +2636,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. */ @@ -2633,32 +2668,25 @@ 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) { - char abuf[256], *s, *d; - int state = 0; - for (s = term->cfg.answerback, d = abuf; *s; s++) { - if (state) { - if (*s >= 'a' && *s <= 'z') - *d++ = (*s - ('a' - 1)); - else if ((*s >= '@' && *s <= '_') || - *s == '?' || (*s & 0x80)) - *d++ = ('@' ^ *s); - else if (*s == '~') - *d++ = '^'; - state = 0; - } else if (*s == '^') { - state = 1; - } else - *d++ = *s; + char abuf[lenof(term->cfg.answerback)], *s, *d; + for (s = term->cfg.answerback, d = abuf; *s;) { + char *n; + char c = ctrlparse(s, &n); + if (n) { + *d++ = c; + s = n; + } else { + *d++ = *s++; + } } lpage_send(term->ldisc, DEFAULT_CODEPAGE, abuf, d - abuf, 0); @@ -2824,7 +2852,9 @@ static void term_out(Terminal *term) if (DIRECT_CHAR(c)) width = 1; if (!width) - width = wcwidth((wchar_t) c); + width = (term->cfg.cjk_ambig_wide ? + mk_wcwidth_cjk((wchar_t) c) : + mk_wcwidth((wchar_t) c)); if (term->wrapnext && term->wrap && width > 0) { cline->lattr |= LATTR_WRAPPED; @@ -2882,6 +2912,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); @@ -3509,7 +3540,7 @@ 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 + 8) @@ -3540,7 +3571,7 @@ 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 + 8) @@ -3797,7 +3828,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); @@ -4538,16 +4569,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: @@ -4642,15 +4673,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) { @@ -4660,10 +4688,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; @@ -4724,26 +4755,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; @@ -4769,7 +4852,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; @@ -4777,7 +4860,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; @@ -4837,6 +4925,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. */ @@ -4856,57 +4946,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; - - if (cursor_background.cc_next) { - termchar *dd = ldata->chars + cursor_background.cc_next; - - while (1) { - unsigned long schar; + 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); - 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); } @@ -4919,7 +4971,7 @@ 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); } @@ -4939,10 +4991,10 @@ 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) { @@ -6188,13 +6240,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. * @@ -6213,3 +6265,17 @@ void term_set_focus(Terminal *term, int has_focus) term->has_focus = has_focus; term_schedule_cblink(term); } + +/* + * Provide "auto" settings for remote tty modes, suitable for an + * application with a terminal window. + */ +char *term_get_ttymode(Terminal *term, const char *mode) +{ + char *val = NULL; + if (strcmp(mode, "ERASE") == 0) { + val = term->cfg.bksp_is_delete ? "^?" : "^H"; + } + /* FIXME: perhaps we should set ONLCR based on cfg.lfhascr as well? */ + return dupstr(val); +}