X-Git-Url: https://git.distorted.org.uk/u/mdw/putty/blobdiff_plain/55fb297890fbd83482db2d2516a9392befb3eb21..e030765f26dba89af7e0136b420bf8229cacefea:/terminal.c diff --git a/terminal.c b/terminal.c index a3a21784..73ab393e 100644 --- a/terminal.c +++ b/terminal.c @@ -1,3 +1,7 @@ +/* + * Terminal emulator. + */ + #include #include #include @@ -61,6 +65,8 @@ #define has_compat(x) ( ((CL_##x)&term->compatibility_level) != 0 ) +char *EMPTY_WINDOW_TITLE = ""; + const char sco2ansicolour[] = { 0, 4, 2, 6, 1, 5, 3, 7 }; #define sel_nl_sz (sizeof(sel_nl)/sizeof(wchar_t)) @@ -94,10 +100,12 @@ static termline *lineptr(Terminal *, int, int, int); static void unlineptr(termline *); static void do_paint(Terminal *, Context, int); static void erase_lots(Terminal *, int, int, int); +static int find_last_nonempty_line(Terminal *, tree234 *); static void swap_screen(Terminal *, int, int, int); static void update_sbar(Terminal *); static void deselect(Terminal *); static void term_print_finish(Terminal *); +static void scroll(Terminal *, int, int, int, int); #ifdef OPTIMISE_SCROLL static void scroll_display(Terminal *, int, int, int); #endif /* OPTIMISE_SCROLL */ @@ -1170,12 +1178,15 @@ static void term_schedule_vbell(Terminal *term, int already_started, /* * Set up power-on settings for the terminal. + * If 'clear' is false, don't actually clear the primary screen, and + * position the cursor below the last non-blank line (scrolling if + * necessary). */ -static void power_on(Terminal *term) +static void power_on(Terminal *term, int clear) { - term->curs.x = term->curs.y = 0; term->alt_x = term->alt_y = 0; term->savecurs.x = term->savecurs.y = 0; + term->alt_savecurs.x = term->alt_savecurs.y = 0; term->alt_t = term->marg_t = 0; if (term->rows != -1) term->alt_b = term->marg_b = term->rows - 1; @@ -1188,18 +1199,22 @@ static void power_on(Terminal *term) } term->alt_om = term->dec_om = term->cfg.dec_om; term->alt_ins = term->insert = FALSE; - term->alt_wnext = term->wrapnext = term->save_wnext = FALSE; + term->alt_wnext = term->wrapnext = + term->save_wnext = term->alt_save_wnext = FALSE; term->alt_wrap = term->wrap = term->cfg.wrap_mode; - term->alt_cset = term->cset = term->save_cset = 0; - term->alt_utf = term->utf = term->save_utf = 0; + term->alt_cset = term->cset = term->save_cset = term->alt_save_cset = 0; + term->alt_utf = term->utf = term->save_utf = term->alt_save_utf = 0; term->utf_state = 0; - term->alt_sco_acs = term->sco_acs = term->save_sco_acs = 0; - term->cset_attr[0] = term->cset_attr[1] = term->save_csattr = CSET_ASCII; + term->alt_sco_acs = term->sco_acs = + term->save_sco_acs = term->alt_save_sco_acs = 0; + term->cset_attr[0] = term->cset_attr[1] = + term->save_csattr = term->alt_save_csattr = CSET_ASCII; term->rvideo = 0; term->in_vbell = FALSE; term->cursor_on = 1; term->big_cursor = 0; - term->default_attr = term->save_attr = term->curr_attr = ATTR_DEFAULT; + term->default_attr = term->save_attr = + term->alt_save_attr = term->curr_attr = ATTR_DEFAULT; term->term_editing = term->term_echoing = FALSE; term->app_cursor_keys = term->cfg.app_cursor; term->app_keypad_keys = term->cfg.app_keypad; @@ -1208,6 +1223,8 @@ static void power_on(Terminal *term) term->erase_char = term->basic_erase_char; term->alt_which = 0; term_print_finish(term); + term->xterm_mouse = 0; + set_raw_mouse_mode(term->frontend, FALSE); { int i; for (i = 0; i < 256; i++) @@ -1217,8 +1234,17 @@ static void power_on(Terminal *term) swap_screen(term, 1, FALSE, FALSE); erase_lots(term, FALSE, TRUE, TRUE); swap_screen(term, 0, FALSE, FALSE); - erase_lots(term, FALSE, TRUE, TRUE); + if (clear) + erase_lots(term, FALSE, TRUE, TRUE); + term->curs.y = find_last_nonempty_line(term, term->screen) + 1; + if (term->curs.y == term->rows) { + term->curs.y--; + scroll(term, 0, term->rows - 1, 1, TRUE); + } + } else { + term->curs.y = 0; } + term->curs.x = 0; term_schedule_tblink(term); term_schedule_cblink(term); } @@ -1283,9 +1309,9 @@ void term_seen_key_event(Terminal *term) /* * Same as power_on(), but an external function. */ -void term_pwron(Terminal *term) +void term_pwron(Terminal *term, int clear) { - power_on(term); + power_on(term, clear); if (term->ldisc) /* cause ldisc to notice changes */ ldisc_send(term->ldisc, NULL, 0, 0); term->disptop = 0; @@ -1424,7 +1450,7 @@ Terminal *term_init(Config *mycfg, struct unicode_data *ucsdata, term->vt52_mode = FALSE; term->cr_lf_return = FALSE; term->seen_disp_event = FALSE; - term->xterm_mouse = term->mouse_is_down = FALSE; + term->mouse_is_down = FALSE; term->reset_132 = FALSE; term->cblinker = term->tblinker = 0; term->has_focus = 1; @@ -1442,7 +1468,7 @@ Terminal *term_init(Config *mycfg, struct unicode_data *ucsdata, term->tabs = NULL; deselect(term); term->rows = term->cols = -1; - power_on(term); + power_on(term, TRUE); term->beephead = term->beeptail = NULL; #ifdef OPTIMISE_SCROLL term->scrollhead = term->scrolltail = NULL; @@ -1588,6 +1614,8 @@ void term_size(Terminal *term, int newrows, int newcols, int newsavelines) addpos234(term->screen, line, 0); term->curs.y += 1; term->savecurs.y += 1; + term->alt_y += 1; + term->alt_savecurs.y += 1; } else { /* Add a new blank line at the bottom of the screen. */ line = newline(term, newcols, FALSE); @@ -1608,6 +1636,8 @@ void term_size(Terminal *term, int newrows, int newcols, int newsavelines) term->tempsblines += 1; term->curs.y -= 1; term->savecurs.y -= 1; + term->alt_y -= 1; + term->alt_savecurs.y -= 1; } term->rows -= 1; } @@ -1667,12 +1697,26 @@ void term_size(Terminal *term, int newrows, int newcols, int newsavelines) term->savecurs.y = 0; if (term->savecurs.y >= newrows) term->savecurs.y = newrows - 1; + if (term->savecurs.x >= newcols) + term->savecurs.x = newcols - 1; + if (term->alt_savecurs.y < 0) + term->alt_savecurs.y = 0; + if (term->alt_savecurs.y >= newrows) + term->alt_savecurs.y = newrows - 1; + if (term->alt_savecurs.x >= newcols) + term->alt_savecurs.x = newcols - 1; if (term->curs.y < 0) term->curs.y = 0; if (term->curs.y >= newrows) term->curs.y = newrows - 1; if (term->curs.x >= newcols) term->curs.x = newcols - 1; + if (term->alt_y < 0) + term->alt_y = 0; + if (term->alt_y >= newrows) + term->alt_y = newrows - 1; + if (term->alt_x >= newcols) + term->alt_x = newcols - 1; term->alt_x = term->alt_y = 0; term->wrapnext = term->alt_wnext = FALSE; @@ -1698,7 +1742,7 @@ void term_provide_resize_fn(Terminal *term, { term->resize_fn = resize_fn; term->resize_ctx = resize_ctx; - if (term->cols > 0 && term->rows > 0) + if (resize_fn && term->cols > 0 && term->rows > 0) resize_fn(resize_ctx, term->cols, term->rows); } @@ -1730,6 +1774,7 @@ static int find_last_nonempty_line(Terminal * term, tree234 * screen) static void swap_screen(Terminal *term, int which, int reset, int keep_cur_pos) { int t; + pos tp; tree234 *ttr; if (!which) @@ -1777,6 +1822,35 @@ static void swap_screen(Terminal *term, int which, int reset, int keep_cur_pos) t = term->sco_acs; if (!reset) term->sco_acs = term->alt_sco_acs; term->alt_sco_acs = t; + + tp = term->savecurs; + if (!reset && !keep_cur_pos) + term->savecurs = term->alt_savecurs; + term->alt_savecurs = tp; + t = term->save_cset; + if (!reset && !keep_cur_pos) + term->save_cset = term->alt_save_cset; + term->alt_save_cset = t; + t = term->save_csattr; + if (!reset && !keep_cur_pos) + term->save_csattr = term->alt_save_csattr; + term->alt_save_csattr = t; + t = term->save_attr; + if (!reset && !keep_cur_pos) + term->save_attr = term->alt_save_attr; + term->alt_save_attr = t; + t = term->save_utf; + if (!reset && !keep_cur_pos) + term->save_utf = term->alt_save_utf; + term->alt_save_utf = t; + t = term->save_wnext; + if (!reset && !keep_cur_pos) + term->save_wnext = term->alt_save_wnext; + term->alt_save_wnext = t; + t = term->save_sco_acs; + if (!reset && !keep_cur_pos) + term->save_sco_acs = term->alt_save_sco_acs; + term->alt_save_sco_acs = t; } if (reset && term->screen) { @@ -1815,13 +1889,18 @@ static void check_selection(Terminal *term, pos from, pos to) static void scroll(Terminal *term, int topline, int botline, int lines, int sb) { termline *line; - int i, seltop, olddisptop, shift; + int i, seltop; +#ifdef OPTIMISE_SCROLL + int olddisptop, shift; +#endif /* OPTIMISE_SCROLL */ if (topline != 0 || term->alt_which != 0) sb = FALSE; +#ifdef OPTIMISE_SCROLL olddisptop = term->disptop; shift = lines; +#endif /* OPTIMISE_SCROLL */ if (lines < 0) { while (lines < 0) { line = delpos234(term->screen, botline); @@ -2311,11 +2390,11 @@ static void toggle_mode(Terminal *term, int mode, int query, int state) swap_screen(term, term->cfg.no_alt_screen ? 0 : state, FALSE, FALSE); term->disptop = 0; break; - case 1000: /* xterm mouse 1 */ + case 1000: /* xterm mouse 1 (normal) */ term->xterm_mouse = state ? 1 : 0; set_raw_mouse_mode(term->frontend, state); break; - case 1002: /* xterm mouse 2 */ + case 1002: /* xterm mouse 2 (inc. button drags) */ term->xterm_mouse = state ? 2 : 0; set_raw_mouse_mode(term->frontend, state); break; @@ -2796,6 +2875,13 @@ static void term_out(Terminal *term) term->wrapnext = FALSE; seen_disp_event(term); term->paste_hold = 0; + + if (term->cfg.crhaslf) { + if (term->curs.y == term->marg_b) + scroll(term, term->marg_t, term->marg_b, 1, TRUE); + else if (term->curs.y < term->rows - 1) + term->curs.y++; + } if (term->logctx) logtraffic(term->logctx, (unsigned char) c, LGTYP_ASCII); break; @@ -3076,7 +3162,7 @@ static void term_out(Terminal *term) break; case 'c': /* RIS: restore power-on settings */ compatibility(VT100); - power_on(term); + power_on(term, TRUE); if (term->ldisc) /* cause ldisc to notice changes */ ldisc_send(term->ldisc, NULL, 0, 0); if (term->reset_132) { @@ -3209,8 +3295,8 @@ static void term_out(Terminal *term) } term->termstate = SEEN_CSI; } else if (c == ';') { - if (++term->esc_nargs <= ARGS_MAX) - term->esc_args[term->esc_nargs - 1] = ARG_DEFAULT; + if (term->esc_nargs < ARGS_MAX) + term->esc_args[term->esc_nargs++] = ARG_DEFAULT; term->termstate = SEEN_CSI; } else if (c < '@') { if (term->esc_query) @@ -3294,10 +3380,17 @@ static void term_out(Terminal *term) break; case 'J': /* ED: erase screen or parts of it */ { - unsigned int i = def(term->esc_args[0], 0) + 1; - if (i > 3) - i = 0; - erase_lots(term, FALSE, !!(i & 2), !!(i & 1)); + unsigned int i = def(term->esc_args[0], 0); + if (i == 3) { + /* Erase Saved Lines (xterm) + * This follows Thomas Dickey's xterm. */ + term_clrsb(term); + } else { + i++; + if (i > 3) + i = 0; + erase_lots(term, FALSE, !!(i & 2), !!(i & 1)); + } } term->disptop = 0; seen_disp_event(term); @@ -3690,7 +3783,7 @@ static void term_out(Terminal *term) if (term->ldisc) ldisc_send(term->ldisc, is_iconic(term->frontend) ? - "\033[1t" : "\033[2t", 4, 0); + "\033[2t" : "\033[1t", 4, 0); break; case 13: if (term->ldisc) { @@ -3702,7 +3795,7 @@ static void term_out(Terminal *term) case 14: if (term->ldisc) { get_window_pixels(term->frontend, &x, &y); - len = sprintf(buf, "\033[4;%d;%dt", x, y); + len = sprintf(buf, "\033[4;%d;%dt", y, x); ldisc_send(term->ldisc, buf, len, 0); } break; @@ -3732,8 +3825,11 @@ static void term_out(Terminal *term) break; case 20: if (term->ldisc && - !term->cfg.no_remote_qtitle) { - p = get_window_title(term->frontend, TRUE); + term->cfg.remote_qtitle_action != TITLE_NONE) { + if(term->cfg.remote_qtitle_action == TITLE_REAL) + p = get_window_title(term->frontend, TRUE); + else + p = EMPTY_WINDOW_TITLE; len = strlen(p); ldisc_send(term->ldisc, "\033]L", 3, 0); ldisc_send(term->ldisc, p, len, 0); @@ -3742,8 +3838,11 @@ static void term_out(Terminal *term) break; case 21: if (term->ldisc && - !term->cfg.no_remote_qtitle) { - p = get_window_title(term->frontend,FALSE); + term->cfg.remote_qtitle_action != TITLE_NONE) { + if(term->cfg.remote_qtitle_action == TITLE_REAL) + p = get_window_title(term->frontend, FALSE); + else + p = EMPTY_WINDOW_TITLE; len = strlen(p); ldisc_send(term->ldisc, "\033]l", 3, 0); ldisc_send(term->ldisc, p, len, 0); @@ -4080,7 +4179,7 @@ static void term_out(Terminal *term) break; case SEEN_OSC_P: { - int max = (term->osc_strlen == 0 ? 21 : 16); + int max = (term->osc_strlen == 0 ? 21 : 15); int val; if ((int)c >= '0' && (int)c <= '9') val = c - '0'; @@ -4676,7 +4775,6 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise) termchar *lchars; int dirty_line, dirty_run, selected; unsigned long attr = 0, cset = 0; - int updated_line = 0; int start = 0; int ccount = 0; int last_run_dirty = 0; @@ -4809,10 +4907,12 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise) != newline[j].attr) { int k; - for (k = laststart; k < j; k++) - term->disptext[i]->chars[k].attr |= ATTR_INVALID; + if (!dirtyrect) { + for (k = laststart; k < j; k++) + term->disptext[i]->chars[k].attr |= ATTR_INVALID; - dirtyrect = TRUE; + dirtyrect = TRUE; + } } if (dirtyrect) @@ -4872,8 +4972,6 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise) if (attr & (TATTR_ACTCURS | TATTR_PASCURS)) do_cursor(ctx, start, i, ch, ccount, attr, ldata->lattr); - - updated_line = 1; } start = j; ccount = 0; @@ -4958,8 +5056,6 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise) if (attr & (TATTR_ACTCURS | TATTR_PASCURS)) do_cursor(ctx, start, i, ch, ccount, attr, ldata->lattr); - - updated_line = 1; } unlineptr(ldata); @@ -5041,6 +5137,31 @@ void term_scroll(Terminal *term, int rel, int where) } /* + * Scroll the scrollback to centre it on the beginning or end of the + * current selection, if any. + */ +void term_scroll_to_selection(Terminal *term, int which_end) +{ + pos target; + int y; + int sbtop = -sblines(term); + + if (term->selstate != SELECTED) + return; + if (which_end) + target = term->selend; + else + target = term->selstart; + + y = target.y - term->rows/2; + if (y < sbtop) + y = sbtop; + else if (y > 0) + y = 0; + term_scroll(term, -1, y); +} + +/* * Helper routine for clipme(): growing buffer. */ typedef struct { @@ -5130,7 +5251,7 @@ static void clipme(Terminal *term, pos top, pos bottom, int rect, int desel) sprintf(cbuf, "", (ldata[top.x] & 0xFFFF)); #else wchar_t cbuf[16], *p; - int set, c; + int c; int x = top.x; if (ldata->chars[x].chr == UCSWIDE) { @@ -5164,10 +5285,18 @@ static void clipme(Terminal *term, pos top, pos bottom, int rect, int desel) break; } - set = (uc & CSET_MASK); c = (uc & ~CSET_MASK); - cbuf[0] = uc; - cbuf[1] = 0; +#ifdef PLATFORM_IS_UTF16 + if (uc > 0x10000 && uc < 0x110000) { + cbuf[0] = 0xD800 | ((uc - 0x10000) >> 10); + cbuf[1] = 0xDC00 | ((uc - 0x10000) & 0x3FF); + cbuf[2] = 0; + } else +#endif + { + cbuf[0] = uc; + cbuf[1] = 0; + } if (DIRECT_FONT(uc)) { if (c >= ' ' && c != 0x7F) { @@ -5559,7 +5688,16 @@ void term_mouse(Terminal *term, Mouse_Button braw, Mouse_Button bcooked, selpoint.x = x; unlineptr(ldata); - if (raw_mouse) { + /* + * If we're in the middle of a selection operation, we ignore raw + * mouse mode until it's done (we must have been not in raw mouse + * mode when it started). + * This makes use of Shift for selection reliable, and avoids the + * host seeing mouse releases for which they never saw corresponding + * presses. + */ + if (raw_mouse && + (term->selstate != ABOUT_TO) && (term->selstate != DRAGGING)) { int encstate = 0, r, c; char abuf[16]; @@ -5730,6 +5868,42 @@ void term_mouse(Terminal *term, Mouse_Button braw, Mouse_Button bcooked, term_update(term); } +int format_arrow_key(char *buf, Terminal *term, int xkey, int ctrl) +{ + char *p = buf; + + if (term->vt52_mode) + p += sprintf((char *) p, "\x1B%c", xkey); + else { + int app_flg = (term->app_cursor_keys && !term->cfg.no_applic_c); +#if 0 + /* + * RDB: VT100 & VT102 manuals both state the app cursor + * keys only work if the app keypad is on. + * + * SGT: That may well be true, but xterm disagrees and so + * does at least one application, so I've #if'ed this out + * and the behaviour is back to PuTTY's original: app + * cursor and app keypad are independently switchable + * modes. If anyone complains about _this_ I'll have to + * put in a configurable option. + */ + if (!term->app_keypad_keys) + app_flg = 0; +#endif + /* Useful mapping of Ctrl-arrows */ + if (ctrl) + app_flg = !app_flg; + + if (app_flg) + p += sprintf((char *) p, "\x1BO%c", xkey); + else + p += sprintf((char *) p, "\x1B[%c", xkey); + } + + return p - buf; +} + void term_key(Terminal *term, Key_Sym keysym, wchar_t *text, size_t tlen, unsigned int modifiers, unsigned int flags) { @@ -5837,7 +6011,7 @@ void term_key(Terminal *term, Key_Sym keysym, wchar_t *text, size_t tlen, if (modifiers & PKM_CONTROL) c &= 0x1f; else if (modifiers & PKM_SHIFT) - c = toupper(c); + c = toupper((unsigned char)c); } *p++ = c; goto done; @@ -6106,20 +6280,7 @@ void term_key(Terminal *term, Key_Sym keysym, wchar_t *text, size_t tlen, case PK_REST: xkey = 'G'; break; /* centre key on number pad */ default: xkey = 0; break; /* else gcc warns `enum value not used' */ } - if (term->vt52_mode) - p += sprintf((char *) p, "\x1B%c", xkey); - else { - int app_flg = (term->app_cursor_keys && !term->cfg.no_applic_c); - - /* Useful mapping of Ctrl-arrows */ - if (modifiers == PKM_CONTROL) - app_flg = !app_flg; - - if (app_flg) - p += sprintf((char *) p, "\x1BO%c", xkey); - else - p += sprintf((char *) p, "\x1B[%c", xkey); - } + p += format_arrow_key(p, term, xkey, modifiers == PKM_CONTROL); goto done; } @@ -6320,6 +6481,7 @@ char *term_get_ttymode(Terminal *term, const char *mode) val = term->cfg.bksp_is_delete ? "^?" : "^H"; } /* FIXME: perhaps we should set ONLCR based on cfg.lfhascr as well? */ + /* FIXME: or ECHO and friends based on local echo state? */ return dupstr(val); } @@ -6363,7 +6525,7 @@ int term_get_userpass_input(Terminal *term, prompts_t *p, */ { int i; - for (i = 0; i < p->n_prompts; i++) + for (i = 0; i < (int)p->n_prompts; i++) memset(p->prompts[i]->result, 0, p->prompts[i]->result_len); } }