X-Git-Url: https://git.distorted.org.uk/u/mdw/putty/blobdiff_plain/5471d09ad63fc6216fb9c2a3b52ca9c93821a054..239b3b3629cc9e149dbc375ae2c42dc6d4f35c49:/terminal.c diff --git a/terminal.c b/terminal.c index 87c151e3..b0ad3565 100644 --- a/terminal.c +++ b/terminal.c @@ -1,5 +1,3 @@ -#include - #include #include #include @@ -59,11 +57,11 @@ static unsigned long *disptext; /* buffer of text on real screen */ static unsigned long *dispcurs; /* location of cursor on real screen */ static unsigned long curstype; /* type of cursor on real screen */ -#define VBELL_TIMEOUT 100 /* millisecond len of visual bell */ +#define VBELL_TIMEOUT (TICKSPERSEC/10) /* visual bell lasts 1/10 sec */ struct beeptime { struct beeptime *next; - long ticks; + unsigned long ticks; }; static struct beeptime *beephead, *beeptail; int nbeeps; @@ -86,6 +84,11 @@ typedef struct { #define incpos(p) ( (p).x == cols ? ((p).x = 0, (p).y++, 1) : ((p).x++, 0) ) #define decpos(p) ( (p).x == 0 ? ((p).x = cols, (p).y--, 1) : ((p).x--, 0) ) +/* Product-order comparisons for rectangular block selection. */ +#define posPlt(p1,p2) ( (p1).y <= (p2).y && (p1).x < (p2).x ) +#define posPle(p1,p2) ( (p1).y <= (p2).y && (p1).x <= (p2).x ) + +static bufchain inbuf; /* terminal input buffer */ static pos curs; /* cursor */ static pos savecurs; /* saved cursor position */ static int marg_t, marg_b; /* scroll margins */ @@ -94,9 +97,9 @@ static int wrap, wrapnext; /* wrap flags */ static int insert; /* insert-mode flag */ static int cset; /* 0 or 1: which char set */ static int save_cset, save_csattr; /* saved with cursor position */ -static int save_utf; /* saved with cursor position */ +static int save_utf, save_wnext; /* saved with cursor position */ static int rvideo; /* global reverse video flag */ -static int rvbell_timeout; /* for ESC[?5hESC[?5l vbell */ +static unsigned long rvbell_startpoint;/* for ESC[?5hESC[?5l vbell */ static int cursor_on; /* cursor enabled flag */ static int reset_132; /* Flag ESC c resets to 80 cols */ static int use_bce; /* Use Background coloured erase */ @@ -110,6 +113,10 @@ static int vt52_bold; /* Force bold on non-bold colours */ static int utf_state; /* Is there a pending UTF-8 character */ static int utf_char; /* and what is it so far. */ static int utf_size; /* The size of the UTF character. */ +static int printing, only_printing; /* Are we doing ANSI printing? */ +static int print_state; /* state of print-end-sequence scan */ +static bufchain printer_buf; /* buffered data for printer */ +static printer_job *print_job; static int xterm_mouse; /* send mouse messages to app */ @@ -162,6 +169,9 @@ static enum { NO_SELECTION, ABOUT_TO, DRAGGING, SELECTED } selstate; static enum { + LEXICOGRAPHIC, RECTANGULAR +} seltype; +static enum { SM_CHAR, SM_WORD, SM_LINE } selmode; static pos selstart, selend, selanchor; @@ -198,9 +208,7 @@ static void erase_lots(int, int, int); static void swap_screen(int); static void update_sbar(void); static void deselect(void); -/* log session to file stuff ... */ -static FILE *lgfp = NULL; -static void logtraffic(unsigned char c, int logmode); +static void term_print_finish(void); /* * Resize a line to make it `cols' columns wide. @@ -290,13 +298,14 @@ static void power_on(void) big_cursor = 0; save_attr = curr_attr = ATTR_DEFAULT; term_editing = term_echoing = FALSE; - ldisc_send(NULL, 0); /* cause ldisc to notice changes */ + ldisc_send(NULL, 0, 0); /* cause ldisc to notice changes */ app_cursor_keys = cfg.app_cursor; app_keypad_keys = cfg.app_keypad; use_bce = cfg.bce; blink_is_real = cfg.blinktext; erase_char = ERASE_CHAR; alt_which = 0; + term_print_finish(); { int i; for (i = 0; i < 256; i++) @@ -318,13 +327,15 @@ void term_update(void) Context ctx; ctx = get_ctx(); if (ctx) { - if (seen_disp_event) - update_sbar(); + int need_sbar_update = seen_disp_event; if ((seen_key_event && (cfg.scroll_on_key)) || (seen_disp_event && (cfg.scroll_on_disp))) { disptop = 0; /* return to main screen */ seen_disp_event = seen_key_event = 0; + need_sbar_update = TRUE; } + if (need_sbar_update) + update_sbar(); do_paint(ctx, TRUE); sys_cursor(curs.x, curs.y - disptop); free_ctx(ctx); @@ -344,6 +355,30 @@ void term_pwron(void) } /* + * When the user reconfigures us, we need to check the forbidden- + * alternate-screen config option, disable raw mouse mode if the + * user has disabled mouse reporting, and abandon a print job if + * the user has disabled printing. + */ +void term_reconfig(void) +{ + if (cfg.no_alt_screen) + swap_screen(0); + if (cfg.no_mouse_rep) { + xterm_mouse = 0; + set_raw_mouse_mode(0); + } + if (cfg.no_remote_charset) { + cset_attr[0] = cset_attr[1] = ATTR_ASCII; + sco_acs = alt_sco_acs = 0; + utf = 0; + } + if (!*cfg.printer) { + term_print_finish(); + } +} + +/* * Clear the scrollback. */ void term_clrsb(void) @@ -496,6 +531,7 @@ void term_size(int newrows, int newcols, int newsavelines) update_sbar(); term_update(); + back->size(); } /* @@ -586,7 +622,7 @@ static void check_selection(pos from, pos to) static void scroll(int topline, int botline, int lines, int sb) { unsigned long *line, *line2; - int i; + int i, seltop; if (topline != 0 || alt_which != 0) sb = FALSE; @@ -636,6 +672,23 @@ static void scroll(int topline, int botline, int lines, int sb) } addpos234(scrollback, line, sblen); line = line2; + + /* + * If the user is currently looking at part of the + * scrollback, and they haven't enabled any options + * that are going to reset the scrollback as a + * result of this movement, then the chances are + * they'd like to keep looking at the same line. So + * we move their viewpoint at the same rate as the + * scroll, at least until their viewpoint hits the + * top end of the scrollback buffer, at which point + * we don't have the choice any more. + * + * Thanks to Jan Holmen Holsten for the idea and + * initial implementation. + */ + if (disptop > -savelines && disptop < 0) + disptop--; } line = resizeline(line, cols); for (i = 0; i < cols; i++) @@ -643,20 +696,40 @@ static void scroll(int topline, int botline, int lines, int sb) line[cols + 1] = 0; addpos234(screen, line, botline); - if (selstart.y >= topline && selstart.y <= botline) { + /* + * If the selection endpoints move into the scrollback, + * we keep them moving until they hit the top. However, + * of course, if the line _hasn't_ moved into the + * scrollback then we don't do this, and cut them off + * at the top of the scroll region. + * + * This applies to selstart and selend (for an existing + * selection), and also selanchor (for one being + * selected as we speak). + */ + seltop = sb ? -savelines : topline; + + if (selstart.y >= seltop && selstart.y <= botline) { selstart.y--; - if (selstart.y < topline) { - selstart.y = topline; + if (selstart.y < seltop) { + selstart.y = seltop; selstart.x = 0; } } - if (selend.y >= topline && selend.y <= botline) { + if (selend.y >= seltop && selend.y <= botline) { selend.y--; - if (selend.y < topline) { - selend.y = topline; + if (selend.y < seltop) { + selend.y = seltop; selend.x = 0; } } + if (selanchor.y >= seltop && selanchor.y <= botline) { + selanchor.y--; + if (selanchor.y < seltop) { + selanchor.y = seltop; + selanchor.x = 0; + } + } lines--; } @@ -701,6 +774,7 @@ static void save_cursor(int save) save_attr = curr_attr; save_cset = cset; save_utf = utf; + save_wnext = wrapnext; save_csattr = cset_attr[cset]; save_sco_acs = sco_acs; } else { @@ -714,6 +788,13 @@ static void save_cursor(int save) curr_attr = save_attr; cset = save_cset; utf = save_utf; + wrapnext = save_wnext; + /* + * wrapnext might reset to False if the x position is no + * longer at the rightmost edge. + */ + if (wrapnext && curs.x < cols-1) + wrapnext = FALSE; cset_attr[cset] = save_csattr; sco_acs = save_sco_acs; fix_cpos; @@ -806,7 +887,7 @@ static void insch(int n) */ static void toggle_mode(int mode, int query, int state) { - long ticks; + unsigned long ticks; if (query) switch (mode) { @@ -824,7 +905,8 @@ static void toggle_mode(int mode, int query, int state) break; case 3: /* 80/132 columns */ deselect(); - request_resize(state ? 132 : 80, rows, 1); + if (!cfg.no_remote_resize) + request_resize(state ? 132 : 80, rows); reset_132 = state; break; case 5: /* reverse video */ @@ -834,15 +916,21 @@ static void toggle_mode(int mode, int query, int state) * effective visual bell, so that ESC[?5hESC[?5l will * always be an actually _visible_ visual bell. */ - ticks = GetTickCount(); - if (rvideo && !state && /* we're turning it off */ - ticks < rvbell_timeout) { /* and it's not long since it was turned on */ + ticks = GETTICKCOUNT(); + /* turn off a previous vbell to avoid inconsistencies */ + if (ticks - vbell_startpoint >= VBELL_TIMEOUT) + in_vbell = FALSE; + if (rvideo && !state && /* we're turning it off... */ + (ticks - rvbell_startpoint) < VBELL_TIMEOUT) { /* ...soon */ + /* If there's no vbell timeout already, or this one lasts + * longer, replace vbell_timeout with ours. */ + if (!in_vbell || + (rvbell_startpoint - vbell_startpoint < VBELL_TIMEOUT)) + vbell_startpoint = rvbell_startpoint; in_vbell = TRUE; /* we may clear rvideo but we set in_vbell */ - if (vbell_timeout < rvbell_timeout) /* don't move vbell end forward */ - vbell_timeout = rvbell_timeout; /* vbell end is at least then */ } else if (!rvideo && state) { /* This is an ON, so we notice the time and save it. */ - rvbell_timeout = ticks + VBELL_TIMEOUT; + rvbell_startpoint = ticks; } rvideo = state; seen_disp_event = TRUE; @@ -860,7 +948,7 @@ static void toggle_mode(int mode, int query, int state) break; case 10: /* set local edit mode */ term_editing = state; - ldisc_send(NULL, 0); /* cause ldisc to notice changes */ + ldisc_send(NULL, 0, 0); /* cause ldisc to notice changes */ break; case 25: /* enable/disable cursor */ compatibility2(OTHER, VT220); @@ -870,7 +958,7 @@ static void toggle_mode(int mode, int query, int state) case 47: /* alternate screen */ compatibility(OTHER); deselect(); - swap_screen(state); + swap_screen(cfg.no_alt_screen ? 0 : state); disptop = 0; break; case 1000: /* xterm mouse 1 */ @@ -889,7 +977,7 @@ static void toggle_mode(int mode, int query, int state) break; case 12: /* set echo mode */ term_echoing = !state; - ldisc_send(NULL, 0); /* cause ldisc to notice changes */ + ldisc_send(NULL, 0, 0); /* cause ldisc to notice changes */ break; case 20: /* Return sends ... */ cr_lf_return = state; @@ -913,41 +1001,144 @@ static void do_osc(void) switch (esc_args[0]) { case 0: case 1: - set_icon(osc_string); + if (!cfg.no_remote_wintitle) + set_icon(osc_string); if (esc_args[0] == 1) break; /* fall through: parameter 0 means set both */ case 2: case 21: - set_title(osc_string); + if (!cfg.no_remote_wintitle) + set_title(osc_string); break; } } } /* + * ANSI printing routines. + */ +static void term_print_setup(void) +{ + bufchain_clear(&printer_buf); + print_job = printer_start_job(cfg.printer); +} +static void term_print_flush(void) +{ + void *data; + int len; + int size; + while ((size = bufchain_size(&printer_buf)) > 5) { + bufchain_prefix(&printer_buf, &data, &len); + if (len > size-5) + len = size-5; + printer_job_data(print_job, data, len); + bufchain_consume(&printer_buf, len); + } +} +static void term_print_finish(void) +{ + void *data; + int len, size; + char c; + + if (!printing && !only_printing) + return; /* we need do nothing */ + + term_print_flush(); + while ((size = bufchain_size(&printer_buf)) > 0) { + bufchain_prefix(&printer_buf, &data, &len); + c = *(char *)data; + if (c == '\033' || c == '\233') { + bufchain_consume(&printer_buf, size); + break; + } else { + printer_job_data(print_job, &c, 1); + bufchain_consume(&printer_buf, 1); + } + } + printer_finish_job(print_job); + print_job = NULL; + printing = only_printing = FALSE; +} + +/* * Remove everything currently in `inbuf' and stick it up on the * in-memory display. There's a big state machine in here to * process escape sequences... */ void term_out(void) { - int c, inbuf_reap; - - for (inbuf_reap = 0; inbuf_reap < inbuf_head; inbuf_reap++) { - c = inbuf[inbuf_reap]; + int c, unget; + unsigned char localbuf[256], *chars; + int nchars = 0; + + unget = -1; + + chars = NULL; /* placate compiler warnings */ + while (nchars > 0 || bufchain_size(&inbuf) > 0) { + if (unget == -1) { + if (nchars == 0) { + void *ret; + bufchain_prefix(&inbuf, &ret, &nchars); + if (nchars > sizeof(localbuf)) + nchars = sizeof(localbuf); + memcpy(localbuf, ret, nchars); + bufchain_consume(&inbuf, nchars); + chars = localbuf; + assert(chars != NULL); + } + c = *chars++; + nchars--; - /* - * Optionally log the session traffic to a file. Useful for - * debugging and possibly also useful for actual logging. - */ - logtraffic((unsigned char) c, LGTYP_DEBUG); + /* + * Optionally log the session traffic to a file. Useful for + * debugging and possibly also useful for actual logging. + */ + if (cfg.logtype == LGTYP_DEBUG) + logtraffic((unsigned char) c, LGTYP_DEBUG); + } else { + c = unget; + unget = -1; + } /* Note only VT220+ are 8-bit VT102 is seven bit, it shouldn't even * be able to display 8-bit characters, but I'll let that go 'cause * of i18n. */ + /* + * If we're printing, add the character to the printer + * buffer. + */ + if (printing) { + bufchain_add(&printer_buf, &c, 1); + + /* + * If we're in print-only mode, we use a much simpler + * state machine designed only to recognise the ESC[4i + * termination sequence. + */ + if (only_printing) { + if (c == '\033') + print_state = 1; + else if (c == (unsigned char)'\233') + print_state = 2; + else if (c == '[' && print_state == 1) + print_state = 2; + else if (c == '4' && print_state == 2) + print_state = 3; + else if (c == 'i' && print_state == 3) + print_state = 4; + else + print_state = 0; + if (print_state == 4) { + term_print_finish(); + } + continue; + } + } + /* First see about all those translations. */ if (termstate == TOPLEVEL) { if (in_utf) @@ -985,9 +1176,9 @@ void term_out(void) case 4: case 5: if ((c & 0xC0) != 0x80) { - inbuf_reap--; /* This causes the faulting character */ - c = UCSERR; /* to be logged twice - not really a */ - utf_state = 0; /* serious problem. */ + unget = c; + c = UCSERR; + utf_state = 0; break; } utf_char = (utf_char << 6) | (c & 0x3f); @@ -1092,7 +1283,8 @@ void term_out(void) curs.x--; wrapnext = FALSE; fix_cpos; - *cpos = (' ' | curr_attr | ATTR_ASCII); + if (!cfg.no_dbackspace) /* destructive bksp might be disabled */ + *cpos = (' ' | curr_attr | ATTR_ASCII); } else /* Or normal C0 controls. */ if ((c & -32) == 0 && termstate < DO_CTRLS) { @@ -1125,15 +1317,15 @@ void term_out(void) } else *d++ = *s; } - lpage_send(CP_ACP, abuf, d - abuf); + lpage_send(DEFAULT_CODEPAGE, abuf, d - abuf, 0); } break; case '\007': { struct beeptime *newbeep; - long ticks; + unsigned long ticks; - ticks = GetTickCount(); + ticks = GETTICKCOUNT(); if (!beep_overloaded) { newbeep = smalloc(sizeof(struct beeptime)); @@ -1162,7 +1354,7 @@ void term_out(void) } if (cfg.bellovl && beep_overloaded && - ticks - lastbeep >= cfg.bellovl_s) { + ticks - lastbeep >= (unsigned)cfg.bellovl_s) { /* * If we're currently overloaded and the * last beep was more than s seconds ago, @@ -1184,12 +1376,12 @@ void term_out(void) * Perform an actual beep if we're not overloaded. */ if (!cfg.bellovl || !beep_overloaded) { - beep(cfg.beep); if (cfg.beep == BELL_VISUAL) { in_vbell = TRUE; - vbell_timeout = ticks + VBELL_TIMEOUT; + vbell_startpoint = ticks; term_update(); - } + } else + beep(cfg.beep); } disptop = 0; } @@ -1309,12 +1501,18 @@ void term_out(void) width = wcwidth((wchar_t) c); switch (width) { case 2: - if (curs.x + 1 != cols) { - *cpos++ = c | ATTR_WIDE | curr_attr; - *cpos++ = UCSWIDE | curr_attr; - curs.x++; - break; + *cpos++ = c | curr_attr; + if (++curs.x == cols) { + *cpos |= LATTR_WRAPPED; + if (curs.y == marg_b) + scroll(marg_t, marg_b, 1, TRUE); + else if (curs.y < rows - 1) + curs.y++; + curs.x = 0; + fix_cpos; } + *cpos++ = UCSWIDE | curr_attr; + break; case 1: *cpos++ = c | curr_attr; break; @@ -1425,13 +1623,14 @@ void term_out(void) break; case 'Z': /* terminal type query */ compatibility(VT100); - ldisc_send(id_string, strlen(id_string)); + ldisc_send(id_string, strlen(id_string), 0); break; case 'c': /* restore power-on settings */ compatibility(VT100); power_on(); if (reset_132) { - request_resize(80, rows, 1); + if (!cfg.no_remote_resize) + request_resize(80, rows); reset_132 = 0; } fix_cpos; @@ -1495,46 +1694,56 @@ void term_out(void) case ANSI('A', '('): compatibility(VT100); - cset_attr[0] = ATTR_GBCHR; + if (!cfg.no_remote_charset) + cset_attr[0] = ATTR_GBCHR; break; case ANSI('B', '('): compatibility(VT100); - cset_attr[0] = ATTR_ASCII; + if (!cfg.no_remote_charset) + cset_attr[0] = ATTR_ASCII; break; case ANSI('0', '('): compatibility(VT100); - cset_attr[0] = ATTR_LINEDRW; + if (!cfg.no_remote_charset) + cset_attr[0] = ATTR_LINEDRW; break; case ANSI('U', '('): compatibility(OTHER); - cset_attr[0] = ATTR_SCOACS; + if (!cfg.no_remote_charset) + cset_attr[0] = ATTR_SCOACS; break; case ANSI('A', ')'): compatibility(VT100); - cset_attr[1] = ATTR_GBCHR; + if (!cfg.no_remote_charset) + cset_attr[1] = ATTR_GBCHR; break; case ANSI('B', ')'): compatibility(VT100); - cset_attr[1] = ATTR_ASCII; + if (!cfg.no_remote_charset) + cset_attr[1] = ATTR_ASCII; break; case ANSI('0', ')'): compatibility(VT100); - cset_attr[1] = ATTR_LINEDRW; + if (!cfg.no_remote_charset) + cset_attr[1] = ATTR_LINEDRW; break; case ANSI('U', ')'): compatibility(OTHER); - cset_attr[1] = ATTR_SCOACS; + if (!cfg.no_remote_charset) + cset_attr[1] = ATTR_SCOACS; break; case ANSI('8', '%'): /* Old Linux code */ case ANSI('G', '%'): compatibility(OTHER); - utf = 1; + if (!cfg.no_remote_charset) + utf = 1; break; case ANSI('@', '%'): compatibility(OTHER); - utf = 0; + if (!cfg.no_remote_charset) + utf = 0; break; } break; @@ -1577,7 +1786,7 @@ void term_out(void) compatibility(OTHER); /* this reports xterm version 136 so that VIM can use the drag messages from the mouse reporting */ - ldisc_send("\033[>0;136;0c", 11); + ldisc_send("\033[>0;136;0c", 11, 0); break; case 'a': /* move right N cols */ compatibility(ANSI); @@ -1673,16 +1882,16 @@ void term_out(void) case 'c': /* terminal type query */ compatibility(VT100); /* This is the response for a VT102 */ - ldisc_send(id_string, strlen(id_string)); + ldisc_send(id_string, strlen(id_string), 0); break; case 'n': /* cursor position query */ if (esc_args[0] == 6) { char buf[32]; sprintf(buf, "\033[%d;%dR", curs.y + 1, curs.x + 1); - ldisc_send(buf, strlen(buf)); + ldisc_send(buf, strlen(buf), 0); } else if (esc_args[0] == 5) { - ldisc_send("\033[0n", 4); + ldisc_send("\033[0n", 4, 0); } break; case 'h': /* toggle modes to high */ @@ -1694,6 +1903,21 @@ void term_out(void) toggle_mode(esc_args[i], esc_query, TRUE); } break; + case 'i': + case ANSI_QUE('i'): + compatibility(VT100); + { + if (esc_nargs != 1) break; + if (esc_args[0] == 5 && *cfg.printer) { + printing = TRUE; + only_printing = !esc_query; + print_state = 0; + term_print_setup(); + } else if (esc_args[0] == 4 && printing) { + term_print_finish(); + } + } + break; case 'l': /* toggle modes to low */ case ANSI_QUE('l'): compatibility(VT100); @@ -1794,12 +2018,15 @@ void term_out(void) break; case 10: /* SCO acs off */ compatibility(SCOANSI); + if (cfg.no_remote_charset) break; sco_acs = 0; break; case 11: /* SCO acs on */ compatibility(SCOANSI); + if (cfg.no_remote_charset) break; sco_acs = 1; break; case 12: /* SCO acs on flipped */ compatibility(SCOANSI); + if (cfg.no_remote_charset) break; sco_acs = 2; break; case 22: /* disable bold */ compatibility2(OTHER, VT220); @@ -1873,11 +2100,110 @@ void term_out(void) * illegal values (eg first arg 1..9) for window changing * and reports. */ - compatibility(VT340TEXT); if (esc_nargs <= 1 && (esc_args[0] < 1 || esc_args[0] >= 24)) { - request_resize(cols, def(esc_args[0], 24), 0); + compatibility(VT340TEXT); + if (!cfg.no_remote_resize) + request_resize(cols, def(esc_args[0], 24)); deselect(); + } else if (esc_nargs >= 1 && + esc_args[0] >= 1 && + esc_args[0] < 24) { + compatibility(OTHER); + + switch (esc_args[0]) { + int x, y, len; + char buf[80], *p; + case 1: + set_iconic(FALSE); + break; + case 2: + set_iconic(TRUE); + break; + case 3: + if (esc_nargs >= 3) { + if (!cfg.no_remote_resize) + move_window(def(esc_args[1], 0), + def(esc_args[2], 0)); + } + break; + case 4: + /* We should resize the window to a given + * size in pixels here, but currently our + * resizing code isn't healthy enough to + * manage it. */ + break; + case 5: + set_zorder(TRUE); /* move to top */ + break; + case 6: + set_zorder(FALSE); /* move to bottom */ + break; + case 7: + refresh_window(); + break; + case 8: + if (esc_nargs >= 3) { + if (!cfg.no_remote_resize) + request_resize(def(esc_args[2], cfg.width), + def(esc_args[1], cfg.height)); + } + break; + case 9: + if (esc_nargs >= 2) + set_zoomed(esc_args[1] ? TRUE : FALSE); + break; + case 11: + ldisc_send(is_iconic() ? "\033[1t" : "\033[2t", + 4, 0); + break; + case 13: + get_window_pos(&x, &y); + len = sprintf(buf, "\033[3;%d;%dt", x, y); + ldisc_send(buf, len, 0); + break; + case 14: + get_window_pixels(&x, &y); + len = sprintf(buf, "\033[4;%d;%dt", x, y); + ldisc_send(buf, len, 0); + break; + case 18: + len = sprintf(buf, "\033[8;%d;%dt", + rows, cols); + ldisc_send(buf, len, 0); + break; + case 19: + /* + * Hmmm. Strictly speaking we + * should return `the size of the + * screen in characters', but + * that's not easy: (a) window + * furniture being what it is it's + * hard to compute, and (b) in + * resize-font mode maximising the + * window wouldn't change the + * number of characters. *shrug*. I + * think we'll ignore it for the + * moment and see if anyone + * complains, and then ask them + * what they would like it to do. + */ + break; + case 20: + p = get_window_title(TRUE); + len = strlen(p); + ldisc_send("\033]L", 3, 0); + ldisc_send(p, len, 0); + ldisc_send("\033\\", 2, 0); + break; + case 21: + p = get_window_title(FALSE); + len = strlen(p); + ldisc_send("\033]l", 3, 0); + ldisc_send(p, len, 0); + ldisc_send("\033\\", 2, 0); + break; + } } break; case 'S': @@ -1902,9 +2228,8 @@ void term_out(void) */ compatibility(VT420); if (esc_nargs == 1 && esc_args[0] > 0) { - request_resize(cols, - def(esc_args[0], cfg.height), - 0); + if (!cfg.no_remote_resize) + request_resize(cols, def(esc_args[0], cfg.height)); deselect(); } break; @@ -1915,8 +2240,8 @@ void term_out(void) */ compatibility(VT340TEXT); if (esc_nargs <= 1) { - request_resize(def(esc_args[0], cfg.width), - rows, 0); + if (!cfg.no_remote_resize) + request_resize(def(esc_args[0], cfg.width), rows); deselect(); } break; @@ -1945,7 +2270,7 @@ void term_out(void) if (i == 0 || i == 1) { strcpy(buf, "\033[2;1;1;112;112;1;0x"); buf[2] += i; - ldisc_send(buf, 20); + ldisc_send(buf, 20, 0); } } break; @@ -2043,10 +2368,12 @@ void term_out(void) * Well we should do a soft reset at this point ... */ if (!has_compat(VT420) && has_compat(VT100)) { - if (reset_132) - request_resize(132, 24, 1); - else - request_resize(80, 24, 1); + if (!cfg.no_remote_resize) { + if (reset_132) + request_resize(132, 24); + else + request_resize(80, 24); + } } #endif break; @@ -2262,7 +2589,7 @@ void term_out(void) termstate = VT52_Y1; break; case 'Z': - ldisc_send("\033/Z", 3); + ldisc_send("\033/Z", 3, 0); break; case '=': app_keypad_keys = TRUE; @@ -2433,7 +2760,8 @@ void term_out(void) check_selection(curs, cursplus); } } - inbuf_head = 0; + + term_print_flush(); } #if 0 @@ -2463,16 +2791,16 @@ static void do_paint(Context ctx, int may_optimise) pos scrpos; char ch[1024]; long cursor_background = ERASE_CHAR; - long ticks; + unsigned long ticks; /* * Check the visual bell state. */ if (in_vbell) { - ticks = GetTickCount(); - if (ticks - vbell_timeout >= 0) - in_vbell = FALSE; - } + ticks = GETTICKCOUNT(); + if (ticks - vbell_startpoint >= VBELL_TIMEOUT) + in_vbell = FALSE; + } rv = (!rvideo ^ !in_vbell ? ATTR_REVERSE : 0); @@ -2501,9 +2829,10 @@ static void do_paint(Context ctx, int may_optimise) if (dispcurs && (curstype != cursor || dispcurs != disptext + our_curs_y * (cols + 1) + curs.x)) { - if (dispcurs > disptext && (dispcurs[-1] & ATTR_WIDE)) + if (dispcurs > disptext && + (*dispcurs & (CHAR_MASK | CSET_MASK)) == UCSWIDE) dispcurs[-1] |= ATTR_INVALID; - if ((*dispcurs & ATTR_WIDE)) + if ( (dispcurs[1] & (CHAR_MASK | CSET_MASK)) == UCSWIDE) dispcurs[1] |= ATTR_INVALID; *dispcurs |= ATTR_INVALID; curstype = 0; @@ -2514,7 +2843,7 @@ static void do_paint(Context ctx, int may_optimise) for (i = 0; i < rows; i++) { unsigned long *ldata; int lattr; - int idx, dirty_line, dirty_run; + int idx, dirty_line, dirty_run, selected; unsigned long attr = 0; int updated_line = 0; int start = 0; @@ -2550,11 +2879,16 @@ static void do_paint(Context ctx, int may_optimise) } tattr |= (tchar & CSET_MASK); tchar &= CHAR_MASK; + if ((d[1] & (CHAR_MASK | CSET_MASK)) == UCSWIDE) + tattr |= ATTR_WIDE; /* Video reversing things */ + if (seltype == LEXICOGRAPHIC) + selected = posle(selstart, scrpos) && poslt(scrpos, selend); + else + selected = posPle(selstart, scrpos) && posPlt(scrpos, selend); tattr = (tattr ^ rv - ^ (posle(selstart, scrpos) && - poslt(scrpos, selend) ? ATTR_REVERSE : 0)); + ^ (selected ? ATTR_REVERSE : 0)); /* 'Real' blinking ? */ if (blink_is_real && (tattr & ATTR_BLINK)) { @@ -2566,6 +2900,17 @@ static void do_paint(Context ctx, int may_optimise) tattr &= ~ATTR_BLINK; } + /* + * Check the font we'll _probably_ be using to see if + * the character is wide when we don't want it to be. + */ + if ((tchar | tattr) != (disptext[idx]& ~ATTR_NARROW)) { + if ((tattr & ATTR_WIDE) == 0 && + CharWidth(ctx, (tchar | tattr) & 0xFFFF) == 2) + tattr |= ATTR_NARROW; + } else if (disptext[idx]&ATTR_NARROW) + tattr |= ATTR_NARROW; + /* Cursor here ? Save the 'background' */ if (i == our_curs_y && j == curs.x) { cursor_background = tattr | tchar; @@ -2647,11 +2992,11 @@ void term_blink(int flg) static long last_tblink = 0; long now, blink_diff; - now = GetTickCount(); + now = GETTICKCOUNT(); blink_diff = now - last_tblink; - /* Make sure the text blinks no more than 2Hz */ - if (blink_diff < 0 || blink_diff > 450) { + /* Make sure the text blinks no more than 2Hz; we'll use 0.45 s period. */ + if (blink_diff < 0 || blink_diff > (TICKSPERSEC * 9 / 20)) { last_tblink = now; tblinker = !tblinker; } @@ -2664,8 +3009,8 @@ void term_blink(int flg) blink_diff = now - last_blink; - /* Make sure the cursor blinks no faster than GetCaretBlinkTime() */ - if (blink_diff >= 0 && blink_diff < (long) GetCaretBlinkTime()) + /* Make sure the cursor blinks no faster than system blink rate */ + if (blink_diff >= 0 && blink_diff < (long) CURSORBLINK) return; last_blink = now; @@ -2686,14 +3031,14 @@ void term_invalidate(void) /* * Paint the window in response to a WM_PAINT message. */ -void term_paint(Context ctx, int l, int t, int r, int b) +void term_paint(Context ctx, int left, int top, int right, int bottom) { - int i, j, left, top, right, bottom; + int i, j; + if (left < 0) left = 0; + if (top < 0) top = 0; + if (right >= cols) right = cols-1; + if (bottom >= rows) bottom = rows-1; - left = l / font_width; - right = (r - 1) / font_width; - top = t / font_height; - bottom = (b - 1) / font_height; for (i = top; i <= bottom && i < rows; i++) { if ((disptext[i * (cols + 1) + cols] & LATTR_MODE) == LATTR_NORM) for (j = left; j <= right && j < cols; j++) @@ -2706,8 +3051,9 @@ void term_paint(Context ctx, int l, int t, int r, int b) /* This should happen soon enough, also for some reason it sometimes * fails to actually do anything when re-sizing ... painting the wrong * window perhaps ? - do_paint (ctx, FALSE); */ + if (alt_pressed) + do_paint (ctx, FALSE); } /* @@ -2730,25 +3076,38 @@ void term_scroll(int rel, int where) term_update(); } -static void clipme(pos top, pos bottom) +static void clipme(pos top, pos bottom, int rect) { wchar_t *workbuf; wchar_t *wbptr; /* where next char goes within workbuf */ + int old_top_x; int wblen = 0; /* workbuf len */ int buflen; /* amount of memory allocated to workbuf */ buflen = 5120; /* Default size */ workbuf = smalloc(buflen * sizeof(wchar_t)); wbptr = workbuf; /* start filling here */ + old_top_x = top.x; /* needed for rect==1 */ while (poslt(top, bottom)) { int nl = FALSE; unsigned long *ldata = lineptr(top.y); pos nlpos; + /* + * nlpos will point at the maximum position on this line we + * should copy up to. So we start it at the end of the + * line... + */ nlpos.y = top.y; nlpos.x = cols; + /* + * ... move it backwards if there's unused space at the end + * of the line (and also set `nl' if this is the case, + * because in normal selection mode this means we need a + * newline at the end)... + */ if (!(ldata[cols] & LATTR_WRAPPED)) { while (((ldata[nlpos.x - 1] & 0xFF) == 0x20 || (DIRECT_CHAR(ldata[nlpos.x - 1]) && @@ -2758,6 +3117,20 @@ static void clipme(pos top, pos bottom) if (poslt(nlpos, bottom)) nl = TRUE; } + + /* + * ... and then clip it to the terminal x coordinate if + * we're doing rectangular selection. (In this case we + * still did the above, so that copying e.g. the right-hand + * column from a table doesn't fill with spaces on the + * right.) + */ + if (rect) { + if (nlpos.x > bottom.x) + nlpos.x = bottom.x; + nl = (top.y < bottom.y); + } + while (poslt(top, bottom) && poslt(top, nlpos)) { #if 0 char cbuf[16], *p; @@ -2804,16 +3177,14 @@ static void clipme(pos top, pos bottom) unsigned char buf[4]; WCHAR wbuf[4]; int rv; - if (IsDBCSLeadByteEx(font_codepage, (BYTE) c)) { + if (is_dbcs_leadbyte(font_codepage, (BYTE) c)) { buf[0] = c; buf[1] = (unsigned char) ldata[top.x + 1]; - rv = MultiByteToWideChar(font_codepage, - 0, buf, 2, wbuf, 4); + rv = mb_to_wc(font_codepage, 0, buf, 2, wbuf, 4); top.x++; } else { buf[0] = c; - rv = MultiByteToWideChar(font_codepage, - 0, buf, 1, wbuf, 4); + rv = mb_to_wc(font_codepage, 0, buf, 1, wbuf, 4); } if (rv > 0) { @@ -2845,10 +3216,12 @@ static void clipme(pos top, pos bottom) } } top.y++; - top.x = 0; + top.x = rect ? old_top_x : 0; } +#if SELECTION_NUL_TERMINATED wblen++; *wbptr++ = 0; +#endif write_clip(workbuf, wblen, FALSE); /* transfer to clipboard */ if (buflen > 0) /* indicates we allocated this buffer */ sfree(workbuf); @@ -2859,7 +3232,7 @@ void term_copyall(void) pos top; top.y = -count234(scrollback); top.x = 0; - clipme(top, curs); + clipme(top, curs, 0); } /* @@ -2957,6 +3330,12 @@ static int wordtype(int uc) break; } + /* For DBCS font's I can't do anything usefull. Even this will sometimes + * fail as there's such a thing as a double width space. :-( + */ + if (dbcs_screenfont && font_codepage == line_codepage) + return (uc != ' '); + if (uc < 0x80) return wordness[uc]; @@ -2975,6 +3354,7 @@ static pos sel_spread_half(pos p, int dir) { unsigned long *ldata; short wvalue; + int topy = -count234(scrollback); ldata = lineptr(p.y); @@ -3001,11 +3381,47 @@ static pos sel_spread_half(pos p, int dir) */ wvalue = wordtype(ldata[p.x]); if (dir == +1) { - while (p.x < cols && wordtype(ldata[p.x + 1]) == wvalue) - p.x++; + while (1) { + if (p.x < cols-1) { + if (wordtype(ldata[p.x + 1]) == wvalue) + p.x++; + else + break; + } else { + if (ldata[cols] & LATTR_WRAPPED) { + unsigned long *ldata2; + ldata2 = lineptr(p.y+1); + if (wordtype(ldata2[0]) == wvalue) { + p.x = 0; + p.y++; + ldata = ldata2; + } else + break; + } else + break; + } + } } else { - while (p.x > 0 && wordtype(ldata[p.x - 1]) == wvalue) - p.x--; + while (1) { + if (p.x > 0) { + if (wordtype(ldata[p.x - 1]) == wvalue) + p.x--; + else + break; + } else { + unsigned long *ldata2; + if (p.y <= topy) + break; + ldata2 = lineptr(p.y-1); + if ((ldata2[cols] & LATTR_WRAPPED) && + wordtype(ldata2[cols-1]) == wvalue) { + p.x = cols-1; + p.y--; + ldata = ldata2; + } else + break; + } + } } break; case SM_LINE: @@ -3020,10 +3436,12 @@ static pos sel_spread_half(pos p, int dir) static void sel_spread(void) { - selstart = sel_spread_half(selstart, -1); - decpos(selend); - selend = sel_spread_half(selend, +1); - incpos(selend); + if (seltype == LEXICOGRAPHIC) { + selstart = sel_spread_half(selstart, -1); + decpos(selend); + selend = sel_spread_half(selend, +1); + incpos(selend); + } } void term_do_paste(void) @@ -3064,7 +3482,7 @@ void term_do_paste(void) /* Assume a small paste will be OK in one go. */ if (paste_len < 256) { - luni_send(paste_buffer, paste_len); + luni_send(paste_buffer, paste_len, 0); if (paste_buffer) sfree(paste_buffer); paste_buffer = 0; @@ -3075,15 +3493,25 @@ void term_do_paste(void) } void term_mouse(Mouse_Button b, Mouse_Action a, int x, int y, - int shift, int ctrl) + int shift, int ctrl, int alt) { pos selpoint; unsigned long *ldata; + int raw_mouse = (xterm_mouse && + !cfg.no_mouse_rep && + !(cfg.mouse_override && shift)); + int default_seltype; - if (y < 0) + if (y < 0) { y = 0; - if (y >= rows) + if (a == MA_DRAG && !raw_mouse) + term_scroll(0, -1); + } + if (y >= rows) { y = rows - 1; + if (a == MA_DRAG && !raw_mouse) + term_scroll(0, +1); + } if (x < 0) { if (y > 0) { x = cols - 1; @@ -3100,7 +3528,7 @@ void term_mouse(Mouse_Button b, Mouse_Action a, int x, int y, if ((ldata[cols] & LATTR_MODE) != LATTR_NORM) selpoint.x /= 2; - if (xterm_mouse) { + if (raw_mouse) { int encstate = 0, r, c; char abuf[16]; static int is_down = 0; @@ -3148,15 +3576,29 @@ void term_mouse(Mouse_Button b, Mouse_Action a, int x, int y, c = x + 33; sprintf(abuf, "\033[M%c%c%c", encstate, c, r); - ldisc_send(abuf, 6); + ldisc_send(abuf, 6, 0); return; } b = translate_button(b); + /* + * Set the selection type (rectangular or normal) at the start + * of a selection attempt, from the state of Alt. + */ + if (!alt ^ !cfg.rect_select) + default_seltype = RECTANGULAR; + else + default_seltype = LEXICOGRAPHIC; + + if (selstate == NO_SELECTION) { + seltype = default_seltype; + } + if (b == MBT_SELECT && a == MA_CLICK) { deselect(); selstate = ABOUT_TO; + seltype = default_seltype; selanchor = selpoint; selmode = SM_CHAR; } else if (b == MBT_SELECT && (a == MA_2CLK || a == MA_3CLK)) { @@ -3172,26 +3614,64 @@ void term_mouse(Mouse_Button b, Mouse_Action a, int x, int y, if (selstate == ABOUT_TO && poseq(selanchor, selpoint)) return; if (b == MBT_EXTEND && a != MA_DRAG && selstate == SELECTED) { - if (posdiff(selpoint, selstart) < - posdiff(selend, selstart) / 2) { - selanchor = selend; - decpos(selanchor); + if (seltype == LEXICOGRAPHIC) { + /* + * For normal selection, we extend by moving + * whichever end of the current selection is closer + * to the mouse. + */ + if (posdiff(selpoint, selstart) < + posdiff(selend, selstart) / 2) { + selanchor = selend; + decpos(selanchor); + } else { + selanchor = selstart; + } } else { - selanchor = selstart; + /* + * For rectangular selection, we have a choice of + * _four_ places to put selanchor and selpoint: the + * four corners of the selection. + */ + if (2*selpoint.x < selstart.x + selend.x) + selanchor.x = selend.x-1; + else + selanchor.x = selstart.x; + + if (2*selpoint.y < selstart.y + selend.y) + selanchor.y = selend.y; + else + selanchor.y = selstart.y; } selstate = DRAGGING; } if (selstate != ABOUT_TO && selstate != DRAGGING) selanchor = selpoint; selstate = DRAGGING; - if (poslt(selpoint, selanchor)) { - selstart = selpoint; - selend = selanchor; - incpos(selend); + if (seltype == LEXICOGRAPHIC) { + /* + * For normal selection, we set (selstart,selend) to + * (selpoint,selanchor) in some order. + */ + if (poslt(selpoint, selanchor)) { + selstart = selpoint; + selend = selanchor; + incpos(selend); + } else { + selstart = selanchor; + selend = selpoint; + incpos(selend); + } } else { - selstart = selanchor; - selend = selpoint; - incpos(selend); + /* + * For rectangular selection, we may need to + * interchange x and y coordinates (if the user has + * dragged in the -x and +y directions, or vice versa). + */ + selstart.x = min(selanchor.x, selpoint.x); + selend.x = 1+max(selanchor.x, selpoint.x); + selstart.y = min(selanchor.y, selpoint.y); + selend.y = max(selanchor.y, selpoint.y); } sel_spread(); } else if ((b == MBT_SELECT || b == MBT_EXTEND) && a == MA_RELEASE) { @@ -3200,13 +3680,17 @@ void term_mouse(Mouse_Button b, Mouse_Action a, int x, int y, * We've completed a selection. We now transfer the * data to the clipboard. */ - clipme(selstart, selend); + clipme(selstart, selend, (seltype == RECTANGULAR)); selstate = SELECTED; } else selstate = NO_SELECTION; } else if (b == MBT_PASTE - && (a == MA_CLICK || a == MA_2CLK || a == MA_3CLK)) { - term_do_paste(); + && (a == MA_CLICK +#if MULTICLICK_ONLY_EVENT + || a == MA_2CLK || a == MA_3CLK +#endif + )) { + request_paste(); } term_update(); @@ -3221,6 +3705,11 @@ void term_nopaste() paste_len = 0; } +int term_paste_pending(void) +{ + return paste_len != 0; +} + void term_paste() { static long last_paste = 0; @@ -3231,7 +3720,7 @@ void term_paste() /* Don't wait forever to paste */ if (paste_hold) { - now = GetTickCount(); + now = GETTICKCOUNT(); paste_diff = now - last_paste; if (paste_diff >= 0 && paste_diff < 450) return; @@ -3244,7 +3733,7 @@ void term_paste() if (paste_buffer[paste_pos + n++] == '\r') break; } - luni_send(paste_buffer + paste_pos, n); + luni_send(paste_buffer + paste_pos, n, 0); paste_pos += n; if (paste_pos < paste_len) { @@ -3283,18 +3772,17 @@ int term_ldisc(int option) */ int from_backend(int is_stderr, char *data, int len) { - while (len--) { - if (inbuf_head >= INBUF_SIZE) - term_out(); - inbuf[inbuf_head++] = *data++; - } + assert(len > 0); + + bufchain_add(&inbuf, data, len); /* - * We process all stdout/stderr data immediately we receive it, - * and don't return until it's all gone. Therefore, there's no - * reason at all to return anything other than zero from this - * function. - * + * term_out() always completely empties inbuf. Therefore, + * there's no reason at all to return anything other than zero + * from this function, because there _can't_ be a question of + * the remote side needing to wait until term_out() has cleared + * a backlog. + * * This is a slightly suboptimal way to deal with SSH2 - in * principle, the window mechanism would allow us to continue * to accept data on forwarded ports and X connections even @@ -3304,78 +3792,8 @@ int from_backend(int is_stderr, char *data, int len) * portability. So we manage stdout buffering the old SSH1 way: * if the terminal processing goes slowly, the whole SSH * connection stops accepting data until it's ready. - * + * * In practice, I can't imagine this causing serious trouble. */ return 0; } - -/* - * Log session traffic. - */ -void logtraffic(unsigned char c, int logmode) -{ - if (cfg.logtype > 0) { - if (cfg.logtype == logmode) { - /* deferred open file from pgm start? */ - if (!lgfp) - logfopen(); - if (lgfp) - fputc(c, lgfp); - } - } -} - -/* open log file append/overwrite mode */ -void logfopen(void) -{ - char buf[256]; - time_t t; - struct tm *tm; - char writemod[4]; - - if (!cfg.logtype) - return; - sprintf(writemod, "wb"); /* default to rewrite */ - lgfp = fopen(cfg.logfilename, "r"); /* file already present? */ - if (lgfp) { - int i; - fclose(lgfp); - i = askappend(cfg.logfilename); - if (i == 1) - writemod[0] = 'a'; /* set append mode */ - else if (i == 0) { /* cancelled */ - lgfp = NULL; - cfg.logtype = 0; /* disable logging */ - return; - } - } - - lgfp = fopen(cfg.logfilename, writemod); - if (lgfp) { /* enter into event log */ - sprintf(buf, "%s session log (%s mode) to file : ", - (writemod[0] == 'a') ? "Appending" : "Writing new", - (cfg.logtype == LGTYP_ASCII ? "ASCII" : - cfg.logtype == LGTYP_DEBUG ? "raw" : "")); - /* Make sure we do not exceed the output buffer size */ - strncat(buf, cfg.logfilename, 128); - buf[strlen(buf)] = '\0'; - logevent(buf); - - /* --- write header line iinto log file */ - fputs("=~=~=~=~=~=~=~=~=~=~=~= PuTTY log ", lgfp); - time(&t); - tm = localtime(&t); - strftime(buf, 24, "%Y.%m.%d %H:%M:%S", tm); - fputs(buf, lgfp); - fputs(" =~=~=~=~=~=~=~=~=~=~=~=\r\n", lgfp); - } -} - -void logfclose(void) -{ - if (lgfp) { - fclose(lgfp); - lgfp = NULL; - } -}