X-Git-Url: https://asedeno.scripts.mit.edu/gitweb/?a=blobdiff_plain;f=terminal.c;h=c79944cda889cb8822be342b4557bebc38de85a9;hb=510f49e405e71ba5c97875e7a019364e1ef5fac9;hp=e7095e0c2db2cf7e7a9e248e699de64ceb944e9d;hpb=7be9af74ec8b97f948d6b3d67ebaf1a97138da33;p=PuTTY.git diff --git a/terminal.c b/terminal.c index e7095e0c..c79944cd 100644 --- a/terminal.c +++ b/terminal.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -65,7 +66,7 @@ #define has_compat(x) ( ((CL_##x)&term->compatibility_level) != 0 ) -char *EMPTY_WINDOW_TITLE = ""; +const char *EMPTY_WINDOW_TITLE = ""; const char sco2ansicolour[] = { 0, 4, 2, 6, 1, 5, 3, 7 }; @@ -98,6 +99,7 @@ const wchar_t sel_nl[] = SEL_NL; static void resizeline(Terminal *, termline *, int); static termline *lineptr(Terminal *, int, int, int); static void unlineptr(termline *); +static void check_line_size(Terminal *, termline *); static void do_paint(Terminal *, Context, int); static void erase_lots(Terminal *, int, int, int); static int find_last_nonempty_line(Terminal *, tree234 *); @@ -109,9 +111,6 @@ static void scroll(Terminal *, int, int, int, int); #ifdef OPTIMISE_SCROLL static void scroll_display(Terminal *, int, int, int); #endif /* OPTIMISE_SCROLL */ -static void term_resume_pasting(Terminal *term); -static void term_paste_callback(void *vterm); -static void term_paste_queue(Terminal *term, int timed); static termline *newline(Terminal *term, int cols, int bce) { @@ -1056,8 +1055,25 @@ static termline *lineptr(Terminal *term, int y, int lineno, int screen) } assert(line != NULL); - resizeline(term, line, term->cols); - /* FIXME: should we sort the compressed scrollback out here? */ + /* + * Here we resize lines to _at least_ the right length, but we + * don't truncate them. Truncation is done as a side effect of + * modifying the line. + * + * The point of this policy is to try to arrange that resizing the + * terminal window repeatedly - e.g. successive steps in an X11 + * opaque window-resize drag, or resizing as a side effect of + * retiling by tiling WMs such as xmonad - does not throw away + * data gratuitously. Specifically, we want a sequence of resize + * operations with no terminal output between them to have the + * same effect as a single resize to the ultimate terminal size, + * and also (for the case in which xmonad narrows a window that's + * scrolling things) we want scrolling up new text at the bottom + * of a narrowed window to avoid truncating lines further up when + * the window is re-widened. + */ + if (term->cols > line->cols) + resizeline(term, line, term->cols); return line; } @@ -1065,6 +1081,22 @@ static termline *lineptr(Terminal *term, int y, int lineno, int screen) #define lineptr(x) (lineptr)(term,x,__LINE__,FALSE) #define scrlineptr(x) (lineptr)(term,x,__LINE__,TRUE) +/* + * Coerce a termline to the terminal's current width. Unlike the + * optional resize in lineptr() above, this is potentially destructive + * of text, since it can shrink as well as grow the line. + * + * We call this whenever a termline is actually going to be modified. + * Helpfully, putting a single call to this function in check_boundary + * deals with _nearly_ all such cases, leaving only a few things like + * bulk erase and ESC#8 to handle separately. + */ +static void check_line_size(Terminal *term, termline *line) +{ + if (term->cols != line->cols) /* trivial optimisation */ + resizeline(term, line, term->cols); +} + static void term_schedule_tblink(Terminal *term); static void term_schedule_cblink(Terminal *term); @@ -1319,7 +1351,7 @@ void term_pwron(Terminal *term, int clear) { power_on(term, clear); if (term->ldisc) /* cause ldisc to notice changes */ - ldisc_send(term->ldisc, NULL, 0, 0); + ldisc_echoedit_update(term->ldisc); term->disptop = 0; deselect(term); term_update(term); @@ -1371,6 +1403,7 @@ void term_copy_stuff_from_conf(Terminal *term) term->no_remote_charset = conf_get_int(term->conf, CONF_no_remote_charset); term->no_remote_resize = conf_get_int(term->conf, CONF_no_remote_resize); term->no_remote_wintitle = conf_get_int(term->conf, CONF_no_remote_wintitle); + term->no_remote_clearscroll = conf_get_int(term->conf, CONF_no_remote_clearscroll); term->rawcnp = conf_get_int(term->conf, CONF_rawcnp); term->rect_select = conf_get_int(term->conf, CONF_rect_select); term->remote_qtitle_action = conf_get_int(term->conf, CONF_remote_qtitle_action); @@ -1496,12 +1529,44 @@ void term_reconfig(Terminal *term, Conf *conf) void term_clrsb(Terminal *term) { unsigned char *line; + int i; + + /* + * Scroll forward to the current screen, if we were back in the + * scrollback somewhere until now. + */ term->disptop = 0; + + /* + * Clear the actual scrollback. + */ while ((line = delpos234(term->scrollback, 0)) != NULL) { sfree(line); /* this is compressed data, not a termline */ } + + /* + * When clearing the scrollback, we also truncate any termlines on + * the current screen which have remembered data from a previous + * larger window size. Rationale: clearing the scrollback is + * sometimes done to protect privacy, so the user intention is + * specifically that we should not retain evidence of what + * previously happened in the terminal, and that ought to include + * evidence to the right as well as evidence above. + */ + for (i = 0; i < term->rows; i++) + check_line_size(term, scrlineptr(i)); + + /* + * There are now no lines of real scrollback which can be pulled + * back into the screen by a resize, and no lines of the alternate + * screen which should be displayed as if part of the scrollback. + */ term->tempsblines = 0; term->alt_sblines = 0; + + /* + * Update the scrollbar to reflect the new state of the world. + */ update_sbar(term); } @@ -2280,10 +2345,11 @@ static void check_boundary(Terminal *term, int x, int y) termline *ldata; /* Validate input coordinates, just in case. */ - if (x == 0 || x > term->cols) + if (x <= 0 || x > term->cols) return; ldata = scrlineptr(y); + check_line_size(term, ldata); if (x == term->cols) { ldata->lattr &= ~LATTR_WRAPPED2; } else { @@ -2354,6 +2420,7 @@ static void erase_lots(Terminal *term, } else { termline *ldata = scrlineptr(start.y); while (poslt(start, end)) { + check_line_size(term, ldata); if (start.x == term->cols) { if (!erase_lattr) ldata->lattr &= ~(LATTR_WRAPPED | LATTR_WRAPPED2); @@ -2383,16 +2450,51 @@ static void insch(Terminal *term, int n) { int dir = (n < 0 ? -1 : +1); int m, j; - pos cursplus; + pos eol; termline *ldata; n = (n < 0 ? -n : n); if (n > term->cols - term->curs.x) n = term->cols - term->curs.x; m = term->cols - term->curs.x - n; - cursplus.y = term->curs.y; - cursplus.x = term->curs.x + n; - check_selection(term, term->curs, cursplus); + + /* + * We must de-highlight the selection if it overlaps any part of + * the region affected by this operation, i.e. the region from the + * current cursor position to end-of-line, _unless_ the entirety + * of the selection is going to be moved to the left or right by + * this operation but otherwise unchanged, in which case we can + * simply move the highlight with the text. + */ + eol.y = term->curs.y; + eol.x = term->cols; + if (poslt(term->curs, term->selend) && poslt(term->selstart, eol)) { + pos okstart = term->curs; + pos okend = eol; + if (dir > 0) { + /* Insertion: n characters at EOL will be splatted. */ + okend.x -= n; + } else { + /* Deletion: n characters at cursor position will be splatted. */ + okstart.x += n; + } + if (posle(okstart, term->selstart) && posle(term->selend, okend)) { + /* Selection is contained entirely in the interval + * [okstart,okend), so we need only adjust the selection + * bounds. */ + term->selstart.x += dir * n; + term->selend.x += dir * n; + assert(term->selstart.x >= term->curs.x); + assert(term->selstart.x < term->cols); + assert(term->selend.x > term->curs.x); + assert(term->selend.x <= term->cols); + } else { + /* Selection is not wholly contained in that interval, so + * we must unhighlight it. */ + deselect(term); + } + } + check_boundary(term, term->curs.x, term->curs.y); if (dir < 0) check_boundary(term, term->curs.x + n, term->curs.y); @@ -2474,7 +2576,7 @@ static void toggle_mode(Terminal *term, int mode, int query, int state) case 10: /* DECEDM: set local edit mode */ term->term_editing = state; if (term->ldisc) /* cause ldisc to notice changes */ - ldisc_send(term->ldisc, NULL, 0, 0); + ldisc_echoedit_update(term->ldisc); break; case 25: /* DECTCEM: enable/disable cursor */ compatibility2(OTHER, VT220); @@ -2485,7 +2587,8 @@ static void toggle_mode(Terminal *term, int mode, int query, int state) compatibility(OTHER); deselect(term); swap_screen(term, term->no_alt_screen ? 0 : state, FALSE, FALSE); - term->disptop = 0; + if (term->scroll_on_disp) + term->disptop = 0; break; case 1000: /* xterm mouse 1 (normal) */ term->xterm_mouse = state ? 1 : 0; @@ -2505,7 +2608,8 @@ static void toggle_mode(Terminal *term, int mode, int query, int state) compatibility(OTHER); deselect(term); swap_screen(term, term->no_alt_screen ? 0 : state, TRUE, TRUE); - term->disptop = 0; + if (term->scroll_on_disp) + term->disptop = 0; break; case 1048: /* save/restore cursor */ if (!term->no_alt_screen) @@ -2521,7 +2625,8 @@ static void toggle_mode(Terminal *term, int mode, int query, int state) swap_screen(term, term->no_alt_screen ? 0 : state, TRUE, FALSE); if (!state && !term->no_alt_screen) save_cursor(term, state); - term->disptop = 0; + if (term->scroll_on_disp) + term->disptop = 0; break; case 2004: /* xterm bracketed paste */ term->bracketed_paste = state ? TRUE : FALSE; @@ -2535,7 +2640,7 @@ static void toggle_mode(Terminal *term, int mode, int query, int state) case 12: /* SRM: set echo mode */ term->term_echoing = !state; if (term->ldisc) /* cause ldisc to notice changes */ - ldisc_send(term->ldisc, NULL, 0, 0); + ldisc_echoedit_update(term->ldisc); break; case 20: /* LNM: Return sends ... */ term->cr_lf_return = state; @@ -2969,7 +3074,6 @@ static void term_out(Terminal *term) term->curs.x = 0; term->wrapnext = FALSE; seen_disp_event(term); - term_resume_pasting(term); if (term->crhaslf) { if (term->curs.y == term->marg_b) @@ -2984,7 +3088,8 @@ static void term_out(Terminal *term) if (has_compat(SCOANSI)) { move(term, 0, 0, 0); erase_lots(term, FALSE, FALSE, TRUE); - term->disptop = 0; + if (term->scroll_on_disp) + term->disptop = 0; term->wrapnext = FALSE; seen_disp_event(term); break; @@ -3000,7 +3105,6 @@ static void term_out(Terminal *term) term->curs.x = 0; term->wrapnext = FALSE; seen_disp_event(term); - term_resume_pasting(term); if (term->logctx) logtraffic(term->logctx, (unsigned char) c, LGTYP_ASCII); break; @@ -3259,13 +3363,14 @@ static void term_out(Terminal *term) compatibility(VT100); power_on(term, TRUE); if (term->ldisc) /* cause ldisc to notice changes */ - ldisc_send(term->ldisc, NULL, 0, 0); + ldisc_echoedit_update(term->ldisc); if (term->reset_132) { if (!term->no_remote_resize) request_resize(term->frontend, 80, term->rows); term->reset_132 = 0; } - term->disptop = 0; + if (term->scroll_on_disp) + term->disptop = 0; seen_disp_event(term); break; case 'H': /* HTS: set a tab */ @@ -3282,6 +3387,7 @@ static void term_out(Terminal *term) for (i = 0; i < term->rows; i++) { ldata = scrlineptr(i); + check_line_size(term, ldata); for (j = 0; j < term->cols; j++) { copy_termchar(ldata, j, &term->basic_erase_char); @@ -3289,7 +3395,8 @@ static void term_out(Terminal *term) } ldata->lattr = LATTR_NORM; } - term->disptop = 0; + if (term->scroll_on_disp) + term->disptop = 0; seen_disp_event(term); scrtop.x = scrtop.y = 0; scrbot.x = 0; @@ -3305,6 +3412,7 @@ static void term_out(Terminal *term) compatibility(VT100); { int nlattr; + termline *ldata; switch (ANSI(c, term->esc_query)) { case ANSI('3', '#'): /* DECDHL: 2*height, top */ @@ -3320,7 +3428,9 @@ static void term_out(Terminal *term) nlattr = LATTR_WIDE; break; } - scrlineptr(term->curs.y)->lattr = nlattr; + ldata = scrlineptr(term->curs.y); + check_line_size(term, ldata); + ldata->lattr = nlattr; } break; /* GZD4: G0 designate 94-set */ @@ -3385,8 +3495,15 @@ static void term_out(Terminal *term) if (term->esc_nargs <= ARGS_MAX) { if (term->esc_args[term->esc_nargs - 1] == ARG_DEFAULT) term->esc_args[term->esc_nargs - 1] = 0; - term->esc_args[term->esc_nargs - 1] = - 10 * term->esc_args[term->esc_nargs - 1] + c - '0'; + if (term->esc_args[term->esc_nargs - 1] <= + UINT_MAX / 10 && + term->esc_args[term->esc_nargs - 1] * 10 <= + UINT_MAX - c - '0') + term->esc_args[term->esc_nargs - 1] = + 10 * term->esc_args[term->esc_nargs - 1] + + c - '0'; + else + term->esc_args[term->esc_nargs - 1] = UINT_MAX; } term->termstate = SEEN_CSI; } else if (c == ';') { @@ -3402,8 +3519,10 @@ static void term_out(Terminal *term) term->esc_query = c; term->termstate = SEEN_CSI; } else +#define CLAMP(arg, lim) ((arg) = ((arg) > (lim)) ? (lim) : (arg)) switch (ANSI(c, term->esc_query)) { case 'A': /* CUU: move up N lines */ + CLAMP(term->esc_args[0], term->rows); move(term, term->curs.x, term->curs.y - def(term->esc_args[0], 1), 1); seen_disp_event(term); @@ -3412,6 +3531,7 @@ static void term_out(Terminal *term) compatibility(ANSI); /* FALLTHROUGH */ case 'B': /* CUD: Cursor down */ + CLAMP(term->esc_args[0], term->rows); move(term, term->curs.x, term->curs.y + def(term->esc_args[0], 1), 1); seen_disp_event(term); @@ -3427,23 +3547,27 @@ static void term_out(Terminal *term) compatibility(ANSI); /* FALLTHROUGH */ case 'C': /* CUF: Cursor right */ + CLAMP(term->esc_args[0], term->cols); move(term, term->curs.x + def(term->esc_args[0], 1), term->curs.y, 1); seen_disp_event(term); break; case 'D': /* CUB: move left N cols */ + CLAMP(term->esc_args[0], term->cols); move(term, term->curs.x - def(term->esc_args[0], 1), term->curs.y, 1); seen_disp_event(term); break; case 'E': /* CNL: move down N lines and CR */ compatibility(ANSI); + CLAMP(term->esc_args[0], term->rows); move(term, 0, term->curs.y + def(term->esc_args[0], 1), 1); seen_disp_event(term); break; case 'F': /* CPL: move up N lines and CR */ compatibility(ANSI); + CLAMP(term->esc_args[0], term->rows); move(term, 0, term->curs.y - def(term->esc_args[0], 1), 1); seen_disp_event(term); @@ -3451,12 +3575,14 @@ static void term_out(Terminal *term) case 'G': /* CHA */ case '`': /* HPA: set horizontal posn */ compatibility(ANSI); + CLAMP(term->esc_args[0], term->cols); move(term, def(term->esc_args[0], 1) - 1, term->curs.y, 0); seen_disp_event(term); break; case 'd': /* VPA: set vertical posn */ compatibility(ANSI); + CLAMP(term->esc_args[0], term->rows); move(term, term->curs.x, ((term->dec_om ? term->marg_t : 0) + def(term->esc_args[0], 1) - 1), @@ -3467,6 +3593,8 @@ static void term_out(Terminal *term) case 'f': /* HVP: set horz and vert posns at once */ if (term->esc_nargs < 2) term->esc_args[1] = ARG_DEFAULT; + CLAMP(term->esc_args[0], term->rows); + CLAMP(term->esc_args[1], term->cols); move(term, def(term->esc_args[1], 1) - 1, ((term->dec_om ? term->marg_t : 0) + def(term->esc_args[0], 1) - 1), @@ -3479,7 +3607,8 @@ static void term_out(Terminal *term) if (i == 3) { /* Erase Saved Lines (xterm) * This follows Thomas Dickey's xterm. */ - term_clrsb(term); + if (!term->no_remote_clearscroll) + term_clrsb(term); } else { i++; if (i > 3) @@ -3487,7 +3616,8 @@ static void term_out(Terminal *term) erase_lots(term, FALSE, !!(i & 2), !!(i & 1)); } } - term->disptop = 0; + if (term->scroll_on_disp) + term->disptop = 0; seen_disp_event(term); break; case 'K': /* EL: erase line or parts of it */ @@ -3501,6 +3631,7 @@ static void term_out(Terminal *term) break; case 'L': /* IL: insert lines */ compatibility(VT102); + CLAMP(term->esc_args[0], term->rows); if (term->curs.y <= term->marg_b) scroll(term, term->curs.y, term->marg_b, -def(term->esc_args[0], 1), FALSE); @@ -3508,6 +3639,7 @@ static void term_out(Terminal *term) break; case 'M': /* DL: delete lines */ compatibility(VT102); + CLAMP(term->esc_args[0], term->rows); if (term->curs.y <= term->marg_b) scroll(term, term->curs.y, term->marg_b, def(term->esc_args[0], 1), @@ -3517,11 +3649,13 @@ static void term_out(Terminal *term) case '@': /* ICH: insert chars */ /* XXX VTTEST says this is vt220, vt510 manual says vt102 */ compatibility(VT102); + CLAMP(term->esc_args[0], term->cols); insch(term, def(term->esc_args[0], 1)); seen_disp_event(term); break; case 'P': /* DCH: delete chars */ compatibility(VT102); + CLAMP(term->esc_args[0], term->cols); insch(term, -def(term->esc_args[0], 1)); seen_disp_event(term); break; @@ -3599,6 +3733,8 @@ static void term_out(Terminal *term) compatibility(VT100); if (term->esc_nargs <= 2) { int top, bot; + CLAMP(term->esc_args[0], term->rows); + CLAMP(term->esc_args[1], term->rows); top = def(term->esc_args[0], 1) - 1; bot = (term->esc_nargs <= 1 || term->esc_args[1] == 0 ? @@ -3831,7 +3967,8 @@ static void term_out(Terminal *term) switch (term->esc_args[0]) { int x, y, len; - char buf[80], *p; + char buf[80]; + const char *p; case 1: set_iconic(term->frontend, FALSE); break; @@ -3886,7 +4023,9 @@ static void term_out(Terminal *term) case 13: if (term->ldisc) { get_window_pos(term->frontend, &x, &y); - len = sprintf(buf, "\033[3;%d;%dt", x, y); + len = sprintf(buf, "\033[3;%u;%ut", + (unsigned)x, + (unsigned)y); ldisc_send(term->ldisc, buf, len, 0); } break; @@ -3951,6 +4090,7 @@ static void term_out(Terminal *term) } break; case 'S': /* SU: Scroll up */ + CLAMP(term->esc_args[0], term->rows); compatibility(SCOANSI); scroll(term, term->marg_t, term->marg_b, def(term->esc_args[0], 1), TRUE); @@ -3958,6 +4098,7 @@ static void term_out(Terminal *term) seen_disp_event(term); break; case 'T': /* SD: Scroll down */ + CLAMP(term->esc_args[0], term->rows); compatibility(SCOANSI); scroll(term, term->marg_t, term->marg_b, -def(term->esc_args[0], 1), TRUE); @@ -4000,6 +4141,7 @@ static void term_out(Terminal *term) /* XXX VTTEST says this is vt220, vt510 manual * says vt100 */ compatibility(ANSIMIN); + CLAMP(term->esc_args[0], term->cols); { int n = def(term->esc_args[0], 1); pos cursplus; @@ -4033,6 +4175,7 @@ static void term_out(Terminal *term) break; case 'Z': /* CBT */ compatibility(OTHER); + CLAMP(term->esc_args[0], term->cols); { int i = def(term->esc_args[0], 1); pos old_curs = term->curs; @@ -4093,7 +4236,7 @@ static void term_out(Terminal *term) break; case ANSI('F', '='): /* set normal foreground */ compatibility(SCOANSI); - if (term->esc_args[0] >= 0 && term->esc_args[0] < 16) { + if (term->esc_args[0] < 16) { long colour = (sco2ansicolour[term->esc_args[0] & 0x7] | (term->esc_args[0] & 0x8)) << @@ -4107,7 +4250,7 @@ static void term_out(Terminal *term) break; case ANSI('G', '='): /* set normal background */ compatibility(SCOANSI); - if (term->esc_args[0] >= 0 && term->esc_args[0] < 16) { + if (term->esc_args[0] < 16) { long colour = (sco2ansicolour[term->esc_args[0] & 0x7] | (term->esc_args[0] & 0x8)) << @@ -4231,7 +4374,11 @@ static void term_out(Terminal *term) case '7': case '8': case '9': - term->esc_args[0] = 10 * term->esc_args[0] + c - '0'; + if (term->esc_args[0] <= UINT_MAX / 10 && + term->esc_args[0] * 10 <= UINT_MAX - c - '0') + term->esc_args[0] = 10 * term->esc_args[0] + c - '0'; + else + term->esc_args[0] = UINT_MAX; break; case 'L': /* @@ -4313,7 +4460,11 @@ static void term_out(Terminal *term) case '7': case '8': case '9': - term->esc_args[0] = 10 * term->esc_args[0] + c - '0'; + if (term->esc_args[0] <= UINT_MAX / 10 && + term->esc_args[0] * 10 <= UINT_MAX - c - '0') + term->esc_args[0] = 10 * term->esc_args[0] + c - '0'; + else + term->esc_args[0] = UINT_MAX; break; default: term->termstate = OSC_STRING; @@ -4394,7 +4545,8 @@ static void term_out(Terminal *term) break; case 'J': erase_lots(term, FALSE, FALSE, TRUE); - term->disptop = 0; + if (term->scroll_on_disp) + term->disptop = 0; break; case 'K': erase_lots(term, TRUE, FALSE, TRUE); @@ -4449,7 +4601,8 @@ static void term_out(Terminal *term) /* compatibility(ATARI) */ move(term, 0, 0, 0); erase_lots(term, FALSE, FALSE, TRUE); - term->disptop = 0; + if (term->scroll_on_disp) + term->disptop = 0; break; case 'L': /* compatibility(ATARI) */ @@ -4472,7 +4625,8 @@ static void term_out(Terminal *term) case 'd': /* compatibility(ATARI) */ erase_lots(term, FALSE, TRUE, FALSE); - term->disptop = 0; + if (term->scroll_on_disp) + term->disptop = 0; break; case 'e': /* compatibility(ATARI) */ @@ -4573,7 +4727,7 @@ static void term_out(Terminal *term) } term_print_flush(term); - if (term->logflush) + if (term->logflush && term->logctx) logflush(term->logctx); } @@ -5293,7 +5447,7 @@ typedef struct { static void clip_addchar(clip_workbuf *b, wchar_t chr, int attr) { if (b->bufpos >= b->buflen) { - b->buflen += 128; + b->buflen *= 2; b->textbuf = sresize(b->textbuf, b->buflen, wchar_t); b->textptr = b->textbuf + b->bufpos; b->attrbuf = sresize(b->attrbuf, b->buflen, int); @@ -5708,48 +5862,6 @@ static void sel_spread(Terminal *term) } } -static void term_resume_pasting(Terminal *term) -{ - expire_timer_context(&term->paste_timer_ctx); - term_paste_queue(term, FALSE); -} - -static void term_paste_timing_callback(void *vterm, unsigned long now) -{ - Terminal *term = *(Terminal **)vterm; - term_resume_pasting(term); -} - -static void term_paste_queue(Terminal *term, int timed) -{ - if (timed) { - /* - * Delay sending the rest of the paste buffer until we have - * seen a newline coming back from the server (indicating that - * it's absorbed the data we've sent so far). As a fallback, - * continue sending anyway after a longish timeout. - * - * We use the pointless structure field term->paste_timer_ctx - * (which is a Terminal *, and we'll make sure it points - * straight back to term) as our timer context, so that it can - * be distinguished from term itself. That way, if we see a - * reason to continue pasting before the timer goes off, we - * can cancel just this timer and none of the other terminal - * timers handling display updates, blinking text and cursor, - * and visual bells. - */ - term->paste_timer_ctx = term; - schedule_timer(450, term_paste_timing_callback, - &term->paste_timer_ctx); - } else { - /* - * Just arrange to call term_paste_callback from the top level - * at the next opportunity. - */ - queue_toplevel_callback(term_paste_callback, term); - } -} - static void term_paste_callback(void *vterm) { Terminal *term = (Terminal *)vterm; @@ -5768,7 +5880,7 @@ static void term_paste_callback(void *vterm) term->paste_pos += n; if (term->paste_pos < term->paste_len) { - term_paste_queue(term, TRUE); + queue_toplevel_callback(term_paste_callback, term); return; } } @@ -5838,7 +5950,7 @@ void term_do_paste(Terminal *term) } get_clip(term->frontend, NULL, NULL); - term_paste_queue(term, FALSE); + queue_toplevel_callback(term_paste_callback, term); } void term_mouse(Terminal *term, Mouse_Button braw, Mouse_Button bcooked, @@ -5898,7 +6010,7 @@ void term_mouse(Terminal *term, Mouse_Button braw, Mouse_Button bcooked, */ if (raw_mouse && (term->selstate != ABOUT_TO) && (term->selstate != DRAGGING)) { - int encstate = 0, r, c; + int encstate = 0, r, c, wheel; char abuf[32]; int len = 0; @@ -5907,22 +6019,35 @@ void term_mouse(Terminal *term, Mouse_Button braw, Mouse_Button bcooked, switch (braw) { case MBT_LEFT: encstate = 0x00; /* left button down */ + wheel = FALSE; break; case MBT_MIDDLE: encstate = 0x01; + wheel = FALSE; break; case MBT_RIGHT: encstate = 0x02; + wheel = FALSE; break; case MBT_WHEEL_UP: encstate = 0x40; + wheel = TRUE; break; case MBT_WHEEL_DOWN: encstate = 0x41; + wheel = TRUE; break; - default: break; /* placate gcc warning about enum use */ + default: + return; } - switch (a) { + if (wheel) { + /* For mouse wheel buttons, we only ever expect to see + * MA_CLICK actions, and we don't try to keep track of + * the buttons being 'pressed' (since without matching + * click/release pairs that's pointless). */ + if (a != MA_CLICK) + return; + } else switch (a) { case MA_DRAG: if (term->xterm_mouse == 1) return; @@ -5939,7 +6064,8 @@ void term_mouse(Terminal *term, Mouse_Button braw, Mouse_Button bcooked, return; term->mouse_is_down = braw; break; - default: break; /* placate gcc warning about enum use */ + default: + return; } if (shift) encstate += 0x04; @@ -5956,7 +6082,8 @@ void term_mouse(Terminal *term, Mouse_Button braw, Mouse_Button bcooked, } else if (c <= 223 && r <= 223) { len = sprintf(abuf, "\033[M%c%c%c", encstate + 32, c + 32, r + 32); } - ldisc_send(term->ldisc, abuf, len, 0); + if (len > 0) + ldisc_send(term->ldisc, abuf, len, 0); } return; } @@ -5990,6 +6117,19 @@ void term_mouse(Terminal *term, Mouse_Button braw, Mouse_Button bcooked, sel_spread(term); } else if ((bcooked == MBT_SELECT && a == MA_DRAG) || (bcooked == MBT_EXTEND && a != MA_RELEASE)) { + if (a == MA_DRAG && + (term->selstate == NO_SELECTION || term->selstate == SELECTED)) { + /* + * This can happen if a front end has passed us a MA_DRAG + * without a prior MA_CLICK. OS X GTK does so, for + * example, if the initial button press was eaten by the + * WM when it activated the window in the first place. The + * nicest thing to do in this situation is to ignore + * further drags, and wait for the user to click in the + * window again properly if they want to select. + */ + return; + } if (term->selstate == ABOUT_TO && poseq(term->selanchor, selpoint)) return; if (bcooked == MBT_EXTEND && a != MA_DRAG && @@ -6125,7 +6265,6 @@ void term_nopaste(Terminal *term) { if (term->paste_len == 0) return; - expire_timer_context(&term->paste_timer_ctx); sfree(term->paste_buffer); term->paste_buffer = NULL; term->paste_len = 0; @@ -6234,9 +6373,11 @@ void term_set_focus(Terminal *term, int has_focus) */ char *term_get_ttymode(Terminal *term, const char *mode) { - char *val = NULL; + const char *val = NULL; if (strcmp(mode, "ERASE") == 0) { val = term->bksp_is_delete ? "^?" : "^H"; + } else if (strcmp(mode, "IUTF8") == 0) { + val = frontend_is_utf8(term->frontend) ? "yes" : "no"; } /* FIXME: perhaps we should set ONLCR based on lfhascr as well? */ /* FIXME: or ECHO and friends based on local echo state? */ @@ -6254,7 +6395,7 @@ struct term_userpass_state { * input. */ int term_get_userpass_input(Terminal *term, prompts_t *p, - unsigned char *in, int inlen) + const unsigned char *in, int inlen) { struct term_userpass_state *s = (struct term_userpass_state *)p->data; if (!s) {