From 4336d540db1c33476df4519ea6f14d81c1c5eb3a Mon Sep 17 00:00:00 2001 From: Ben Harris Date: Sun, 7 Mar 1999 23:19:02 +0000 Subject: [PATCH] Lots of things, notably: * Tidied state machine, with better handling of unknown escape and control sequences. * Support for automatic newline mode, with lfhascr now being a session variable. * #include for mem*() and str*(). [originally from svn r74] --- terminal.c | 241 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 143 insertions(+), 98 deletions(-) diff --git a/terminal.c b/terminal.c index bfbaf881..1bec9cc4 100644 --- a/terminal.c +++ b/terminal.c @@ -2,6 +2,7 @@ #include #endif /* not macintosh */ +#include #include #include @@ -27,6 +28,7 @@ static int curs_x, curs_y; /* cursor */ static int save_x, save_y; /* saved cursor position */ static int marg_t, marg_b; /* scroll margins */ static int dec_om; /* DEC origin mode flag */ +static int lfhascr; /* Auto-cr mode flag */ static int wrap, wrapnext; /* wrap flags */ static int insert; /* insert-mode flag */ static int cset; /* 0 or 1: which char set */ @@ -61,9 +63,10 @@ static int nl_count; static enum { TOPLEVEL, IGNORE_NEXT, - SEEN_ESC, SEEN_CSI, SET_GL, SET_GR, + SEEN_ESC, SEEN_CSI, SEEN_GZD4, SEEN_G1D4, SEEN_OSC, SEEN_OSC_P, SEEN_OSC_W, OSC_STRING, OSC_MAYBE_ST, - SEEN_ESCHASH + SEEN_ESCHASH, + SEEN_ESC_CONFUSED, SEEN_CSI_CONFUSED, } termstate; static enum { @@ -118,6 +121,7 @@ static void power_on(void) { alt_cset = cset = 0; cset_attr[0] = cset_attr[1] = ATTR_ASCII; rvideo = 0; + lfhascr = cfg.lfhascr; save_attr = curr_attr = ATTR_DEFAULT; app_cursor_keys = cfg.app_cursor; app_keypad_keys = cfg.app_keypad; @@ -552,6 +556,9 @@ static void toggle_mode (int mode, int query, int state) { case 4: /* set insert mode */ insert = state; break; + case 20: /* line feed/new line mode */ + lfhascr = state; + break; } } @@ -579,6 +586,20 @@ static void do_osc(void) { } } +enum c0 { + NUL, SOH, STX, ETX, EOT, ENQ, ACK, BEL, + BS, HT, LF, VT, FF, CR, SO, SI, + DLE, DC1, DC2, DC3, DC4, NAK, SYN, ETB, + CAN, EM, SUB, ESC, IS1, IS2, IS3, IS4 +}; + +enum c1 { + BPH = 0x82, NBH, IND, NEL, SSA, ESA, + HTS, HTJ, VTS, PLD, PLU, RI, SS1, SS2, + DCS, PU1, PU2, STS, CCH, MW, SPA, EPA, + SOS, SCI = 0x9a, CSI, ST, OSC, PM, APC +}; + /* * Remove everything currently in `inbuf' and stick it up on the * in-memory display. There's a big state machine in here to @@ -587,15 +608,17 @@ static void do_osc(void) { void term_out(void) { int c; int must_update = FALSE; + int reprocess = FALSE; - while ( (c = inbuf_getc()) != -1) { + while (reprocess || (c = inbuf_getc()) != -1) { #ifdef LOG - { + if (!reprocess) { static FILE *fp = NULL; if (!fp) fp = fopen("putty.log", "wb"); if (fp) fputc (c, fp); } #endif + reprocess = FALSE; switch (termstate) { case TOPLEVEL: do_toplevel: @@ -619,25 +642,6 @@ void term_out(void) { disptop = scrtop; must_update = TRUE; break; - case '\016': - cset = 1; - break; - case '\017': - cset = 0; - break; - case '\033': - termstate = SEEN_ESC; - break; - case 0233: - termstate = SEEN_CSI; - esc_nargs = 1; - esc_args[0] = ARG_DEFAULT; - esc_query = FALSE; - break; - case 0235: - termstate = SEEN_OSC; - esc_args[0] = 0; - break; case '\015': curs_x = 0; wrapnext = FALSE; @@ -648,11 +652,12 @@ void term_out(void) { case '\013': case '\014': case '\012': + case 'IND': if (curs_y == marg_b) scroll (marg_t, marg_b, 1, TRUE); else if (curs_y < rows-1) curs_y++; - if (cfg.lfhascr) + if (lfhascr) curs_x = 0; fix_cpos; wrapnext = FALSE; @@ -673,8 +678,56 @@ void term_out(void) { disptop = scrtop; must_update = TRUE; break; + case '\016': + cset = 1; + break; + case '\017': + cset = 0; + break; + case '\033': + termstate = SEEN_ESC; + break; + case NEL: /* exactly equivalent to CR-LF */ + curs_x = 0; + wrapnext = FALSE; + if (curs_y == marg_b) + scroll (marg_t, marg_b, 1, TRUE); + else if (curs_y < rows-1) + curs_y++; + fix_cpos; + wrapnext = FALSE; + nl_count++; + disptop = scrtop; + break; + case HTS: /* set a tab */ + tabs[curs_x] = TRUE; + break; + case RI: /* reverse index - backwards LF */ + if (curs_y == marg_t) + scroll (marg_t, marg_b, -1, TRUE); + else if (curs_y > 0) + curs_y--; + fix_cpos; + wrapnext = FALSE; + disptop = scrtop; + must_update = TRUE; + break; + case SCI: /* terminal type query */ + /* This sequence is standardised as something else entirely. */ + back->send ("\033[?6c", 5); + break; + case 0233: + termstate = SEEN_CSI; + esc_nargs = 1; + esc_args[0] = ARG_DEFAULT; + esc_query = FALSE; + break; + case 0235: + termstate = SEEN_OSC; + esc_args[0] = 0; + break; default: - if (c >= ' ' && c != 0234) { + if (c >= ' ' && c < 0x7f || c >= 0xa0 ) { if (wrapnext) { cpos[1] = ATTR_WRAPPED; if (curs_y == marg_b) @@ -699,6 +752,7 @@ void term_out(void) { } disptop = scrtop; } + break; } break; case IGNORE_NEXT: @@ -717,32 +771,29 @@ void term_out(void) { } /* else fall through */ case SEEN_ESC: + /* + * According to ECMA-35, an escape sequence consists of + * ESC, a sequence (possibly empty) of intermediate bytes + * from column 02 (SPACE--/), and a final byte from + * columns 03-07 (0--~). + */ termstate = TOPLEVEL; - switch (c) { - case '\005': case '\007': case '\b': case '\016': case '\017': - case '\033': case 0233: case 0234: case 0235: case '\015': - case '\013': case '\014': case '\012': case '\t': - termstate = TOPLEVEL; - goto do_toplevel; /* hack... */ - case ' ': /* some weird sequence? */ - termstate = IGNORE_NEXT; - break; - case '[': /* enter CSI mode */ - termstate = SEEN_CSI; - esc_nargs = 1; - esc_args[0] = ARG_DEFAULT; - esc_query = FALSE; - break; - case ']': /* xterm escape sequences */ - termstate = SEEN_OSC; - esc_args[0] = 0; + if (c >= 0x40 && c < 0x60) { + /* Fe sequences -- C1 control as an escape sequence */ + c += 0x40; + reprocess = TRUE; + } else switch (c) { + /* nF sequences -- with intermediate bytes */ + case '#': /* Single control functions */ + termstate = SEEN_ESCHASH; break; - case '(': /* should set GL */ - termstate = SET_GL; + case '(': /* GZD4: should set G0 */ + termstate = SEEN_GZD4; break; - case ')': /* should set GR */ - termstate = SET_GR; + case ')': /* G1D4: should set G1 */ + termstate = SEEN_G1D4; break; + /* Fp sequences -- private control functions */ case '7': /* save cursor */ save_cursor (TRUE); break; @@ -757,63 +808,47 @@ void term_out(void) { case '>': app_keypad_keys = FALSE; break; - case 'D': /* exactly equivalent to LF */ - if (curs_y == marg_b) - scroll (marg_t, marg_b, 1, TRUE); - else if (curs_y < rows-1) - curs_y++; - fix_cpos; - wrapnext = FALSE; - disptop = scrtop; - nl_count++; - break; - case 'E': /* exactly equivalent to CR-LF */ - curs_x = 0; - wrapnext = FALSE; - if (curs_y == marg_b) - scroll (marg_t, marg_b, 1, TRUE); - else if (curs_y < rows-1) - curs_y++; - fix_cpos; - wrapnext = FALSE; - nl_count++; - disptop = scrtop; - break; - case 'M': /* reverse index - backwards LF */ - if (curs_y == marg_t) - scroll (marg_t, marg_b, -1, TRUE); - else if (curs_y > 0) - curs_y--; - fix_cpos; - wrapnext = FALSE; - disptop = scrtop; - must_update = TRUE; - break; - case 'Z': /* terminal type query */ - back->send ("\033[?6c", 5); - break; - case 'c': /* restore power-on settings */ + /* Fs sequences -- standardised control functions */ + case 'c': /* RIS: restore power-on settings */ power_on(); fix_cpos; disptop = scrtop; must_update = TRUE; break; - case '#': /* ESC # 8 fills screen with Es :-) */ - termstate = SEEN_ESCHASH; - break; - case 'H': /* set a tab */ - tabs[curs_x] = TRUE; + default: + termstate = SEEN_ESC_CONFUSED; + reprocess = TRUE; break; } break; + case SEEN_ESC_CONFUSED: + /* + * We're in an escape sequence, but we no longer know what + * it means and we just want it to go away + */ + termstate = TOPLEVEL; + if (c < 0x20 || c >= 0x7f) + /* + * ECMA-35 says this isn't allowed, so we can do what + * we like. + */ + reprocess = TRUE; + else if (c <= 0x30) + /* Intermediate byte -- more to come */ + termstate = SEEN_ESC_CONFUSED; + /* Otherwise, that was a final byte and we're free! */ + break; case SEEN_CSI: + /* + * In theory, a control sequence consists of CSI, then a + * sequence (possibly empty) of parameter bytes (0--?) + * then a sequence (possibly empty) of intermediate bytes + * (SPACE--/), then a final byte (@--~). We're rather + * more relaxed, and don't differentiate between parameter + * and intermediate bytes. + */ termstate = TOPLEVEL; /* default */ switch (c) { - case '\005': case '\007': case '\b': case '\016': case '\017': - case '\033': case 0233: case 0234: case 0235: case '\015': - case '\013': case '\014': case '\012': case '\t': - termstate = TOPLEVEL; - goto do_toplevel; /* hack... */ case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (esc_nargs <= ARGS_MAX) { @@ -1059,19 +1094,29 @@ void term_out(void) { } } break; + default: + termstate = SEEN_CSI_CONFUSED; + reprocess = TRUE; + break; } break; - case SET_GL: - case SET_GR: + case SEEN_CSI_CONFUSED: + termstate = TOPLEVEL; + if (c < 0x20 || c >= 0x7f) + reprocess = TRUE; + else if (c < 0x40) + termstate = SEEN_CSI_CONFUSED; + case SEEN_GZD4: + case SEEN_G1D4: switch (c) { case 'A': - cset_attr[termstate == SET_GL ? 0 : 1] = ATTR_GBCHR; + cset_attr[termstate == SEEN_GZD4 ? 0 : 1] = ATTR_GBCHR; break; case '0': - cset_attr[termstate == SET_GL ? 0 : 1] = ATTR_LINEDRW; + cset_attr[termstate == SEEN_GZD4 ? 0 : 1] = ATTR_LINEDRW; break; default: /* specifically, 'B' */ - cset_attr[termstate == SET_GL ? 0 : 1] = ATTR_ASCII; + cset_attr[termstate == SEEN_GZD4 ? 0 : 1] = ATTR_ASCII; break; } termstate = TOPLEVEL; -- 2.45.2