X-Git-Url: https://git.distorted.org.uk/~mdw/sgt/putty/blobdiff_plain/f4e49163a4c95893a108e9b7773b272d3a59b9c1..8c028e6547c06622ebd8a2c59f5720faa2268304:/terminal.c diff --git a/terminal.c b/terminal.c index a65d7157..7b69e06e 100644 --- a/terminal.c +++ b/terminal.c @@ -63,7 +63,7 @@ static unsigned long curstype; /* type of cursor on real screen */ struct beeptime { struct beeptime *next; - long ticks; + unsigned long ticks; }; static struct beeptime *beephead, *beeptail; int nbeeps; @@ -86,6 +86,10 @@ 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 */ @@ -95,9 +99,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 */ @@ -163,6 +167,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; @@ -199,10 +206,6 @@ 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 xlatlognam(char *d, char *s, char *hostname, struct tm *tm); /* * Resize a line to make it `cols' columns wide. @@ -676,7 +679,7 @@ static void scroll(int topline, int botline, int lines, int sb) * selection), and also selanchor (for one being * selected as we speak). */ - seltop = sb ? -savelines : 0; + seltop = sb ? -savelines : topline; if (selstart.y >= seltop && selstart.y <= botline) { selstart.y--; @@ -743,6 +746,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 { @@ -756,6 +760,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; @@ -848,7 +859,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) { @@ -877,14 +888,20 @@ static void toggle_mode(int mode, int query, int state) * 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 */ + /* 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; @@ -1194,7 +1211,7 @@ void term_out(void) case '\007': { struct beeptime *newbeep; - long ticks; + unsigned long ticks; ticks = GetTickCount(); @@ -1225,7 +1242,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, @@ -1250,7 +1267,7 @@ void term_out(void) beep(cfg.beep); if (cfg.beep == BELL_VISUAL) { in_vbell = TRUE; - vbell_timeout = ticks + VBELL_TIMEOUT; + vbell_startpoint = ticks; term_update(); } } @@ -1942,11 +1959,107 @@ 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)) { + compatibility(VT340TEXT); 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) { + 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) { + 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': @@ -2528,16 +2641,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; - } + if (ticks - vbell_startpoint >= VBELL_TIMEOUT) + in_vbell = FALSE; + } rv = (!rvideo ^ !in_vbell ? ATTR_REVERSE : 0); @@ -2580,7 +2693,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; @@ -2620,9 +2733,12 @@ static void do_paint(Context ctx, int may_optimise) 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)) { @@ -2810,25 +2926,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]) && @@ -2838,6 +2967,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; @@ -2925,7 +3068,7 @@ static void clipme(pos top, pos bottom) } } top.y++; - top.x = 0; + top.x = rect ? old_top_x : 0; } wblen++; *wbptr++ = 0; @@ -2939,7 +3082,7 @@ void term_copyall(void) pos top; top.y = -count234(scrollback); top.x = 0; - clipme(top, curs); + clipme(top, curs, 0); } /* @@ -3061,6 +3204,7 @@ static pos sel_spread_half(pos p, int dir) { unsigned long *ldata; short wvalue; + int topy = -count234(scrollback); ldata = lineptr(p.y); @@ -3087,11 +3231,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: @@ -3106,10 +3286,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) @@ -3161,11 +3343,12 @@ 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.mouse_override && shift); + int default_seltype; if (y < 0) { y = 0; @@ -3247,9 +3430,23 @@ void term_mouse(Mouse_Button b, Mouse_Action a, int x, int y, 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)) { @@ -3265,26 +3462,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) { @@ -3293,7 +3528,7 @@ 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; @@ -3376,6 +3611,8 @@ int term_ldisc(int option) */ int from_backend(int is_stderr, char *data, int len) { + assert(len > 0); + bufchain_add(&inbuf, data, len); /* @@ -3399,138 +3636,3 @@ int from_backend(int is_stderr, char *data, int len) */ 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); - } - } -} - -void settimstr(char *ta, int no_sec); -char *subslfcode(char *dest, char *src, char *dstrt); -char *stpncpy(char *dst, const char *src, size_t maxlen); -char timdatbuf[20]; -char currlogfilename[FILENAME_MAX]; - -/* 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 */ - - time(&t); - tm = *localtime(&t); - - /* substitute special codes in file name */ - xlatlognam(currlogfilename,cfg.logfilename,cfg.host, &tm); - - lgfp = fopen(currlogfilename, "r"); /* file already present? */ - if (lgfp) { - int i; - fclose(lgfp); - i = askappend(currlogfilename); - if (i == 1) - writemod[0] = 'a'; /* set append mode */ - else if (i == 0) { /* cancelled */ - lgfp = NULL; - cfg.logtype = 0; /* disable logging */ - return; - } - } - - lgfp = fopen(currlogfilename, 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, currlogfilename, 128); - buf[strlen(buf)] = '\0'; - logevent(buf); - - /* --- write header line into log file */ - fputs("=~=~=~=~=~=~=~=~=~=~=~= PuTTY log ", lgfp); - 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; - } -} - -/* - * translate format codes into time/date strings - * and insert them into log file name - * - * "&Y":YYYY "&m":MM "&d":DD "&T":hhmm "&h": "&&":& - */ -static void xlatlognam(char *d, char *s, char *hostname, struct tm *tm) { - char buf[10], *bufp; - int size; - char *ds = d; /* save start pos. */ - int len = FILENAME_MAX-1; - - while (*s) { - /* Let (bufp, len) be the string to append. */ - bufp = buf; /* don't usually override this */ - if (*s == '&') { - char c; - s++; - if (*s) switch (c = *s++, tolower(c)) { - case 'y': - size = strftime(buf, sizeof(buf), "%Y", tm); - break; - case 'm': - size = strftime(buf, sizeof(buf), "%m", tm); - break; - case 'd': - size = strftime(buf, sizeof(buf), "%d", tm); - break; - case 't': - size = strftime(buf, sizeof(buf), "%H%M%S", tm); - break; - case 'h': - bufp = hostname; - size = strlen(bufp); - break; - default: - buf[0] = '&'; - size = 1; - if (c != '&') - buf[size++] = c; - } - } else { - buf[0] = *s++; - size = 1; - } - if (size > len) - size = len; - memcpy(d, bufp, size); - d += size; - len -= size; - } - *d = '\0'; -}